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