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