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