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