xref: /plugin/statistics/Query.php (revision 10dcb86f38cab1d9cfa19911e1d59c086626d7c8)
1b6632b6eSAndreas Gohr<?php
2b6632b6eSAndreas Gohr
3b6632b6eSAndreas Gohrnamespace dokuwiki\plugin\statistics;
4b6632b6eSAndreas Gohr
5f9e60319SAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB;
6b6632b6eSAndreas Gohruse helper_plugin_statistics;
7b6632b6eSAndreas Gohr
8f9e60319SAndreas Gohr/**
9f9e60319SAndreas Gohr * This class defines a bunch of SQL queries to fetch various statistics from the database
10f9e60319SAndreas Gohr */
11b6632b6eSAndreas Gohrclass Query
12b6632b6eSAndreas Gohr{
13f9e60319SAndreas Gohr    protected helper_plugin_statistics $hlp;
14f9e60319SAndreas Gohr    protected SQLiteDB $db;
15f9e60319SAndreas Gohr    protected string $from;
16f9e60319SAndreas Gohr    protected string $to;
17f9e60319SAndreas Gohr    protected string $limit = '';
18b6632b6eSAndreas Gohr
19f9e60319SAndreas Gohr    /**
20f9e60319SAndreas Gohr     * @param helper_plugin_statistics $hlp
21f9e60319SAndreas Gohr     */
22b6632b6eSAndreas Gohr    public function __construct(helper_plugin_statistics $hlp)
23b6632b6eSAndreas Gohr    {
24b6632b6eSAndreas Gohr        $this->hlp = $hlp;
25f9e60319SAndreas Gohr        $this->db = $hlp->getDB();
267428e816SAndreas Gohr        $today = date('Y-m-d');
277428e816SAndreas Gohr        $this->setTimeFrame($today, $today);
28f9e60319SAndreas Gohr        $this->setPagination(0, 20);
297428e816SAndreas Gohr    }
307428e816SAndreas Gohr
317428e816SAndreas Gohr    /**
327428e816SAndreas Gohr     * Set the time frame for all queries
33f9e60319SAndreas Gohr     *
34f9e60319SAndreas Gohr     * @param string $from The start date as YYYY-MM-DD
35f9e60319SAndreas Gohr     * @param string $to The end date as YYYY-MM-DD
367428e816SAndreas Gohr     */
37f9e60319SAndreas Gohr    public function setTimeFrame(string $from, string $to): void
387428e816SAndreas Gohr    {
39f9e60319SAndreas Gohr        try {
40f9e60319SAndreas Gohr            $from = new \DateTime($from);
41f9e60319SAndreas Gohr            $to = new \DateTime($to);
42f9e60319SAndreas Gohr        } catch (\Exception $e) {
43f9e60319SAndreas Gohr            $from = new \DateTime();
44f9e60319SAndreas Gohr            $to = new \DateTime();
45f9e60319SAndreas Gohr        }
46f9e60319SAndreas Gohr        $from->setTime(0, 0);
47f9e60319SAndreas Gohr        $to->setTime(23, 59, 59);
487428e816SAndreas Gohr
49f9e60319SAndreas Gohr        $this->from = $from->format('Y-m-d H:i:s');
50f9e60319SAndreas Gohr        $this->to = $to->format('Y-m-d H:i:s');
51f9e60319SAndreas Gohr    }
52f9e60319SAndreas Gohr
53f9e60319SAndreas Gohr    /**
54f9e60319SAndreas Gohr     * Set the pagination settings for some queries
55f9e60319SAndreas Gohr     *
56f9e60319SAndreas Gohr     * @param int $start The start offset
57f9e60319SAndreas Gohr     * @param int $limit The number of results. If one more is returned, there is another page
58f9e60319SAndreas Gohr     * @return void
59f9e60319SAndreas Gohr     */
60f9e60319SAndreas Gohr    public function setPagination(int $start, int $limit)
61f9e60319SAndreas Gohr    {
62f9e60319SAndreas Gohr        // when a limit is set, one more is fetched to indicate when a next page exists
63f9e60319SAndreas Gohr        if ($limit) $limit += 1;
64f9e60319SAndreas Gohr
65f9e60319SAndreas Gohr        if ($limit) {
66f9e60319SAndreas Gohr            $this->limit = " LIMIT $start,$limit";
67f9e60319SAndreas Gohr        } elseif ($start) {
68f9e60319SAndreas Gohr            $this->limit = " OFFSET $start";
69f9e60319SAndreas Gohr        }
70b6632b6eSAndreas Gohr    }
71b6632b6eSAndreas Gohr
72b6632b6eSAndreas Gohr    /**
73b6632b6eSAndreas Gohr     * Return some aggregated statistics
74b6632b6eSAndreas Gohr     */
75f9e60319SAndreas Gohr    public function aggregate(): array
76b6632b6eSAndreas Gohr    {
77b6632b6eSAndreas Gohr        $data = [];
78b6632b6eSAndreas Gohr
79*10dcb86fSAndreas Gohr        // Count referrer types by joining with referers table
80*10dcb86fSAndreas Gohr        $sql = "SELECT
81*10dcb86fSAndreas Gohr                    CASE
82*10dcb86fSAndreas Gohr                        WHEN R.engine IS NOT NULL THEN 'search'
83*10dcb86fSAndreas Gohr                        WHEN R.url IS NOT NULL AND R.url != '' THEN 'external'
84*10dcb86fSAndreas Gohr                        ELSE 'direct'
85*10dcb86fSAndreas Gohr                    END as ref_type,
86*10dcb86fSAndreas Gohr                    COUNT(*) as cnt
87*10dcb86fSAndreas Gohr                  FROM pageviews as P
88*10dcb86fSAndreas Gohr                  LEFT JOIN referers as R ON P.ref_id = R.id
89*10dcb86fSAndreas Gohr                  LEFT JOIN sessions as S ON P.session = S.session
90*10dcb86fSAndreas Gohr                 WHERE P.dt >= ? AND P.dt <= ?
91*10dcb86fSAndreas Gohr                   AND S.ua_type = ?
92b6632b6eSAndreas Gohr              GROUP BY ref_type";
93f9e60319SAndreas Gohr        $result = $this->db->queryAll($sql, [$this->from, $this->to, 'browser']);
94b6632b6eSAndreas Gohr
95f9e60319SAndreas Gohr        foreach ($result as $row) {
96b6632b6eSAndreas Gohr            if ($row['ref_type'] == 'search') $data['search'] = $row['cnt'];
97b6632b6eSAndreas Gohr            if ($row['ref_type'] == 'external') $data['external'] = $row['cnt'];
98b6632b6eSAndreas Gohr            if ($row['ref_type'] == 'internal') $data['internal'] = $row['cnt'];
99*10dcb86fSAndreas Gohr            if ($row['ref_type'] == 'direct') $data['direct'] = $row['cnt'];
100b6632b6eSAndreas Gohr        }
101b6632b6eSAndreas Gohr
102b6632b6eSAndreas Gohr        // general user and session info
103*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(DISTINCT P.session) as sessions,
104*10dcb86fSAndreas Gohr                       COUNT(P.session) as views,
105*10dcb86fSAndreas Gohr                       COUNT(DISTINCT S.user) as users,
106*10dcb86fSAndreas Gohr                       COUNT(DISTINCT S.uid) as visitors
107*10dcb86fSAndreas Gohr                  FROM pageviews as P
108*10dcb86fSAndreas Gohr                  LEFT JOIN sessions as S ON P.session = S.session
109*10dcb86fSAndreas Gohr                 WHERE P.dt >= ? AND P.dt <= ?
110*10dcb86fSAndreas Gohr                   AND S.ua_type = ?";
111f9e60319SAndreas Gohr        $result = $this->db->queryRecord($sql, [$this->from, $this->to, 'browser']);
112b6632b6eSAndreas Gohr
113*10dcb86fSAndreas Gohr        $data['users'] = $result['users'];
114f9e60319SAndreas Gohr        $data['sessions'] = $result['sessions'];
115f9e60319SAndreas Gohr        $data['pageviews'] = $result['views'];
116f9e60319SAndreas Gohr        $data['visitors'] = $result['visitors'];
117b6632b6eSAndreas Gohr
118*10dcb86fSAndreas Gohr        // calculate bounce rate (sessions with only 1 page view)
119b6632b6eSAndreas Gohr        if ($data['sessions']) {
120b6632b6eSAndreas Gohr            $sql = "SELECT COUNT(*) as cnt
121*10dcb86fSAndreas Gohr                      FROM (
122*10dcb86fSAndreas Gohr                          SELECT P.session, COUNT(*) as views
123*10dcb86fSAndreas Gohr                            FROM pageviews as P
124*10dcb86fSAndreas Gohr                            LEFT JOIN sessions as S ON P.session = S.session
125*10dcb86fSAndreas Gohr                           WHERE P.dt >= ? AND P.dt <= ?
126*10dcb86fSAndreas Gohr                             AND S.ua_type = ?
127*10dcb86fSAndreas Gohr                        GROUP BY P.session
128*10dcb86fSAndreas Gohr                          HAVING views = 1
129*10dcb86fSAndreas Gohr                      )";
130*10dcb86fSAndreas Gohr            $count = $this->db->queryValue($sql, [$this->from, $this->to, 'browser']);
131f9e60319SAndreas Gohr            $data['bouncerate'] = $count * 100 / $data['sessions'];
132f9e60319SAndreas Gohr            $data['newvisitors'] = $count * 100 / $data['sessions'];
133b6632b6eSAndreas Gohr        }
134b6632b6eSAndreas Gohr
135b6632b6eSAndreas Gohr        // calculate avg. number of views per session
136b6632b6eSAndreas Gohr        $sql = "SELECT AVG(views) as cnt
137*10dcb86fSAndreas Gohr                  FROM (
138*10dcb86fSAndreas Gohr                      SELECT P.session, COUNT(*) as views
139*10dcb86fSAndreas Gohr                        FROM pageviews as P
140*10dcb86fSAndreas Gohr                        LEFT JOIN sessions as S ON P.session = S.session
141*10dcb86fSAndreas Gohr                       WHERE P.dt >= ? AND P.dt <= ?
142*10dcb86fSAndreas Gohr                         AND S.ua_type = ?
143*10dcb86fSAndreas Gohr                    GROUP BY P.session
144*10dcb86fSAndreas Gohr                  )";
145*10dcb86fSAndreas Gohr        $data['avgpages'] = $this->db->queryValue($sql, [$this->from, $this->to, 'browser']);
146b6632b6eSAndreas Gohr
147b6632b6eSAndreas Gohr        // average time spent on the site
148*10dcb86fSAndreas Gohr        $sql = "SELECT AVG((julianday(end) - julianday(dt)) * 24 * 60) as time
149*10dcb86fSAndreas Gohr                  FROM sessions as S
150*10dcb86fSAndreas Gohr                 WHERE S.dt >= ? AND S.dt <= ?
151*10dcb86fSAndreas Gohr                   AND S.dt != S.end
152*10dcb86fSAndreas Gohr                   AND DATE(S.dt) = DATE(S.end)
153*10dcb86fSAndreas Gohr                   AND S.ua_type = ?";
154*10dcb86fSAndreas Gohr        $data['timespent'] = $this->db->queryValue($sql, [$this->from, $this->to, 'browser']);
155b6632b6eSAndreas Gohr
156b6632b6eSAndreas Gohr        // logins
157b6632b6eSAndreas Gohr        $sql = "SELECT COUNT(*) as logins
1587428e816SAndreas Gohr                  FROM logins as A
1597428e816SAndreas Gohr                 WHERE A.dt >= ? AND A.dt <= ?
1607428e816SAndreas Gohr                   AND (type = ? OR type = ?)";
161f9e60319SAndreas Gohr        $data['logins'] = $this->db->queryValue($sql, [$this->from, $this->to, 'l', 'p']);
162b6632b6eSAndreas Gohr
163b6632b6eSAndreas Gohr        // registrations
164b6632b6eSAndreas Gohr        $sql = "SELECT COUNT(*) as registrations
1657428e816SAndreas Gohr                  FROM logins as A
1667428e816SAndreas Gohr                 WHERE A.dt >= ? AND A.dt <= ?
1677428e816SAndreas Gohr                   AND type = ?";
168f9e60319SAndreas Gohr        $data['registrations'] = $this->db->queryValue($sql, [$this->from, $this->to, 'C']);
169b6632b6eSAndreas Gohr
170*10dcb86fSAndreas Gohr        // current users (based on recent sessions)
171*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(DISTINCT uid) as current
172*10dcb86fSAndreas Gohr                  FROM sessions
173*10dcb86fSAndreas Gohr                 WHERE end >= datetime('now', '-10 minutes')";
174f9e60319SAndreas Gohr        $data['current'] = $this->db->queryValue($sql);
175b6632b6eSAndreas Gohr
176b6632b6eSAndreas Gohr        return $data;
177b6632b6eSAndreas Gohr    }
178b6632b6eSAndreas Gohr
179b6632b6eSAndreas Gohr
180b6632b6eSAndreas Gohr    /**
181b6632b6eSAndreas Gohr     * Return some trend data about visits and edits in the wiki
182f9e60319SAndreas Gohr     *
183f9e60319SAndreas Gohr     * @param bool $hours Use hour resolution rather than days
184f9e60319SAndreas Gohr     * @return array
185b6632b6eSAndreas Gohr     */
186f9e60319SAndreas Gohr    public function dashboardviews(bool $hours = false): array
187b6632b6eSAndreas Gohr    {
188b6632b6eSAndreas Gohr        if ($hours) {
189*10dcb86fSAndreas Gohr            $TIME = 'strftime(\'%H\', P.dt)';
190b6632b6eSAndreas Gohr        } else {
191*10dcb86fSAndreas Gohr            $TIME = 'DATE(P.dt)';
192b6632b6eSAndreas Gohr        }
193b6632b6eSAndreas Gohr
194b6632b6eSAndreas Gohr        $data = [];
195b6632b6eSAndreas Gohr
196b6632b6eSAndreas Gohr        // access trends
197b6632b6eSAndreas Gohr        $sql = "SELECT $TIME as time,
198*10dcb86fSAndreas Gohr                       COUNT(DISTINCT P.session) as sessions,
199*10dcb86fSAndreas Gohr                       COUNT(P.session) as pageviews,
200*10dcb86fSAndreas Gohr                       COUNT(DISTINCT S.uid) as visitors
201*10dcb86fSAndreas Gohr                  FROM pageviews as P
202*10dcb86fSAndreas Gohr                  LEFT JOIN sessions as S ON P.session = S.session
203*10dcb86fSAndreas Gohr                 WHERE P.dt >= ? AND P.dt <= ?
204*10dcb86fSAndreas Gohr                   AND S.ua_type = ?
205b6632b6eSAndreas Gohr              GROUP BY $TIME
206b6632b6eSAndreas Gohr              ORDER BY time";
207f9e60319SAndreas Gohr        $result = $this->db->queryAll($sql, [$this->from, $this->to, 'browser']);
208b6632b6eSAndreas Gohr        foreach ($result as $row) {
209b6632b6eSAndreas Gohr            $data[$row['time']]['sessions'] = $row['sessions'];
210b6632b6eSAndreas Gohr            $data[$row['time']]['pageviews'] = $row['pageviews'];
211b6632b6eSAndreas Gohr            $data[$row['time']]['visitors'] = $row['visitors'];
212b6632b6eSAndreas Gohr        }
213b6632b6eSAndreas Gohr        return $data;
214b6632b6eSAndreas Gohr    }
215b6632b6eSAndreas Gohr
216f9e60319SAndreas Gohr    /**
217f9e60319SAndreas Gohr     * @param bool $hours Use hour resolution rather than days
218f9e60319SAndreas Gohr     * @return array
219f9e60319SAndreas Gohr     */
220f9e60319SAndreas Gohr    public function dashboardwiki(bool $hours = false): array
221b6632b6eSAndreas Gohr    {
222b6632b6eSAndreas Gohr        if ($hours) {
2237428e816SAndreas Gohr            $TIME = 'strftime(\'%H\', dt)';
224b6632b6eSAndreas Gohr        } else {
225b6632b6eSAndreas Gohr            $TIME = 'DATE(dt)';
226b6632b6eSAndreas Gohr        }
227b6632b6eSAndreas Gohr
228b6632b6eSAndreas Gohr        $data = [];
229b6632b6eSAndreas Gohr
230b6632b6eSAndreas Gohr        // edit trends
231b6632b6eSAndreas Gohr        foreach (['E', 'C', 'D'] as $type) {
232b6632b6eSAndreas Gohr            $sql = "SELECT $TIME as time,
233b6632b6eSAndreas Gohr                           COUNT(*) as cnt
2347428e816SAndreas Gohr                      FROM edits as A
2357428e816SAndreas Gohr                     WHERE A.dt >= ? AND A.dt <= ?
2367428e816SAndreas Gohr                       AND type = ?
237b6632b6eSAndreas Gohr                  GROUP BY $TIME
238b6632b6eSAndreas Gohr                  ORDER BY time";
239f9e60319SAndreas Gohr            $result = $this->db->queryAll($sql, [$this->from, $this->to, $type]);
240b6632b6eSAndreas Gohr            foreach ($result as $row) {
241b6632b6eSAndreas Gohr                $data[$row['time']][$type] = $row['cnt'];
242b6632b6eSAndreas Gohr            }
243b6632b6eSAndreas Gohr        }
244b6632b6eSAndreas Gohr        ksort($data);
245b6632b6eSAndreas Gohr        return $data;
246b6632b6eSAndreas Gohr    }
247b6632b6eSAndreas Gohr
248f9e60319SAndreas Gohr    /**
249f9e60319SAndreas Gohr     * @param string $info Which type of history to select (FIXME which ones are there?)
250f9e60319SAndreas Gohr     * @param string $interval Group data by this interval (days, weeks, months)
251f9e60319SAndreas Gohr     * @return array
252f9e60319SAndreas Gohr     */
253f9e60319SAndreas Gohr    public function history(string $info, string $interval = 'day'): array
254b6632b6eSAndreas Gohr    {
255b6632b6eSAndreas Gohr        if ($interval == 'weeks') {
2567428e816SAndreas Gohr            $TIME = 'strftime(\'%Y\', dt), strftime(\'%W\', dt)';
257b6632b6eSAndreas Gohr        } elseif ($interval == 'months') {
2587428e816SAndreas Gohr            $TIME = 'strftime(\'%Y-%m\', dt)';
259b6632b6eSAndreas Gohr        } else {
260a087824eSAnna Dabrowska            $TIME = 'strftime(\'%d-%m\', dt)';
261b6632b6eSAndreas Gohr        }
262b6632b6eSAndreas Gohr
263b6632b6eSAndreas Gohr        $mod = 1;
264b6632b6eSAndreas Gohr        if ($info == 'media_size' || $info == 'page_size') {
265b6632b6eSAndreas Gohr            $mod = 1024 * 1024;
266b6632b6eSAndreas Gohr        }
267b6632b6eSAndreas Gohr
268b6632b6eSAndreas Gohr        $sql = "SELECT $TIME as time,
2697428e816SAndreas Gohr                       AVG(value)/$mod as cnt
2707428e816SAndreas Gohr                  FROM history as A
2717428e816SAndreas Gohr                 WHERE A.dt >= ? AND A.dt <= ?
2727428e816SAndreas Gohr                   AND info = ?
273b6632b6eSAndreas Gohr                  GROUP BY $TIME
274b6632b6eSAndreas Gohr                  ORDER BY $TIME";
275f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, $info]);
276b6632b6eSAndreas Gohr    }
277b6632b6eSAndreas Gohr
278f9e60319SAndreas Gohr    /**
279f9e60319SAndreas Gohr     * @return array
280f9e60319SAndreas Gohr     */
281f9e60319SAndreas Gohr    public function searchengines(): array
282b6632b6eSAndreas Gohr    {
283*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, R.engine
284*10dcb86fSAndreas Gohr                  FROM pageviews as P
285*10dcb86fSAndreas Gohr                  LEFT JOIN referers as R ON P.ref_id = R.id
286*10dcb86fSAndreas Gohr                 WHERE P.dt >= ? AND P.dt <= ?
287*10dcb86fSAndreas Gohr                   AND R.engine IS NOT NULL
288*10dcb86fSAndreas Gohr                   AND R.engine != ''
289*10dcb86fSAndreas Gohr              GROUP BY R.engine
290*10dcb86fSAndreas Gohr              ORDER BY cnt DESC, R.engine" .
291f9e60319SAndreas Gohr            $this->limit;
292f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to]);
293b6632b6eSAndreas Gohr    }
294b6632b6eSAndreas Gohr
295f9e60319SAndreas Gohr    /**
296f9e60319SAndreas Gohr     * @param bool $extern Limit results to external search engine (true) or dokuwiki (false)
297f9e60319SAndreas Gohr     * @return array
298f9e60319SAndreas Gohr     */
299211caa5dSAndreas Gohr    public function searchphrases(bool $extern = false): array
300b6632b6eSAndreas Gohr    {
301b6632b6eSAndreas Gohr        if ($extern) {
302*10dcb86fSAndreas Gohr            $WHERE = "S.query != '' AND (R.engine IS NULL OR R.engine != ?)";
3037428e816SAndreas Gohr            $engineParam = 'dokuwiki';
304b6632b6eSAndreas Gohr            $I = '';
305b6632b6eSAndreas Gohr        } else {
306*10dcb86fSAndreas Gohr            $WHERE = "S.query != '' AND R.engine = ?";
3077428e816SAndreas Gohr            $engineParam = 'dokuwiki';
308b6632b6eSAndreas Gohr            $I = 'i';
309b6632b6eSAndreas Gohr        }
310*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, S.query, S.query as ${I}lookup
311*10dcb86fSAndreas Gohr                  FROM search as S
312*10dcb86fSAndreas Gohr                  LEFT JOIN referers as R ON S.query = R.url
313*10dcb86fSAndreas Gohr                 WHERE S.dt >= ? AND S.dt <= ?
314b6632b6eSAndreas Gohr                   AND $WHERE
315*10dcb86fSAndreas Gohr              GROUP BY S.query
316*10dcb86fSAndreas Gohr              ORDER BY cnt DESC, S.query" .
317f9e60319SAndreas Gohr            $this->limit;
318f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, $engineParam]);
319b6632b6eSAndreas Gohr    }
320b6632b6eSAndreas Gohr
321f9e60319SAndreas Gohr    /**
322f9e60319SAndreas Gohr     * @param bool $extern Limit results to external search engine (true) or dokuwiki (false)
323f9e60319SAndreas Gohr     * @return array
324f9e60319SAndreas Gohr     */
325211caa5dSAndreas Gohr    public function searchwords(bool $extern = false): array
326b6632b6eSAndreas Gohr    {
327b6632b6eSAndreas Gohr        if ($extern) {
328*10dcb86fSAndreas Gohr            $WHERE = "R.engine IS NULL OR R.engine != ?";
3297428e816SAndreas Gohr            $engineParam = 'dokuwiki';
330b6632b6eSAndreas Gohr            $I = '';
331b6632b6eSAndreas Gohr        } else {
332*10dcb86fSAndreas Gohr            $WHERE = "R.engine = ?";
3337428e816SAndreas Gohr            $engineParam = 'dokuwiki';
334b6632b6eSAndreas Gohr            $I = 'i';
335b6632b6eSAndreas Gohr        }
336*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, SW.word, SW.word as ${I}lookup
337*10dcb86fSAndreas Gohr                  FROM search as S
338*10dcb86fSAndreas Gohr                  LEFT JOIN searchwords as SW ON S.id = SW.sid
339*10dcb86fSAndreas Gohr                  LEFT JOIN referers as R ON S.query = R.url
340*10dcb86fSAndreas Gohr                 WHERE S.dt >= ? AND S.dt <= ?
341*10dcb86fSAndreas Gohr                   AND SW.word IS NOT NULL
342b6632b6eSAndreas Gohr                   AND $WHERE
343*10dcb86fSAndreas Gohr              GROUP BY SW.word
344*10dcb86fSAndreas Gohr              ORDER BY cnt DESC, SW.word" .
345f9e60319SAndreas Gohr            $this->limit;
346f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, $engineParam]);
347b6632b6eSAndreas Gohr    }
348b6632b6eSAndreas Gohr
349f9e60319SAndreas Gohr    /**
350f9e60319SAndreas Gohr     * @return array
351f9e60319SAndreas Gohr     */
352f9e60319SAndreas Gohr    public function outlinks(): array
353b6632b6eSAndreas Gohr    {
354b6632b6eSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, link as url
3557428e816SAndreas Gohr                  FROM outlinks as A
3567428e816SAndreas Gohr                 WHERE A.dt >= ? AND A.dt <= ?
357b6632b6eSAndreas Gohr              GROUP BY link
358b6632b6eSAndreas Gohr              ORDER BY cnt DESC, link" .
359f9e60319SAndreas Gohr            $this->limit;
360f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to]);
361b6632b6eSAndreas Gohr    }
362b6632b6eSAndreas Gohr
363f9e60319SAndreas Gohr    /**
364f9e60319SAndreas Gohr     * @return array
365f9e60319SAndreas Gohr     */
366f9e60319SAndreas Gohr    public function pages(): array
367b6632b6eSAndreas Gohr    {
368*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, P.page
369*10dcb86fSAndreas Gohr                  FROM pageviews as P
370*10dcb86fSAndreas Gohr                  LEFT JOIN sessions as S ON P.session = S.session
371*10dcb86fSAndreas Gohr                 WHERE P.dt >= ? AND P.dt <= ?
372*10dcb86fSAndreas Gohr                   AND S.ua_type = ?
373*10dcb86fSAndreas Gohr              GROUP BY P.page
374*10dcb86fSAndreas Gohr              ORDER BY cnt DESC, P.page" .
375f9e60319SAndreas Gohr            $this->limit;
376f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'browser']);
377b6632b6eSAndreas Gohr    }
378b6632b6eSAndreas Gohr
379f9e60319SAndreas Gohr    /**
380f9e60319SAndreas Gohr     * @return array
381f9e60319SAndreas Gohr     */
382f9e60319SAndreas Gohr    public function edits(): array
383b6632b6eSAndreas Gohr    {
384b6632b6eSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, page
3857428e816SAndreas Gohr                  FROM edits as A
3867428e816SAndreas Gohr                 WHERE A.dt >= ? AND A.dt <= ?
387b6632b6eSAndreas Gohr              GROUP BY page
388b6632b6eSAndreas Gohr              ORDER BY cnt DESC, page" .
389f9e60319SAndreas Gohr            $this->limit;
390f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to]);
391b6632b6eSAndreas Gohr    }
392b6632b6eSAndreas Gohr
393f9e60319SAndreas Gohr    /**
394f9e60319SAndreas Gohr     * @return array
395f9e60319SAndreas Gohr     */
396f9e60319SAndreas Gohr    public function images(): array
397b6632b6eSAndreas Gohr    {
398b6632b6eSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, media, SUM(size) as filesize
3997428e816SAndreas Gohr                  FROM media as A
4007428e816SAndreas Gohr                 WHERE A.dt >= ? AND A.dt <= ?
4017428e816SAndreas Gohr                   AND mime1 = ?
402b6632b6eSAndreas Gohr              GROUP BY media
403b6632b6eSAndreas Gohr              ORDER BY cnt DESC, media" .
404f9e60319SAndreas Gohr            $this->limit;
405f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'image']);
406b6632b6eSAndreas Gohr    }
407b6632b6eSAndreas Gohr
408f9e60319SAndreas Gohr    /**
409f9e60319SAndreas Gohr     * @return array
410f9e60319SAndreas Gohr     */
411f9e60319SAndreas Gohr    public function imagessum(): array
412b6632b6eSAndreas Gohr    {
413b6632b6eSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, SUM(size) as filesize
4147428e816SAndreas Gohr                  FROM media as A
4157428e816SAndreas Gohr                 WHERE A.dt >= ? AND A.dt <= ?
4167428e816SAndreas Gohr                   AND mime1 = ?";
417f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'image']);
418b6632b6eSAndreas Gohr    }
419b6632b6eSAndreas Gohr
420f9e60319SAndreas Gohr    /**
421f9e60319SAndreas Gohr     * @return array
422f9e60319SAndreas Gohr     */
423f9e60319SAndreas Gohr    public function downloads(): array
424b6632b6eSAndreas Gohr    {
425b6632b6eSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, media, SUM(size) as filesize
4267428e816SAndreas Gohr                  FROM media as A
4277428e816SAndreas Gohr                 WHERE A.dt >= ? AND A.dt <= ?
4287428e816SAndreas Gohr                   AND mime1 != ?
429b6632b6eSAndreas Gohr              GROUP BY media
430b6632b6eSAndreas Gohr              ORDER BY cnt DESC, media" .
431f9e60319SAndreas Gohr            $this->limit;
432f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'image']);
433b6632b6eSAndreas Gohr    }
434b6632b6eSAndreas Gohr
435f9e60319SAndreas Gohr    /**
436f9e60319SAndreas Gohr     * @return array
437f9e60319SAndreas Gohr     */
438f9e60319SAndreas Gohr    public function downloadssum(): array
439b6632b6eSAndreas Gohr    {
440b6632b6eSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, SUM(size) as filesize
4417428e816SAndreas Gohr                  FROM media as A
4427428e816SAndreas Gohr                 WHERE A.dt >= ? AND A.dt <= ?
4437428e816SAndreas Gohr                   AND mime1 != ?";
444f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'image']);
445b6632b6eSAndreas Gohr    }
446b6632b6eSAndreas Gohr
447f9e60319SAndreas Gohr    /**
448f9e60319SAndreas Gohr     * @return array
449f9e60319SAndreas Gohr     */
450f9e60319SAndreas Gohr    public function referer(): array
451b6632b6eSAndreas Gohr    {
452*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, R.url
453*10dcb86fSAndreas Gohr                  FROM pageviews as P
454*10dcb86fSAndreas Gohr                  LEFT JOIN sessions as S ON P.session = S.session
455*10dcb86fSAndreas Gohr                  LEFT JOIN referers as R ON P.ref_id = R.id
456*10dcb86fSAndreas Gohr                 WHERE P.dt >= ? AND P.dt <= ?
457*10dcb86fSAndreas Gohr                   AND S.ua_type = ?
458*10dcb86fSAndreas Gohr                   AND R.url IS NOT NULL
459*10dcb86fSAndreas Gohr                   AND R.url != ''
460*10dcb86fSAndreas Gohr                   AND R.engine IS NULL
461*10dcb86fSAndreas Gohr              GROUP BY R.url
462*10dcb86fSAndreas Gohr              ORDER BY cnt DESC, R.url" .
463f9e60319SAndreas Gohr            $this->limit;
464*10dcb86fSAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'browser']);
465b6632b6eSAndreas Gohr    }
466b6632b6eSAndreas Gohr
467f9e60319SAndreas Gohr    /**
468f9e60319SAndreas Gohr     * @return array
469f9e60319SAndreas Gohr     */
470f9e60319SAndreas Gohr    public function newreferer(): array
471b6632b6eSAndreas Gohr    {
472*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, R.url
473*10dcb86fSAndreas Gohr                  FROM pageviews as P
474*10dcb86fSAndreas Gohr                  LEFT JOIN sessions as S ON P.session = S.session
475*10dcb86fSAndreas Gohr                  LEFT JOIN referers as R ON P.ref_id = R.id
476*10dcb86fSAndreas Gohr                 WHERE P.dt >= ? AND P.dt <= ?
477*10dcb86fSAndreas Gohr                   AND S.ua_type = ?
478*10dcb86fSAndreas Gohr                   AND R.url IS NOT NULL
479*10dcb86fSAndreas Gohr                   AND R.url != ''
480*10dcb86fSAndreas Gohr                   AND R.engine IS NULL
481*10dcb86fSAndreas Gohr                   AND R.dt >= ?
482*10dcb86fSAndreas Gohr              GROUP BY R.url
483*10dcb86fSAndreas Gohr              ORDER BY cnt DESC, R.url" .
484f9e60319SAndreas Gohr            $this->limit;
485*10dcb86fSAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'browser', $this->from]);
486b6632b6eSAndreas Gohr    }
487b6632b6eSAndreas Gohr
488f9e60319SAndreas Gohr    /**
489f9e60319SAndreas Gohr     * @return array
490f9e60319SAndreas Gohr     */
491f9e60319SAndreas Gohr    public function countries(): array
492b6632b6eSAndreas Gohr    {
493*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(DISTINCT P.session) as cnt, I.country
494*10dcb86fSAndreas Gohr                  FROM pageviews as P
495*10dcb86fSAndreas Gohr                  LEFT JOIN iplocation as I ON P.ip = I.ip
496*10dcb86fSAndreas Gohr                 WHERE P.dt >= ? AND P.dt <= ?
497*10dcb86fSAndreas Gohr                   AND I.country IS NOT NULL
498*10dcb86fSAndreas Gohr                   AND I.country != ''
499*10dcb86fSAndreas Gohr              GROUP BY I.code
500*10dcb86fSAndreas Gohr              ORDER BY cnt DESC, I.country" .
501f9e60319SAndreas Gohr            $this->limit;
502f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to]);
503b6632b6eSAndreas Gohr    }
504b6632b6eSAndreas Gohr
505f9e60319SAndreas Gohr    /**
506f9e60319SAndreas Gohr     * @param bool $ext return extended information
507f9e60319SAndreas Gohr     * @return array
508f9e60319SAndreas Gohr     */
509f9e60319SAndreas Gohr    public function browsers(bool $ext = true): array
510b6632b6eSAndreas Gohr    {
511b6632b6eSAndreas Gohr        if ($ext) {
512*10dcb86fSAndreas Gohr            $sel = 'S.ua_info as browser, S.ua_ver';
513*10dcb86fSAndreas Gohr            $grp = 'S.ua_info, S.ua_ver';
514b6632b6eSAndreas Gohr        } else {
515*10dcb86fSAndreas Gohr            $grp = 'S.ua_info';
516*10dcb86fSAndreas Gohr            $sel = 'S.ua_info';
517b6632b6eSAndreas Gohr        }
518b6632b6eSAndreas Gohr
519*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(DISTINCT S.session) as cnt, $sel
520*10dcb86fSAndreas Gohr                  FROM sessions as S
521*10dcb86fSAndreas Gohr                 WHERE S.dt >= ? AND S.dt <= ?
522*10dcb86fSAndreas Gohr                   AND S.ua_type = ?
523b6632b6eSAndreas Gohr              GROUP BY $grp
524*10dcb86fSAndreas Gohr              ORDER BY cnt DESC, S.ua_info" .
525f9e60319SAndreas Gohr            $this->limit;
526f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'browser']);
527b6632b6eSAndreas Gohr    }
528b6632b6eSAndreas Gohr
529f9e60319SAndreas Gohr    /**
530f9e60319SAndreas Gohr     * @return array
531f9e60319SAndreas Gohr     */
532f9e60319SAndreas Gohr    public function os(): array
533b6632b6eSAndreas Gohr    {
534*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(DISTINCT S.session) as cnt, S.os
535*10dcb86fSAndreas Gohr                  FROM sessions as S
536*10dcb86fSAndreas Gohr                 WHERE S.dt >= ? AND S.dt <= ?
537*10dcb86fSAndreas Gohr                   AND S.ua_type = ?
538*10dcb86fSAndreas Gohr              GROUP BY S.os
539*10dcb86fSAndreas Gohr              ORDER BY cnt DESC, S.os" .
540f9e60319SAndreas Gohr            $this->limit;
541f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'browser']);
542b6632b6eSAndreas Gohr    }
543b6632b6eSAndreas Gohr
544f9e60319SAndreas Gohr    /**
545f9e60319SAndreas Gohr     * @return array
546f9e60319SAndreas Gohr     */
547f9e60319SAndreas Gohr    public function topuser(): array
548b6632b6eSAndreas Gohr    {
549*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, S.user
550*10dcb86fSAndreas Gohr                  FROM pageviews as P
551*10dcb86fSAndreas Gohr                  LEFT JOIN sessions as S ON P.session = S.session
552*10dcb86fSAndreas Gohr                 WHERE P.dt >= ? AND P.dt <= ?
553*10dcb86fSAndreas Gohr                   AND S.ua_type = ?
554*10dcb86fSAndreas Gohr                   AND S.user IS NOT NULL
555*10dcb86fSAndreas Gohr                   AND S.user != ?
556*10dcb86fSAndreas Gohr              GROUP BY S.user
557*10dcb86fSAndreas Gohr              ORDER BY cnt DESC, S.user" .
558f9e60319SAndreas Gohr            $this->limit;
559f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'browser', '']);
560b6632b6eSAndreas Gohr    }
561b6632b6eSAndreas Gohr
562f9e60319SAndreas Gohr    /**
563f9e60319SAndreas Gohr     * @return array
564f9e60319SAndreas Gohr     */
565f9e60319SAndreas Gohr    public function topeditor(): array
566b6632b6eSAndreas Gohr    {
567b6632b6eSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, user
568*10dcb86fSAndreas Gohr                  FROM edits as E
569*10dcb86fSAndreas Gohr             LEFT JOIN sessions as S ON E.session = S.session
570*10dcb86fSAndreas Gohr                 WHERE E.dt >= ? AND E.dt <= ?
571*10dcb86fSAndreas Gohr                   AND S.user != ?
572b6632b6eSAndreas Gohr              GROUP BY user
573b6632b6eSAndreas Gohr              ORDER BY cnt DESC, user" .
574f9e60319SAndreas Gohr            $this->limit;
575f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, '']);
576b6632b6eSAndreas Gohr    }
577b6632b6eSAndreas Gohr
578f9e60319SAndreas Gohr    /**
579f9e60319SAndreas Gohr     * @return array
580f9e60319SAndreas Gohr     */
581f9e60319SAndreas Gohr    public function topgroup(): array
582b6632b6eSAndreas Gohr    {
583*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, G.`group`
584*10dcb86fSAndreas Gohr                  FROM pageviews as P
585*10dcb86fSAndreas Gohr                  LEFT JOIN sessions as S ON P.session = S.session
586*10dcb86fSAndreas Gohr                  LEFT JOIN groups as G ON S.user = G.user
587*10dcb86fSAndreas Gohr                 WHERE P.dt >= ? AND P.dt <= ?
588*10dcb86fSAndreas Gohr                   AND S.ua_type = ?
589*10dcb86fSAndreas Gohr                   AND G.`group` IS NOT NULL
590*10dcb86fSAndreas Gohr              GROUP BY G.`group`
591*10dcb86fSAndreas Gohr              ORDER BY cnt DESC, G.`group`" .
592f9e60319SAndreas Gohr            $this->limit;
593*10dcb86fSAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'browser']);
594b6632b6eSAndreas Gohr    }
595b6632b6eSAndreas Gohr
596f9e60319SAndreas Gohr    /**
597f9e60319SAndreas Gohr     * @return array
598f9e60319SAndreas Gohr     */
599f9e60319SAndreas Gohr    public function topgroupedit(): array
600b6632b6eSAndreas Gohr    {
601*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(*) as cnt, G.`group`
602*10dcb86fSAndreas Gohr                  FROM edits as E
603*10dcb86fSAndreas Gohr                  LEFT JOIN sessions as S ON E.session = S.session
604*10dcb86fSAndreas Gohr                  LEFT JOIN groups as G ON S.user = G.user
605*10dcb86fSAndreas Gohr                 WHERE E.dt >= ? AND E.dt <= ?
606*10dcb86fSAndreas Gohr                   AND G.`group` IS NOT NULL
607*10dcb86fSAndreas Gohr              GROUP BY G.`group`
608*10dcb86fSAndreas Gohr              ORDER BY cnt DESC, G.`group`" .
609f9e60319SAndreas Gohr            $this->limit;
610*10dcb86fSAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to]);
611b6632b6eSAndreas Gohr    }
612b6632b6eSAndreas Gohr
613b6632b6eSAndreas Gohr
614f9e60319SAndreas Gohr    /**
615f9e60319SAndreas Gohr     * @return array
616f9e60319SAndreas Gohr     */
617f9e60319SAndreas Gohr    public function resolution(): array
618b6632b6eSAndreas Gohr    {
619*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(DISTINCT S.uid) as cnt,
620*10dcb86fSAndreas Gohr                       ROUND(P.screen_x/100)*100 as res_x,
621*10dcb86fSAndreas Gohr                       ROUND(P.screen_y/100)*100 as res_y,
622*10dcb86fSAndreas Gohr                       CAST(ROUND(P.screen_x/100)*100 AS int) || 'x' || CAST(ROUND(P.screen_y/100)*100 AS int) as resolution
623*10dcb86fSAndreas Gohr                  FROM pageviews as P
624*10dcb86fSAndreas Gohr                  LEFT JOIN sessions as S ON P.session = S.session
625*10dcb86fSAndreas Gohr                 WHERE P.dt >= ? AND P.dt <= ?
626*10dcb86fSAndreas Gohr                   AND S.ua_type = ?
627*10dcb86fSAndreas Gohr                   AND P.screen_x != ?
628*10dcb86fSAndreas Gohr                   AND P.screen_y != ?
629b6632b6eSAndreas Gohr              GROUP BY resolution
630b6632b6eSAndreas Gohr              ORDER BY cnt DESC" .
631f9e60319SAndreas Gohr            $this->limit;
632f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'browser', 0, 0]);
633b6632b6eSAndreas Gohr    }
634b6632b6eSAndreas Gohr
635f9e60319SAndreas Gohr    /**
636f9e60319SAndreas Gohr     * @return array
637f9e60319SAndreas Gohr     */
638f9e60319SAndreas Gohr    public function viewport(): array
639b6632b6eSAndreas Gohr    {
640*10dcb86fSAndreas Gohr        $sql = "SELECT COUNT(DISTINCT S.uid) as cnt,
641*10dcb86fSAndreas Gohr                       ROUND(P.view_x/100)*100 as res_x,
642*10dcb86fSAndreas Gohr                       ROUND(P.view_y/100)*100 as res_y,
643*10dcb86fSAndreas Gohr                       CAST(ROUND(P.view_x/100)*100 AS int) || 'x' || CAST(ROUND(P.view_y/100)*100 AS int) as resolution
644*10dcb86fSAndreas Gohr                  FROM pageviews as P
645*10dcb86fSAndreas Gohr                  LEFT JOIN sessions as S ON P.session = S.session
646*10dcb86fSAndreas Gohr                 WHERE P.dt >= ? AND P.dt <= ?
647*10dcb86fSAndreas Gohr                   AND S.ua_type = ?
648*10dcb86fSAndreas Gohr                   AND P.view_x != ?
649*10dcb86fSAndreas Gohr                   AND P.view_y != ?
650b6632b6eSAndreas Gohr              GROUP BY resolution
651b6632b6eSAndreas Gohr              ORDER BY cnt DESC" .
652f9e60319SAndreas Gohr            $this->limit;
653b6632b6eSAndreas Gohr
654f9e60319SAndreas Gohr        return $this->db->queryAll($sql, [$this->from, $this->to, 'browser', 0, 0]);
655b6632b6eSAndreas Gohr    }
656b6632b6eSAndreas Gohr
657f9e60319SAndreas Gohr    /**
658f9e60319SAndreas Gohr     * @return array
659f9e60319SAndreas Gohr     */
660f9e60319SAndreas Gohr    public function seenusers(): array
661b6632b6eSAndreas Gohr    {
662*10dcb86fSAndreas Gohr        $sql = "SELECT `user`, MAX(`dt`) as dt
663*10dcb86fSAndreas Gohr                  FROM users
664*10dcb86fSAndreas Gohr                 WHERE `user` IS NOT NULL
665*10dcb86fSAndreas Gohr                   AND `user` != ''
666*10dcb86fSAndreas Gohr              GROUP BY `user`
667b6632b6eSAndreas Gohr              ORDER BY `dt` DESC" .
668f9e60319SAndreas Gohr            $this->limit;
669b6632b6eSAndreas Gohr
670f9e60319SAndreas Gohr        return $this->db->queryAll($sql);
671b6632b6eSAndreas Gohr    }
672b6632b6eSAndreas Gohr}
673