xref: /plugin/statistics/Logger.php (revision fced2f86d98b2c7fa4ac86084d4710e161a8ca0a)
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 Gohr
16762f4807SAndreas Gohrclass Logger
17762f4807SAndreas Gohr{
18762f4807SAndreas Gohr    /** @var helper_plugin_statistics The statistics helper plugin instance */
19762f4807SAndreas Gohr    protected helper_plugin_statistics $hlp;
20762f4807SAndreas Gohr
21762f4807SAndreas Gohr    /** @var SQLiteDB The SQLite database instance */
22762f4807SAndreas Gohr    protected SQLiteDB $db;
23762f4807SAndreas Gohr
24762f4807SAndreas Gohr    /** @var string The full user agent string */
25762f4807SAndreas Gohr    protected string $uaAgent;
26762f4807SAndreas Gohr
27762f4807SAndreas Gohr    /** @var string The type of user agent (browser, robot, feedreader) */
28762f4807SAndreas Gohr    protected string $uaType = 'browser';
29762f4807SAndreas Gohr
30762f4807SAndreas Gohr    /** @var string The browser/client name */
31762f4807SAndreas Gohr    protected string $uaName;
32762f4807SAndreas Gohr
33762f4807SAndreas Gohr    /** @var string The browser/client version */
34762f4807SAndreas Gohr    protected string $uaVersion;
35762f4807SAndreas Gohr
36762f4807SAndreas Gohr    /** @var string The operating system/platform */
37762f4807SAndreas Gohr    protected string $uaPlatform;
38762f4807SAndreas Gohr
39762f4807SAndreas Gohr    /** @var string The unique user identifier */
40762f4807SAndreas Gohr    protected string $uid;
41762f4807SAndreas Gohr
42c7cad24dSAndreas Gohr    /** @var DokuHTTPClient|null The HTTP client instance for testing */
43c7cad24dSAndreas Gohr    protected ?DokuHTTPClient $httpClient = null;
44c7cad24dSAndreas Gohr
45762f4807SAndreas Gohr
46762f4807SAndreas Gohr    /**
47762f4807SAndreas Gohr     * Constructor
48762f4807SAndreas Gohr     *
49762f4807SAndreas Gohr     * Parses browser info and set internal vars
50762f4807SAndreas Gohr     */
51c7cad24dSAndreas Gohr    public function __construct(helper_plugin_statistics $hlp, ?DokuHTTPClient $httpClient = null)
52762f4807SAndreas Gohr    {
53762f4807SAndreas Gohr        global $INPUT;
54762f4807SAndreas Gohr
55762f4807SAndreas Gohr        $this->hlp = $hlp;
56762f4807SAndreas Gohr        $this->db = $this->hlp->getDB();
57c7cad24dSAndreas Gohr        $this->httpClient = $httpClient;
58762f4807SAndreas Gohr
59762f4807SAndreas Gohr        $ua = trim($INPUT->server->str('HTTP_USER_AGENT'));
60762f4807SAndreas Gohr
61762f4807SAndreas Gohr        AbstractDeviceParser::setVersionTruncation(AbstractDeviceParser::VERSION_TRUNCATION_MAJOR);
62762f4807SAndreas Gohr        $dd = new DeviceDetector($ua); // FIXME we could use client hints, but need to add headers
63762f4807SAndreas Gohr        $dd->discardBotInformation();
64762f4807SAndreas Gohr        $dd->parse();
65762f4807SAndreas Gohr
6600f786d8SAndreas Gohr        if ($dd->isFeedReader()) {
6700f786d8SAndreas Gohr            $this->uaType = 'feedreader';
6800f786d8SAndreas Gohr        } else if ($dd->isBot()) {
69762f4807SAndreas Gohr            $this->uaType = 'robot';
70762f4807SAndreas Gohr
71762f4807SAndreas Gohr            // for now ignore bots
72762f4807SAndreas Gohr            throw new \RuntimeException('Bot detected, not logging');
73762f4807SAndreas Gohr        }
74762f4807SAndreas Gohr
75762f4807SAndreas Gohr        $this->uaAgent = $ua;
7605786d83SAndreas Gohr        $this->uaName = Browser::getBrowserFamily($dd->getClient('name')) ?: 'Unknown';
7700f786d8SAndreas Gohr        $this->uaVersion = $dd->getClient('version') ?: '0';
7805786d83SAndreas Gohr        $this->uaPlatform = OperatingSystem::getOsFamily($dd->getOs('name')) ?: 'Unknown';
79762f4807SAndreas Gohr        $this->uid = $this->getUID();
80762f4807SAndreas Gohr
81762f4807SAndreas Gohr
82762f4807SAndreas Gohr        $this->logLastseen();
83762f4807SAndreas Gohr    }
84762f4807SAndreas Gohr
85762f4807SAndreas Gohr    /**
86762f4807SAndreas Gohr     * Should be called before logging
87762f4807SAndreas Gohr     *
88762f4807SAndreas Gohr     * This starts a transaction, so all logging is done in one go
89762f4807SAndreas Gohr     */
90762f4807SAndreas Gohr    public function begin(): void
91762f4807SAndreas Gohr    {
92762f4807SAndreas Gohr        $this->hlp->getDB()->getPdo()->beginTransaction();
93762f4807SAndreas Gohr    }
94762f4807SAndreas Gohr
95762f4807SAndreas Gohr    /**
96762f4807SAndreas Gohr     * Should be called after logging
97762f4807SAndreas Gohr     *
98762f4807SAndreas Gohr     * This commits the transaction started in begin()
99762f4807SAndreas Gohr     */
100762f4807SAndreas Gohr    public function end(): void
101762f4807SAndreas Gohr    {
102762f4807SAndreas Gohr        $this->hlp->getDB()->getPdo()->commit();
103762f4807SAndreas Gohr    }
104762f4807SAndreas Gohr
105762f4807SAndreas Gohr    /**
106762f4807SAndreas Gohr     * Get the unique user ID
107762f4807SAndreas Gohr     *
108762f4807SAndreas Gohr     * @return string The unique user identifier
109762f4807SAndreas Gohr     */
110762f4807SAndreas Gohr    protected function getUID(): string
111762f4807SAndreas Gohr    {
112762f4807SAndreas Gohr        global $INPUT;
113762f4807SAndreas Gohr
114762f4807SAndreas Gohr        $uid = $INPUT->str('uid');
115762f4807SAndreas Gohr        if (!$uid) $uid = get_doku_pref('plgstats', false);
116762f4807SAndreas Gohr        if (!$uid) $uid = session_id();
117b188870fSAndreas Gohr        set_doku_pref('plgstats', $uid);
118762f4807SAndreas Gohr        return $uid;
119762f4807SAndreas Gohr    }
120762f4807SAndreas Gohr
121762f4807SAndreas Gohr    /**
122762f4807SAndreas Gohr     * Return the user's session ID
123762f4807SAndreas Gohr     *
124762f4807SAndreas Gohr     * This is usually our own managed session, not a PHP session (only in fallback)
125762f4807SAndreas Gohr     *
126762f4807SAndreas Gohr     * @return string The session identifier
127762f4807SAndreas Gohr     */
128762f4807SAndreas Gohr    protected function getSession(): string
129762f4807SAndreas Gohr    {
130762f4807SAndreas Gohr        global $INPUT;
131762f4807SAndreas Gohr
132762f4807SAndreas Gohr        $ses = $INPUT->str('ses');
133762f4807SAndreas Gohr        if (!$ses) $ses = get_doku_pref('plgstatsses', false);
134762f4807SAndreas Gohr        if (!$ses) $ses = session_id();
135b188870fSAndreas Gohr        set_doku_pref('plgstatsses', $ses);
136762f4807SAndreas Gohr        return $ses;
137762f4807SAndreas Gohr    }
138762f4807SAndreas Gohr
139762f4807SAndreas Gohr    /**
140762f4807SAndreas Gohr     * Log that we've seen the user (authenticated only)
141762f4807SAndreas Gohr     */
142762f4807SAndreas Gohr    public function logLastseen(): void
143762f4807SAndreas Gohr    {
144762f4807SAndreas Gohr        global $INPUT;
145762f4807SAndreas Gohr
146762f4807SAndreas Gohr        if (empty($INPUT->server->str('REMOTE_USER'))) return;
147762f4807SAndreas Gohr
148762f4807SAndreas Gohr        $this->db->exec(
149762f4807SAndreas Gohr            'REPLACE INTO lastseen (user, dt) VALUES (?, CURRENT_TIMESTAMP)',
150762f4807SAndreas Gohr            $INPUT->server->str('REMOTE_USER'),
151762f4807SAndreas Gohr        );
152762f4807SAndreas Gohr    }
153762f4807SAndreas Gohr
154762f4807SAndreas Gohr    /**
155762f4807SAndreas Gohr     * Log actions by groups
156762f4807SAndreas Gohr     *
157762f4807SAndreas Gohr     * @param string $type The type of access to log ('view','edit')
158762f4807SAndreas Gohr     * @param array $groups The groups to log
159762f4807SAndreas Gohr     */
160762f4807SAndreas Gohr    public function logGroups(string $type, array $groups): void
161762f4807SAndreas Gohr    {
162483101d3SAndreas Gohr        if (!$groups) return;
163762f4807SAndreas Gohr
164483101d3SAndreas Gohr        $toLog = (array)$this->hlp->getConf('loggroups');
165*fced2f86SAnna Dabrowska
166*fced2f86SAnna Dabrowska        // if specific groups are configured, limit logging to them only
167*fced2f86SAnna Dabrowska        $groups = !empty(array_filter($toLog)) ? array_intersect($groups, $toLog) : $groups;
168483101d3SAndreas Gohr        if (!$groups) return;
169762f4807SAndreas Gohr
170483101d3SAndreas Gohr        $placeholders = join(',', array_fill(0, count($groups), '(?, ?)'));
171762f4807SAndreas Gohr        $params = [];
172483101d3SAndreas Gohr        $sql = "INSERT INTO groups (`type`, `group`) VALUES $placeholders";
173762f4807SAndreas Gohr        foreach ($groups as $group) {
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    /**
182762f4807SAndreas Gohr     * Log external search queries
183762f4807SAndreas Gohr     *
184762f4807SAndreas Gohr     * Will not write anything if the referer isn't a search engine
185762f4807SAndreas Gohr     *
186762f4807SAndreas Gohr     * @param string $referer The HTTP referer URL
187762f4807SAndreas Gohr     * @param string $type Reference to the type variable that will be modified
188762f4807SAndreas Gohr     */
189762f4807SAndreas Gohr    public function logExternalSearch(string $referer, string &$type): void
190762f4807SAndreas Gohr    {
191762f4807SAndreas Gohr        global $INPUT;
192762f4807SAndreas Gohr
193762f4807SAndreas Gohr        $searchEngine = new SearchEngines($referer);
194762f4807SAndreas Gohr
195762f4807SAndreas Gohr        if (!$searchEngine->isSearchEngine()) {
196762f4807SAndreas Gohr            return; // not a search engine
197762f4807SAndreas Gohr        }
198762f4807SAndreas Gohr
199762f4807SAndreas Gohr        $type = 'search';
200762f4807SAndreas Gohr        $query = $searchEngine->getQuery();
201762f4807SAndreas Gohr
202762f4807SAndreas Gohr        // log it!
203762f4807SAndreas Gohr        $words = explode(' ', Clean::stripspecials($query, ' ', '\._\-:\*'));
204762f4807SAndreas Gohr        $this->logSearch($INPUT->str('p'), $query, $words, $searchEngine->getEngine());
205762f4807SAndreas Gohr    }
206762f4807SAndreas Gohr
207762f4807SAndreas Gohr    /**
208762f4807SAndreas Gohr     * Log search data to the search related tables
209762f4807SAndreas Gohr     *
210762f4807SAndreas Gohr     * @param string $page The page being searched from
211762f4807SAndreas Gohr     * @param string $query The search query
212762f4807SAndreas Gohr     * @param array $words Array of search words
213762f4807SAndreas Gohr     * @param string $engine The search engine name
214762f4807SAndreas Gohr     */
215762f4807SAndreas Gohr    public function logSearch(string $page, string $query, array $words, string $engine): void
216762f4807SAndreas Gohr    {
217762f4807SAndreas Gohr        $sid = $this->db->exec(
218762f4807SAndreas Gohr            'INSERT INTO search (dt, page, query, engine) VALUES (CURRENT_TIMESTAMP, ?, ?, ?)',
219762f4807SAndreas Gohr            $page, $query, $engine
220762f4807SAndreas Gohr        );
221762f4807SAndreas Gohr        if (!$sid) return;
222762f4807SAndreas Gohr
223762f4807SAndreas Gohr        foreach ($words as $word) {
224762f4807SAndreas Gohr            if (!$word) continue;
225762f4807SAndreas Gohr            $this->db->exec(
226762f4807SAndreas Gohr                'INSERT INTO searchwords (sid, word) VALUES (?, ?)',
227762f4807SAndreas Gohr                $sid, $word
228762f4807SAndreas Gohr            );
229762f4807SAndreas Gohr        }
230762f4807SAndreas Gohr    }
231762f4807SAndreas Gohr
232762f4807SAndreas Gohr    /**
233762f4807SAndreas Gohr     * Log that the session was seen
234762f4807SAndreas Gohr     *
235762f4807SAndreas Gohr     * This is used to calculate the time people spend on the whole site
236762f4807SAndreas Gohr     * during their session
237762f4807SAndreas Gohr     *
238762f4807SAndreas Gohr     * Viewcounts are used for bounce calculation
239762f4807SAndreas Gohr     *
240762f4807SAndreas Gohr     * @param int $addview set to 1 to count a view
241762f4807SAndreas Gohr     */
242762f4807SAndreas Gohr    public function logSession(int $addview = 0): void
243762f4807SAndreas Gohr    {
244762f4807SAndreas Gohr        // only log browser sessions
245762f4807SAndreas Gohr        if ($this->uaType != 'browser') return;
246762f4807SAndreas Gohr
247762f4807SAndreas Gohr        $session = $this->getSession();
248762f4807SAndreas Gohr        $this->db->exec(
249762f4807SAndreas Gohr            'INSERT OR REPLACE INTO session (
250762f4807SAndreas Gohr                session, dt, end, views, uid
251762f4807SAndreas Gohr             ) VALUES (
252762f4807SAndreas Gohr                ?,
253762f4807SAndreas Gohr                CURRENT_TIMESTAMP,
254762f4807SAndreas Gohr                CURRENT_TIMESTAMP,
255762f4807SAndreas Gohr                COALESCE((SELECT views FROM session WHERE session = ?) + ?, ?),
256762f4807SAndreas Gohr                ?
257762f4807SAndreas Gohr             )',
258762f4807SAndreas Gohr            $session, $session, $addview, $addview, $this->uid
259762f4807SAndreas Gohr        );
260762f4807SAndreas Gohr    }
261762f4807SAndreas Gohr
262762f4807SAndreas Gohr    /**
263762f4807SAndreas Gohr     * Resolve IP to country/city and store in database
264762f4807SAndreas Gohr     *
265762f4807SAndreas Gohr     * @param string $ip The IP address to resolve
266762f4807SAndreas Gohr     */
267762f4807SAndreas Gohr    public function logIp(string $ip): void
268762f4807SAndreas Gohr    {
269762f4807SAndreas Gohr        // check if IP already known and up-to-date
270762f4807SAndreas Gohr        $result = $this->db->queryValue(
271762f4807SAndreas Gohr            "SELECT ip
272762f4807SAndreas Gohr             FROM   iplocation
273762f4807SAndreas Gohr             WHERE  ip = ?
274762f4807SAndreas Gohr               AND  lastupd > date('now', '-30 days')",
275762f4807SAndreas Gohr            $ip
276762f4807SAndreas Gohr        );
277762f4807SAndreas Gohr        if ($result) return;
278762f4807SAndreas Gohr
279c7cad24dSAndreas Gohr        $http = $this->httpClient ?: new DokuHTTPClient();
280762f4807SAndreas Gohr        $http->timeout = 10;
281762f4807SAndreas Gohr        $json = $http->get('http://ip-api.com/json/' . $ip); // yes, it's HTTP only
282762f4807SAndreas Gohr
283762f4807SAndreas Gohr        if (!$json) return; // FIXME log error
284762f4807SAndreas Gohr        try {
285762f4807SAndreas Gohr            $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
286762f4807SAndreas Gohr        } catch (\JsonException $e) {
287762f4807SAndreas Gohr            return; // FIXME log error
288762f4807SAndreas Gohr        }
289a10aed88SAndreas Gohr        if (!isset($data['status']) || $data['status'] !== 'success') {
290a10aed88SAndreas Gohr            return; // FIXME log error
291a10aed88SAndreas Gohr        }
292762f4807SAndreas Gohr
293762f4807SAndreas Gohr        $host = gethostbyaddr($ip);
294762f4807SAndreas Gohr        $this->db->exec(
295762f4807SAndreas Gohr            'INSERT OR REPLACE INTO iplocation (
296762f4807SAndreas Gohr                    ip, country, code, city, host, lastupd
297762f4807SAndreas Gohr                 ) VALUES (
298762f4807SAndreas Gohr                    ?, ?, ?, ?, ?, CURRENT_TIMESTAMP
299762f4807SAndreas Gohr                 )',
300762f4807SAndreas Gohr            $ip, $data['country'], $data['countryCode'], $data['city'], $host
301762f4807SAndreas Gohr        );
302762f4807SAndreas Gohr    }
303762f4807SAndreas Gohr
304762f4807SAndreas Gohr    /**
305762f4807SAndreas Gohr     * Log a click on an external link
306762f4807SAndreas Gohr     *
307762f4807SAndreas Gohr     * Called from log.php
308762f4807SAndreas Gohr     */
309762f4807SAndreas Gohr    public function logOutgoing(): void
310762f4807SAndreas Gohr    {
311762f4807SAndreas Gohr        global $INPUT;
312762f4807SAndreas Gohr
313762f4807SAndreas Gohr        if (!$INPUT->str('ol')) return;
314762f4807SAndreas Gohr
315762f4807SAndreas Gohr        $link = $INPUT->str('ol');
316762f4807SAndreas Gohr        $link_md5 = md5($link);
317762f4807SAndreas Gohr        $session = $this->getSession();
318762f4807SAndreas Gohr        $page = $INPUT->str('p');
319762f4807SAndreas Gohr
320762f4807SAndreas Gohr        $this->db->exec(
321762f4807SAndreas Gohr            'INSERT INTO outlinks (
322762f4807SAndreas Gohr                dt, session, page, link_md5, link
323762f4807SAndreas Gohr             ) VALUES (
324762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?
325762f4807SAndreas Gohr             )',
326762f4807SAndreas Gohr            $session, $page, $link_md5, $link
327762f4807SAndreas Gohr        );
328762f4807SAndreas Gohr    }
329762f4807SAndreas Gohr
330762f4807SAndreas Gohr    /**
331762f4807SAndreas Gohr     * Log a page access
332762f4807SAndreas Gohr     *
333762f4807SAndreas Gohr     * Called from log.php
334762f4807SAndreas Gohr     */
335762f4807SAndreas Gohr    public function logAccess(): void
336762f4807SAndreas Gohr    {
337762f4807SAndreas Gohr        global $INPUT, $USERINFO;
338762f4807SAndreas Gohr
339762f4807SAndreas Gohr        if (!$INPUT->str('p')) return;
340762f4807SAndreas Gohr
341762f4807SAndreas Gohr        # FIXME check referer against blacklist and drop logging for bad boys
342762f4807SAndreas Gohr
343762f4807SAndreas Gohr        // handle referer
344762f4807SAndreas Gohr        $referer = trim($INPUT->str('r'));
345762f4807SAndreas Gohr        if ($referer) {
346762f4807SAndreas Gohr            $ref = $referer;
347762f4807SAndreas Gohr            $ref_md5 = md5($referer);
348762f4807SAndreas Gohr            if (str_starts_with($referer, DOKU_URL)) {
349762f4807SAndreas Gohr                $ref_type = 'internal';
350762f4807SAndreas Gohr            } else {
351762f4807SAndreas Gohr                $ref_type = 'external';
352762f4807SAndreas Gohr                $this->logExternalSearch($referer, $ref_type);
353762f4807SAndreas Gohr            }
354762f4807SAndreas Gohr        } else {
355762f4807SAndreas Gohr            $ref = '';
356762f4807SAndreas Gohr            $ref_md5 = '';
357762f4807SAndreas Gohr            $ref_type = '';
358762f4807SAndreas Gohr        }
359762f4807SAndreas Gohr
360762f4807SAndreas Gohr        $page = $INPUT->str('p');
361762f4807SAndreas Gohr        $ip = clientIP(true);
362762f4807SAndreas Gohr        $sx = $INPUT->int('sx');
363762f4807SAndreas Gohr        $sy = $INPUT->int('sy');
364762f4807SAndreas Gohr        $vx = $INPUT->int('vx');
365762f4807SAndreas Gohr        $vy = $INPUT->int('vy');
366762f4807SAndreas Gohr        $js = $INPUT->int('js');
367762f4807SAndreas Gohr        $user = $INPUT->server->str('REMOTE_USER');
368762f4807SAndreas Gohr        $session = $this->getSession();
369762f4807SAndreas Gohr
370762f4807SAndreas Gohr        $this->db->exec(
371762f4807SAndreas Gohr            'INSERT INTO access (
372762f4807SAndreas Gohr                dt, page, ip, ua, ua_info, ua_type, ua_ver, os, ref, ref_md5, ref_type,
373762f4807SAndreas Gohr                screen_x, screen_y, view_x, view_y, js, user, session, uid
374762f4807SAndreas Gohr             ) VALUES (
375762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
376762f4807SAndreas Gohr                ?, ?, ?, ?, ?, ?, ?, ?
377762f4807SAndreas Gohr             )',
378762f4807SAndreas Gohr            $page, $ip, $this->uaAgent, $this->uaName, $this->uaType, $this->uaVersion, $this->uaPlatform,
379762f4807SAndreas Gohr            $ref, $ref_md5, $ref_type, $sx, $sy, $vx, $vy, $js, $user, $session, $this->uid
380762f4807SAndreas Gohr        );
381762f4807SAndreas Gohr
382762f4807SAndreas Gohr        if ($ref_md5) {
383762f4807SAndreas Gohr            $this->db->exec(
384762f4807SAndreas Gohr                'INSERT OR IGNORE INTO refseen (
385762f4807SAndreas Gohr                    ref_md5, dt
386762f4807SAndreas Gohr                 ) VALUES (
387762f4807SAndreas Gohr                    ?, CURRENT_TIMESTAMP
388762f4807SAndreas Gohr                 )',
389762f4807SAndreas Gohr                $ref_md5
390762f4807SAndreas Gohr            );
391762f4807SAndreas Gohr        }
392762f4807SAndreas Gohr
393762f4807SAndreas Gohr        // log group access
394762f4807SAndreas Gohr        if (isset($USERINFO['grps'])) {
395762f4807SAndreas Gohr            $this->logGroups('view', $USERINFO['grps']);
396762f4807SAndreas Gohr        }
397762f4807SAndreas Gohr
398762f4807SAndreas Gohr        // resolve the IP
399762f4807SAndreas Gohr        $this->logIp(clientIP(true));
400762f4807SAndreas Gohr    }
401762f4807SAndreas Gohr
402762f4807SAndreas Gohr    /**
403762f4807SAndreas Gohr     * Log access to a media file
404762f4807SAndreas Gohr     *
405762f4807SAndreas Gohr     * Called from action.php
406762f4807SAndreas Gohr     *
407762f4807SAndreas Gohr     * @param string $media The media ID
408762f4807SAndreas Gohr     * @param string $mime The media's mime type
409762f4807SAndreas Gohr     * @param bool $inline Is this displayed inline?
410762f4807SAndreas Gohr     * @param int $size Size of the media file
411762f4807SAndreas Gohr     */
412762f4807SAndreas Gohr    public function logMedia(string $media, string $mime, bool $inline, int $size): void
413762f4807SAndreas Gohr    {
414762f4807SAndreas Gohr        global $INPUT;
415762f4807SAndreas Gohr
416762f4807SAndreas Gohr        [$mime1, $mime2] = explode('/', strtolower($mime));
417762f4807SAndreas Gohr        $inline = $inline ? 1 : 0;
418762f4807SAndreas Gohr        $size = (int)$size;
419762f4807SAndreas Gohr
420762f4807SAndreas Gohr        $ip = clientIP(true);
421762f4807SAndreas Gohr        $user = $INPUT->server->str('REMOTE_USER');
422762f4807SAndreas Gohr        $session = $this->getSession();
423762f4807SAndreas Gohr
424762f4807SAndreas Gohr        $this->db->exec(
425762f4807SAndreas Gohr            'INSERT INTO media (
426762f4807SAndreas Gohr                dt, media, ip, ua, ua_info, ua_type, ua_ver, os, user, session, uid,
427762f4807SAndreas Gohr                size, mime1, mime2, inline
428762f4807SAndreas Gohr             ) VALUES (
429762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
430762f4807SAndreas Gohr                ?, ?, ?, ?
431762f4807SAndreas Gohr             )',
432762f4807SAndreas Gohr            $media, $ip, $this->uaAgent, $this->uaName, $this->uaType, $this->uaVersion, $this->uaPlatform,
433762f4807SAndreas Gohr            $user, $session, $this->uid, $size, $mime1, $mime2, $inline
434762f4807SAndreas Gohr        );
435762f4807SAndreas Gohr    }
436762f4807SAndreas Gohr
437762f4807SAndreas Gohr    /**
438762f4807SAndreas Gohr     * Log page edits
439762f4807SAndreas Gohr     *
440762f4807SAndreas Gohr     * @param string $page The page that was edited
441762f4807SAndreas Gohr     * @param string $type The type of edit (create, edit, etc.)
442762f4807SAndreas Gohr     */
443762f4807SAndreas Gohr    public function logEdit(string $page, string $type): void
444762f4807SAndreas Gohr    {
445762f4807SAndreas Gohr        global $INPUT, $USERINFO;
446762f4807SAndreas Gohr
447762f4807SAndreas Gohr        $ip = clientIP(true);
448762f4807SAndreas Gohr        $user = $INPUT->server->str('REMOTE_USER');
449762f4807SAndreas Gohr        $session = $this->getSession();
450762f4807SAndreas Gohr
451762f4807SAndreas Gohr        $this->db->exec(
452762f4807SAndreas Gohr            'INSERT INTO edits (
453762f4807SAndreas Gohr                dt, page, type, ip, user, session, uid
454762f4807SAndreas Gohr             ) VALUES (
455762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?
456762f4807SAndreas Gohr             )',
457762f4807SAndreas Gohr            $page, $type, $ip, $user, $session, $this->uid
458762f4807SAndreas Gohr        );
459762f4807SAndreas Gohr
460762f4807SAndreas Gohr        // log group access
461762f4807SAndreas Gohr        if (isset($USERINFO['grps'])) {
462762f4807SAndreas Gohr            $this->logGroups('edit', $USERINFO['grps']);
463762f4807SAndreas Gohr        }
464762f4807SAndreas Gohr    }
465762f4807SAndreas Gohr
466762f4807SAndreas Gohr    /**
467762f4807SAndreas Gohr     * Log login/logoffs and user creations
468762f4807SAndreas Gohr     *
469762f4807SAndreas Gohr     * @param string $type The type of login event (login, logout, create)
470762f4807SAndreas Gohr     * @param string $user The username (optional, will use current user if empty)
471762f4807SAndreas Gohr     */
472762f4807SAndreas Gohr    public function logLogin(string $type, string $user = ''): void
473762f4807SAndreas Gohr    {
474762f4807SAndreas Gohr        global $INPUT;
475762f4807SAndreas Gohr
476762f4807SAndreas Gohr        if (!$user) $user = $INPUT->server->str('REMOTE_USER');
477762f4807SAndreas Gohr
478762f4807SAndreas Gohr        $ip = clientIP(true);
479762f4807SAndreas Gohr        $session = $this->getSession();
480762f4807SAndreas Gohr
481762f4807SAndreas Gohr        $this->db->exec(
482762f4807SAndreas Gohr            'INSERT INTO logins (
483762f4807SAndreas Gohr                dt, type, ip, user, session, uid
484762f4807SAndreas Gohr             ) VALUES (
485762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?
486762f4807SAndreas Gohr             )',
487762f4807SAndreas Gohr            $type, $ip, $user, $session, $this->uid
488762f4807SAndreas Gohr        );
489762f4807SAndreas Gohr    }
490762f4807SAndreas Gohr
491762f4807SAndreas Gohr    /**
492762f4807SAndreas Gohr     * Log the current page count and size as today's history entry
493762f4807SAndreas Gohr     */
494762f4807SAndreas Gohr    public function logHistoryPages(): void
495762f4807SAndreas Gohr    {
496762f4807SAndreas Gohr        global $conf;
497762f4807SAndreas Gohr
498762f4807SAndreas Gohr        // use the popularity plugin's search method to find the wanted data
499762f4807SAndreas Gohr        /** @var helper_plugin_popularity $pop */
500762f4807SAndreas Gohr        $pop = plugin_load('helper', 'popularity');
501b188870fSAndreas Gohr        $list = $this->initEmptySearchList();
502762f4807SAndreas Gohr        search($list, $conf['datadir'], [$pop, 'searchCountCallback'], ['all' => false], '');
503762f4807SAndreas Gohr        $page_count = $list['file_count'];
504762f4807SAndreas Gohr        $page_size = $list['file_size'];
505762f4807SAndreas Gohr
506762f4807SAndreas Gohr        $this->db->exec(
507762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
508762f4807SAndreas Gohr                info, value, dt
509762f4807SAndreas Gohr             ) VALUES (
510483101d3SAndreas Gohr                ?, ?, CURRENT_TIMESTAMP
511762f4807SAndreas Gohr             )',
512762f4807SAndreas Gohr            'page_count', $page_count
513762f4807SAndreas Gohr        );
514762f4807SAndreas Gohr        $this->db->exec(
515762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
516762f4807SAndreas Gohr                info, value, dt
517762f4807SAndreas Gohr             ) VALUES (
518483101d3SAndreas Gohr                ?, ?, CURRENT_TIMESTAMP
519762f4807SAndreas Gohr             )',
520762f4807SAndreas Gohr            'page_size', $page_size
521762f4807SAndreas Gohr        );
522762f4807SAndreas Gohr    }
523762f4807SAndreas Gohr
524762f4807SAndreas Gohr    /**
525762f4807SAndreas Gohr     * Log the current media count and size as today's history entry
526762f4807SAndreas Gohr     */
527762f4807SAndreas Gohr    public function logHistoryMedia(): void
528762f4807SAndreas Gohr    {
529762f4807SAndreas Gohr        global $conf;
530762f4807SAndreas Gohr
531762f4807SAndreas Gohr        // use the popularity plugin's search method to find the wanted data
532762f4807SAndreas Gohr        /** @var helper_plugin_popularity $pop */
533762f4807SAndreas Gohr        $pop = plugin_load('helper', 'popularity');
534b188870fSAndreas Gohr        $list = $this->initEmptySearchList();
535762f4807SAndreas Gohr        search($list, $conf['mediadir'], [$pop, 'searchCountCallback'], ['all' => true], '');
536762f4807SAndreas Gohr        $media_count = $list['file_count'];
537762f4807SAndreas Gohr        $media_size = $list['file_size'];
538762f4807SAndreas Gohr
539762f4807SAndreas Gohr        $this->db->exec(
540762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
541762f4807SAndreas Gohr                info, value, dt
542762f4807SAndreas Gohr             ) VALUES (
543483101d3SAndreas Gohr                ?, ?, CURRENT_TIMESTAMP
544762f4807SAndreas Gohr             )',
545762f4807SAndreas Gohr            'media_count', $media_count
546762f4807SAndreas Gohr        );
547762f4807SAndreas Gohr        $this->db->exec(
548762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
549762f4807SAndreas Gohr                info, value, dt
550762f4807SAndreas Gohr             ) VALUES (
551483101d3SAndreas Gohr                ?, ?, CURRENT_TIMESTAMP
552762f4807SAndreas Gohr             )',
553762f4807SAndreas Gohr            'media_size', $media_size
554762f4807SAndreas Gohr        );
555762f4807SAndreas Gohr    }
556b188870fSAndreas Gohr
557b188870fSAndreas Gohr    /**
558b188870fSAndreas Gohr     * @todo can be dropped in favor of helper_plugin_popularity::initEmptySearchList() once it's public
559b188870fSAndreas Gohr     * @return array
560b188870fSAndreas Gohr     */
561b188870fSAndreas Gohr    protected function initEmptySearchList()
562b188870fSAndreas Gohr    {
563b188870fSAndreas Gohr        return array_fill_keys([
564b188870fSAndreas Gohr            'file_count',
565b188870fSAndreas Gohr            'file_size',
566b188870fSAndreas Gohr            'file_max',
567b188870fSAndreas Gohr            'file_min',
568b188870fSAndreas Gohr            'dir_count',
569b188870fSAndreas Gohr            'dir_nest',
570b188870fSAndreas Gohr            'file_oldest'
571b188870fSAndreas Gohr        ], 0);
572b188870fSAndreas Gohr    }
573762f4807SAndreas Gohr}
574