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