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