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