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