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