xref: /plugin/statistics/Query.php (revision 7428e816a000a79987e124279d701cb1f094e3b3)
1<?php
2
3namespace dokuwiki\plugin\statistics;
4
5use helper_plugin_statistics;
6
7class Query
8{
9    protected $hlp;
10    protected $from;
11    protected $to;
12
13    public function __construct(helper_plugin_statistics $hlp)
14    {
15        $this->hlp = $hlp;
16        $today = date('Y-m-d');
17        $this->setTimeFrame($today, $today);
18    }
19
20    /**
21     * Set the time frame for all queries
22     */
23    public function setTimeFrame($from, $to)
24    {
25        // fixme add better sanity checking here:
26        $from = preg_replace('/[^\d\-]+/', '', $from);
27        $to = preg_replace('/[^\d\-]+/', '', $to);
28        if (!$from) $from = date('Y-m-d');
29        if (!$to) $to = date('Y-m-d');
30
31        $this->from = $from. ' 00:00:00';
32        $this->to = $to. ' 23:59:59';
33    }
34
35    /**
36     * Return some aggregated statistics
37     */
38    public function aggregate()
39    {
40        $data = [];
41
42        $sql = "SELECT ref_type, COUNT(*) as cnt
43                  FROM access as A
44                 WHERE A.dt >= ? AND A.dt <= ?
45                   AND ua_type = ?
46              GROUP BY ref_type";
47        $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser']);
48
49        if (is_array($result)) foreach ($result as $row) {
50            if ($row['ref_type'] == 'search') $data['search'] = $row['cnt'];
51            if ($row['ref_type'] == 'external') $data['external'] = $row['cnt'];
52            if ($row['ref_type'] == 'internal') $data['internal'] = $row['cnt'];
53            if ($row['ref_type'] == '') $data['direct'] = $row['cnt'];
54        }
55
56        // general user and session info
57        $sql = "SELECT COUNT(DISTINCT session) as sessions,
58                       COUNT(session) as views,
59                       COUNT(DISTINCT user) as users,
60                       COUNT(DISTINCT uid) as visitors
61                  FROM access as A
62                 WHERE A.dt >= ? AND A.dt <= ?
63                   AND ua_type = ?";
64        $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser']);
65
66        $data['users'] = max($result[0]['users'] - 1, 0); // subtract empty user
67        $data['sessions'] = $result[0]['sessions'];
68        $data['pageviews'] = $result[0]['views'];
69        $data['visitors'] = $result[0]['visitors'];
70
71        // calculate bounce rate
72        if ($data['sessions']) {
73            $sql = "SELECT COUNT(*) as cnt
74                      FROM session as A
75                     WHERE A.dt >= ? AND A.dt <= ?
76                       AND views = ?";
77            $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 1]);
78            $data['bouncerate'] = $result[0]['cnt'] * 100 / $data['sessions'];
79            $data['newvisitors'] = $result[0]['cnt'] * 100 / $data['sessions'];
80        }
81
82        // calculate avg. number of views per session
83        $sql = "SELECT AVG(views) as cnt
84                  FROM session as A
85                 WHERE A.dt >= ? AND A.dt <= ?";
86        $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to]);
87        $data['avgpages'] = $result[0]['cnt'];
88
89        /* not used currently
90                $sql = "SELECT COUNT(id) as robots
91                          FROM ".$this->hlp->prefix."access as A
92                         WHERE $tlimit
93                           AND ua_type = 'robot'";
94                $result = $this->hlp->runSQL($sql);
95                $data['robots'] = $result[0]['robots'];
96        */
97
98        // average time spent on the site
99        $sql = "SELECT AVG(end - dt)/60 as time
100                  FROM session as A
101                 WHERE A.dt >= ? AND A.dt <= ?
102                   AND dt != end
103                   AND DATE(dt) = DATE(end)";
104        $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to]);
105        $data['timespent'] = $result[0]['time'];
106
107        // logins
108        $sql = "SELECT COUNT(*) as logins
109                  FROM logins as A
110                 WHERE A.dt >= ? AND A.dt <= ?
111                   AND (type = ? OR type = ?)";
112        $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'l', 'p']);
113        $data['logins'] = $result[0]['logins'];
114
115        // registrations
116        $sql = "SELECT COUNT(*) as registrations
117                  FROM logins as A
118                 WHERE A.dt >= ? AND A.dt <= ?
119                   AND type = ?";
120        $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'C']);
121        $data['registrations'] = $result[0]['registrations'];
122
123        // current users
124        $sql = "SELECT COUNT(*) as current
125                  FROM lastseen
126                 WHERE dt >= datetime('now', '-10 minutes')";
127        $result = $this->hlp->getDB()->queryAll($sql);
128        $data['current'] = $result[0]['current'];
129
130        return $data;
131    }
132
133    /**
134     * standard statistics follow, only accesses made by browsers are counted
135     * for general stats like browser or OS only visitors not pageviews are counted
136     */
137
138    /**
139     * Return some trend data about visits and edits in the wiki
140     */
141    public function dashboardviews($hours = false)
142    {
143        if ($hours) {
144            $TIME = 'strftime(\'%H\', dt)';
145        } else {
146            $TIME = 'DATE(dt)';
147        }
148
149        $data = [];
150
151        // access trends
152        $sql = "SELECT $TIME as time,
153                       COUNT(DISTINCT session) as sessions,
154                       COUNT(session) as pageviews,
155                       COUNT(DISTINCT uid) as visitors
156                  FROM access as A
157                 WHERE A.dt >= ? AND A.dt <= ?
158                   AND ua_type = ?
159              GROUP BY $TIME
160              ORDER BY time";
161        $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser']);
162        foreach ($result as $row) {
163            $data[$row['time']]['sessions'] = $row['sessions'];
164            $data[$row['time']]['pageviews'] = $row['pageviews'];
165            $data[$row['time']]['visitors'] = $row['visitors'];
166        }
167        return $data;
168    }
169
170    public function dashboardwiki($hours = false)
171    {
172        if ($hours) {
173            $TIME = 'strftime(\'%H\', dt)';
174        } else {
175            $TIME = 'DATE(dt)';
176        }
177
178        $data = [];
179
180        // edit trends
181        foreach (['E', 'C', 'D'] as $type) {
182            $sql = "SELECT $TIME as time,
183                           COUNT(*) as cnt
184                      FROM edits as A
185                     WHERE A.dt >= ? AND A.dt <= ?
186                       AND type = ?
187                  GROUP BY $TIME
188                  ORDER BY time";
189            $result = $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, $type]);
190            foreach ($result as $row) {
191                $data[$row['time']][$type] = $row['cnt'];
192            }
193        }
194        ksort($data);
195        return $data;
196    }
197
198    public function history($info, $interval = false)
199    {
200        if ($interval == 'weeks') {
201            $TIME = 'strftime(\'%Y\', dt), strftime(\'%W\', dt)';
202        } elseif ($interval == 'months') {
203            $TIME = 'strftime(\'%Y-%m\', dt)';
204        } else {
205            $TIME = 'dt';
206        }
207
208        $mod = 1;
209        if ($info == 'media_size' || $info == 'page_size') {
210            $mod = 1024 * 1024;
211        }
212
213        $sql = "SELECT $TIME as time,
214                       AVG(value)/$mod as cnt
215                  FROM history as A
216                 WHERE A.dt >= ? AND A.dt <= ?
217                   AND info = ?
218                  GROUP BY $TIME
219                  ORDER BY $TIME";
220        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, $info]);
221    }
222
223    public function searchengines($start = 0, $limit = 20)
224    {
225        $sql = "SELECT COUNT(*) as cnt, engine as eflag, engine
226                  FROM search as A
227                 WHERE A.dt >= ? AND A.dt <= ?
228              GROUP BY engine
229              ORDER BY cnt DESC, engine" .
230            $this->mklimit($start, $limit);
231        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to]);
232    }
233
234    public function searchphrases($extern, $start = 0, $limit = 20)
235    {
236        if ($extern) {
237            $WHERE = "engine != ?";
238            $engineParam = 'dokuwiki';
239            $I = '';
240        } else {
241            $WHERE = "engine = ?";
242            $engineParam = 'dokuwiki';
243            $I = 'i';
244        }
245        $sql = "SELECT COUNT(*) as cnt, query, query as ${I}lookup
246                  FROM search as A
247                 WHERE A.dt >= ? AND A.dt <= ?
248                   AND $WHERE
249              GROUP BY query
250              ORDER BY cnt DESC, query" .
251            $this->mklimit($start, $limit);
252        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, $engineParam]);
253    }
254
255    public function searchwords($extern, $start = 0, $limit = 20)
256    {
257        if ($extern) {
258            $WHERE = "engine != ?";
259            $engineParam = 'dokuwiki';
260            $I = '';
261        } else {
262            $WHERE = "engine = ?";
263            $engineParam = 'dokuwiki';
264            $I = 'i';
265        }
266        $sql = "SELECT COUNT(*) as cnt, word, word as ${I}lookup
267                  FROM search as A,
268                       searchwords as B
269                 WHERE A.dt >= ? AND A.dt <= ?
270                   AND A.id = B.sid
271                   AND $WHERE
272              GROUP BY word
273              ORDER BY cnt DESC, word" .
274            $this->mklimit($start, $limit);
275        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, $engineParam]);
276    }
277
278    public function outlinks($start = 0, $limit = 20)
279    {
280        $sql = "SELECT COUNT(*) as cnt, link as url
281                  FROM outlinks as A
282                 WHERE A.dt >= ? AND A.dt <= ?
283              GROUP BY link
284              ORDER BY cnt DESC, link" .
285            $this->mklimit($start, $limit);
286        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to]);
287    }
288
289    public function pages($start = 0, $limit = 20)
290    {
291        $sql = "SELECT COUNT(*) as cnt, page
292                  FROM access as A
293                 WHERE A.dt >= ? AND A.dt <= ?
294                   AND ua_type = ?
295              GROUP BY page
296              ORDER BY cnt DESC, page" .
297            $this->mklimit($start, $limit);
298        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser']);
299    }
300
301    public function edits($start = 0, $limit = 20)
302    {
303        $sql = "SELECT COUNT(*) as cnt, page
304                  FROM edits as A
305                 WHERE A.dt >= ? AND A.dt <= ?
306              GROUP BY page
307              ORDER BY cnt DESC, page" .
308            $this->mklimit($start, $limit);
309        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to]);
310    }
311
312    public function images($start = 0, $limit = 20)
313    {
314        $sql = "SELECT COUNT(*) as cnt, media, SUM(size) as filesize
315                  FROM media as A
316                 WHERE A.dt >= ? AND A.dt <= ?
317                   AND mime1 = ?
318              GROUP BY media
319              ORDER BY cnt DESC, media" .
320            $this->mklimit($start, $limit);
321        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'image']);
322    }
323
324    public function imagessum()
325    {
326        $sql = "SELECT COUNT(*) as cnt, SUM(size) as filesize
327                  FROM media as A
328                 WHERE A.dt >= ? AND A.dt <= ?
329                   AND mime1 = ?";
330        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'image']);
331    }
332
333    public function downloads($start = 0, $limit = 20)
334    {
335        $sql = "SELECT COUNT(*) as cnt, media, SUM(size) as filesize
336                  FROM media as A
337                 WHERE A.dt >= ? AND A.dt <= ?
338                   AND mime1 != ?
339              GROUP BY media
340              ORDER BY cnt DESC, media" .
341            $this->mklimit($start, $limit);
342        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'image']);
343    }
344
345    public function downloadssum()
346    {
347        $sql = "SELECT COUNT(*) as cnt, SUM(size) as filesize
348                  FROM media as A
349                 WHERE A.dt >= ? AND A.dt <= ?
350                   AND mime1 != ?";
351        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'image']);
352    }
353
354    public function referer($start = 0, $limit = 20)
355    {
356        $sql = "SELECT COUNT(*) as cnt, ref as url
357                  FROM access as A
358                 WHERE A.dt >= ? AND A.dt <= ?
359                   AND ua_type = ?
360                   AND ref_type = ?
361              GROUP BY ref_md5
362              ORDER BY cnt DESC, url" .
363            $this->mklimit($start, $limit);
364        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser', 'external']);
365    }
366
367    public function newreferer($start = 0, $limit = 20)
368    {
369        $sql = "SELECT COUNT(*) as cnt, ref as url
370                  FROM access as B,
371                       refseen as A
372                 WHERE B.dt >= ? AND B.dt <= ?
373                   AND ua_type = ?
374                   AND ref_type = ?
375                   AND A.ref_md5 = B.ref_md5
376              GROUP BY A.ref_md5
377              ORDER BY cnt DESC, url" .
378            $this->mklimit($start, $limit);
379        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser', 'external']);
380    }
381
382    public function countries($start = 0, $limit = 20)
383    {
384        $sql = "SELECT COUNT(DISTINCT session) as cnt, B.code AS cflag, B.country
385                  FROM access as A,
386                       iplocation as B
387                 WHERE A.dt >= ? AND A.dt <= ?
388                   AND A.ip = B.ip
389              GROUP BY B.code
390              ORDER BY cnt DESC, B.country" .
391            $this->mklimit($start, $limit);
392        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to]);
393    }
394
395    public function browsers($start = 0, $limit = 20, $ext = true)
396    {
397        if ($ext) {
398            $sel = 'ua_info as bflag, ua_info as browser, ua_ver';
399            $grp = 'ua_info, ua_ver';
400        } else {
401            $grp = 'ua_info';
402            $sel = 'ua_info';
403        }
404
405        $sql = "SELECT COUNT(DISTINCT session) as cnt, $sel
406                  FROM access as A
407                 WHERE A.dt >= ? AND A.dt <= ?
408                   AND ua_type = ?
409              GROUP BY $grp
410              ORDER BY cnt DESC, ua_info" .
411            $this->mklimit($start, $limit);
412        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser']);
413    }
414
415    public function os($start = 0, $limit = 20)
416    {
417        $sql = "SELECT COUNT(DISTINCT session) as cnt, os as osflag, os
418                  FROM access as A
419                 WHERE A.dt >= ? AND A.dt <= ?
420                   AND ua_type = ?
421              GROUP BY os
422              ORDER BY cnt DESC, os" .
423            $this->mklimit($start, $limit);
424        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser']);
425    }
426
427    public function topuser($start = 0, $limit = 20)
428    {
429        $sql = "SELECT COUNT(*) as cnt, user
430                  FROM access as A
431                 WHERE A.dt >= ? AND A.dt <= ?
432                   AND ua_type = ?
433                   AND user != ?
434              GROUP BY user
435              ORDER BY cnt DESC, user" .
436            $this->mklimit($start, $limit);
437        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser', '']);
438    }
439
440    public function topeditor($start = 0, $limit = 20)
441    {
442        $sql = "SELECT COUNT(*) as cnt, user
443                  FROM edits as A
444                 WHERE A.dt >= ? AND A.dt <= ?
445                   AND user != ?
446              GROUP BY user
447              ORDER BY cnt DESC, user" .
448            $this->mklimit($start, $limit);
449        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, '']);
450    }
451
452    public function topgroup($start = 0, $limit = 20)
453    {
454        $sql = "SELECT COUNT(*) as cnt, `group`
455                  FROM groups as A
456                 WHERE A.dt >= ? AND A.dt <= ?
457                   AND `type` = ?
458              GROUP BY `group`
459              ORDER BY cnt DESC, `group`" .
460            $this->mklimit($start, $limit);
461        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'view']);
462    }
463
464    public function topgroupedit($start = 0, $limit = 20)
465    {
466        $sql = "SELECT COUNT(*) as cnt, `group`
467                  FROM groups as A
468                 WHERE A.dt >= ? AND A.dt <= ?
469                   AND `type` = ?
470              GROUP BY `group`
471              ORDER BY cnt DESC, `group`" .
472            $this->mklimit($start, $limit);
473        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'edit']);
474    }
475
476
477    public function resolution($start = 0, $limit = 20)
478    {
479        $sql = "SELECT COUNT(DISTINCT uid) as cnt,
480                       ROUND(screen_x/100)*100 as res_x,
481                       ROUND(screen_y/100)*100 as res_y,
482                       (ROUND(screen_x/100)*100 || 'x' || ROUND(screen_y/100)*100) as resolution
483                  FROM access as A
484                 WHERE A.dt >= ? AND A.dt <= ?
485                   AND ua_type  = ?
486                   AND screen_x != ?
487                   AND screen_y != ?
488              GROUP BY resolution
489              ORDER BY cnt DESC" .
490            $this->mklimit($start, $limit);
491        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser', 0, 0]);
492    }
493
494    public function viewport($start = 0, $limit = 20)
495    {
496        $sql = "SELECT COUNT(DISTINCT uid) as cnt,
497                       ROUND(view_x/100)*100 as res_x,
498                       ROUND(view_y/100)*100 as res_y,
499                       (ROUND(view_x/100)*100 || 'x' || ROUND(view_y/100)*100) as resolution
500                  FROM access as A
501                 WHERE A.dt >= ? AND A.dt <= ?
502                   AND ua_type  = ?
503                   AND view_x != ?
504                   AND view_y != ?
505              GROUP BY resolution
506              ORDER BY cnt DESC" .
507            $this->mklimit($start, $limit);
508
509        return $this->hlp->getDB()->queryAll($sql, [$this->from, $this->to, 'browser', 0, 0]);
510    }
511
512    public function seenusers($start = 0, $limit = 20)
513    {
514        $sql = "SELECT `user`, `dt`
515                  FROM " . $this->hlp->prefix . "lastseen as A
516              ORDER BY `dt` DESC" .
517            $this->mklimit($start, $limit);
518
519        return $this->hlp->getDB()->queryAll($sql);
520    }
521
522
523    /**
524     * Builds a limit clause
525     */
526    public function mklimit($start, $limit)
527    {
528        $start = (int)$start;
529        $limit = (int)$limit;
530        if ($limit) {
531            $limit += 1;
532            return " LIMIT $start,$limit";
533        } elseif ($start) {
534            return " OFFSET $start";
535        }
536        return '';
537    }
538
539}
540