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