*/ // 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 'Detailed Statistics'; 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 ''; echo ''; echo ''; echo ''; echo ''; echo ''; 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 ''; 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 ''; } echo ''; } foreach($result as $row){ echo ''; foreach($row as $k => $v){ echo ''; } echo ''; } echo '
'.hsc($h).'
'; 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 ''.hsc($v).''; }elseif($k == 'os'){ if(empty($v)){ echo 'unknown'; }else{ include_once(dirname(__FILE__).'/inc/operating_systems.php'); echo $OSHashLib[$v]; } }elseif($k == 'osflag'){ echo ''.hsc($v).''; }elseif($k == 'cflag'){ echo ''.hsc($v).''; }elseif($k == 'html'){ echo $v; }else{ echo hsc($v); } 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 } }