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    {
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) . ' &hellip; ' . 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