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>'; 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>'; 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 $this->html_graph('dashboardviews', 700, 280); 271 $this->html_graph('dashboardwiki', 700, 280); 272 echo '</div>'; 273 274 $quickgraphs = [ 275 ['lbl' => 'dash_mostpopular', 'query' => 'pages', 'opt' => 'page'], 276 ['lbl' => 'dash_newincoming', 'query' => 'newreferer', 'opt' => 'newreferer'], 277 ['lbl' => 'dash_topsearch', 'query' => 'searchphrases', 'opt' => 'internalsearchphrases'], 278 ]; 279 280 foreach ($quickgraphs as $graph) { 281 $params = [ 282 'do' => 'admin', 283 'page' => 'statistics', 284 'f' => $this->from, 285 't' => $this->to, 286 'opt' => $graph['opt'], 287 ]; 288 289 echo '<div>'; 290 echo '<h2>' . $this->getLang($graph['lbl']) . '</h2>'; 291 $result = call_user_func([$this->hlp->getQuery(), $graph['query']]); 292 $this->html_resulttable($result); 293 echo '<p><a href="?' . buildURLparams($params) . '" class="more">' . $this->getLang('more') . '…</a></p>'; 294 echo '</div>'; 295 } 296 } 297 298 public function html_history() 299 { 300 echo '<p>' . $this->getLang('intro_history') . '</p>'; 301 $this->html_graph('history_page_count', 600, 200); 302 $this->html_graph('history_page_size', 600, 200); 303 $this->html_graph('history_media_count', 600, 200); 304 $this->html_graph('history_media_size', 600, 200); 305 } 306 307 public function html_countries() 308 { 309 echo '<p>' . $this->getLang('intro_countries') . '</p>'; 310 $this->html_graph('countries', 300, 300); 311 $result = $this->hlp->getQuery()->countries(); 312 $this->html_resulttable($result, '', 150); 313 } 314 315 public function html_page() 316 { 317 echo '<p>' . $this->getLang('intro_page') . '</p>'; 318 $result = $this->hlp->getQuery()->pages(); 319 $this->html_resulttable($result, '', 150); 320 } 321 322 public function html_edits() 323 { 324 echo '<p>' . $this->getLang('intro_edits') . '</p>'; 325 $result = $this->hlp->getQuery()->edits(); 326 $this->html_resulttable($result, '', 150); 327 } 328 329 public function html_images() 330 { 331 echo '<p>' . $this->getLang('intro_images') . '</p>'; 332 333 $result = $this->hlp->getQuery()->imagessum(); 334 echo '<p>'; 335 echo sprintf($this->getLang('trafficsum'), $result[0]['cnt'], filesize_h($result[0]['filesize'])); 336 echo '</p>'; 337 338 $result = $this->hlp->getQuery()->images(); 339 $this->html_resulttable($result, '', 150); 340 } 341 342 public function html_downloads() 343 { 344 echo '<p>' . $this->getLang('intro_downloads') . '</p>'; 345 346 $result = $this->hlp->getQuery()->downloadssum(); 347 echo '<p>'; 348 echo sprintf($this->getLang('trafficsum'), $result[0]['cnt'], filesize_h($result[0]['filesize'])); 349 echo '</p>'; 350 351 $result = $this->hlp->getQuery()->downloads(); 352 $this->html_resulttable($result, '', 150); 353 } 354 355 public function html_browsers() 356 { 357 echo '<p>' . $this->getLang('intro_browsers') . '</p>'; 358 $this->html_graph('browsers', 300, 300); 359 $result = $this->hlp->getQuery()->browsers(false); 360 $this->html_resulttable($result, '', 150); 361 } 362 363 public function html_topuser() 364 { 365 echo '<p>' . $this->getLang('intro_topuser') . '</p>'; 366 $this->html_graph('topuser', 300, 300); 367 $result = $this->hlp->getQuery()->topuser(); 368 $this->html_resulttable($result, '', 150); 369 } 370 371 public function html_topeditor() 372 { 373 echo '<p>' . $this->getLang('intro_topeditor') . '</p>'; 374 $this->html_graph('topeditor', 300, 300); 375 $result = $this->hlp->getQuery()->topeditor(); 376 $this->html_resulttable($result, '', 150); 377 } 378 379 public function html_topgroup() 380 { 381 echo '<p>' . $this->getLang('intro_topgroup') . '</p>'; 382 $this->html_graph('topgroup', 300, 300); 383 $result = $this->hlp->getQuery()->topgroup(); 384 $this->html_resulttable($result, '', 150); 385 } 386 387 public function html_topgroupedit() 388 { 389 echo '<p>' . $this->getLang('intro_topgroupedit') . '</p>'; 390 $this->html_graph('topgroupedit', 300, 300); 391 $result = $this->hlp->getQuery()->topgroupedit(); 392 $this->html_resulttable($result, '', 150); 393 } 394 395 public function html_os() 396 { 397 echo '<p>' . $this->getLang('intro_os') . '</p>'; 398 $this->html_graph('os', 300, 300); 399 $result = $this->hlp->getQuery()->os(); 400 $this->html_resulttable($result, '', 150); 401 } 402 403 public function html_referer() 404 { 405 $result = $this->hlp->getQuery()->aggregate(); 406 407 $all = $result['search'] + $result['external'] + $result['direct']; 408 409 if ($all) { 410 printf( 411 '<p>' . $this->getLang('intro_referer') . '</p>', 412 $all, 413 $result['direct'], 414 (100 * $result['direct'] / $all), 415 $result['search'], 416 (100 * $result['search'] / $all), 417 $result['external'], 418 (100 * $result['external'] / $all) 419 ); 420 } 421 422 $result = $this->hlp->getQuery()->referer(); 423 $this->html_resulttable($result, '', 150); 424 } 425 426 public function html_newreferer() 427 { 428 echo '<p>' . $this->getLang('intro_newreferer') . '</p>'; 429 430 $result = $this->hlp->getQuery()->newreferer(); 431 $this->html_resulttable($result, '', 150); 432 } 433 434 public function html_outlinks() 435 { 436 echo '<p>' . $this->getLang('intro_outlinks') . '</p>'; 437 $result = $this->hlp->getQuery()->outlinks(); 438 $this->html_resulttable($result, '', 150); 439 } 440 441 public function html_searchphrases() 442 { 443 echo '<p>' . $this->getLang('intro_searchphrases') . '</p>'; 444 $result = $this->hlp->getQuery()->searchphrases(true); 445 $this->html_resulttable($result, '', 150); 446 } 447 448 public function html_searchwords() 449 { 450 echo '<p>' . $this->getLang('intro_searchwords') . '</p>'; 451 $result = $this->hlp->getQuery()->searchwords(true); 452 $this->html_resulttable($result, '', 150); 453 } 454 455 public function html_internalsearchphrases() 456 { 457 echo '<p>' . $this->getLang('intro_internalsearchphrases') . '</p>'; 458 $result = $this->hlp->getQuery()->searchphrases(false); 459 $this->html_resulttable($result, '', 150); 460 } 461 462 public function html_internalsearchwords() 463 { 464 echo '<p>' . $this->getLang('intro_internalsearchwords') . '</p>'; 465 $result = $this->hlp->getQuery()->searchwords(false); 466 $this->html_resulttable($result, '', 150); 467 } 468 469 public function html_searchengines() 470 { 471 echo '<p>' . $this->getLang('intro_searchengines') . '</p>'; 472 $this->html_graph('searchengines', 400, 200); 473 $result = $this->hlp->getQuery()->searchengines(); 474 $this->html_resulttable($result, '', 150); 475 } 476 477 public function html_resolution() 478 { 479 echo '<p>' . $this->getLang('intro_resolution') . '</p>'; 480 $this->html_graph('resolution', 650, 490); 481 $result = $this->hlp->getQuery()->resolution(); 482 $this->html_resulttable($result, '', 150); 483 } 484 485 public function html_viewport() 486 { 487 echo '<p>' . $this->getLang('intro_viewport') . '</p>'; 488 $this->html_graph('viewport', 650, 490); 489 $result = $this->hlp->getQuery()->viewport(); 490 $this->html_resulttable($result, '', 150); 491 } 492 493 public function html_seenusers() 494 { 495 echo '<p>' . $this->getLang('intro_seenusers') . '</p>'; 496 $result = $this->hlp->getQuery()->seenusers(); 497 $this->html_resulttable($result, '', 150); 498 } 499 500 /** 501 * Display a result in a HTML table 502 */ 503 public function html_resulttable($result, $header = '', $pager = 0) 504 { 505 echo '<table class="inline">'; 506 if (is_array($header)) { 507 echo '<tr>'; 508 foreach ($header as $h) { 509 echo '<th>' . hsc($h) . '</th>'; 510 } 511 echo '</tr>'; 512 } 513 514 $count = 0; 515 if (is_array($result)) foreach ($result as $row) { 516 echo '<tr>'; 517 foreach ($row as $k => $v) { 518 if ($k == 'res_x') continue; 519 if ($k == 'res_y') continue; 520 521 echo '<td class="plg_stats_X' . $k . '">'; 522 if ($k == 'page') { 523 echo '<a href="' . wl($v) . '" class="wikilink1">'; 524 echo hsc($v); 525 echo '</a>'; 526 } elseif ($k == 'media') { 527 echo '<a href="' . ml($v) . '" class="wikilink1">'; 528 echo hsc($v); 529 echo '</a>'; 530 } elseif ($k == 'filesize') { 531 echo filesize_h($v); 532 } elseif ($k == 'url') { 533 $url = hsc($v); 534 $url = preg_replace('/^https?:\/\/(www\.)?/', '', $url); 535 if (strlen($url) > 45) { 536 $url = substr($url, 0, 30) . ' … ' . substr($url, -15); 537 } 538 echo '<a href="' . $v . '" class="urlextern">'; 539 echo $url; 540 echo '</a>'; 541 } elseif ($k == 'ilookup') { 542 echo '<a href="' . wl('', ['id' => $v, 'do' => 'search']) . '">Search</a>'; 543 } elseif ($k == 'lookup') { 544 echo '<a href="http://www.google.com/search?q=' . rawurlencode($v) . '">'; 545 echo '<img src="' . DOKU_BASE . 'lib/plugins/statistics/ico/search/google.png" alt="Google" />'; 546 echo '</a> '; 547 548 echo '<a href="http://search.yahoo.com/search?p=' . rawurlencode($v) . '">'; 549 echo '<img src="' . DOKU_BASE . 'lib/plugins/statistics/ico/search/yahoo.png" alt="Yahoo!" />'; 550 echo '</a> '; 551 552 echo '<a href="http://www.bing.com/search?q=' . rawurlencode($v) . '">'; 553 echo '<img src="' . DOKU_BASE . 'lib/plugins/statistics/ico/search/bing.png" alt="Bing" />'; 554 echo '</a> '; 555 } elseif ($k == 'engine') { 556 // FIXME thhis is not correct anymore 557 if (isset($SEARCHENGINEINFO[$v])) { 558 echo '<a href="' . $SEARCHENGINEINFO[$v][1] . '">' . $SEARCHENGINEINFO[$v][0] . '</a>'; 559 } else { 560 echo hsc(ucwords($v)); 561 } 562 } elseif ($k == 'html') { 563 echo $v; 564 } else { 565 echo hsc($v); 566 } 567 echo '</td>'; 568 } 569 echo '</tr>'; 570 571 if ($pager && ($count == $pager)) break; 572 $count++; 573 } 574 echo '</table>'; 575 576 if ($pager) $this->html_pager($pager, count($result) > $pager); 577 } 578} 579