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