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