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