xref: /plugin/statistics/admin.php (revision df19f3f7ff425a4e7510d013b63f2db4d401460f)
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&amp;page=statistics&amp;opt=' . $page .
186                        '&amp;f=' . $this->from .
187                        '&amp;t=' . $this->to,
188                        $this->getLang($page),
189                        2,
190                        ''
191                    );
192                }
193            } else {
194                $toc[] = html_mktocitem(
195                    '?do=admin&amp;page=statistics&amp;opt=' . $key .
196                    '&amp;f=' . $this->from .
197                    '&amp;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        echo '<p>' . $this->getLang("intro_$name") . '</p>';
360
361
362        $graph = $this->hlp->getGraph($this->from, $this->to, 300, 300);
363        $graph->sumUpPieChart($name);
364
365        $result = $this->hlp->getQuery()->$name();
366        $this->html_resulttable($result, '', 150);
367    }
368
369    public function printTableAndScatterGraph()
370    {
371        echo '<p>' . $this->getLang('intro_resolution') . '</p>';
372        $this->html_graph('resolution', 650, 490);
373        $result = $this->hlp->getQuery()->resolution();
374        $this->html_resulttable($result, '', 150);
375    }
376
377    public function printTable($name)
378    {
379        echo '<p>' . $this->getLang("intro_$name") . '</p>';
380        $result = $this->hlp->getQuery()->$name();
381        $this->html_resulttable($result, '', 150);
382    }
383
384
385    public function printImages()
386    {
387        echo '<p>' . $this->getLang('intro_images') . '</p>';
388
389        $result = $this->hlp->getQuery()->imagessum();
390        echo '<p>';
391        echo sprintf($this->getLang('trafficsum'), $result[0]['cnt'], filesize_h($result[0]['filesize']));
392        echo '</p>';
393
394        $result = $this->hlp->getQuery()->images();
395        $this->html_resulttable($result, '', 150);
396    }
397
398    public function printDownloads()
399    {
400        echo '<p>' . $this->getLang('intro_downloads') . '</p>';
401
402        $result = $this->hlp->getQuery()->downloadssum();
403        echo '<p>';
404        echo sprintf($this->getLang('trafficsum'), $result[0]['cnt'], filesize_h($result[0]['filesize']));
405        echo '</p>';
406
407        $result = $this->hlp->getQuery()->downloads();
408        $this->html_resulttable($result, '', 150);
409    }
410
411    public function printReferer()
412    {
413        $result = $this->hlp->getQuery()->aggregate();
414
415        if ($result['referers']) {
416            printf(
417                '<p>' . $this->getLang('intro_referer') . '</p>',
418                $result['referers'],
419                $result['direct'],
420                (100 * $result['direct'] / $result['referers']),
421                $result['search'],
422                (100 * $result['search'] / $result['referers']),
423                $result['external'],
424                (100 * $result['external'] / $result['referers'])
425            );
426        }
427
428        $result = $this->hlp->getQuery()->referer();
429        $this->html_resulttable($result, '', 150);
430    }
431
432    // endregion
433
434
435    /**
436     * Display a result in a HTML table
437     */
438    public function html_resulttable($result, $header = '', $pager = 0)
439    {
440        echo '<table class="inline">';
441        if (is_array($header)) {
442            echo '<tr>';
443            foreach ($header as $h) {
444                echo '<th>' . hsc($h) . '</th>';
445            }
446            echo '</tr>';
447        }
448
449        $count = 0;
450        if (is_array($result)) foreach ($result as $row) {
451            echo '<tr>';
452            foreach ($row as $k => $v) {
453                if ($k == 'res_x') continue;
454                if ($k == 'res_y') continue;
455
456                echo '<td class="plg_stats_X' . $k . '">';
457                if ($k == 'page') {
458                    echo '<a href="' . wl($v) . '" class="wikilink1">';
459                    echo hsc($v);
460                    echo '</a>';
461                } elseif ($k == 'media') {
462                    echo '<a href="' . ml($v) . '" class="wikilink1">';
463                    echo hsc($v);
464                    echo '</a>';
465                } elseif ($k == 'filesize') {
466                    echo filesize_h($v);
467                } elseif ($k == 'url') {
468                    $url = hsc($v);
469                    $url = preg_replace('/^https?:\/\/(www\.)?/', '', $url);
470                    if (strlen($url) > 45) {
471                        $url = substr($url, 0, 30) . ' &hellip; ' . substr($url, -15);
472                    }
473                    echo '<a href="' . $v . '" class="urlextern">';
474                    echo $url;
475                    echo '</a>';
476                } elseif ($k == 'ilookup') {
477                    echo '<a href="' . wl('', ['id' => $v, 'do' => 'search']) . '">Search</a>';
478                } elseif ($k == 'engine') {
479                    $name = SearchEngines::getName($v);
480                    $url = SearchEngines::getURL($v);
481                    if ($url) {
482                        echo '<a href="' . $url . '">' . hsc($name) . '</a>';
483                    } else {
484                        echo hsc($name);
485                    }
486                } elseif ($k == 'html') {
487                    echo $v;
488                } else {
489                    echo hsc($v);
490                }
491                echo '</td>';
492            }
493            echo '</tr>';
494
495            if ($pager && ($count == $pager)) break;
496            $count++;
497        }
498        echo '</table>';
499
500        if ($pager) $this->html_pager($pager, count($result) > $pager);
501    }
502}
503