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