xref: /plugin/statistics/Logger.php (revision 2adee4c68974f633621c42f9308b229422ef36a9)
1762f4807SAndreas Gohr<?php
2762f4807SAndreas Gohr
3762f4807SAndreas Gohrnamespace dokuwiki\plugin\statistics;
4762f4807SAndreas Gohr
5762f4807SAndreas Gohruse DeviceDetector\DeviceDetector;
6762f4807SAndreas Gohruse DeviceDetector\Parser\Client\Browser;
7762f4807SAndreas Gohruse DeviceDetector\Parser\Device\AbstractDeviceParser;
8762f4807SAndreas Gohruse DeviceDetector\Parser\OperatingSystem;
9762f4807SAndreas Gohruse dokuwiki\HTTP\DokuHTTPClient;
10762f4807SAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB;
11762f4807SAndreas Gohruse dokuwiki\Utf8\Clean;
12762f4807SAndreas Gohruse helper_plugin_popularity;
13762f4807SAndreas Gohruse helper_plugin_statistics;
14762f4807SAndreas Gohr
15762f4807SAndreas Gohrclass Logger
16762f4807SAndreas Gohr{
17762f4807SAndreas Gohr    /** @var helper_plugin_statistics The statistics helper plugin instance */
18762f4807SAndreas Gohr    protected helper_plugin_statistics $hlp;
19762f4807SAndreas Gohr
20762f4807SAndreas Gohr    /** @var SQLiteDB The SQLite database instance */
21762f4807SAndreas Gohr    protected SQLiteDB $db;
22762f4807SAndreas Gohr
23762f4807SAndreas Gohr    /** @var string The full user agent string */
24762f4807SAndreas Gohr    protected string $uaAgent;
25762f4807SAndreas Gohr
26762f4807SAndreas Gohr    /** @var string The type of user agent (browser, robot, feedreader) */
27762f4807SAndreas Gohr    protected string $uaType = 'browser';
28762f4807SAndreas Gohr
29762f4807SAndreas Gohr    /** @var string The browser/client name */
30762f4807SAndreas Gohr    protected string $uaName;
31762f4807SAndreas Gohr
32762f4807SAndreas Gohr    /** @var string The browser/client version */
33762f4807SAndreas Gohr    protected string $uaVersion;
34762f4807SAndreas Gohr
35762f4807SAndreas Gohr    /** @var string The operating system/platform */
36762f4807SAndreas Gohr    protected string $uaPlatform;
37762f4807SAndreas Gohr
38762f4807SAndreas Gohr    /** @var string The unique user identifier */
39762f4807SAndreas Gohr    protected string $uid;
40762f4807SAndreas Gohr
41c7cad24dSAndreas Gohr    /** @var DokuHTTPClient|null The HTTP client instance for testing */
42c7cad24dSAndreas Gohr    protected ?DokuHTTPClient $httpClient = null;
43c7cad24dSAndreas Gohr
44762f4807SAndreas Gohr
45762f4807SAndreas Gohr    /**
46762f4807SAndreas Gohr     * Constructor
47762f4807SAndreas Gohr     *
48762f4807SAndreas Gohr     * Parses browser info and set internal vars
49762f4807SAndreas Gohr     */
50c7cad24dSAndreas Gohr    public function __construct(helper_plugin_statistics $hlp, ?DokuHTTPClient $httpClient = null)
51762f4807SAndreas Gohr    {
52762f4807SAndreas Gohr        global $INPUT;
53762f4807SAndreas Gohr
54762f4807SAndreas Gohr        $this->hlp = $hlp;
55762f4807SAndreas Gohr        $this->db = $this->hlp->getDB();
56c7cad24dSAndreas Gohr        $this->httpClient = $httpClient;
57762f4807SAndreas Gohr
58762f4807SAndreas Gohr        $ua = trim($INPUT->server->str('HTTP_USER_AGENT'));
59762f4807SAndreas Gohr
60762f4807SAndreas Gohr        AbstractDeviceParser::setVersionTruncation(AbstractDeviceParser::VERSION_TRUNCATION_MAJOR);
61762f4807SAndreas Gohr        $dd = new DeviceDetector($ua); // FIXME we could use client hints, but need to add headers
62762f4807SAndreas Gohr        $dd->discardBotInformation();
63762f4807SAndreas Gohr        $dd->parse();
64762f4807SAndreas Gohr
6500f786d8SAndreas Gohr        if ($dd->isFeedReader()) {
6600f786d8SAndreas Gohr            $this->uaType = 'feedreader';
6700f786d8SAndreas Gohr        } elseif ($dd->isBot()) {
68762f4807SAndreas Gohr            $this->uaType = 'robot';
69762f4807SAndreas Gohr            // for now ignore bots
70762f4807SAndreas Gohr            throw new \RuntimeException('Bot detected, not logging');
71762f4807SAndreas Gohr        }
72762f4807SAndreas Gohr
73762f4807SAndreas Gohr        $this->uaAgent = $ua;
7405786d83SAndreas Gohr        $this->uaName = Browser::getBrowserFamily($dd->getClient('name')) ?: 'Unknown';
7500f786d8SAndreas Gohr        $this->uaVersion = $dd->getClient('version') ?: '0';
7605786d83SAndreas Gohr        $this->uaPlatform = OperatingSystem::getOsFamily($dd->getOs('name')) ?: 'Unknown';
77762f4807SAndreas Gohr        $this->uid = $this->getUID();
78762f4807SAndreas Gohr
79762f4807SAndreas Gohr
80762f4807SAndreas Gohr        $this->logLastseen();
81762f4807SAndreas Gohr    }
82762f4807SAndreas Gohr
83762f4807SAndreas Gohr    /**
84762f4807SAndreas Gohr     * Should be called before logging
85762f4807SAndreas Gohr     *
86762f4807SAndreas Gohr     * This starts a transaction, so all logging is done in one go
87762f4807SAndreas Gohr     */
88762f4807SAndreas Gohr    public function begin(): void
89762f4807SAndreas Gohr    {
90762f4807SAndreas Gohr        $this->hlp->getDB()->getPdo()->beginTransaction();
91762f4807SAndreas Gohr    }
92762f4807SAndreas Gohr
93762f4807SAndreas Gohr    /**
94762f4807SAndreas Gohr     * Should be called after logging
95762f4807SAndreas Gohr     *
96762f4807SAndreas Gohr     * This commits the transaction started in begin()
97762f4807SAndreas Gohr     */
98762f4807SAndreas Gohr    public function end(): void
99762f4807SAndreas Gohr    {
100762f4807SAndreas Gohr        $this->hlp->getDB()->getPdo()->commit();
101762f4807SAndreas Gohr    }
102762f4807SAndreas Gohr
103762f4807SAndreas Gohr    /**
104762f4807SAndreas Gohr     * Get the unique user ID
105762f4807SAndreas Gohr     *
106762f4807SAndreas Gohr     * @return string The unique user identifier
107762f4807SAndreas Gohr     */
108762f4807SAndreas Gohr    protected function getUID(): string
109762f4807SAndreas Gohr    {
110762f4807SAndreas Gohr        global $INPUT;
111762f4807SAndreas Gohr
112762f4807SAndreas Gohr        $uid = $INPUT->str('uid');
113762f4807SAndreas Gohr        if (!$uid) $uid = get_doku_pref('plgstats', false);
114762f4807SAndreas Gohr        if (!$uid) $uid = session_id();
115b188870fSAndreas Gohr        set_doku_pref('plgstats', $uid);
116762f4807SAndreas Gohr        return $uid;
117762f4807SAndreas Gohr    }
118762f4807SAndreas Gohr
119762f4807SAndreas Gohr    /**
120762f4807SAndreas Gohr     * Return the user's session ID
121762f4807SAndreas Gohr     *
122762f4807SAndreas Gohr     * This is usually our own managed session, not a PHP session (only in fallback)
123762f4807SAndreas Gohr     *
124762f4807SAndreas Gohr     * @return string The session identifier
125762f4807SAndreas Gohr     */
126762f4807SAndreas Gohr    protected function getSession(): string
127762f4807SAndreas Gohr    {
128762f4807SAndreas Gohr        global $INPUT;
129762f4807SAndreas Gohr
130762f4807SAndreas Gohr        $ses = $INPUT->str('ses');
131762f4807SAndreas Gohr        if (!$ses) $ses = get_doku_pref('plgstatsses', false);
132762f4807SAndreas Gohr        if (!$ses) $ses = session_id();
133b188870fSAndreas Gohr        set_doku_pref('plgstatsses', $ses);
134762f4807SAndreas Gohr        return $ses;
135762f4807SAndreas Gohr    }
136762f4807SAndreas Gohr
137762f4807SAndreas Gohr    /**
138762f4807SAndreas Gohr     * Log that we've seen the user (authenticated only)
139762f4807SAndreas Gohr     */
140762f4807SAndreas Gohr    public function logLastseen(): void
141762f4807SAndreas Gohr    {
142762f4807SAndreas Gohr        global $INPUT;
143762f4807SAndreas Gohr
144762f4807SAndreas Gohr        if (empty($INPUT->server->str('REMOTE_USER'))) return;
145762f4807SAndreas Gohr
146762f4807SAndreas Gohr        $this->db->exec(
147762f4807SAndreas Gohr            'REPLACE INTO lastseen (user, dt) VALUES (?, CURRENT_TIMESTAMP)',
148762f4807SAndreas Gohr            $INPUT->server->str('REMOTE_USER'),
149762f4807SAndreas Gohr        );
150762f4807SAndreas Gohr    }
151762f4807SAndreas Gohr
152762f4807SAndreas Gohr    /**
153762f4807SAndreas Gohr     * Log actions by groups
154762f4807SAndreas Gohr     *
1550b8b7abaSAnna Dabrowska     * @param int $pid Id of access data row (foreign key)
156762f4807SAndreas Gohr     * @param string $type The type of access to log ('view','edit')
157762f4807SAndreas Gohr     * @param array $groups The groups to log
158762f4807SAndreas Gohr     */
1590b8b7abaSAnna Dabrowska    public function logGroups(int $pid, string $type, array $groups): void
160762f4807SAndreas Gohr    {
161*2adee4c6SAndreas Gohr        if ($groups === [] || !$pid) return;
162762f4807SAndreas Gohr
163483101d3SAndreas Gohr        $toLog = (array)$this->hlp->getConf('loggroups');
164fced2f86SAnna Dabrowska
165fced2f86SAnna Dabrowska        // if specific groups are configured, limit logging to them only
166*2adee4c6SAndreas Gohr        $groups = empty(array_filter($toLog)) ? $groups : array_intersect($groups, $toLog);
167483101d3SAndreas Gohr        if (!$groups) return;
168762f4807SAndreas Gohr
169*2adee4c6SAndreas Gohr        $placeholders = implode(',', array_fill(0, count($groups), '(?, ?, ?)'));
170762f4807SAndreas Gohr        $params = [];
1710b8b7abaSAnna Dabrowska        $sql = "INSERT INTO groups (`pid`, `type`, `group`) VALUES $placeholders";
172762f4807SAndreas Gohr        foreach ($groups as $group) {
1730b8b7abaSAnna Dabrowska            $params[] = $pid;
174762f4807SAndreas Gohr            $params[] = $type;
175762f4807SAndreas Gohr            $params[] = $group;
176762f4807SAndreas Gohr        }
177762f4807SAndreas Gohr        $sql = rtrim($sql, ',');
178762f4807SAndreas Gohr        $this->db->exec($sql, $params);
179762f4807SAndreas Gohr    }
180762f4807SAndreas Gohr
181762f4807SAndreas Gohr    /**
1820b8b7abaSAnna Dabrowska     * Log email domain, skip logging if no domain is found
1830b8b7abaSAnna Dabrowska     *
1840b8b7abaSAnna Dabrowska     * @param int $pid Id of access data row (foreign key)
1850b8b7abaSAnna Dabrowska     * @param string $type The type of access to log ('view','edit')
1860b8b7abaSAnna Dabrowska     * @param string $mail The email to extract the domain from
1870b8b7abaSAnna Dabrowska     */
1880b8b7abaSAnna Dabrowska    public function logDomain(int $pid, string $type, string $mail): void
1890b8b7abaSAnna Dabrowska    {
1900b8b7abaSAnna Dabrowska        if (!$pid) return;
1910b8b7abaSAnna Dabrowska
1920b8b7abaSAnna Dabrowska        $pos = strrpos($mail, '@');
1930b8b7abaSAnna Dabrowska        if (!$pos) return;
1940b8b7abaSAnna Dabrowska        $domain = substr($mail, $pos + 1);
1950b8b7abaSAnna Dabrowska        if (empty($domain)) return;
1960b8b7abaSAnna Dabrowska
1970b8b7abaSAnna Dabrowska        $sql = "INSERT INTO domain (`pid`, `type`, `domain`) VALUES (?, ?, ?)";
1980b8b7abaSAnna Dabrowska        $this->db->exec($sql, [$pid, $type, $domain]);
1990b8b7abaSAnna Dabrowska    }
2000b8b7abaSAnna Dabrowska
2010b8b7abaSAnna Dabrowska    /**
202762f4807SAndreas Gohr     * Log external search queries
203762f4807SAndreas Gohr     *
204762f4807SAndreas Gohr     * Will not write anything if the referer isn't a search engine
205762f4807SAndreas Gohr     *
206762f4807SAndreas Gohr     * @param string $referer The HTTP referer URL
207762f4807SAndreas Gohr     * @param string $type Reference to the type variable that will be modified
208762f4807SAndreas Gohr     */
209762f4807SAndreas Gohr    public function logExternalSearch(string $referer, string &$type): void
210762f4807SAndreas Gohr    {
211762f4807SAndreas Gohr        global $INPUT;
212762f4807SAndreas Gohr
213762f4807SAndreas Gohr        $searchEngine = new SearchEngines($referer);
214762f4807SAndreas Gohr
215762f4807SAndreas Gohr        if (!$searchEngine->isSearchEngine()) {
216762f4807SAndreas Gohr            return; // not a search engine
217762f4807SAndreas Gohr        }
218762f4807SAndreas Gohr
219762f4807SAndreas Gohr        $type = 'search';
220762f4807SAndreas Gohr        $query = $searchEngine->getQuery();
221762f4807SAndreas Gohr
222762f4807SAndreas Gohr        // log it!
223d40a6291SAnna Dabrowska        $words = [];
224d40a6291SAnna Dabrowska        if ($query) {
225762f4807SAndreas Gohr            $words = explode(' ', Clean::stripspecials($query, ' ', '\._\-:\*'));
226d40a6291SAnna Dabrowska        }
227d40a6291SAnna Dabrowska        $this->logSearch($INPUT->str('p'), $searchEngine->getEngine(), $query, $words);
228762f4807SAndreas Gohr    }
229762f4807SAndreas Gohr
230762f4807SAndreas Gohr    /**
231762f4807SAndreas Gohr     * Log search data to the search related tables
232762f4807SAndreas Gohr     *
233762f4807SAndreas Gohr     * @param string $page The page being searched from
234762f4807SAndreas Gohr     * @param string $engine The search engine name
235d40a6291SAnna Dabrowska     * @param string|null $query The search query
236d40a6291SAnna Dabrowska     * @param array|null $words Array of search words
237762f4807SAndreas Gohr     */
238d40a6291SAnna Dabrowska    public function logSearch(string $page, string $engine, ?string $query, ?array $words): void
239762f4807SAndreas Gohr    {
240762f4807SAndreas Gohr        $sid = $this->db->exec(
241762f4807SAndreas Gohr            'INSERT INTO search (dt, page, query, engine) VALUES (CURRENT_TIMESTAMP, ?, ?, ?)',
242*2adee4c6SAndreas Gohr            $page,
243*2adee4c6SAndreas Gohr            $query ?? '',
244*2adee4c6SAndreas Gohr            $engine
245762f4807SAndreas Gohr        );
246762f4807SAndreas Gohr        if (!$sid) return;
247762f4807SAndreas Gohr
248762f4807SAndreas Gohr        foreach ($words as $word) {
249762f4807SAndreas Gohr            if (!$word) continue;
250762f4807SAndreas Gohr            $this->db->exec(
251762f4807SAndreas Gohr                'INSERT INTO searchwords (sid, word) VALUES (?, ?)',
252*2adee4c6SAndreas Gohr                $sid,
253*2adee4c6SAndreas Gohr                $word
254762f4807SAndreas Gohr            );
255762f4807SAndreas Gohr        }
256762f4807SAndreas Gohr    }
257762f4807SAndreas Gohr
258762f4807SAndreas Gohr    /**
259762f4807SAndreas Gohr     * Log that the session was seen
260762f4807SAndreas Gohr     *
261762f4807SAndreas Gohr     * This is used to calculate the time people spend on the whole site
262762f4807SAndreas Gohr     * during their session
263762f4807SAndreas Gohr     *
264762f4807SAndreas Gohr     * Viewcounts are used for bounce calculation
265762f4807SAndreas Gohr     *
266762f4807SAndreas Gohr     * @param int $addview set to 1 to count a view
267762f4807SAndreas Gohr     */
268762f4807SAndreas Gohr    public function logSession(int $addview = 0): void
269762f4807SAndreas Gohr    {
270762f4807SAndreas Gohr        // only log browser sessions
271762f4807SAndreas Gohr        if ($this->uaType != 'browser') return;
272762f4807SAndreas Gohr
273762f4807SAndreas Gohr        $session = $this->getSession();
274762f4807SAndreas Gohr        $this->db->exec(
275762f4807SAndreas Gohr            'INSERT OR REPLACE INTO session (
276762f4807SAndreas Gohr                session, dt, end, views, uid
277762f4807SAndreas Gohr             ) VALUES (
278762f4807SAndreas Gohr                ?,
279762f4807SAndreas Gohr                CURRENT_TIMESTAMP,
280762f4807SAndreas Gohr                CURRENT_TIMESTAMP,
281762f4807SAndreas Gohr                COALESCE((SELECT views FROM session WHERE session = ?) + ?, ?),
282762f4807SAndreas Gohr                ?
283762f4807SAndreas Gohr             )',
284*2adee4c6SAndreas Gohr            $session,
285*2adee4c6SAndreas Gohr            $session,
286*2adee4c6SAndreas Gohr            $addview,
287*2adee4c6SAndreas Gohr            $addview,
288*2adee4c6SAndreas Gohr            $this->uid
289762f4807SAndreas Gohr        );
290762f4807SAndreas Gohr    }
291762f4807SAndreas Gohr
292762f4807SAndreas Gohr    /**
293762f4807SAndreas Gohr     * Resolve IP to country/city and store in database
294762f4807SAndreas Gohr     *
295762f4807SAndreas Gohr     * @param string $ip The IP address to resolve
296762f4807SAndreas Gohr     */
297762f4807SAndreas Gohr    public function logIp(string $ip): void
298762f4807SAndreas Gohr    {
299762f4807SAndreas Gohr        // check if IP already known and up-to-date
300762f4807SAndreas Gohr        $result = $this->db->queryValue(
301762f4807SAndreas Gohr            "SELECT ip
302762f4807SAndreas Gohr             FROM   iplocation
303762f4807SAndreas Gohr             WHERE  ip = ?
304762f4807SAndreas Gohr               AND  lastupd > date('now', '-30 days')",
305762f4807SAndreas Gohr            $ip
306762f4807SAndreas Gohr        );
307762f4807SAndreas Gohr        if ($result) return;
308762f4807SAndreas Gohr
309c7cad24dSAndreas Gohr        $http = $this->httpClient ?: new DokuHTTPClient();
310762f4807SAndreas Gohr        $http->timeout = 10;
311762f4807SAndreas Gohr        $json = $http->get('http://ip-api.com/json/' . $ip); // yes, it's HTTP only
312762f4807SAndreas Gohr
313762f4807SAndreas Gohr        if (!$json) return; // FIXME log error
314762f4807SAndreas Gohr        try {
315762f4807SAndreas Gohr            $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
316762f4807SAndreas Gohr        } catch (\JsonException $e) {
317762f4807SAndreas Gohr            return; // FIXME log error
318762f4807SAndreas Gohr        }
319a10aed88SAndreas Gohr        if (!isset($data['status']) || $data['status'] !== 'success') {
320a10aed88SAndreas Gohr            return; // FIXME log error
321a10aed88SAndreas Gohr        }
322762f4807SAndreas Gohr
323762f4807SAndreas Gohr        $host = gethostbyaddr($ip);
324762f4807SAndreas Gohr        $this->db->exec(
325762f4807SAndreas Gohr            'INSERT OR REPLACE INTO iplocation (
326762f4807SAndreas Gohr                    ip, country, code, city, host, lastupd
327762f4807SAndreas Gohr                 ) VALUES (
328762f4807SAndreas Gohr                    ?, ?, ?, ?, ?, CURRENT_TIMESTAMP
329762f4807SAndreas Gohr                 )',
330*2adee4c6SAndreas Gohr            $ip,
331*2adee4c6SAndreas Gohr            $data['country'],
332*2adee4c6SAndreas Gohr            $data['countryCode'],
333*2adee4c6SAndreas Gohr            $data['city'],
334*2adee4c6SAndreas Gohr            $host
335762f4807SAndreas Gohr        );
336762f4807SAndreas Gohr    }
337762f4807SAndreas Gohr
338762f4807SAndreas Gohr    /**
339762f4807SAndreas Gohr     * Log a click on an external link
340762f4807SAndreas Gohr     *
341762f4807SAndreas Gohr     * Called from log.php
342762f4807SAndreas Gohr     */
343762f4807SAndreas Gohr    public function logOutgoing(): void
344762f4807SAndreas Gohr    {
345762f4807SAndreas Gohr        global $INPUT;
346762f4807SAndreas Gohr
347762f4807SAndreas Gohr        if (!$INPUT->str('ol')) return;
348762f4807SAndreas Gohr
349762f4807SAndreas Gohr        $link = $INPUT->str('ol');
350762f4807SAndreas Gohr        $link_md5 = md5($link);
351762f4807SAndreas Gohr        $session = $this->getSession();
352762f4807SAndreas Gohr        $page = $INPUT->str('p');
353762f4807SAndreas Gohr
354762f4807SAndreas Gohr        $this->db->exec(
355762f4807SAndreas Gohr            'INSERT INTO outlinks (
356762f4807SAndreas Gohr                dt, session, page, link_md5, link
357762f4807SAndreas Gohr             ) VALUES (
358762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?
359762f4807SAndreas Gohr             )',
360*2adee4c6SAndreas Gohr            $session,
361*2adee4c6SAndreas Gohr            $page,
362*2adee4c6SAndreas Gohr            $link_md5,
363*2adee4c6SAndreas Gohr            $link
364762f4807SAndreas Gohr        );
365762f4807SAndreas Gohr    }
366762f4807SAndreas Gohr
367762f4807SAndreas Gohr    /**
368762f4807SAndreas Gohr     * Log a page access
369762f4807SAndreas Gohr     *
370762f4807SAndreas Gohr     * Called from log.php
371762f4807SAndreas Gohr     */
372762f4807SAndreas Gohr    public function logAccess(): void
373762f4807SAndreas Gohr    {
374762f4807SAndreas Gohr        global $INPUT, $USERINFO;
375762f4807SAndreas Gohr
376762f4807SAndreas Gohr        if (!$INPUT->str('p')) return;
377762f4807SAndreas Gohr
378762f4807SAndreas Gohr        # FIXME check referer against blacklist and drop logging for bad boys
379762f4807SAndreas Gohr
380762f4807SAndreas Gohr        // handle referer
381762f4807SAndreas Gohr        $referer = trim($INPUT->str('r'));
382762f4807SAndreas Gohr        if ($referer) {
383762f4807SAndreas Gohr            $ref = $referer;
384762f4807SAndreas Gohr            $ref_md5 = md5($referer);
385762f4807SAndreas Gohr            if (str_starts_with($referer, DOKU_URL)) {
386762f4807SAndreas Gohr                $ref_type = 'internal';
387762f4807SAndreas Gohr            } else {
388762f4807SAndreas Gohr                $ref_type = 'external';
389762f4807SAndreas Gohr                $this->logExternalSearch($referer, $ref_type);
390762f4807SAndreas Gohr            }
391762f4807SAndreas Gohr        } else {
392762f4807SAndreas Gohr            $ref = '';
393762f4807SAndreas Gohr            $ref_md5 = '';
394762f4807SAndreas Gohr            $ref_type = '';
395762f4807SAndreas Gohr        }
396762f4807SAndreas Gohr
397762f4807SAndreas Gohr        $page = $INPUT->str('p');
398762f4807SAndreas Gohr        $ip = clientIP(true);
399762f4807SAndreas Gohr        $sx = $INPUT->int('sx');
400762f4807SAndreas Gohr        $sy = $INPUT->int('sy');
401762f4807SAndreas Gohr        $vx = $INPUT->int('vx');
402762f4807SAndreas Gohr        $vy = $INPUT->int('vy');
403762f4807SAndreas Gohr        $js = $INPUT->int('js');
404762f4807SAndreas Gohr        $user = $INPUT->server->str('REMOTE_USER');
405762f4807SAndreas Gohr        $session = $this->getSession();
406762f4807SAndreas Gohr
4070b8b7abaSAnna Dabrowska        $accessId = $this->db->exec(
408762f4807SAndreas Gohr            'INSERT INTO access (
409762f4807SAndreas Gohr                dt, page, ip, ua, ua_info, ua_type, ua_ver, os, ref, ref_md5, ref_type,
410762f4807SAndreas Gohr                screen_x, screen_y, view_x, view_y, js, user, session, uid
411762f4807SAndreas Gohr             ) VALUES (
412762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
413762f4807SAndreas Gohr                ?, ?, ?, ?, ?, ?, ?, ?
414762f4807SAndreas Gohr             )',
415*2adee4c6SAndreas Gohr            $page,
416*2adee4c6SAndreas Gohr            $ip,
417*2adee4c6SAndreas Gohr            $this->uaAgent,
418*2adee4c6SAndreas Gohr            $this->uaName,
419*2adee4c6SAndreas Gohr            $this->uaType,
420*2adee4c6SAndreas Gohr            $this->uaVersion,
421*2adee4c6SAndreas Gohr            $this->uaPlatform,
422*2adee4c6SAndreas Gohr            $ref,
423*2adee4c6SAndreas Gohr            $ref_md5,
424*2adee4c6SAndreas Gohr            $ref_type,
425*2adee4c6SAndreas Gohr            $sx,
426*2adee4c6SAndreas Gohr            $sy,
427*2adee4c6SAndreas Gohr            $vx,
428*2adee4c6SAndreas Gohr            $vy,
429*2adee4c6SAndreas Gohr            $js,
430*2adee4c6SAndreas Gohr            $user,
431*2adee4c6SAndreas Gohr            $session,
432*2adee4c6SAndreas Gohr            $this->uid
433762f4807SAndreas Gohr        );
434762f4807SAndreas Gohr
435762f4807SAndreas Gohr        if ($ref_md5) {
436762f4807SAndreas Gohr            $this->db->exec(
437762f4807SAndreas Gohr                'INSERT OR IGNORE INTO refseen (
438762f4807SAndreas Gohr                    ref_md5, dt
439762f4807SAndreas Gohr                 ) VALUES (
440762f4807SAndreas Gohr                    ?, CURRENT_TIMESTAMP
441762f4807SAndreas Gohr                 )',
442762f4807SAndreas Gohr                $ref_md5
443762f4807SAndreas Gohr            );
444762f4807SAndreas Gohr        }
445762f4807SAndreas Gohr
446762f4807SAndreas Gohr        // log group access
447762f4807SAndreas Gohr        if (isset($USERINFO['grps'])) {
4480b8b7abaSAnna Dabrowska            $this->logGroups($accessId, 'view', $USERINFO['grps']);
4490b8b7abaSAnna Dabrowska        }
4500b8b7abaSAnna Dabrowska        // log email domain
4510b8b7abaSAnna Dabrowska        if (!empty($USERINFO['mail'])) {
4520b8b7abaSAnna Dabrowska            $this->logDomain($accessId, 'view', $USERINFO['mail']);
453762f4807SAndreas Gohr        }
454762f4807SAndreas Gohr
455762f4807SAndreas Gohr        // resolve the IP
456762f4807SAndreas Gohr        $this->logIp(clientIP(true));
457762f4807SAndreas Gohr    }
458762f4807SAndreas Gohr
459762f4807SAndreas Gohr    /**
460762f4807SAndreas Gohr     * Log access to a media file
461762f4807SAndreas Gohr     *
462762f4807SAndreas Gohr     * Called from action.php
463762f4807SAndreas Gohr     *
464762f4807SAndreas Gohr     * @param string $media The media ID
465762f4807SAndreas Gohr     * @param string $mime The media's mime type
466762f4807SAndreas Gohr     * @param bool $inline Is this displayed inline?
467762f4807SAndreas Gohr     * @param int $size Size of the media file
468762f4807SAndreas Gohr     */
469762f4807SAndreas Gohr    public function logMedia(string $media, string $mime, bool $inline, int $size): void
470762f4807SAndreas Gohr    {
471762f4807SAndreas Gohr        global $INPUT;
472762f4807SAndreas Gohr
473762f4807SAndreas Gohr        [$mime1, $mime2] = explode('/', strtolower($mime));
474762f4807SAndreas Gohr        $inline = $inline ? 1 : 0;
475762f4807SAndreas Gohr
476762f4807SAndreas Gohr        $ip = clientIP(true);
477762f4807SAndreas Gohr        $user = $INPUT->server->str('REMOTE_USER');
478762f4807SAndreas Gohr        $session = $this->getSession();
479762f4807SAndreas Gohr
480762f4807SAndreas Gohr        $this->db->exec(
481762f4807SAndreas Gohr            'INSERT INTO media (
482762f4807SAndreas Gohr                dt, media, ip, ua, ua_info, ua_type, ua_ver, os, user, session, uid,
483762f4807SAndreas Gohr                size, mime1, mime2, inline
484762f4807SAndreas Gohr             ) VALUES (
485762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
486762f4807SAndreas Gohr                ?, ?, ?, ?
487762f4807SAndreas Gohr             )',
488*2adee4c6SAndreas Gohr            $media,
489*2adee4c6SAndreas Gohr            $ip,
490*2adee4c6SAndreas Gohr            $this->uaAgent,
491*2adee4c6SAndreas Gohr            $this->uaName,
492*2adee4c6SAndreas Gohr            $this->uaType,
493*2adee4c6SAndreas Gohr            $this->uaVersion,
494*2adee4c6SAndreas Gohr            $this->uaPlatform,
495*2adee4c6SAndreas Gohr            $user,
496*2adee4c6SAndreas Gohr            $session,
497*2adee4c6SAndreas Gohr            $this->uid,
498*2adee4c6SAndreas Gohr            $size,
499*2adee4c6SAndreas Gohr            $mime1,
500*2adee4c6SAndreas Gohr            $mime2,
501*2adee4c6SAndreas Gohr            $inline
502762f4807SAndreas Gohr        );
503762f4807SAndreas Gohr    }
504762f4807SAndreas Gohr
505762f4807SAndreas Gohr    /**
506762f4807SAndreas Gohr     * Log page edits
507762f4807SAndreas Gohr     *
508762f4807SAndreas Gohr     * @param string $page The page that was edited
509762f4807SAndreas Gohr     * @param string $type The type of edit (create, edit, etc.)
510762f4807SAndreas Gohr     */
511762f4807SAndreas Gohr    public function logEdit(string $page, string $type): void
512762f4807SAndreas Gohr    {
513762f4807SAndreas Gohr        global $INPUT, $USERINFO;
514762f4807SAndreas Gohr
515762f4807SAndreas Gohr        $ip = clientIP(true);
516762f4807SAndreas Gohr        $user = $INPUT->server->str('REMOTE_USER');
517762f4807SAndreas Gohr        $session = $this->getSession();
518762f4807SAndreas Gohr
5190b8b7abaSAnna Dabrowska        $editId = $this->db->exec(
520762f4807SAndreas Gohr            'INSERT INTO edits (
521762f4807SAndreas Gohr                dt, page, type, ip, user, session, uid
522762f4807SAndreas Gohr             ) VALUES (
523762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?
524762f4807SAndreas Gohr             )',
525*2adee4c6SAndreas Gohr            $page,
526*2adee4c6SAndreas Gohr            $type,
527*2adee4c6SAndreas Gohr            $ip,
528*2adee4c6SAndreas Gohr            $user,
529*2adee4c6SAndreas Gohr            $session,
530*2adee4c6SAndreas Gohr            $this->uid
531762f4807SAndreas Gohr        );
532762f4807SAndreas Gohr
533762f4807SAndreas Gohr        // log group access
534762f4807SAndreas Gohr        if (isset($USERINFO['grps'])) {
5350b8b7abaSAnna Dabrowska            $this->logGroups($editId, 'edit', $USERINFO['grps']);
5360b8b7abaSAnna Dabrowska        }
5370b8b7abaSAnna Dabrowska
5380b8b7abaSAnna Dabrowska        // log email domain
5390b8b7abaSAnna Dabrowska        if (!empty($USERINFO['mail'])) {
5400b8b7abaSAnna Dabrowska            $this->logDomain($editId, 'edit', $USERINFO['mail']);
541762f4807SAndreas Gohr        }
542762f4807SAndreas Gohr    }
543762f4807SAndreas Gohr
544762f4807SAndreas Gohr    /**
545762f4807SAndreas Gohr     * Log login/logoffs and user creations
546762f4807SAndreas Gohr     *
547762f4807SAndreas Gohr     * @param string $type The type of login event (login, logout, create)
548762f4807SAndreas Gohr     * @param string $user The username (optional, will use current user if empty)
549762f4807SAndreas Gohr     */
550762f4807SAndreas Gohr    public function logLogin(string $type, string $user = ''): void
551762f4807SAndreas Gohr    {
552762f4807SAndreas Gohr        global $INPUT;
553762f4807SAndreas Gohr
554762f4807SAndreas Gohr        if (!$user) $user = $INPUT->server->str('REMOTE_USER');
555762f4807SAndreas Gohr
556762f4807SAndreas Gohr        $ip = clientIP(true);
557762f4807SAndreas Gohr        $session = $this->getSession();
558762f4807SAndreas Gohr
559762f4807SAndreas Gohr        $this->db->exec(
560762f4807SAndreas Gohr            'INSERT INTO logins (
561762f4807SAndreas Gohr                dt, type, ip, user, session, uid
562762f4807SAndreas Gohr             ) VALUES (
563762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?
564762f4807SAndreas Gohr             )',
565*2adee4c6SAndreas Gohr            $type,
566*2adee4c6SAndreas Gohr            $ip,
567*2adee4c6SAndreas Gohr            $user,
568*2adee4c6SAndreas Gohr            $session,
569*2adee4c6SAndreas Gohr            $this->uid
570762f4807SAndreas Gohr        );
571762f4807SAndreas Gohr    }
572762f4807SAndreas Gohr
573762f4807SAndreas Gohr    /**
574762f4807SAndreas Gohr     * Log the current page count and size as today's history entry
575762f4807SAndreas Gohr     */
576762f4807SAndreas Gohr    public function logHistoryPages(): void
577762f4807SAndreas Gohr    {
578762f4807SAndreas Gohr        global $conf;
579762f4807SAndreas Gohr
580762f4807SAndreas Gohr        // use the popularity plugin's search method to find the wanted data
581762f4807SAndreas Gohr        /** @var helper_plugin_popularity $pop */
582762f4807SAndreas Gohr        $pop = plugin_load('helper', 'popularity');
583b188870fSAndreas Gohr        $list = $this->initEmptySearchList();
584762f4807SAndreas Gohr        search($list, $conf['datadir'], [$pop, 'searchCountCallback'], ['all' => false], '');
585762f4807SAndreas Gohr        $page_count = $list['file_count'];
586762f4807SAndreas Gohr        $page_size = $list['file_size'];
587762f4807SAndreas Gohr
588762f4807SAndreas Gohr        $this->db->exec(
589762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
590762f4807SAndreas Gohr                info, value, dt
591762f4807SAndreas Gohr             ) VALUES (
592483101d3SAndreas Gohr                ?, ?, CURRENT_TIMESTAMP
593762f4807SAndreas Gohr             )',
594*2adee4c6SAndreas Gohr            'page_count',
595*2adee4c6SAndreas Gohr            $page_count
596762f4807SAndreas Gohr        );
597762f4807SAndreas Gohr        $this->db->exec(
598762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
599762f4807SAndreas Gohr                info, value, dt
600762f4807SAndreas Gohr             ) VALUES (
601483101d3SAndreas Gohr                ?, ?, CURRENT_TIMESTAMP
602762f4807SAndreas Gohr             )',
603*2adee4c6SAndreas Gohr            'page_size',
604*2adee4c6SAndreas Gohr            $page_size
605762f4807SAndreas Gohr        );
606762f4807SAndreas Gohr    }
607762f4807SAndreas Gohr
608762f4807SAndreas Gohr    /**
609762f4807SAndreas Gohr     * Log the current media count and size as today's history entry
610762f4807SAndreas Gohr     */
611762f4807SAndreas Gohr    public function logHistoryMedia(): void
612762f4807SAndreas Gohr    {
613762f4807SAndreas Gohr        global $conf;
614762f4807SAndreas Gohr
615762f4807SAndreas Gohr        // use the popularity plugin's search method to find the wanted data
616762f4807SAndreas Gohr        /** @var helper_plugin_popularity $pop */
617762f4807SAndreas Gohr        $pop = plugin_load('helper', 'popularity');
618b188870fSAndreas Gohr        $list = $this->initEmptySearchList();
619762f4807SAndreas Gohr        search($list, $conf['mediadir'], [$pop, 'searchCountCallback'], ['all' => true], '');
620762f4807SAndreas Gohr        $media_count = $list['file_count'];
621762f4807SAndreas Gohr        $media_size = $list['file_size'];
622762f4807SAndreas Gohr
623762f4807SAndreas Gohr        $this->db->exec(
624762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
625762f4807SAndreas Gohr                info, value, dt
626762f4807SAndreas Gohr             ) VALUES (
627483101d3SAndreas Gohr                ?, ?, CURRENT_TIMESTAMP
628762f4807SAndreas Gohr             )',
629*2adee4c6SAndreas Gohr            'media_count',
630*2adee4c6SAndreas Gohr            $media_count
631762f4807SAndreas Gohr        );
632762f4807SAndreas Gohr        $this->db->exec(
633762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
634762f4807SAndreas Gohr                info, value, dt
635762f4807SAndreas Gohr             ) VALUES (
636483101d3SAndreas Gohr                ?, ?, CURRENT_TIMESTAMP
637762f4807SAndreas Gohr             )',
638*2adee4c6SAndreas Gohr            'media_size',
639*2adee4c6SAndreas Gohr            $media_size
640762f4807SAndreas Gohr        );
641762f4807SAndreas Gohr    }
642b188870fSAndreas Gohr
643b188870fSAndreas Gohr    /**
644b188870fSAndreas Gohr     * @todo can be dropped in favor of helper_plugin_popularity::initEmptySearchList() once it's public
645b188870fSAndreas Gohr     * @return array
646b188870fSAndreas Gohr     */
647b188870fSAndreas Gohr    protected function initEmptySearchList()
648b188870fSAndreas Gohr    {
649b188870fSAndreas Gohr        return array_fill_keys([
650b188870fSAndreas Gohr            'file_count',
651b188870fSAndreas Gohr            'file_size',
652b188870fSAndreas Gohr            'file_max',
653b188870fSAndreas Gohr            'file_min',
654b188870fSAndreas Gohr            'dir_count',
655b188870fSAndreas Gohr            'dir_nest',
656b188870fSAndreas Gohr            'file_oldest'
657b188870fSAndreas Gohr        ], 0);
658b188870fSAndreas Gohr    }
659762f4807SAndreas Gohr}
660