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 var $opt = ''; 22 var $from = ''; 23 var $to = ''; 24 var $start = ''; 25 var $tlimit = ''; 26 27 /** 28 * Access for managers allowed 29 */ 30 function forAdminOnly(){ 31 return false; 32 } 33 34 /** 35 * return sort order for position in admin menu 36 */ 37 function getMenuSort() { 38 return 150; 39 } 40 41 /** 42 * handle user request 43 */ 44 function handle() { 45 $this->opt = preg_replace('/[^a-z]+/','',$_REQUEST['opt']); 46 $this->start = (int) $_REQUEST['s']; 47 $this->setTimeframe($_REQUEST['f'],$_REQUEST['t']); 48 } 49 50 /** 51 * set limit clause 52 */ 53 function setTimeframe($from,$to){ 54 // fixme add better sanity checking here: 55 $from = preg_replace('/[^\d\-]+/','',$from); 56 $to = preg_replace('/[^\d\-]+/','',$to); 57 if(!$from) $from = date('Y-m-d'); 58 if(!$to) $to = date('Y-m-d'); 59 60 //setup limit clause 61 $tlimit = "A.dt >= '$from 00:00:00' AND A.dt <= '$to 23:59:59'"; 62 $this->tlimit = $tlimit; 63 $this->from = $from; 64 $this->to = $to; 65 } 66 67 /** 68 * Output the Statistics 69 */ 70 function html() { 71 echo '<h1>Access Statistics</h1>'; 72 $this->html_timeselect(); 73 74 $method = 'html_'.$this->opt; 75 if(method_exists($this,$method)){ 76 $this->$method(); 77 }else{ 78 $this->html_dashboard(); 79 } 80 } 81 82 function getTOC(){ 83 $pages = array('dashboard','page','referer','newreferer','outlinks', 84 'searchphrases','searchwords','searchengines','browser', 85 'os','country','resolution'); 86 87 $toc = array(); 88 foreach($pages as $page){ 89 $toc[] = array( 90 'link' => '?do=admin&page=statistics&opt='.$page.'&f='.$this->from.'&t='.$this->to, 91 'title' => $this->getLang($page), 92 'level' => 1, 93 'type' => 'ul' 94 ); 95 } 96 return $toc; 97 } 98 99 function html_pager($limit,$next){ 100 echo '<div class="plg_stats_pager">'; 101 102 if($this->start > 0){ 103 $go = max($this->start - $limit, 0); 104 echo '<a href="?do=admin&page=statistics&opt='.$this->opt.'&f='.$this->from.'&t='.$this->to.'&s='.$go.'" class="prev">previous page</a>'; 105 } 106 107 if($next){ 108 $go = $this->start + $limit; 109 echo '<a href="?do=admin&page=statistics&opt='.$this->opt.'&f='.$this->from.'&t='.$this->to.'&s='.$go.'" class="next">next page</a>'; 110 } 111 echo '</div>'; 112 } 113 114 /** 115 * Print the time selection menu 116 */ 117 function html_timeselect(){ 118 $now = date('Y-m-d'); 119 $yday = date('Y-m-d',time()-(60*60*24)); 120 $week = date('Y-m-d',time()-(60*60*24*7)); 121 $month = date('Y-m-d',time()-(60*60*24*30)); 122 123 echo '<div class="plg_stats_timeselect">'; 124 echo '<span>Select the timeframe:</span>'; 125 echo '<ul>'; 126 127 echo '<li>'; 128 echo '<a href="?do=admin&page=statistics&opt='.$this->opt.'&f='.$now.'&t='.$now.'">'; 129 echo 'today'; 130 echo '</a>'; 131 echo '</li>'; 132 133 echo '<li>'; 134 echo '<a href="?do=admin&page=statistics&opt='.$this->opt.'&f='.$yday.'&t='.$yday.'">'; 135 echo 'yesterday'; 136 echo '</a>'; 137 echo '</li>'; 138 139 echo '<li>'; 140 echo '<a href="?do=admin&page=statistics&opt='.$this->opt.'&f='.$week.'&t='.$now.'">'; 141 echo 'last 7 days'; 142 echo '</a>'; 143 echo '</li>'; 144 145 echo '<li>'; 146 echo '<a href="?do=admin&page=statistics&opt='.$this->opt.'&f='.$month.'&t='.$now.'">'; 147 echo 'last 30 days'; 148 echo '</a>'; 149 echo '</li>'; 150 151 echo '</ul>'; 152 153 154 echo '<form action="" method="get">'; 155 echo '<input type="hidden" name="do" value="admin" />'; 156 echo '<input type="hidden" name="page" value="statistics" />'; 157 echo '<input type="hidden" name="opt" value="'.$this->opt.'" />'; 158 echo '<input type="text" name="f" value="'.$this->from.'" class="edit" />'; 159 echo '<input type="text" name="t" value="'.$this->to.'" class="edit" />'; 160 echo '<input type="submit" value="go" class="button" />'; 161 echo '</form>'; 162 163 echo '</div>'; 164 } 165 166 167 /** 168 * Print an introductionary screen 169 */ 170 function html_dashboard(){ 171 echo '<p>This page gives you a quick overview on what is happening in your Wiki. For detailed lists 172 choose a topic from the list.</p>'; 173 174 175 echo '<div class="plg_stats_dashboard">'; 176 177 // general info 178 echo '<div class="plg_stats_top">'; 179 $result = $this->sql_aggregate($this->tlimit); 180 echo '<ul>'; 181 echo '<li><span>'.$result['pageviews'].'</span> page views </li>'; 182 echo '<li><span>'.$result['sessions'].'</span> visits (sessions) </li>'; 183 echo '<li><span>'.$result['visitors'].'</span> unique visitors </li>'; 184 echo '<li><span>'.$result['users'].'</span> logged in users</li>'; 185 186 echo '</ul>'; 187 echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/img.php?img=trend&f='.$this->from.'&t='.$this->to.'" />'; 188 echo '</div>'; 189 190 191 // top pages today 192 echo '<div>'; 193 echo '<h2>Most popular pages</h2>'; 194 $result = $this->sql_pages($this->tlimit,$this->start,15); 195 $this->html_resulttable($result); 196 echo '<a href="?do=admin&page=statistics&opt=page&f='.$this->from.'&t='.$this->to.'" class="more">more</a>'; 197 echo '</div>'; 198 199 // top referer today 200 echo '<div>'; 201 echo '<h2>Newest incoming links</h2>'; 202 $result = $this->sql_newreferer($this->tlimit,$this->start,15); 203 $this->html_resulttable($result); 204 echo '<a href="?do=admin&page=statistics&opt=newreferer&f='.$this->from.'&t='.$this->to.'" class="more">more</a>'; 205 echo '</div>'; 206 207 // top searches today 208 echo '<div>'; 209 echo '<h2>Top search phrases</h2>'; 210 $result = $this->sql_searchphrases($this->tlimit,$this->start,15); 211 $this->html_resulttable($result); 212 echo '<a href="?do=admin&page=statistics&opt=searchphrases&f='.$this->from.'&t='.$this->to.'" class="more">more</a>'; 213 echo '</div>'; 214 215 echo '</div>'; 216 } 217 218 function html_country(){ 219 echo '<div class="plg_stats_full">'; 220 echo '<h2>Visitor\'s Countries</h2>'; 221 echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/img.php?img=country&f='.$this->from.'&t='.$this->to.'" />'; 222 $result = $this->sql_countries($this->tlimit,$this->start,150); 223 $this->html_resulttable($result,'',150); 224 echo '</div>'; 225 } 226 227 function html_page(){ 228 echo '<div class="plg_stats_full">'; 229 echo '<h2>Popular Pages</h2>'; 230 $result = $this->sql_pages($this->tlimit,$this->start,150); 231 $this->html_resulttable($result,'',150); 232 echo '</div>'; 233 } 234 235 function html_browser(){ 236 echo '<div class="plg_stats_full">'; 237 echo '<h2>Browser Shootout</h2>'; 238 echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/img.php?img=browser&f='.$this->from.'&t='.$this->to.'" />'; 239 $result = $this->sql_browsers($this->tlimit,$this->start,150,true); 240 $this->html_resulttable($result,'',150); 241 echo '</div>'; 242 } 243 244 function html_os(){ 245 echo '<div class="plg_stats_full">'; 246 echo '<h2>Operating Systems</h2>'; 247 $result = $this->sql_os($this->tlimit,$this->start,150,true); 248 $this->html_resulttable($result,'',150); 249 echo '</div>'; 250 } 251 252 function html_referer(){ 253 echo '<div class="plg_stats_full">'; 254 echo '<h2>Incoming Links</h2>'; 255 $result = $this->sql_aggregate($this->tlimit); 256 257 $all = $result['search']+$result['external']+$result['direct']; 258 259 if($all){ 260 printf("<p>Of all %d external visits, %d (%.1f%%) were bookmarked (direct) accesses, 261 %d (%.1f%%) came from search engines and %d (%.1f%%) were referred through 262 links from other pages.</p>",$all,$result['direct'],(100*$result['direct']/$all), 263 $result['search'],(100*$result['search']/$all),$result['external'], 264 (100*$result['external']/$all)); 265 } 266 267 $result = $this->sql_referer($this->tlimit,$this->start,150); 268 $this->html_resulttable($result,'',150); 269 echo '</div>'; 270 } 271 272 function html_newreferer(){ 273 echo '<div class="plg_stats_full">'; 274 echo '<h2>New Incoming Links</h2>'; 275 echo '<p>The following incoming links where first logged in the selected time frame, 276 and have never been seen before.</p>'; 277 278 $result = $this->sql_newreferer($this->tlimit,$this->start,150); 279 $this->html_resulttable($result,'',150); 280 echo '</div>'; 281 } 282 283 function html_outlinks(){ 284 echo '<div class="plg_stats_full">'; 285 echo '<h2>Outgoing Links</h2>'; 286 287 $result = $this->sql_outlinks($this->tlimit,$this->start,150); 288 $this->html_resulttable($result,'',150); 289 echo '</div>'; 290 } 291 292 function html_searchphrases(){ 293 echo '<div class="plg_stats_full">'; 294 echo '<h2>Search Phrases</h2>'; 295 296 $result = $this->sql_searchphrases($this->tlimit,$this->start,150); 297 $this->html_resulttable($result,'',150); 298 echo '</div>'; 299 } 300 301 function html_searchwords(){ 302 echo '<div class="plg_stats_full">'; 303 echo '<h2>Search Words</h2>'; 304 305 $result = $this->sql_searchwords($this->tlimit,$this->start,150); 306 $this->html_resulttable($result,'',150); 307 echo '</div>'; 308 } 309 310 function html_searchengines(){ 311 echo '<div class="plg_stats_full">'; 312 echo '<h2>Search Engines</h2>'; 313 314 $result = $this->sql_searchengines($this->tlimit,$this->start,150); 315 $this->html_resulttable($result,'',150); 316 echo '</div>'; 317 } 318 319 320 function html_resolution(){ 321 echo '<div class="plg_stats_full">'; 322 echo '<h2>Resolution</h2>'; 323 $result = $this->sql_resolution($this->tlimit,$this->start,150); 324 $this->html_resulttable($result,'',150); 325 326 echo '<p>While the data above gives you some info about the resolution your visitors use, it does not tell you 327 much about about the real size of their browser windows. The graphic below shows the size distribution of 328 the view port (document area) of your visitor\'s browsers. Please note that this data can not be logged 329 in all browsers. Because users may resize their browser window while browsing your site the statistics may 330 be flawed. Take it with a grain of salt.</p>'; 331 332 echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/img.php?img=view&f='.$this->from.'&t='.$this->to.'" />'; 333 echo '</div>'; 334 } 335 336 337 /** 338 * Display a result in a HTML table 339 */ 340 function html_resulttable($result,$header='',$pager=0){ 341 echo '<table>'; 342 if(is_array($header)){ 343 echo '<tr>'; 344 foreach($header as $h){ 345 echo '<th>'.hsc($h).'</th>'; 346 } 347 echo '</tr>'; 348 } 349 350 $count = 0; 351 if(is_array($result)) foreach($result as $row){ 352 echo '<tr>'; 353 foreach($row as $k => $v){ 354 echo '<td class="plg_stats_X'.$k.'">'; 355 if($k == 'page'){ 356 echo '<a href="'.wl($v).'" class="wikilink1">'; 357 echo hsc($v); 358 echo '</a>'; 359 }elseif($k == 'url'){ 360 $url = hsc($v); 361 $url = preg_replace('/^https?:\/\/(www\.)?/','',$url); 362 if(strlen($url) > 45){ 363 $url = substr($url,0,30).' … '.substr($url,-15); 364 } 365 echo '<a href="'.$v.'" class="urlextern">'; 366 echo $url; 367 echo '</a>'; 368 }elseif($k == 'lookup'){ 369 echo '<a href="http://www.google.com/search?q='.rawurlencode($v).'">'; 370 echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/ico/search/google.png" alt="lookup in Google" border="0" />'; 371 echo '</a> '; 372 373 echo '<a href="http://search.yahoo.com/search?p='.rawurlencode($v).'">'; 374 echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/ico/search/yahoo.png" alt="lookup in Yahoo" border="0" />'; 375 echo '</a> '; 376 377 echo '<a href="http://search.msn.com/results.aspx?q='.rawurlencode($v).'">'; 378 echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/ico/search/msn.png" alt="lookup in MSN Live" border="0" />'; 379 echo '</a> '; 380 381 }elseif($k == 'engine'){ 382 include_once(dirname(__FILE__).'/inc/search_engines.php'); 383 echo $SearchEnginesHashLib[$v]; 384 }elseif($k == 'browser'){ 385 include_once(dirname(__FILE__).'/inc/browsers.php'); 386 echo $BrowsersHashIDLib[$v]; 387 }elseif($k == 'bflag'){ 388 include_once(dirname(__FILE__).'/inc/browsers.php'); 389 echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/ico/browser/'.$BrowsersHashIcon[$v].'.png" alt="'.hsc($v).'" />'; 390 }elseif($k == 'os'){ 391 if(empty($v)){ 392 echo 'unknown'; 393 }else{ 394 include_once(dirname(__FILE__).'/inc/operating_systems.php'); 395 echo $OSHashLib[$v]; 396 } 397 }elseif($k == 'osflag'){ 398 echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/ico/os/'.hsc($v).'.png" alt="'.hsc($v).'" />'; 399 }elseif($k == 'cflag'){ 400 echo '<img src="'.DOKU_BASE.'lib/plugins/statistics/ico/flags/'.hsc($v).'.png" alt="'.hsc($v).'" width="18" height="12" />'; 401 }elseif($k == 'html'){ 402 echo $v; 403 }else{ 404 echo hsc($v); 405 } 406 echo '</td>'; 407 } 408 echo '</tr>'; 409 410 if($pager && ($count == $pager)) break; 411 $count++; 412 } 413 echo '</table>'; 414 415 if($pager) $this->html_pager($pager,count($result) > $pager); 416 } 417 418 /** 419 * Create an image 420 */ 421 function img_build($img){ 422 include(dirname(__FILE__).'/inc/AGC.class.php'); 423 424 switch($img){ 425 case 'country': 426 // build top countries + other 427 $result = $this->sql_countries($this->tlimit,$this->start,0); 428 $data = array(); 429 $top = 0; 430 foreach($result as $row){ 431 if($top < 7){ 432 $data[$row['country']] = $row['cnt']; 433 }else{ 434 $data['other'] += $row['cnt']; 435 } 436 $top++; 437 } 438 $pie = new AGC(300, 200); 439 $pie->setProp("showkey",true); 440 $pie->setProp("showval",false); 441 $pie->setProp("showgrid",false); 442 $pie->setProp("type","pie"); 443 $pie->setProp("keyinfo",1); 444 $pie->setProp("keysize",8); 445 $pie->setProp("keywidspc",-50); 446 $pie->setProp("key",array_keys($data)); 447 $pie->addBulkPoints(array_values($data)); 448 @$pie->graph(); 449 $pie->showGraph(); 450 break; 451 case 'browser': 452 // build top browsers + other 453 include_once(dirname(__FILE__).'/inc/browsers.php'); 454 455 $result = $this->sql_browsers($this->tlimit,$this->start,0,false); 456 $data = array(); 457 $top = 0; 458 foreach($result as $row){ 459 if($top < 5){ 460 $data[strip_tags($BrowsersHashIDLib[$row['ua_info']])] = $row['cnt']; 461 }else{ 462 $data['other'] += $row['cnt']; 463 } 464 $top++; 465 } 466 $pie = new AGC(300, 200); 467 $pie->setProp("showkey",true); 468 $pie->setProp("showval",false); 469 $pie->setProp("showgrid",false); 470 $pie->setProp("type","pie"); 471 $pie->setProp("keyinfo",1); 472 $pie->setProp("keysize",8); 473 $pie->setProp("keywidspc",-50); 474 $pie->setProp("key",array_keys($data)); 475 $pie->addBulkPoints(array_values($data)); 476 @$pie->graph(); 477 $pie->showGraph(); 478 break; 479 case 'view': 480 481 $graph = new AGC(400, 200); 482 $graph->setColor('color',0,'blue'); 483 $graph->setColor('color',1,'red'); 484 $graph->setProp("showkey",true); 485 $graph->setProp("key",'view port width',0); 486 $graph->setProp("key",'view port height',1); 487 488 $result = $this->sql_viewport($this->tlimit,0,0,true); 489 foreach($result as $row){ 490 $graph->addPoint($row['cnt'],$row['res_x'],0); 491 } 492 493 $result = $this->sql_viewport($this->tlimit,0,0,false); 494 foreach($result as $row){ 495 $graph->addPoint($row['cnt'],$row['res_y'],1); 496 } 497 498 @$graph->graph(); 499 $graph->showGraph(); 500 501 break; 502 case 'trend': 503 $hours = ($this->from == $this->to); 504 $result = $this->sql_trend($this->tlimit,$hours); 505 $data1 = array(); 506 $data2 = array(); 507 508 $graph = new AGC(400, 150); 509 $graph->setProp("type","bar"); 510 $graph->setProp("showgrid",false); 511 $graph->setProp("barwidth",.8); 512 513 $graph->setColor('color',0,'blue'); 514 $graph->setColor('color',1,'red'); 515 $graph->setColor('color',2,'yellow'); 516 517 if($hours){ 518 //preset $hours 519 for($i=0;$i<24;$i++){ 520 $data1[$i] = 0; 521 $data2[$i] = 0; 522 $data3[$i] = 0; 523 $graph->setProp("scale",array(' 0h',' 4h',' 8h',' 12h',' 16h',' 20h',' 24h')); 524 } 525 }else{ 526 $graph->setProp("scale",array(next(array_keys($data1)),$this->to)); 527 } 528 529 foreach($result as $row){ 530 $data1[$row['time']] = $row['pageviews']; 531 $data2[$row['time']] = $row['sessions']; 532 $data3[$row['time']] = $row['visitors']; 533 } 534 535 foreach($data1 as $key => $val){ 536 $graph->addPoint($val,$key,0); 537 } 538 foreach($data2 as $key => $val){ 539 $graph->addPoint($val,$key,1); 540 } 541 foreach($data3 as $key => $val){ 542 $graph->addPoint($val,$key,2); 543 } 544 545 @$graph->graph(); 546 $graph->showGraph(); 547 548 default: 549 $this->sendGIF(); 550 } 551 } 552 553 554 /** 555 * Return some aggregated statistics 556 */ 557 function sql_aggregate($tlimit){ 558 $data = array(); 559 560 $sql = "SELECT ref_type, COUNT(*) as cnt 561 FROM ".$this->getConf('db_prefix')."access as A 562 WHERE $tlimit 563 AND ua_type = 'browser' 564 GROUP BY ref_type"; 565 $result = $this->runSQL($sql); 566 567 if(is_array($result)) foreach($result as $row){ 568 if($row['ref_type'] == 'search') $data['search'] = $row['cnt']; 569 if($row['ref_type'] == 'external') $data['external'] = $row['cnt']; 570 if($row['ref_type'] == 'internal') $data['internal'] = $row['cnt']; 571 if($row['ref_type'] == '') $data['direct'] = $row['cnt']; 572 } 573 574 $sql = "SELECT COUNT(DISTINCT session) as sessions, 575 COUNT(session) as views, 576 COUNT(DISTINCT user) as users, 577 COUNT(DISTINCT uid) as visitors 578 FROM ".$this->getConf('db_prefix')."access as A 579 WHERE $tlimit 580 AND ua_type = 'browser'"; 581 $result = $this->runSQL($sql); 582 583 $data['users'] = max($result[0]['users'] - 1,0); // subtract empty user 584 $data['sessions'] = $result[0]['sessions']; 585 $data['pageviews'] = $result[0]['views']; 586 $data['visitors'] = $result[0]['visitors']; 587 588 $sql = "SELECT COUNT(id) as robots 589 FROM ".$this->getConf('db_prefix')."access as A 590 WHERE $tlimit 591 AND ua_type = 'robot'"; 592 $result = $this->runSQL($sql); 593 $data['robots'] = $result[0]['robots']; 594 595 return $data; 596 } 597 598 /** 599 * standard statistics follow, only accesses made by browsers are counted 600 * for general stats like browser or OS only visitors not pageviews are counted 601 */ 602 function sql_trend($tlimit,$hours=false){ 603 if($hours){ 604 $sql = "SELECT HOUR(dt) as time, 605 COUNT(DISTINCT session) as sessions, 606 COUNT(session) as pageviews, 607 COUNT(DISTINCT uid) as visitors 608 FROM ".$this->getConf('db_prefix')."access as A 609 WHERE $tlimit 610 AND ua_type = 'browser' 611 GROUP BY HOUR(dt) 612 ORDER BY time"; 613 }else{ 614 $sql = "SELECT DATE(dt) as time, 615 COUNT(DISTINCT session) as sessions, 616 COUNT(session) as pageviews, 617 COUNT(DISTINCT uid) as visitors 618 FROM ".$this->getConf('db_prefix')."access as A 619 WHERE $tlimit 620 AND ua_type = 'browser' 621 GROUP BY DATE(dt) 622 ORDER BY time"; 623 } 624 return $this->runSQL($sql); 625 } 626 627 function sql_searchengines($tlimit,$start=0,$limit=20){ 628 $sql = "SELECT COUNT(*) as cnt, engine 629 FROM ".$this->getConf('db_prefix')."search as A 630 WHERE $tlimit 631 GROUP BY engine 632 ORDER BY cnt DESC, engine". 633 $this->sql_limit($start,$limit); 634 return $this->runSQL($sql); 635 } 636 637 function sql_searchphrases($tlimit,$start=0,$limit=20){ 638 $sql = "SELECT COUNT(*) as cnt, query, query as lookup 639 FROM ".$this->getConf('db_prefix')."search as A 640 WHERE $tlimit 641 GROUP BY query 642 ORDER BY cnt DESC, query". 643 $this->sql_limit($start,$limit); 644 return $this->runSQL($sql); 645 } 646 647 function sql_searchwords($tlimit,$start=0,$limit=20){ 648 $sql = "SELECT COUNT(*) as cnt, word, word as lookup 649 FROM ".$this->getConf('db_prefix')."search as A, 650 ".$this->getConf('db_prefix')."searchwords as B 651 WHERE $tlimit 652 AND A.id = B.sid 653 GROUP BY word 654 ORDER BY cnt DESC, word". 655 $this->sql_limit($start,$limit); 656 return $this->runSQL($sql); 657 } 658 659 function sql_outlinks($tlimit,$start=0,$limit=20){ 660 $sql = "SELECT COUNT(*) as cnt, link as url 661 FROM ".$this->getConf('db_prefix')."outlinks as A 662 WHERE $tlimit 663 GROUP BY link 664 ORDER BY cnt DESC, link". 665 $this->sql_limit($start,$limit); 666 return $this->runSQL($sql); 667 } 668 669 function sql_pages($tlimit,$start=0,$limit=20){ 670 $sql = "SELECT COUNT(*) as cnt, page 671 FROM ".$this->getConf('db_prefix')."access as A 672 WHERE $tlimit 673 AND ua_type = 'browser' 674 GROUP BY page 675 ORDER BY cnt DESC, page". 676 $this->sql_limit($start,$limit); 677 return $this->runSQL($sql); 678 } 679 680 function sql_referer($tlimit,$start=0,$limit=20){ 681 $sql = "SELECT COUNT(*) as cnt, ref as url 682 FROM ".$this->getConf('db_prefix')."access as A 683 WHERE $tlimit 684 AND ua_type = 'browser' 685 AND ref_type = 'external' 686 GROUP BY ref_md5 687 ORDER BY cnt DESC, url". 688 $this->sql_limit($start,$limit); 689 return $this->runSQL($sql); 690 } 691 692 function sql_newreferer($tlimit,$start=0,$limit=20){ 693 $sql = "SELECT COUNT(*) as cnt, ref as url 694 FROM ".$this->getConf('db_prefix')."access as B, 695 ".$this->getConf('db_prefix')."refseen as A 696 WHERE $tlimit 697 AND ua_type = 'browser' 698 AND ref_type = 'external' 699 AND A.ref_md5 = B.ref_md5 700 GROUP BY A.ref_md5 701 ORDER BY cnt DESC, url". 702 $this->sql_limit($start,$limit); 703 return $this->runSQL($sql); 704 } 705 706 function sql_countries($tlimit,$start=0,$limit=20){ 707 $sql = "SELECT COUNT(DISTINCT session) as cnt, B.code AS cflag, B.country 708 FROM ".$this->getConf('db_prefix')."access as A, 709 ".$this->getConf('db_prefix')."iplocation as B 710 WHERE $tlimit 711 AND A.ip = B.ip 712 GROUP BY B.country 713 ORDER BY cnt DESC, B.country". 714 $this->sql_limit($start,$limit); 715 return $this->runSQL($sql); 716 } 717 718 function sql_browsers($tlimit,$start=0,$limit=20,$ext=true){ 719 if($ext){ 720 $sel = 'ua_info as bflag, ua_info as browser, ua_ver'; 721 $grp = 'ua_info, ua_ver'; 722 }else{ 723 $grp = 'ua_info'; 724 $sel = 'ua_info'; 725 } 726 727 $sql = "SELECT COUNT(DISTINCT session) as cnt, $sel 728 FROM ".$this->getConf('db_prefix')."access as A 729 WHERE $tlimit 730 AND ua_type = 'browser' 731 GROUP BY $grp 732 ORDER BY cnt DESC, ua_info". 733 $this->sql_limit($start,$limit); 734 return $this->runSQL($sql); 735 } 736 737 function sql_os($tlimit,$start=0,$limit=20){ 738 $sql = "SELECT COUNT(DISTINCT session) as cnt, os as osflag, os 739 FROM ".$this->getConf('db_prefix')."access as A 740 WHERE $tlimit 741 AND ua_type = 'browser' 742 GROUP BY os 743 ORDER BY cnt DESC, os". 744 $this->sql_limit($start,$limit); 745 return $this->runSQL($sql); 746 } 747 748 function sql_resolution($tlimit,$start=0,$limit=20){ 749 $sql = "SELECT COUNT(DISTINCT session) as cnt, CONCAT(screen_x,'x',screen_y) as res 750 FROM ".$this->getConf('db_prefix')."access as A 751 WHERE $tlimit 752 AND ua_type = 'browser' 753 AND screen_x != 0 754 GROUP BY screen_x, screen_y 755 ORDER BY cnt DESC, screen_x". 756 $this->sql_limit($start,$limit); 757 return $this->runSQL($sql); 758 } 759 760 function sql_viewport($tlimit,$start=0,$limit=20,$x=true){ 761 if($x){ 762 $col = 'view_x'; 763 $res = 'res_x'; 764 }else{ 765 $col = 'view_y'; 766 $res = 'res_y'; 767 } 768 769 $sql = "SELECT COUNT(*) as cnt, 770 ROUND($col/10)*10 as $res 771 FROM ".$this->getConf('db_prefix')."access as A 772 WHERE $tlimit 773 AND ua_type = 'browser' 774 AND $col != 0 775 GROUP BY $res 776 ORDER BY cnt DESC, $res". 777 $this->sql_limit($start,$limit); 778 return $this->runSQL($sql); 779 } 780 781 782 /** 783 * Builds a limit clause 784 */ 785 function sql_limit($start,$limit){ 786 $start = (int) $start; 787 $limit = (int) $limit; 788 if($limit){ 789 $limit += 1; 790 return " LIMIT $start,$limit"; 791 }elseif($start){ 792 return " OFFSET $start"; 793 } 794 return ''; 795 } 796 797 /** 798 * Return a link to the DB, opening the connection if needed 799 */ 800 function dbLink(){ 801 // connect to DB if needed 802 if(!$this->dblink){ 803 $this->dblink = mysql_connect($this->getConf('db_server'), 804 $this->getConf('db_user'), 805 $this->getConf('db_password')); 806 if(!$this->dblink){ 807 msg('DB Error: connection failed',-1); 808 return null; 809 } 810 // set utf-8 811 if(!mysql_db_query($this->getConf('db_database'),'set names utf8',$this->dblink)){ 812 msg('DB Error: could not set UTF-8 ('.mysql_error($this->dblink).')',-1); 813 return null; 814 } 815 } 816 return $this->dblink; 817 } 818 819 /** 820 * Simple function to run a DB query 821 */ 822 function runSQL($sql_string) { 823 $link = $this->dbLink(); 824 825 $result = mysql_db_query($this->conf['db_database'],$sql_string,$link); 826 if(!$result){ 827 msg('DB Error: '.mysql_error($link).' '.hsc($sql_string),-1); 828 return null; 829 } 830 831 $resultarray = array(); 832 833 //mysql_db_query returns 1 on a insert statement -> no need to ask for results 834 if ($result != 1) { 835 for($i=0; $i< mysql_num_rows($result); $i++) { 836 $temparray = mysql_fetch_assoc($result); 837 $resultarray[]=$temparray; 838 } 839 mysql_free_result($result); 840 } 841 842 if (mysql_insert_id($link)) { 843 $resultarray = mysql_insert_id($link); //give back ID on insert 844 } 845 846 return $resultarray; 847 } 848 849 /** 850 * Returns a short name for a User Agent and sets type, version and os info 851 */ 852 function ua_info($ua,&$type,&$ver,&$os){ 853 $ua = strtr($ua,' +','__'); 854 $ua = strtolower($ua); 855 856 // common browsers 857 $regvermsie = '/msie([+_ ]|)([\d\.]*)/i'; 858 $regvernetscape = '/netscape.?\/([\d\.]*)/i'; 859 $regverfirefox = '/firefox\/([\d\.]*)/i'; 860 $regversvn = '/svn\/([\d\.]*)/i'; 861 $regvermozilla = '/mozilla(\/|)([\d\.]*)/i'; 862 $regnotie = '/webtv|omniweb|opera/i'; 863 $regnotnetscape = '/gecko|compatible|opera|galeon|safari/i'; 864 865 $name = ''; 866 # IE ? 867 if(preg_match($regvermsie,$ua,$m) && !preg_match($regnotie,$ua)){ 868 $type = 'browser'; 869 $ver = $m[2]; 870 $name = 'msie'; 871 } 872 # Firefox ? 873 elseif (preg_match($regverfirefox,$ua,$m)){ 874 $type = 'browser'; 875 $ver = $m[1]; 876 $name = 'firefox'; 877 } 878 # Subversion ? 879 elseif (preg_match($regversvn,$ua,$m)){ 880 $type = 'rcs'; 881 $ver = $m[1]; 882 $name = 'svn'; 883 } 884 # Netscape 6.x, 7.x ... ? 885 elseif (preg_match($regvernetscape,$ua,$m)){ 886 $type = 'browser'; 887 $ver = $m[1]; 888 $name = 'netscape'; 889 } 890 # Netscape 3.x, 4.x ... ? 891 elseif(preg_match($regvermozilla,$ua,$m) && !preg_match($regnotnetscape,$ua)){ 892 $type = 'browser'; 893 $ver = $m[2]; 894 $name = 'netscape'; 895 }else{ 896 include(dirname(__FILE__).'/inc/browsers.php'); 897 foreach($BrowsersSearchIDOrder as $regex){ 898 if(preg_match('/'.$regex.'/',$ua)){ 899 // it's a browser! 900 $type = 'browser'; 901 $name = strtolower($regex); 902 break; 903 } 904 } 905 } 906 907 // check versions for Safari and Opera 908 if($name == 'safari'){ 909 if(preg_match('/safari\/([\d\.]*)/i',$ua,$match)){ 910 $ver = $BrowsersSafariBuildToVersionHash[$match[1]]; 911 } 912 }elseif($name == 'opera'){ 913 if(preg_match('/opera[\/ ]([\d\.]*)/i',$ua,$match)){ 914 $ver = $match[1]; 915 } 916 } 917 918 919 // check OS for browsers 920 if($type == 'browser'){ 921 include(dirname(__FILE__).'/inc/operating_systems.php'); 922 foreach($OSSearchIDOrder as $regex){ 923 if(preg_match('/'.$regex.'/',$ua)){ 924 $os = $OSHashID[$regex]; 925 break; 926 } 927 } 928 929 } 930 931 // are we done now? 932 if($name) return $name; 933 934 include(dirname(__FILE__).'/inc/robots.php'); 935 foreach($RobotsSearchIDOrder as $regex){ 936 if(preg_match('/'.$regex.'/',$ua)){ 937 // it's a robot! 938 $type = 'robot'; 939 return strtolower($regex); 940 } 941 } 942 943 // dunno 944 return ''; 945 } 946 947 /** 948 * Log search queries 949 */ 950 function log_search($referer,&$type){ 951 $referer = strtolower($referer); 952 $ref = strtr($referer,' +','__'); 953 954 include(dirname(__FILE__).'/inc/search_engines.php'); 955 956 foreach($SearchEnginesSearchIDOrder as $regex){ 957 if(preg_match('/'.$regex.'/',$ref)){ 958 if(!$NotSearchEnginesKeys[$regex] || 959 !preg_match('/'.$NotSearchEnginesKeys[$regex].'/',$ref)){ 960 // it's a search engine! 961 $type = 'search'; 962 break; 963 } 964 } 965 } 966 if($type != 'search') return; // we're done here 967 968 // extract query 969 $engine = $SearchEnginesHashID[$regex]; 970 $param = $SearchEnginesKnownUrl[$engine]; 971 if($param && preg_match('/'.$param.'(.*?)[&$]/',$referer,$match)){ 972 $query = array_pop($match); 973 }elseif(preg_match('/'.$WordsToExtractSearchUrl.'(.*?)[&$]/',$referer,$match)){ 974 $query = array_pop($match); 975 } 976 if(!$query) return; // we failed 977 978 // clean the query 979 $query = preg_replace('/^(cache|related):[^\+]+/','',$query); // non-search queries 980 $query = preg_replace('/%0[ad]/',' ',$query); // LF CR 981 $query = preg_replace('/%2[02789abc]/',' ',$query); // space " ' ( ) * + , 982 $query = preg_replace('/%3a/',' ',$query); // : 983 $query = strtr($query,'+\'()"*,:',' '); // badly encoded 984 $query = preg_replace('/ +/',' ',$query); // ws compact 985 $query = trim($query); 986 $query = urldecode($query); 987 if(!utf8_check($query)) $query = utf8_encode($query); // assume latin1 if not utf8 988 $query = utf8_strtolower($query); 989 990 // log it! 991 $page = addslashes($_REQUEST['p']); 992 $query = addslashes($query); 993 $sql = "INSERT INTO ".$this->getConf('db_prefix')."search 994 SET dt = NOW(), 995 page = '$page', 996 query = '$query', 997 engine = '$engine'"; 998 $id = $this->runSQL($sql); 999 if(is_null($id)){ 1000 global $MSG; 1001 print_r($MSG); 1002 return; 1003 } 1004 1005 // log single keywords 1006 $words = explode(' ',utf8_stripspecials($query,' ','\._\-:\*')); 1007 foreach($words as $word){ 1008 if(!$word) continue; 1009 $word = addslashes($word); 1010 $sql = "INSERT DELAYED INTO ".$this->getConf('db_prefix')."searchwords 1011 SET sid = $id, 1012 word = '$word'"; 1013 $ok = $this->runSQL($sql); 1014 if(is_null($ok)){ 1015 global $MSG; 1016 print_r($MSG); 1017 } 1018 } 1019 } 1020 1021 /** 1022 * Resolve IP to country/city 1023 */ 1024 function log_ip($ip){ 1025 // check if IP already known and up-to-date 1026 $sql = "SELECT ip 1027 FROM ".$this->getConf('db_prefix')."iplocation 1028 WHERE ip ='".addslashes($ip)."' 1029 AND lastupd > DATE_SUB(CURDATE(),INTERVAL 30 DAY)"; 1030 $result = $this->runSQL($sql); 1031 if($result[0]['ip']) return; 1032 1033 $http = new DokuHTTPClient(); 1034 $http->timeout = 10; 1035 $data = $http->get('http://api.hostip.info/get_html.php?ip='.$ip); 1036 1037 if(preg_match('/^Country: (.*?) \((.*?)\)\nCity: (.*?)$/s',$data,$match)){ 1038 $country = addslashes(trim($match[1])); 1039 $code = addslashes(strtolower(trim($match[2]))); 1040 $city = addslashes(trim($match[3])); 1041 $host = addslashes(gethostbyaddr($ip)); 1042 $ip = addslashes($ip); 1043 1044 $sql = "REPLACE INTO ".$this->getConf('db_prefix')."iplocation 1045 SET ip = '$ip', 1046 country = '$country', 1047 code = '$code', 1048 city = '$city', 1049 host = '$host'"; 1050 $this->runSQL($sql); 1051 } 1052 } 1053 1054 /** 1055 * log a click on an external link 1056 * 1057 * called from log.php 1058 */ 1059 function log_outgoing(){ 1060 if(!$_REQUEST['ol']) return; 1061 1062 $link_md5 = md5($link); 1063 $link = addslashes($_REQUEST['ol']); 1064 $session = addslashes(session_id()); 1065 $page = addslashes($_REQUEST['p']); 1066 1067 $sql = "INSERT DELAYED INTO ".$this->getConf('db_prefix')."outlinks 1068 SET dt = NOW(), 1069 session = '$session', 1070 page = '$page', 1071 link_md5 = '$link_md5', 1072 link = '$link'"; 1073 $ok = $this->runSQL($sql); 1074 if(is_null($ok)){ 1075 global $MSG; 1076 print_r($MSG); 1077 } 1078 } 1079 1080 /** 1081 * log a page access 1082 * 1083 * called from log.php 1084 */ 1085 function log_access(){ 1086 if(!$_REQUEST['p']) return; 1087 1088 # FIXME check referer against blacklist and drop logging for bad boys 1089 1090 // handle referer 1091 $referer = trim($_REQUEST['r']); 1092 if($referer){ 1093 $ref = addslashes($referer); 1094 $ref_md5 = ($ref) ? md5($referer) : ''; 1095 if(strpos($referer,DOKU_URL) === 0){ 1096 $ref_type = 'internal'; 1097 }else{ 1098 $ref_type = 'external'; 1099 $this->log_search($referer,$ref_type); 1100 } 1101 }else{ 1102 $ref = ''; 1103 $ref_md5 = ''; 1104 $ref_type = ''; 1105 } 1106 1107 // handle user agent 1108 $agent = trim($_SERVER['HTTP_USER_AGENT']); 1109 1110 $ua = addslashes($agent); 1111 $ua_type = ''; 1112 $ua_ver = ''; 1113 $os = ''; 1114 $ua_info = addslashes($this->ua_info($agent,$ua_type,$ua_ver,$os)); 1115 1116 $page = addslashes($_REQUEST['p']); 1117 $ip = addslashes($_SERVER['REMOTE_ADDR']); 1118 $sx = (int) $_REQUEST['sx']; 1119 $sy = (int) $_REQUEST['sy']; 1120 $vx = (int) $_REQUEST['vx']; 1121 $vy = (int) $_REQUEST['vy']; 1122 $js = (int) $_REQUEST['js']; 1123 $uid = addslashes($_REQUEST['uid']); 1124 $user = addslashes($_SERVER['REMOTE_USER']); 1125 $session = addslashes(session_id()); 1126 if(!$uid) $uid = $session; 1127 1128 $sql = "INSERT DELAYED INTO ".$this->getConf('db_prefix')."access 1129 SET dt = NOW(), 1130 page = '$page', 1131 ip = '$ip', 1132 ua = '$ua', 1133 ua_info = '$ua_info', 1134 ua_type = '$ua_type', 1135 ua_ver = '$ua_ver', 1136 os = '$os', 1137 ref = '$ref', 1138 ref_md5 = '$ref_md5', 1139 ref_type = '$ref_type', 1140 screen_x = '$sx', 1141 screen_y = '$sy', 1142 view_x = '$vx', 1143 view_y = '$vy', 1144 js = '$js', 1145 user = '$user', 1146 session = '$session', 1147 uid = '$uid'"; 1148 $ok = $this->runSQL($sql); 1149 if(is_null($ok)){ 1150 global $MSG; 1151 print_r($MSG); 1152 } 1153 1154 $sql = "INSERT DELAYED IGNORE INTO ".$this->getConf('db_prefix')."refseen 1155 SET ref_md5 = '$ref_md5', 1156 dt = NOW()"; 1157 $ok = $this->runSQL($sql); 1158 if(is_null($ok)){ 1159 global $MSG; 1160 print_r($MSG); 1161 } 1162 1163 // resolve the IP 1164 $this->log_ip($_SERVER['REMOTE_ADDR']); 1165 } 1166 1167 /** 1168 * Just send a 1x1 pixel blank gif to the browser 1169 * 1170 * @called from log.php 1171 * 1172 * @author Andreas Gohr <andi@splitbrain.org> 1173 * @author Harry Fuecks <fuecks@gmail.com> 1174 */ 1175 function sendGIF(){ 1176 $img = base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7'); 1177 header('Content-Type: image/gif'); 1178 header('Content-Length: '.strlen($img)); 1179 header('Connection: Close'); 1180 print $img; 1181 flush(); 1182 // Browser should drop connection after this 1183 // Thinks it's got the whole image 1184 } 1185 1186} 1187