1<?php 2/** 3 * statistics plugin 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <gohr@cosmocode.de> 7 */ 8 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11 12if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 13require_once(DOKU_PLUGIN.'admin.php'); 14 15/** 16 * All DokuWiki plugins to extend the admin function 17 * need to inherit from this class 18 */ 19class admin_plugin_statistics extends DokuWiki_Admin_Plugin { 20 var $dblink = null; 21 22 /** 23 * return some info 24 */ 25 function getInfo(){ 26 return confToHash(dirname(__FILE__).'/info.txt'); 27 } 28 29 /** 30 * Access for managers allowed 31 */ 32 function forAdminOnly(){ 33 return false; 34 } 35 36 /** 37 * return sort order for position in admin menu 38 */ 39 function getMenuSort() { 40 return 150; 41 } 42 43 /** 44 * handle user request 45 */ 46 function handle() { 47 } 48 49 /** 50 * fixme build statistics here 51 */ 52 function html() { 53 // fixme build a navigation menu in a TOC here 54 55 switch($_REQUEST['opt']){ 56 57 default: 58 echo $this->locale_xhtml('intro'); 59 echo $this->html_dashboard(); 60 } 61 } 62 63 function html_timeselect(){ 64 echo '<form>'; 65 66 echo '</form>'; 67 } 68 69 70 function html_dashboard(){ 71 72 // top pages today 73 $sql = "SELECT page, COUNT(*) as cnt 74 FROM ".$this->getConf('db_prefix')."access 75 WHERE DATE(dt) = CURDATE() 76 AND ua_type = 'browser' 77 GROUP BY page 78 ORDER BY cnt DESC, page 79 LIMIT 20"; 80 $result = $this->runSQL($sql); 81 $this->html_resulttable($result,array('Pages','Count')); 82 83 // top referer today 84 $sql = "SELECT ref as url, COUNT(*) as cnt 85 FROM ".$this->getConf('db_prefix')."access 86 WHERE DATE(dt) = CURDATE() 87 AND ua_type = 'browser' 88 AND ref_type = 'external' 89 GROUP BY ref_md5 90 ORDER BY cnt DESC, url 91 LIMIT 20"; 92 $result = $this->runSQL($sql); 93 $this->html_resulttable($result,array('Incoming Links','Count')); 94 } 95 96 /** 97 * Display a result in a HTML table 98 */ 99 function html_resulttable($result,$header){ 100 echo '<table>'; 101 echo '<tr>'; 102 foreach($header as $h){ 103 echo '<th>'.hsc($h).'</th>'; 104 } 105 echo '</tr>'; 106 107 foreach($result as $row){ 108 echo '<tr>'; 109 foreach($row as $k => $v){ 110 echo '<td class="stats_'.$k.'">'; 111 if($k == 'page'){ 112 echo '<a href="'.wl($v).'" class="wikilink1">'; 113 echo hsc($v); 114 echo '</a>'; 115 }elseif($k == 'url'){ 116 echo '<a href="'.$v.'" class="urlextern">'; 117 echo hsc($v); 118 echo '</a>'; 119 }elseif($k == 'html'){ 120 echo $v; 121 }else{ 122 echo hsc($v); 123 } 124 echo '</td>'; 125 } 126 echo '</tr>'; 127 } 128 echo '</table>'; 129 } 130 131 132 /** 133 * Return a link to the DB, opening the connection if needed 134 */ 135 function dbLink(){ 136 // connect to DB if needed 137 if(!$this->dblink){ 138 $this->dblink = mysql_connect($this->getConf('db_server'), 139 $this->getConf('db_user'), 140 $this->getConf('db_password')); 141 if(!$this->dblink){ 142 msg('DB Error: connection failed',-1); 143 return null; 144 } 145 // set utf-8 146 if(!mysql_db_query($this->getConf('db_database'),'set names utf8',$this->dblink)){ 147 msg('DB Error: could not set UTF-8 ('.mysql_error($this->dblink).')',-1); 148 return null; 149 } 150 } 151 return $this->dblink; 152 } 153 154 /** 155 * Simple function to run a DB query 156 */ 157 function runSQL($sql_string) { 158 $link = $this->dbLink(); 159 160 $result = mysql_db_query($this->conf['db_database'],$sql_string,$link); 161 if(!$result){ 162 msg('DB Error: '.mysql_error($link),-1); 163 return null; 164 } 165 166 $resultarray = array(); 167 168 //mysql_db_query returns 1 on a insert statement -> no need to ask for results 169 if ($result != 1) { 170 for($i=0; $i< mysql_num_rows($result); $i++) { 171 $temparray = mysql_fetch_assoc($result); 172 $resultarray[]=$temparray; 173 } 174 mysql_free_result($result); 175 } 176 177 if (mysql_insert_id($link)) { 178 $resultarray = mysql_insert_id($link); //give back ID on insert 179 } 180 181 return $resultarray; 182 } 183 184 /** 185 * Returns a short name for a User Agent and sets type, version and os info 186 */ 187 function ua_info($ua,&$type,&$ver,&$os){ 188 $ua = strtr($ua,' +','__'); 189 $ua = strtolower($ua); 190 191 // common browsers 192 $regvermsie = '/msie([+_ ]|)([\d\.]*)/i'; 193 $regvernetscape = '/netscape.?\/([\d\.]*)/i'; 194 $regverfirefox = '/firefox\/([\d\.]*)/i'; 195 $regversvn = '/svn\/([\d\.]*)/i'; 196 $regvermozilla = '/mozilla(\/|)([\d\.]*)/i'; 197 $regnotie = '/webtv|omniweb|opera/i'; 198 $regnotnetscape = '/gecko|compatible|opera|galeon|safari/i'; 199 200 $name = ''; 201 # IE ? 202 if(preg_match($regvermsie,$ua,$m) && !preg_match($regnotie,$ua)){ 203 $type = 'browser'; 204 $ver = $m[2]; 205 $name = 'msie'; 206 } 207 # Firefox ? 208 elseif (preg_match($regverfirefox,$ua,$m)){ 209 $type = 'browser'; 210 $ver = $m[1]; 211 $name = 'firefox'; 212 } 213 # Subversion ? 214 elseif (preg_match($regversvn,$ua,$m)){ 215 $type = 'rcs'; 216 $ver = $m[1]; 217 $name = 'svn'; 218 } 219 # Netscape 6.x, 7.x ... ? 220 elseif (preg_match($regvernetscape,$ua,$m)){ 221 $type = 'browser'; 222 $ver = $m[1]; 223 $name = 'netscape'; 224 } 225 # Netscape 3.x, 4.x ... ? 226 elseif(preg_match($regvermozilla,$ua,$m) && !preg_match($regnotnetscape,$ua)){ 227 $type = 'browser'; 228 $ver = $m[2]; 229 $name = 'netscape'; 230 }else{ 231 include(dirname(__FILE__).'/inc/browsers.php'); 232 foreach($BrowsersSearchIDOrder as $regex){ 233 if(preg_match('/'.$regex.'/',$ua)){ 234 // it's a browser! 235 $type = 'browser'; 236 $name = strtolower($regex); 237 break; 238 } 239 } 240 } 241 242 // check OS for browsers 243 if($type == 'browser'){ 244 include(dirname(__FILE__).'/inc/operating_systems.php'); 245 foreach($OSSearchIDOrder as $regex){ 246 if(preg_match('/'.$regex.'/',$ua)){ 247 $os = $OSHashID[$regex]; 248 break; 249 } 250 } 251 252 } 253 254 // are we done now? 255 if($name) return $name; 256 257 include(dirname(__FILE__).'/inc/robots.php'); 258 foreach($RobotsSearchIDOrder as $regex){ 259 if(preg_match('/'.$regex.'/',$ua)){ 260 // it's a robot! 261 $type = 'robot'; 262 return strtolower($regex); 263 } 264 } 265 266 // dunno 267 return ''; 268 } 269 270 /** 271 * 272 * @fixme: put search engine queries in seperate table here 273 */ 274 function log_search($referer,&$type){ 275 $referer = strtr($referer,' +','__'); 276 $referer = strtolower($referer); 277 278 include(dirname(__FILE__).'/inc/search_engines.php'); 279 280 foreach($SearchEnginesSearchIDOrder as $regex){ 281 if(preg_match('/'.$regex.'/',$referer)){ 282 if(!$NotSearchEnginesKeys[$regex] || 283 !preg_match('/'.$NotSearchEnginesKeys[$regex].'/',$referer)){ 284 // it's a search engine! 285 $type = 'search'; 286 break; 287 } 288 } 289 } 290 if($type != 'search') return; // we're done here 291 292 #fixme now do the keyword magic! 293 } 294 295 /** 296 * Resolve IP to country/city 297 */ 298 function log_ip($ip){ 299 // check if IP already known and up-to-date 300 $sql = "SELECT ip 301 FROM ".$this->getConf('db_prefix')."iplocation 302 WHERE ip ='".addslashes($ip)."' 303 AND lastupd > DATE_SUB(CURDATE(),INTERVAL 30 DAY)"; 304 $result = $this->runSQL($sql); 305 if($result[0]['ip']) return; 306 307 $http = new DokuHTTPClient(); 308 $http->timeout = 10; 309 $data = $http->get('http://api.hostip.info/get_html.php?ip='.$ip); 310 311 if(preg_match('/^Country: (.*?) \((.*?)\)\nCity: (.*?)$/s',$data,$match)){ 312 $country = addslashes(trim($match[1])); 313 $code = addslashes(strtolower(trim($match[2]))); 314 $city = addslashes(trim($match[3])); 315 $host = addslashes(gethostbyaddr($ip)); 316 $ip = addslashes($ip); 317 318 $sql = "REPLACE INTO ".$this->getConf('db_prefix')."iplocation 319 SET ip = '$ip', 320 country = '$country', 321 code = '$code', 322 city = '$city', 323 host = '$host'"; 324 $this->runSQL($sql); 325 } 326 } 327 328 /** 329 * log a page access 330 * 331 * called from log.php 332 */ 333 function log_access(){ 334 if(!$_REQUEST['p']) return; 335 336 # FIXME check referer against blacklist and drop logging for bad boys 337 338 // handle referer 339 $referer = trim($_REQUEST['r']); 340 if($referer){ 341 $ref = addslashes($referer); 342 $ref_md5 = ($ref) ? md5($referer) : ''; 343 if(strpos($referer,DOKU_URL) === 0){ 344 $ref_type = 'internal'; 345 }else{ 346 $ref_type = 'external'; 347 $this->log_search($referer,$ref_type); 348 } 349 }else{ 350 $ref = ''; 351 $ref_md5 = ''; 352 $ref_type = ''; 353 } 354 355 // handle user agent 356 $agent = trim($_SERVER['HTTP_USER_AGENT']); 357 358 $ua = addslashes($agent); 359 $ua_type = ''; 360 $ua_ver = ''; 361 $os = ''; 362 $ua_info = addslashes($this->ua_info($agent,$ua_type,$ua_ver,$os)); 363 364 $page = addslashes($_REQUEST['p']); 365 $ip = addslashes($_SERVER['REMOTE_ADDR']); 366 $sx = (int) $_REQUEST['sx']; 367 $sy = (int) $_REQUEST['sy']; 368 $vx = (int) $_REQUEST['vx']; 369 $vy = (int) $_REQUEST['vy']; 370 $user = addslashes($_SERVER['REMOTE_USER']); 371 $session = addslashes(session_id()); 372 373 $sql = "INSERT DELAYED INTO ".$this->getConf('db_prefix')."access 374 SET page = '$page', 375 ip = '$ip', 376 ua = '$ua', 377 ua_info = '$ua_info', 378 ua_type = '$ua_type', 379 ua_ver = '$ua_ver', 380 os = '$os', 381 ref = '$ref', 382 ref_md5 = '$ref_md5', 383 ref_type = '$ref_type', 384 screen_x = '$sx', 385 screen_y = '$sy', 386 view_x = '$vx', 387 view_y = '$vy', 388 user = '$user', 389 session = '$session'"; 390 $ok = $this->runSQL($sql); 391 if(is_null($ok)){ 392 global $MSG; 393 print_r($MSG); 394 } 395 396 // resolve the IP 397 $this->log_ip($_SERVER['REMOTE_ADDR']); 398 } 399 400 /** 401 * Just send a 1x1 pixel blank gif to the browser 402 * 403 * @called from log.php 404 * 405 * @author Andreas Gohr <andi@splitbrain.org> 406 * @author Harry Fuecks <fuecks@gmail.com> 407 */ 408 function sendGIF(){ 409 $img = base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7'); 410 header('Content-Type: image/gif'); 411 header('Content-Length: '.strlen($img)); 412 header('Connection: Close'); 413 print $img; 414 flush(); 415 // Browser should drop connection after this 416 // Thinks it's got the whole image 417 } 418 419} 420