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