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