*/
// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'admin.php');
/**
* All DokuWiki plugins to extend the admin function
* need to inherit from this class
*/
class admin_plugin_statistics extends DokuWiki_Admin_Plugin {
var $dblink = null;
var $opt = '';
var $from = '';
var $to = '';
var $start = '';
var $tlimit = '';
/**
* return some info
*/
function getInfo(){
return confToHash(dirname(__FILE__).'/info.txt');
}
/**
* Access for managers allowed
*/
function forAdminOnly(){
return false;
}
/**
* return sort order for position in admin menu
*/
function getMenuSort() {
return 150;
}
/**
* handle user request
*/
function handle() {
$this->opt = preg_replace('/[^a-z]+/','',$_REQUEST['opt']);
$this->start = (int) $_REQUEST['s'];
// fixme add better sanity checking here:
$this->from = preg_replace('/[^\d\-]+/','',$_REQUEST['f']);
$this->to = preg_replace('/[^\d\-]+/','',$_REQUEST['t']);
if(!$this->from) $this->from = date('Y-m-d');
if(!$this->to) $this->to = date('Y-m-d');
//setup limit clause
if($this->from != $this->to){
$this->tlimit = "DATE(A.dt) >= DATE('".$this->from."') AND DATE(A.dt) <= DATE('".$this->to."')";
}else{
$this->tlimit = "DATE(A.dt) = DATE('".$this->from."')";
}
}
/**
* fixme build statistics here
*/
function html() {
$this->html_toc();
echo '
Access Statistics
';
$this->html_timeselect();
switch($this->opt){
case 'country':
$this->html_country();
break;
case 'page':
$this->html_page();
break;
case 'browser':
$this->html_browser();
break;
case 'os':
$this->html_os();
break;
case 'referer':
$this->html_referer();
break;
case 'newreferer':
$this->html_newreferer();
break;
default:
$this->html_dashboard();
}
}
function html_toc(){
echo '';
echo '';
echo '
';
echo '
';
echo '';
echo '';
echo '';
echo '';
echo '';
echo '';
echo '';
echo '
';
echo '
';
echo '
';
}
/**
* Print the time selection menu
*/
function html_timeselect(){
$now = date('Y-m-d');
$yday = date('Y-m-d',time()-(60*60*24));
$week = date('Y-m-d',time()-(60*60*24*7));
$month = date('Y-m-d',time()-(60*60*24*30));
echo '';
echo '
Select the timeframe:';
echo '
';
echo '
';
echo '
';
}
/**
* Print an introductionary screen
*/
function html_dashboard(){
echo 'This page gives you a quick overview on what is happening in your Wiki. For detailed lists
choose a topic from the list.
';
echo '';
// general info
echo '
';
$result = $this->sql_aggregate($this->tlimit);
echo '
';
echo '- '.$result['pageviews'].' page views
';
echo '- '.$result['sessions'].' visitors (sessions)
';
echo '- '.$result['users'].' logged in users
';
echo '
';
echo '

';
echo '
';
// top pages today
echo '
';
echo '
Most popular pages
';
$result = $this->sql_pages($this->tlimit,$this->start,15);
$this->html_resulttable($result);
echo '';
// top referer today
echo '
';
echo '
Newest incoming links
';
$result = $this->sql_newreferer($this->tlimit,$this->start,15);
$this->html_resulttable($result);
echo '';
// top countries today
echo '
';
echo '
Visitor\'s top countries
';
echo '

';
// $result = $this->sql_countries($this->tlimit,$this->start,15);
// $this->html_resulttable($result,array('','Countries','Count'));
echo '
';
echo '
';
}
function html_country(){
echo '';
echo '
Visitor\'s Countries
';
echo '

';
$result = $this->sql_countries($this->tlimit,$this->start,150);
$this->html_resulttable($result);
echo '
';
}
function html_page(){
echo '';
echo '
Popular Pages
';
$result = $this->sql_pages($this->tlimit,$this->start,150);
$this->html_resulttable($result);
echo '';
}
function html_browser(){
echo '';
echo '
Browser Shootout
';
echo '

';
$result = $this->sql_browsers($this->tlimit,$this->start,150,true);
$this->html_resulttable($result);
echo '
';
}
function html_os(){
echo '';
echo '
Operating Systems
';
// echo '

';
$result = $this->sql_os($this->tlimit,$this->start,150,true);
$this->html_resulttable($result);
echo '
';
}
function html_referer(){
echo '';
echo '
Incoming Links
';
$result = $this->sql_aggregate($this->tlimit);
$all = $result['search']+$result['external']+$result['direct'];
if($all){
printf("
Of all %d external visits, %d (%.1f%%) were bookmarked (direct) accesses,
%d (%.1f%%) came from search engines and %d (%.1f%%) were referred through
links from other pages.
",$all,$result['direct'],(100*$result['direct']/$all),
$result['search'],(100*$result['search']/$all),$result['external'],
(100*$result['external']/$all));
}
$result = $this->sql_referer($this->tlimit,$this->start,150);
$this->html_resulttable($result);
echo '
';
}
function html_newreferer(){
echo '';
echo '
New Incoming Links
';
echo '
The following incoming links where first logged in the selected time frame,
and have never been seen before.
';
$result = $this->sql_newreferer($this->tlimit,$this->start,150);
$this->html_resulttable($result);
echo '
';
}
/**
* Display a result in a HTML table
*/
function html_resulttable($result,$header=''){
echo '';
if(is_array($header)){
echo '';
foreach($header as $h){
echo '| '.hsc($h).' | ';
}
echo '
';
}
foreach($result as $row){
echo '';
foreach($row as $k => $v){
echo '';
if($k == 'page'){
echo '';
echo hsc($v);
echo '';
}elseif($k == 'url'){
$url = hsc($v);
if(strlen($url) > 45){
$url = substr($url,0,30).' … '.substr($url,-15);
}
echo '';
echo $url;
echo '';
}elseif($k == 'browser'){
include_once(dirname(__FILE__).'/inc/browsers.php');
echo $BrowsersHashIDLib[$v];
}elseif($k == 'bflag'){
include_once(dirname(__FILE__).'/inc/browsers.php');
echo ' ';
}elseif($k == 'os'){
if(empty($v)){
echo 'unknown';
}else{
include_once(dirname(__FILE__).'/inc/operating_systems.php');
echo $OSHashLib[$v];
}
}elseif($k == 'osflag'){
echo ' ';
}elseif($k == 'cflag'){
echo ' ';
}elseif($k == 'html'){
echo $v;
}else{
echo hsc($v);
}
echo ' | ';
}
echo '
';
}
echo '
';
}
/**
* Create an image
*/
function img_build($img){
include(dirname(__FILE__).'/inc/AGC.class.php');
switch($img){
case 'country':
// build top countries + other
$result = $this->sql_countries($this->tlimit,$this->start,0);
$data = array();
$top = 0;
foreach($result as $row){
if($top < 7){
$data[$row['country']] = $row['cnt'];
}else{
$data['other'] += $row['cnt'];
}
$top++;
}
$pie = new AGC(300, 200);
$pie->setProp("showkey",true);
$pie->setProp("showval",false);
$pie->setProp("showgrid",false);
$pie->setProp("type","pie");
$pie->setProp("keyinfo",1);
$pie->setProp("keysize",8);
$pie->setProp("keywidspc",-50);
$pie->setProp("key",array_keys($data));
$pie->addBulkPoints(array_values($data));
@$pie->graph();
$pie->showGraph();
break;
case 'browser':
// build top browsers + other
include_once(dirname(__FILE__).'/inc/browsers.php');
$result = $this->sql_browsers($this->tlimit,$this->start,0,false);
$data = array();
$top = 0;
foreach($result as $row){
if($top < 5){
$data[strip_tags($BrowsersHashIDLib[$row['ua_info']])] = $row['cnt'];
}else{
$data['other'] += $row['cnt'];
}
$top++;
}
$pie = new AGC(300, 200);
$pie->setProp("showkey",true);
$pie->setProp("showval",false);
$pie->setProp("showgrid",false);
$pie->setProp("type","pie");
$pie->setProp("keyinfo",1);
$pie->setProp("keysize",8);
$pie->setProp("keywidspc",-50);
$pie->setProp("key",array_keys($data));
$pie->addBulkPoints(array_values($data));
@$pie->graph();
$pie->showGraph();
break;
case 'trend':
$hours = ($this->from == $this->to);
$result = $this->sql_trend($this->tlimit,$hours);
$data1 = array();
$data2 = array();
$graph = new AGC(400, 150);
$graph->setProp("type","bar");
$graph->setProp("showgrid",false);
$graph->setProp("barwidth",.8);
$graph->setColor('color',0,'blue');
$graph->setColor('color',1,'red');
if($hours){
//preset $hours
for($i=0;$i<24;$i++){
$data1[$i] = 0;
$data2[$i] = 0;
$graph->setProp("scale",array(' 0h',' 4h',' 8h',' 12h',' 16h',' 20h',' 24h'));
}
}else{
$graph->setProp("scale",array(next(array_keys($data1)),$this->to));
}
foreach($result as $row){
$data1[$row['time']] = $row['pageviews'];
$data2[$row['time']] = $row['sessions'];
}
foreach($data1 as $key => $val){
$graph->addPoint($val,$key,0);
}
foreach($data2 as $key => $val){
$graph->addPoint($val,$key,1);
}
@$graph->graph();
$graph->showGraph();
default:
$this->sendGIF();
}
}
/**
* Return some aggregated statistics
*/
function sql_aggregate($tlimit){
$data = array();
$sql = "SELECT ref_type, COUNT(*) as cnt
FROM ".$this->getConf('db_prefix')."access as A
WHERE $tlimit
AND ua_type = 'browser'
GROUP BY ref_type";
$result = $this->runSQL($sql);
foreach($result as $row){
if($row['ref_type'] == 'search') $data['search'] = $row['cnt'];
if($row['ref_type'] == 'external') $data['external'] = $row['cnt'];
if($row['ref_type'] == 'internal') $data['internal'] = $row['cnt'];
if($row['ref_type'] == '') $data['direct'] = $row['cnt'];
}
$sql = "SELECT COUNT(DISTINCT session) as sessions,
COUNT(session) as views,
COUNT(DISTINCT user) as users
FROM ".$this->getConf('db_prefix')."access as A
WHERE $tlimit
AND ua_type = 'browser'";
$result = $this->runSQL($sql);
$data['users'] = max($result[0]['users'] - 1,0); // subtract empty user
$data['sessions'] = $result[0]['sessions'];
$data['pageviews'] = $result[0]['views'];
$sql = "SELECT COUNT(id) as robots
FROM ".$this->getConf('db_prefix')."access as A
WHERE $tlimit
AND ua_type = 'robot'";
$result = $this->runSQL($sql);
$data['robots'] = $result[0]['robots'];
return $data;
}
/**
* standard statistics follow, only accesses made by browsers are counted
* for general stats like browser or OS only visitors not pageviews are counted
*/
function sql_trend($tlimit,$hours=false){
if($hours){
$sql = "SELECT HOUR(dt) as time,
COUNT(DISTINCT session) as sessions,
COUNT(session) as pageviews
FROM ".$this->getConf('db_prefix')."access as A
WHERE $tlimit
AND ua_type = 'browser'
GROUP BY HOUR(dt)
ORDER BY time";
}else{
$sql = "SELECT DATE(dt) as time,
COUNT(DISTINCT session) as sessions,
COUNT(session) as pageviews
FROM ".$this->getConf('db_prefix')."access as A
WHERE $tlimit
AND ua_type = 'browser'
GROUP BY DATE(dt)
ORDER BY time";
}
return $this->runSQL($sql);
}
function sql_pages($tlimit,$start=0,$limit=20){
$sql = "SELECT COUNT(*) as cnt, page
FROM ".$this->getConf('db_prefix')."access as A
WHERE $tlimit
AND ua_type = 'browser'
GROUP BY page
ORDER BY cnt DESC, page".
$this->sql_limit($start,$limit);
return $this->runSQL($sql);
}
function sql_referer($tlimit,$start=0,$limit=20){
$sql = "SELECT COUNT(*) as cnt, ref as url
FROM ".$this->getConf('db_prefix')."access as A
WHERE $tlimit
AND ua_type = 'browser'
AND ref_type = 'external'
GROUP BY ref_md5
ORDER BY cnt DESC, url".
$this->sql_limit($start,$limit);
return $this->runSQL($sql);
}
function sql_newreferer($tlimit,$start=0,$limit=20){
$sql = "SELECT COUNT(*) as cnt, ref as url
FROM ".$this->getConf('db_prefix')."access as A
WHERE ua_type = 'browser'
AND ref_type = 'external'
GROUP BY ref_md5
HAVING DATE(MIN(dt)) >= DATE('".$this->from."')
AND DATE(MIN(dt)) <= DATE('".$this->to."')
ORDER BY cnt DESC, url".
$this->sql_limit($start,$limit);
return $this->runSQL($sql);
}
function sql_countries($tlimit,$start=0,$limit=20){
$sql = "SELECT COUNT(DISTINCT session) as cnt, B.code AS cflag, B.country
FROM ".$this->getConf('db_prefix')."access as A,
".$this->getConf('db_prefix')."iplocation as B
WHERE $tlimit
AND A.ip = B.ip
GROUP BY B.country
ORDER BY cnt DESC, B.country".
$this->sql_limit($start,$limit);
return $this->runSQL($sql);
}
function sql_browsers($tlimit,$start=0,$limit=20,$ext=true){
if($ext){
$sel = 'ua_info as bflag, ua_info as browser, ua_ver';
$grp = 'ua_info, ua_ver';
}else{
$grp = 'ua_info';
$sel = 'ua_info';
}
$sql = "SELECT COUNT(DISTINCT session) as cnt, $sel
FROM ".$this->getConf('db_prefix')."access as A
WHERE $tlimit
AND ua_type = 'browser'
GROUP BY $grp
ORDER BY cnt DESC, ua_info".
$this->sql_limit($start,$limit);
return $this->runSQL($sql);
}
function sql_os($tlimit,$start=0,$limit=20){
$sql = "SELECT COUNT(DISTINCT session) as cnt, os as osflag, os
FROM ".$this->getConf('db_prefix')."access as A
WHERE $tlimit
AND ua_type = 'browser'
GROUP BY os
ORDER BY cnt DESC, os".
$this->sql_limit($start,$limit);
return $this->runSQL($sql);
}
/**
* Builds a limit clause
*/
function sql_limit($start,$limit){
$start = (int) $start;
$limit = (int) $limit;
if($limit){
return " LIMIT $start,$limit";
}elseif($start){
return " OFFSET $start";
}
return '';
}
/**
* Return a link to the DB, opening the connection if needed
*/
function dbLink(){
// connect to DB if needed
if(!$this->dblink){
$this->dblink = mysql_connect($this->getConf('db_server'),
$this->getConf('db_user'),
$this->getConf('db_password'));
if(!$this->dblink){
msg('DB Error: connection failed',-1);
return null;
}
// set utf-8
if(!mysql_db_query($this->getConf('db_database'),'set names utf8',$this->dblink)){
msg('DB Error: could not set UTF-8 ('.mysql_error($this->dblink).')',-1);
return null;
}
}
return $this->dblink;
}
/**
* Simple function to run a DB query
*/
function runSQL($sql_string) {
$link = $this->dbLink();
$result = mysql_db_query($this->conf['db_database'],$sql_string,$link);
if(!$result){
msg('DB Error: '.mysql_error($link).' '.hsc($sql_string),-1);
return null;
}
$resultarray = array();
//mysql_db_query returns 1 on a insert statement -> no need to ask for results
if ($result != 1) {
for($i=0; $i< mysql_num_rows($result); $i++) {
$temparray = mysql_fetch_assoc($result);
$resultarray[]=$temparray;
}
mysql_free_result($result);
}
if (mysql_insert_id($link)) {
$resultarray = mysql_insert_id($link); //give back ID on insert
}
return $resultarray;
}
/**
* Returns a short name for a User Agent and sets type, version and os info
*/
function ua_info($ua,&$type,&$ver,&$os){
$ua = strtr($ua,' +','__');
$ua = strtolower($ua);
// common browsers
$regvermsie = '/msie([+_ ]|)([\d\.]*)/i';
$regvernetscape = '/netscape.?\/([\d\.]*)/i';
$regverfirefox = '/firefox\/([\d\.]*)/i';
$regversvn = '/svn\/([\d\.]*)/i';
$regvermozilla = '/mozilla(\/|)([\d\.]*)/i';
$regnotie = '/webtv|omniweb|opera/i';
$regnotnetscape = '/gecko|compatible|opera|galeon|safari/i';
$name = '';
# IE ?
if(preg_match($regvermsie,$ua,$m) && !preg_match($regnotie,$ua)){
$type = 'browser';
$ver = $m[2];
$name = 'msie';
}
# Firefox ?
elseif (preg_match($regverfirefox,$ua,$m)){
$type = 'browser';
$ver = $m[1];
$name = 'firefox';
}
# Subversion ?
elseif (preg_match($regversvn,$ua,$m)){
$type = 'rcs';
$ver = $m[1];
$name = 'svn';
}
# Netscape 6.x, 7.x ... ?
elseif (preg_match($regvernetscape,$ua,$m)){
$type = 'browser';
$ver = $m[1];
$name = 'netscape';
}
# Netscape 3.x, 4.x ... ?
elseif(preg_match($regvermozilla,$ua,$m) && !preg_match($regnotnetscape,$ua)){
$type = 'browser';
$ver = $m[2];
$name = 'netscape';
}else{
include(dirname(__FILE__).'/inc/browsers.php');
foreach($BrowsersSearchIDOrder as $regex){
if(preg_match('/'.$regex.'/',$ua)){
// it's a browser!
$type = 'browser';
$name = strtolower($regex);
break;
}
}
}
// check versions for Safari and Opera
if($name == 'safari'){
if(preg_match('/safari\/([\d\.]*)/i',$ua,$match)){
$ver = $BrowsersSafariBuildToVersionHash[$match[1]];
}
}elseif($name == 'opera'){
if(preg_match('/opera[\/ ]([\d\.]*)/i',$ua,$match)){
$ver = $match[1];
}
}
// check OS for browsers
if($type == 'browser'){
include(dirname(__FILE__).'/inc/operating_systems.php');
foreach($OSSearchIDOrder as $regex){
if(preg_match('/'.$regex.'/',$ua)){
$os = $OSHashID[$regex];
break;
}
}
}
// are we done now?
if($name) return $name;
include(dirname(__FILE__).'/inc/robots.php');
foreach($RobotsSearchIDOrder as $regex){
if(preg_match('/'.$regex.'/',$ua)){
// it's a robot!
$type = 'robot';
return strtolower($regex);
}
}
// dunno
return '';
}
/**
*
* @fixme: put search engine queries in seperate table here
*/
function log_search($referer,&$type){
$referer = strtr($referer,' +','__');
$referer = strtolower($referer);
include(dirname(__FILE__).'/inc/search_engines.php');
foreach($SearchEnginesSearchIDOrder as $regex){
if(preg_match('/'.$regex.'/',$referer)){
if(!$NotSearchEnginesKeys[$regex] ||
!preg_match('/'.$NotSearchEnginesKeys[$regex].'/',$referer)){
// it's a search engine!
$type = 'search';
break;
}
}
}
if($type != 'search') return; // we're done here
#fixme now do the keyword magic!
}
/**
* Resolve IP to country/city
*/
function log_ip($ip){
// check if IP already known and up-to-date
$sql = "SELECT ip
FROM ".$this->getConf('db_prefix')."iplocation
WHERE ip ='".addslashes($ip)."'
AND lastupd > DATE_SUB(CURDATE(),INTERVAL 30 DAY)";
$result = $this->runSQL($sql);
if($result[0]['ip']) return;
$http = new DokuHTTPClient();
$http->timeout = 10;
$data = $http->get('http://api.hostip.info/get_html.php?ip='.$ip);
if(preg_match('/^Country: (.*?) \((.*?)\)\nCity: (.*?)$/s',$data,$match)){
$country = addslashes(trim($match[1]));
$code = addslashes(strtolower(trim($match[2])));
$city = addslashes(trim($match[3]));
$host = addslashes(gethostbyaddr($ip));
$ip = addslashes($ip);
$sql = "REPLACE INTO ".$this->getConf('db_prefix')."iplocation
SET ip = '$ip',
country = '$country',
code = '$code',
city = '$city',
host = '$host'";
$this->runSQL($sql);
}
}
/**
* log a page access
*
* called from log.php
*/
function log_access(){
if(!$_REQUEST['p']) return;
# FIXME check referer against blacklist and drop logging for bad boys
// handle referer
$referer = trim($_REQUEST['r']);
if($referer){
$ref = addslashes($referer);
$ref_md5 = ($ref) ? md5($referer) : '';
if(strpos($referer,DOKU_URL) === 0){
$ref_type = 'internal';
}else{
$ref_type = 'external';
$this->log_search($referer,$ref_type);
}
}else{
$ref = '';
$ref_md5 = '';
$ref_type = '';
}
// handle user agent
$agent = trim($_SERVER['HTTP_USER_AGENT']);
$ua = addslashes($agent);
$ua_type = '';
$ua_ver = '';
$os = '';
$ua_info = addslashes($this->ua_info($agent,$ua_type,$ua_ver,$os));
$page = addslashes($_REQUEST['p']);
$ip = addslashes($_SERVER['REMOTE_ADDR']);
$sx = (int) $_REQUEST['sx'];
$sy = (int) $_REQUEST['sy'];
$vx = (int) $_REQUEST['vx'];
$vy = (int) $_REQUEST['vy'];
$js = (int) $_REQUEST['js'];
$user = addslashes($_SERVER['REMOTE_USER']);
$session = addslashes(session_id());
$sql = "INSERT DELAYED INTO ".$this->getConf('db_prefix')."access
SET dt = NOW(),
page = '$page',
ip = '$ip',
ua = '$ua',
ua_info = '$ua_info',
ua_type = '$ua_type',
ua_ver = '$ua_ver',
os = '$os',
ref = '$ref',
ref_md5 = '$ref_md5',
ref_type = '$ref_type',
screen_x = '$sx',
screen_y = '$sy',
view_x = '$vx',
view_y = '$vy',
js = '$js',
user = '$user',
session = '$session'";
$ok = $this->runSQL($sql);
if(is_null($ok)){
global $MSG;
print_r($MSG);
}
// resolve the IP
$this->log_ip($_SERVER['REMOTE_ADDR']);
}
/**
* Just send a 1x1 pixel blank gif to the browser
*
* @called from log.php
*
* @author Andreas Gohr
* @author Harry Fuecks
*/
function sendGIF(){
$img = base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7');
header('Content-Type: image/gif');
header('Content-Length: '.strlen($img));
header('Connection: Close');
print $img;
flush();
// Browser should drop connection after this
// Thinks it's got the whole image
}
}