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