1<?php 2 3use dokuwiki\Extension\AdminPlugin; 4 5/** 6 * statistics plugin 7 * 8 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 9 * @author Andreas Gohr <gohr@splitbrain.org> 10 */ 11 12// must be run within Dokuwiki 13if (!defined('DOKU_INC')) die(); 14 15/** 16 * All DokuWiki plugins to extend the admin function 17 * need to inherit from this class 18 */ 19class admin_plugin_statistics extends AdminPlugin 20{ 21 /** @var string the currently selected page */ 22 protected $opt = ''; 23 24 /** @var string from date in YYYY-MM-DD */ 25 protected $from = ''; 26 /** @var string to date in YYYY-MM-DD */ 27 protected $to = ''; 28 /** @var int Offset to use when displaying paged data */ 29 protected $start = 0; 30 31 /** @var string MySQL timelimit statement */ 32 protected $tlimit = ''; 33 34 /** @var helper_plugin_statistics */ 35 protected $hlp; 36 37 /** 38 * Available statistic pages 39 */ 40 protected $pages = ['dashboard' => 1, 'content' => ['page', 'edits', 'images', 'downloads', 'history'], 'users' => ['topuser', 'topeditor', 'topgroup', 'topgroupedit', 'seenusers'], 'links' => ['referer', 'newreferer', 'outlinks'], 'search' => ['searchengines', 'searchphrases', 'searchwords', 'internalsearchphrases', 'internalsearchwords'], 'technology' => ['browsers', 'os', 'countries', 'resolution', 'viewport']]; 41 42 /** @var array keeps a list of all real content pages, generated from above array */ 43 protected $allowedpages = []; 44 45 /** 46 * Initialize the helper 47 */ 48 public function __construct() 49 { 50 $this->hlp = plugin_load('helper', 'statistics'); 51 52 // build a list of pages 53 foreach ($this->pages as $key => $val) { 54 if (is_array($val)) { 55 $this->allowedpages = array_merge($this->allowedpages, $val); 56 } else { 57 $this->allowedpages[] = $key; 58 } 59 } 60 } 61 62 /** 63 * Access for managers allowed 64 */ 65 public function forAdminOnly() 66 { 67 return false; 68 } 69 70 /** 71 * return sort order for position in admin menu 72 */ 73 public function getMenuSort() 74 { 75 return 350; 76 } 77 78 /** 79 * handle user request 80 */ 81 public function handle() 82 { 83 $this->opt = preg_replace('/[^a-z]+/', '', $_REQUEST['opt']); 84 if (!in_array($this->opt, $this->allowedpages)) $this->opt = 'dashboard'; 85 86 $this->start = (int) $_REQUEST['s']; 87 $this->setTimeframe($_REQUEST['f'], $_REQUEST['t']); 88 } 89 90 /** 91 * set limit clause 92 */ 93 public function setTimeframe($from, $to) 94 { 95 // swap if wrong order 96 if ($from > $to) [$from, $to] = [$to, $from]; 97 98 $this->tlimit = $this->hlp->Query()->mktlimit($from, $to); 99 $this->from = $from; 100 $this->to = $to; 101 } 102 103 /** 104 * Output the Statistics 105 */ 106 public function html() 107 { 108 echo '<div id="plugin__statistics">'; 109 echo '<h1>' . $this->getLang('menu') . '</h1>'; 110 $this->html_timeselect(); 111 tpl_flush(); 112 113 $method = 'html_' . $this->opt; 114 if (method_exists($this, $method)) { 115 echo '<div class="plg_stats_' . $this->opt . '">'; 116 echo '<h2>' . $this->getLang($this->opt) . '</h2>'; 117 $this->$method(); 118 echo '</div>'; 119 } 120 echo '</div>'; 121 } 122 123 /** 124 * Return the TOC 125 * 126 * @return array 127 */ 128 public function getTOC() 129 { 130 $toc = []; 131 foreach ($this->pages as $key => $info) { 132 if (is_array($info)) { 133 $toc[] = html_mktocitem( 134 '', 135 $this->getLang($key), 136 1, 137 '' 138 ); 139 140 foreach ($info as $page) { 141 $toc[] = html_mktocitem( 142 '?do=admin&page=statistics&opt=' . $page . '&f=' . $this->from . '&t=' . $this->to, 143 $this->getLang($page), 144 2, 145 '' 146 ); 147 } 148 } else { 149 $toc[] = html_mktocitem( 150 '?do=admin&page=statistics&opt=' . $key . '&f=' . $this->from . '&t=' . $this->to, 151 $this->getLang($key), 152 1, 153 '' 154 ); 155 } 156 } 157 return $toc; 158 } 159 160 public function html_graph($name, $width, $height) 161 { 162 $url = DOKU_BASE . 'lib/plugins/statistics/img.php?img=' . $name . 163 '&f=' . $this->from . '&t=' . $this->to; 164 echo '<img src="' . $url . '" class="graph" width="' . $width . '" height="' . $height . '"/>'; 165 } 166 167 /** 168 * Outputs pagination links 169 * 170 * @param int $limit 171 * @param int $next 172 */ 173 public function html_pager($limit, $next) 174 { 175 echo '<div class="plg_stats_pager">'; 176 177 if ($this->start > 0) { 178 $go = max($this->start - $limit, 0); 179 echo '<a href="?do=admin&page=statistics&opt=' . $this->opt . '&f=' . $this->from . '&t=' . $this->to . '&s=' . $go . '" class="prev button">' . $this->getLang('prev') . '</a>'; 180 } 181 182 if ($next) { 183 $go = $this->start + $limit; 184 echo '<a href="?do=admin&page=statistics&opt=' . $this->opt . '&f=' . $this->from . '&t=' . $this->to . '&s=' . $go . '" class="next button">' . $this->getLang('next') . '</a>'; 185 } 186 echo '</div>'; 187 } 188 189 /** 190 * Print the time selection menu 191 */ 192 public function html_timeselect() 193 { 194 $today = date('Y-m-d'); 195 $last1 = date('Y-m-d', time() - (60 * 60 * 24)); 196 $last7 = date('Y-m-d', time() - (60 * 60 * 24 * 7)); 197 $last30 = date('Y-m-d', time() - (60 * 60 * 24 * 30)); 198 199 echo '<div class="plg_stats_timeselect">'; 200 echo '<span>' . $this->getLang('time_select') . '</span> '; 201 202 echo '<form action="' . DOKU_SCRIPT . '" method="get">'; 203 echo '<input type="hidden" name="do" value="admin" />'; 204 echo '<input type="hidden" name="page" value="statistics" />'; 205 echo '<input type="hidden" name="opt" value="' . $this->opt . '" />'; 206 echo '<input type="text" name="f" value="' . $this->from . '" class="edit datepicker" />'; 207 echo '<input type="text" name="t" value="' . $this->to . '" class="edit datepicker" />'; 208 echo '<input type="submit" value="go" class="button" />'; 209 echo '</form>'; 210 211 echo '<ul>'; 212 foreach (['today', 'last1', 'last7', 'last30'] as $time) { 213 echo '<li>'; 214 echo '<a href="?do=admin&page=statistics&opt=' . $this->opt . '&f=' . ${$time} . '&t=' . $today . '">'; 215 echo $this->getLang('time_' . $time); 216 echo '</a>'; 217 echo '</li>'; 218 } 219 echo '</ul>'; 220 221 echo '</div>'; 222 } 223 224 /** 225 * Print an introductionary screen 226 */ 227 public function html_dashboard() 228 { 229 echo '<p>' . $this->getLang('intro_dashboard') . '</p>'; 230 231 // general info 232 echo '<div class="plg_stats_top">'; 233 $result = $this->hlp->Query()->aggregate($this->tlimit); 234 235 echo '<ul class="left">'; 236 foreach (['pageviews', 'sessions', 'visitors', 'users', 'logins', 'current'] as $name) { 237 echo '<li><div class="li">' . sprintf($this->getLang('dash_' . $name), $result[$name]) . '</div></li>'; 238 } 239 echo '</ul>'; 240 241 echo '<ul class="left">'; 242 foreach (['bouncerate', 'timespent', 'avgpages', 'newvisitors', 'registrations'] as $name) { 243 echo '<li><div class="li">' . sprintf($this->getLang('dash_' . $name), $result[$name]) . '</div></li>'; 244 } 245 echo '</ul>'; 246 247 echo '<br style="clear: left" />'; 248 249 $this->html_graph('dashboardviews', 700, 280); 250 $this->html_graph('dashboardwiki', 700, 280); 251 echo '</div>'; 252 253 // top pages today 254 echo '<div>'; 255 echo '<h2>' . $this->getLang('dash_mostpopular') . '</h2>'; 256 $result = $this->hlp->Query()->pages($this->tlimit, $this->start, 15); 257 $this->html_resulttable($result); 258 echo '<a href="?do=admin&page=statistics&opt=page&f=' . $this->from . '&t=' . $this->to . '" class="more button">' . $this->getLang('more') . '</a>'; 259 echo '</div>'; 260 261 // top referer today 262 echo '<div>'; 263 echo '<h2>' . $this->getLang('dash_newincoming') . '</h2>'; 264 $result = $this->hlp->Query()->newreferer($this->tlimit, $this->start, 15); 265 $this->html_resulttable($result); 266 echo '<a href="?do=admin&page=statistics&opt=newreferer&f=' . $this->from . '&t=' . $this->to . '" class="more button">' . $this->getLang('more') . '</a>'; 267 echo '</div>'; 268 269 // top searches today 270 echo '<div>'; 271 echo '<h2>' . $this->getLang('dash_topsearch') . '</h2>'; 272 $result = $this->hlp->Query()->searchphrases(true, $this->tlimit, $this->start, 15); 273 $this->html_resulttable($result); 274 echo '<a href="?do=admin&page=statistics&opt=searchphrases&f=' . $this->from . '&t=' . $this->to . '" class="more button">' . $this->getLang('more') . '</a>'; 275 echo '</div>'; 276 } 277 278 public function html_history() 279 { 280 echo '<p>' . $this->getLang('intro_history') . '</p>'; 281 $this->html_graph('history_page_count', 600, 200); 282 $this->html_graph('history_page_size', 600, 200); 283 $this->html_graph('history_media_count', 600, 200); 284 $this->html_graph('history_media_size', 600, 200); 285 } 286 287 public function html_countries() 288 { 289 echo '<p>' . $this->getLang('intro_countries') . '</p>'; 290 $this->html_graph('countries', 400, 200); 291 $result = $this->hlp->Query()->countries($this->tlimit, $this->start, 150); 292 $this->html_resulttable($result, '', 150); 293 } 294 295 public function html_page() 296 { 297 echo '<p>' . $this->getLang('intro_page') . '</p>'; 298 $result = $this->hlp->Query()->pages($this->tlimit, $this->start, 150); 299 $this->html_resulttable($result, '', 150); 300 } 301 302 public function html_edits() 303 { 304 echo '<p>' . $this->getLang('intro_edits') . '</p>'; 305 $result = $this->hlp->Query()->edits($this->tlimit, $this->start, 150); 306 $this->html_resulttable($result, '', 150); 307 } 308 309 public function html_images() 310 { 311 echo '<p>' . $this->getLang('intro_images') . '</p>'; 312 313 $result = $this->hlp->Query()->imagessum($this->tlimit); 314 echo '<p>'; 315 echo sprintf($this->getLang('trafficsum'), $result[0]['cnt'], filesize_h($result[0]['filesize'])); 316 echo '</p>'; 317 318 $result = $this->hlp->Query()->images($this->tlimit, $this->start, 150); 319 $this->html_resulttable($result, '', 150); 320 } 321 322 public function html_downloads() 323 { 324 echo '<p>' . $this->getLang('intro_downloads') . '</p>'; 325 326 $result = $this->hlp->Query()->downloadssum($this->tlimit); 327 echo '<p>'; 328 echo sprintf($this->getLang('trafficsum'), $result[0]['cnt'], filesize_h($result[0]['filesize'])); 329 echo '</p>'; 330 331 $result = $this->hlp->Query()->downloads($this->tlimit, $this->start, 150); 332 $this->html_resulttable($result, '', 150); 333 } 334 335 public function html_browsers() 336 { 337 echo '<p>' . $this->getLang('intro_browsers') . '</p>'; 338 $this->html_graph('browsers', 400, 200); 339 $result = $this->hlp->Query()->browsers($this->tlimit, $this->start, 150, true); 340 $this->html_resulttable($result, '', 150); 341 } 342 343 public function html_topuser() 344 { 345 echo '<p>' . $this->getLang('intro_topuser') . '</p>'; 346 $this->html_graph('topuser', 400, 200); 347 $result = $this->hlp->Query()->topuser($this->tlimit, $this->start, 150, true); 348 $this->html_resulttable($result, '', 150); 349 } 350 351 public function html_topeditor() 352 { 353 echo '<p>' . $this->getLang('intro_topeditor') . '</p>'; 354 $this->html_graph('topeditor', 400, 200); 355 $result = $this->hlp->Query()->topeditor($this->tlimit, $this->start, 150, true); 356 $this->html_resulttable($result, '', 150); 357 } 358 359 public function html_topgroup() 360 { 361 echo '<p>' . $this->getLang('intro_topgroup') . '</p>'; 362 $this->html_graph('topgroup', 400, 200); 363 $result = $this->hlp->Query()->topgroup($this->tlimit, $this->start, 150, true); 364 $this->html_resulttable($result, '', 150); 365 } 366 367 public function html_topgroupedit() 368 { 369 echo '<p>' . $this->getLang('intro_topgroupedit') . '</p>'; 370 $this->html_graph('topgroupedit', 400, 200); 371 $result = $this->hlp->Query()->topgroupedit($this->tlimit, $this->start, 150, true); 372 $this->html_resulttable($result, '', 150); 373 } 374 375 public function html_os() 376 { 377 echo '<p>' . $this->getLang('intro_os') . '</p>'; 378 $this->html_graph('os', 400, 200); 379 $result = $this->hlp->Query()->os($this->tlimit, $this->start, 150, true); 380 $this->html_resulttable($result, '', 150); 381 } 382 383 public function html_referer() 384 { 385 $result = $this->hlp->Query()->aggregate($this->tlimit); 386 387 $all = $result['search'] + $result['external'] + $result['direct']; 388 389 if ($all) { 390 printf( 391 '<p>' . $this->getLang('intro_referer') . '</p>', 392 $all, 393 $result['direct'], 394 (100 * $result['direct'] / $all), 395 $result['search'], 396 (100 * $result['search'] / $all), 397 $result['external'], 398 (100 * $result['external'] / $all) 399 ); 400 } 401 402 $result = $this->hlp->Query()->referer($this->tlimit, $this->start, 150); 403 $this->html_resulttable($result, '', 150); 404 } 405 406 public function html_newreferer() 407 { 408 echo '<p>' . $this->getLang('intro_newreferer') . '</p>'; 409 410 $result = $this->hlp->Query()->newreferer($this->tlimit, $this->start, 150); 411 $this->html_resulttable($result, '', 150); 412 } 413 414 public function html_outlinks() 415 { 416 echo '<p>' . $this->getLang('intro_outlinks') . '</p>'; 417 $result = $this->hlp->Query()->outlinks($this->tlimit, $this->start, 150); 418 $this->html_resulttable($result, '', 150); 419 } 420 421 public function html_searchphrases() 422 { 423 echo '<p>' . $this->getLang('intro_searchphrases') . '</p>'; 424 $result = $this->hlp->Query()->searchphrases(true, $this->tlimit, $this->start, 150); 425 $this->html_resulttable($result, '', 150); 426 } 427 428 public function html_searchwords() 429 { 430 echo '<p>' . $this->getLang('intro_searchwords') . '</p>'; 431 $result = $this->hlp->Query()->searchwords(true, $this->tlimit, $this->start, 150); 432 $this->html_resulttable($result, '', 150); 433 } 434 435 public function html_internalsearchphrases() 436 { 437 echo '<p>' . $this->getLang('intro_internalsearchphrases') . '</p>'; 438 $result = $this->hlp->Query()->searchphrases(false, $this->tlimit, $this->start, 150); 439 $this->html_resulttable($result, '', 150); 440 } 441 442 public function html_internalsearchwords() 443 { 444 echo '<p>' . $this->getLang('intro_internalsearchwords') . '</p>'; 445 $result = $this->hlp->Query()->searchwords(false, $this->tlimit, $this->start, 150); 446 $this->html_resulttable($result, '', 150); 447 } 448 449 public function html_searchengines() 450 { 451 echo '<p>' . $this->getLang('intro_searchengines') . '</p>'; 452 $this->html_graph('searchengines', 400, 200); 453 $result = $this->hlp->Query()->searchengines($this->tlimit, $this->start, 150); 454 $this->html_resulttable($result, '', 150); 455 } 456 457 public function html_resolution() 458 { 459 echo '<p>' . $this->getLang('intro_resolution') . '</p>'; 460 $this->html_graph('resolution', 650, 490); 461 $result = $this->hlp->Query()->resolution($this->tlimit, $this->start, 150); 462 $this->html_resulttable($result, '', 150); 463 } 464 465 public function html_viewport() 466 { 467 echo '<p>' . $this->getLang('intro_viewport') . '</p>'; 468 $this->html_graph('viewport', 650, 490); 469 $result = $this->hlp->Query()->viewport($this->tlimit, $this->start, 150); 470 $this->html_resulttable($result, '', 150); 471 } 472 473 public function html_seenusers() 474 { 475 echo '<p>' . $this->getLang('intro_seenusers') . '</p>'; 476 $result = $this->hlp->Query()->seenusers($this->tlimit, $this->start, 150); 477 $this->html_resulttable($result, '', 150); 478 } 479 480 /** 481 * Display a result in a HTML table 482 */ 483 public function html_resulttable($result, $header = '', $pager = 0) 484 { 485 echo '<table class="inline">'; 486 if (is_array($header)) { 487 echo '<tr>'; 488 foreach ($header as $h) { 489 echo '<th>' . hsc($h) . '</th>'; 490 } 491 echo '</tr>'; 492 } 493 494 $count = 0; 495 if (is_array($result)) foreach ($result as $row) { 496 echo '<tr>'; 497 foreach ($row as $k => $v) { 498 if ($k == 'res_x') continue; 499 if ($k == 'res_y') continue; 500 501 echo '<td class="plg_stats_X' . $k . '">'; 502 if ($k == 'page') { 503 echo '<a href="' . wl($v) . '" class="wikilink1">'; 504 echo hsc($v); 505 echo '</a>'; 506 } elseif ($k == 'media') { 507 echo '<a href="' . ml($v) . '" class="wikilink1">'; 508 echo hsc($v); 509 echo '</a>'; 510 } elseif ($k == 'filesize') { 511 echo filesize_h($v); 512 } elseif ($k == 'url') { 513 $url = hsc($v); 514 $url = preg_replace('/^https?:\/\/(www\.)?/', '', $url); 515 if (strlen($url) > 45) { 516 $url = substr($url, 0, 30) . ' … ' . substr($url, -15); 517 } 518 echo '<a href="' . $v . '" class="urlextern">'; 519 echo $url; 520 echo '</a>'; 521 } elseif ($k == 'ilookup') { 522 echo '<a href="' . wl('', ['id' => $v, 'do' => 'search']) . '">Search</a>'; 523 } elseif ($k == 'lookup') { 524 echo '<a href="http://www.google.com/search?q=' . rawurlencode($v) . '">'; 525 echo '<img src="' . DOKU_BASE . 'lib/plugins/statistics/ico/search/google.png" alt="Google" border="0" />'; 526 echo '</a> '; 527 528 echo '<a href="http://search.yahoo.com/search?p=' . rawurlencode($v) . '">'; 529 echo '<img src="' . DOKU_BASE . 'lib/plugins/statistics/ico/search/yahoo.png" alt="Yahoo!" border="0" />'; 530 echo '</a> '; 531 532 echo '<a href="http://www.bing.com/search?q=' . rawurlencode($v) . '">'; 533 echo '<img src="' . DOKU_BASE . 'lib/plugins/statistics/ico/search/bing.png" alt="Bing" border="0" />'; 534 echo '</a> '; 535 } elseif ($k == 'engine') { 536 include_once(__DIR__ . '/inc/searchengines.php'); 537 if (isset($SEARCHENGINEINFO[$v])) { 538 echo '<a href="' . $SEARCHENGINEINFO[$v][1] . '">' . $SEARCHENGINEINFO[$v][0] . '</a>'; 539 } else { 540 echo hsc(ucwords($v)); 541 } 542 } elseif ($k == 'eflag') { 543 $this->html_icon('search', $v); 544 } elseif ($k == 'bflag') { 545 $this->html_icon('browser', $v); 546 } elseif ($k == 'osflag') { 547 $this->html_icon('os', $v); 548 } elseif ($k == 'cflag') { 549 $this->html_icon('flags', $v); 550 } elseif ($k == 'html') { 551 echo $v; 552 } else { 553 echo hsc($v); 554 } 555 echo '</td>'; 556 } 557 echo '</tr>'; 558 559 if ($pager && ($count == $pager)) break; 560 $count++; 561 } 562 echo '</table>'; 563 564 if ($pager) $this->html_pager($pager, count($result) > $pager); 565 } 566 567 public function html_icon($type, $value) 568 { 569 $value = strtolower(preg_replace('/[^\w]+/', '', $value)); 570 $value = str_replace(' ', '_', $value); 571 572 $file = 'lib/plugins/statistics/ico/' . $type . '/' . $value . '.png'; 573 if ($type == 'flags') { 574 $w = 18; 575 $h = 12; 576 } else { 577 $w = 16; 578 $h = 16; 579 } 580 if (file_exists(DOKU_INC . $file)) { 581 echo '<img src="' . DOKU_BASE . $file . '" alt="' . hsc($value) . '" width="' . $w . '" height="' . $h . '" />'; 582 } 583 } 584} 585