*/
// 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 'referer':
$this->html_referer();
break;
default:
$this->html_dashboard();
}
}
function html_toc(){
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 '';
// top pages today
echo '
';
echo '
Most popular pages
';
$result = $this->sql_pages($this->tlimit,$this->start,15);
$this->html_resulttable($result,array('Pages','Count'));
echo '';
// top referer today
echo '
';
echo '
Top incoming links
';
$result = $this->sql_referer($this->tlimit,$this->start,15);
$this->html_resulttable($result,array('Incoming Links','Count'));
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
';
$result = $this->sql_countries($this->tlimit,$this->start,150);
$this->html_resulttable($result,array('','Countries','Count'));
echo '';
}
function html_page(){
echo '';
echo '
Popular Pages
';
$result = $this->sql_pages($this->tlimit,$this->start,150);
$this->html_resulttable($result,array('Page','Count'));
echo '';
}
function html_referer(){
echo '';
echo '
Incoming Links
';
$result = $this->sql_referer($this->tlimit,$this->start,150);
$this->html_resulttable($result,array('Incoming Link','Count'));
echo '';
}
/**
* Display a result in a HTML table
*/
function html_resulttable($result,$header){
echo '';
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) > 50){
$url = substr($url,0,30).' … '.substr($url,-20);
}
echo '';
echo $url;
echo '';
}elseif($k == 'html'){
echo $v;
}elseif($k == 'cflag'){
echo ' ';
}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;
default:
$this->sendGIF();
}
}
function sql_pages($tlimit,$start=0,$limit=20){
$sql = "SELECT page, COUNT(*) as cnt
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 ref as url, COUNT(*) as cnt
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_countries($tlimit,$start=0,$limit=20){
$sql = "SELECT B.code AS cflag, B.country, COUNT(*) as cnt
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);
}
/**
* 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),-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 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'];
$user = addslashes($_SERVER['REMOTE_USER']);
$session = addslashes(session_id());
$sql = "INSERT DELAYED INTO ".$this->getConf('db_prefix')."access
SET 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',
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
}
}