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