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