xref: /plugin/statistics/Logger.php (revision 4a163f509d699546ac698844a4c1763ef1e213a1)
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;
9*4a163f50SAndreas Gohruse dokuwiki\ErrorHandler;
10762f4807SAndreas Gohruse dokuwiki\HTTP\DokuHTTPClient;
11762f4807SAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB;
12762f4807SAndreas Gohruse dokuwiki\Utf8\Clean;
13762f4807SAndreas Gohruse helper_plugin_popularity;
14762f4807SAndreas Gohruse helper_plugin_statistics;
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
39*4a163f50SAndreas Gohr    /** @var string|null The user name, if available */
40*4a163f50SAndreas Gohr    protected ?string $user = null;
41*4a163f50SAndreas Gohr
42762f4807SAndreas Gohr    /** @var string The unique user identifier */
43762f4807SAndreas Gohr    protected string $uid;
44762f4807SAndreas Gohr
45*4a163f50SAndreas Gohr    /** @var string The session identifier */
46*4a163f50SAndreas Gohr    protected string $session;
47*4a163f50SAndreas Gohr
48*4a163f50SAndreas Gohr    /** @var int|null The ID of the main access log entry if any */
49*4a163f50SAndreas Gohr    protected ?int $hit = null;
50*4a163f50SAndreas Gohr
51c7cad24dSAndreas Gohr    /** @var DokuHTTPClient|null The HTTP client instance for testing */
52c7cad24dSAndreas Gohr    protected ?DokuHTTPClient $httpClient = null;
53c7cad24dSAndreas Gohr
54*4a163f50SAndreas Gohr    // region lifecycle
55762f4807SAndreas Gohr
56762f4807SAndreas Gohr    /**
57762f4807SAndreas Gohr     * Constructor
58762f4807SAndreas Gohr     *
59762f4807SAndreas Gohr     * Parses browser info and set internal vars
60762f4807SAndreas Gohr     */
61c7cad24dSAndreas Gohr    public function __construct(helper_plugin_statistics $hlp, ?DokuHTTPClient $httpClient = null)
62762f4807SAndreas Gohr    {
63762f4807SAndreas Gohr        global $INPUT;
64762f4807SAndreas Gohr
65762f4807SAndreas Gohr        $this->hlp = $hlp;
66762f4807SAndreas Gohr        $this->db = $this->hlp->getDB();
67c7cad24dSAndreas Gohr        $this->httpClient = $httpClient;
68762f4807SAndreas Gohr
69*4a163f50SAndreas Gohr        // FIXME if we already have a session, we should not re-parse the user agent
70762f4807SAndreas Gohr
71*4a163f50SAndreas Gohr        $ua = trim($INPUT->server->str('HTTP_USER_AGENT'));
72762f4807SAndreas Gohr        AbstractDeviceParser::setVersionTruncation(AbstractDeviceParser::VERSION_TRUNCATION_MAJOR);
73762f4807SAndreas Gohr        $dd = new DeviceDetector($ua); // FIXME we could use client hints, but need to add headers
74762f4807SAndreas Gohr        $dd->discardBotInformation();
75762f4807SAndreas Gohr        $dd->parse();
76762f4807SAndreas Gohr
7700f786d8SAndreas Gohr        if ($dd->isFeedReader()) {
7800f786d8SAndreas Gohr            $this->uaType = 'feedreader';
7900f786d8SAndreas Gohr        } elseif ($dd->isBot()) {
80762f4807SAndreas Gohr            $this->uaType = 'robot';
81762f4807SAndreas Gohr            // for now ignore bots
82c5d2f052SAndreas Gohr            throw new IgnoreException('Bot detected, not logging');
83762f4807SAndreas Gohr        }
84762f4807SAndreas Gohr
85762f4807SAndreas Gohr        $this->uaAgent = $ua;
8605786d83SAndreas Gohr        $this->uaName = Browser::getBrowserFamily($dd->getClient('name')) ?: 'Unknown';
8700f786d8SAndreas Gohr        $this->uaVersion = $dd->getClient('version') ?: '0';
8805786d83SAndreas Gohr        $this->uaPlatform = OperatingSystem::getOsFamily($dd->getOs('name')) ?: 'Unknown';
89762f4807SAndreas Gohr        $this->uid = $this->getUID();
90*4a163f50SAndreas Gohr        $this->session = $this->getSession();
91*4a163f50SAndreas Gohr        $this->user = $INPUT->server->str('REMOTE_USER') ?: null;
92762f4807SAndreas Gohr    }
93762f4807SAndreas Gohr
94762f4807SAndreas Gohr    /**
95762f4807SAndreas Gohr     * Should be called before logging
96762f4807SAndreas Gohr     *
97*4a163f50SAndreas Gohr     * This starts a transaction, so all logging is done in one go. It also logs the user and session data.
98762f4807SAndreas Gohr     */
99762f4807SAndreas Gohr    public function begin(): void
100762f4807SAndreas Gohr    {
101762f4807SAndreas Gohr        $this->hlp->getDB()->getPdo()->beginTransaction();
102*4a163f50SAndreas Gohr
103*4a163f50SAndreas Gohr        $this->logUser();
104*4a163f50SAndreas Gohr        $this->logGroups();
105*4a163f50SAndreas Gohr        $this->logDomain();
106*4a163f50SAndreas Gohr        $this->logSession();
107762f4807SAndreas Gohr    }
108762f4807SAndreas Gohr
109762f4807SAndreas Gohr    /**
110762f4807SAndreas Gohr     * Should be called after logging
111762f4807SAndreas Gohr     *
112762f4807SAndreas Gohr     * This commits the transaction started in begin()
113762f4807SAndreas Gohr     */
114762f4807SAndreas Gohr    public function end(): void
115762f4807SAndreas Gohr    {
116762f4807SAndreas Gohr        $this->hlp->getDB()->getPdo()->commit();
117762f4807SAndreas Gohr    }
118762f4807SAndreas Gohr
119*4a163f50SAndreas Gohr    // endregion
120*4a163f50SAndreas Gohr    // region data gathering
121*4a163f50SAndreas Gohr
122762f4807SAndreas Gohr    /**
123762f4807SAndreas Gohr     * Get the unique user ID
124762f4807SAndreas Gohr     *
125762f4807SAndreas Gohr     * @return string The unique user identifier
126762f4807SAndreas Gohr     */
127762f4807SAndreas Gohr    protected function getUID(): string
128762f4807SAndreas Gohr    {
129762f4807SAndreas Gohr        global $INPUT;
130762f4807SAndreas Gohr
131762f4807SAndreas Gohr        $uid = $INPUT->str('uid');
132762f4807SAndreas Gohr        if (!$uid) $uid = get_doku_pref('plgstats', false);
133762f4807SAndreas Gohr        if (!$uid) $uid = session_id();
134b188870fSAndreas Gohr        set_doku_pref('plgstats', $uid);
135762f4807SAndreas Gohr        return $uid;
136762f4807SAndreas Gohr    }
137762f4807SAndreas Gohr
138762f4807SAndreas Gohr    /**
139762f4807SAndreas Gohr     * Return the user's session ID
140762f4807SAndreas Gohr     *
141762f4807SAndreas Gohr     * This is usually our own managed session, not a PHP session (only in fallback)
142762f4807SAndreas Gohr     *
143762f4807SAndreas Gohr     * @return string The session identifier
144762f4807SAndreas Gohr     */
145762f4807SAndreas Gohr    protected function getSession(): string
146762f4807SAndreas Gohr    {
147762f4807SAndreas Gohr        global $INPUT;
148762f4807SAndreas Gohr
149*4a163f50SAndreas Gohr        // FIXME session setting needs work. It should be reset on user change, maybe we do rely on the PHP session?
150*4a163f50SAndreas Gohr        // We also want to store the user agent in the session table, so this needs also change the session ID
151762f4807SAndreas Gohr        $ses = $INPUT->str('ses');
152762f4807SAndreas Gohr        if (!$ses) $ses = get_doku_pref('plgstatsses', false);
153762f4807SAndreas Gohr        if (!$ses) $ses = session_id();
154b188870fSAndreas Gohr        set_doku_pref('plgstatsses', $ses);
155762f4807SAndreas Gohr        return $ses;
156762f4807SAndreas Gohr    }
157762f4807SAndreas Gohr
158*4a163f50SAndreas Gohr    // endregion
159*4a163f50SAndreas Gohr    // region automatic logging
160762f4807SAndreas Gohr
161*4a163f50SAndreas Gohr    /**
162*4a163f50SAndreas Gohr     * Log the user was seen
163*4a163f50SAndreas Gohr     */
164*4a163f50SAndreas Gohr    protected function logUser(): void
165*4a163f50SAndreas Gohr    {
166*4a163f50SAndreas Gohr        if (!$this->user) return;
167762f4807SAndreas Gohr
168762f4807SAndreas Gohr        $this->db->exec(
169*4a163f50SAndreas Gohr            'INSERT INTO users (user, dt)
170*4a163f50SAndreas Gohr                  VALUES (?, CURRENT_TIMESTAMP)
171*4a163f50SAndreas Gohr            ON CONFLICT (user) DO UPDATE SET
172*4a163f50SAndreas Gohr                         dt = CURRENT_TIMESTAMP
173*4a163f50SAndreas Gohr                   WHERE excluded.user = users.user
174*4a163f50SAndreas Gohr            ',
175*4a163f50SAndreas Gohr            $this->user
176*4a163f50SAndreas Gohr        );
177*4a163f50SAndreas Gohr
178*4a163f50SAndreas Gohr    }
179*4a163f50SAndreas Gohr
180*4a163f50SAndreas Gohr    /**
181*4a163f50SAndreas Gohr     * Log the session and user agent information
182*4a163f50SAndreas Gohr     */
183*4a163f50SAndreas Gohr    protected function logSession(): void
184*4a163f50SAndreas Gohr    {
185*4a163f50SAndreas Gohr        $this->db->exec(
186*4a163f50SAndreas Gohr            'INSERT INTO sessions (session, dt, end, uid, user, ua, ua_info, ua_type, ua_ver, os)
187*4a163f50SAndreas Gohr                  VALUES (?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?, ?)
188*4a163f50SAndreas Gohr             ON CONFLICT (session) DO UPDATE SET
189*4a163f50SAndreas Gohr                         end = CURRENT_TIMESTAMP
190*4a163f50SAndreas Gohr                   WHERE excluded.session = sessions.session
191*4a163f50SAndreas Gohr             ',
192*4a163f50SAndreas Gohr            $this->session,
193*4a163f50SAndreas Gohr            $this->uid,
194*4a163f50SAndreas Gohr            $this->user,
195*4a163f50SAndreas Gohr            $this->uaAgent,
196*4a163f50SAndreas Gohr            $this->uaName,
197*4a163f50SAndreas Gohr            $this->uaType,
198*4a163f50SAndreas Gohr            $this->uaVersion,
199*4a163f50SAndreas Gohr            $this->uaPlatform
200762f4807SAndreas Gohr        );
201762f4807SAndreas Gohr    }
202762f4807SAndreas Gohr
203762f4807SAndreas Gohr    /**
204*4a163f50SAndreas Gohr     * Log all groups for the user
205762f4807SAndreas Gohr     *
206*4a163f50SAndreas Gohr     * @todo maybe this should be done only once per session?
207762f4807SAndreas Gohr     */
208*4a163f50SAndreas Gohr    protected function logGroups(): void
209762f4807SAndreas Gohr    {
210*4a163f50SAndreas Gohr        global $USERINFO;
211762f4807SAndreas Gohr
212*4a163f50SAndreas Gohr        if (!$this->user) return;
213*4a163f50SAndreas Gohr        if (!isset($USERINFO['grps'])) return;
214*4a163f50SAndreas Gohr        if (!is_array($USERINFO['grps'])) return;
215*4a163f50SAndreas Gohr        $groups = $USERINFO['grps'];
216fced2f86SAnna Dabrowska
217*4a163f50SAndreas Gohr        $this->db->exec('DELETE FROM groups WHERE user = ?', $this->user);
218762f4807SAndreas Gohr
2192adee4c6SAndreas Gohr        $placeholders = implode(',', array_fill(0, count($groups), '(?, ?, ?)'));
220762f4807SAndreas Gohr        $params = [];
221*4a163f50SAndreas Gohr        $sql = "INSERT INTO groups (`user`, `group`) VALUES $placeholders";
222762f4807SAndreas Gohr        foreach ($groups as $group) {
223*4a163f50SAndreas Gohr            $params[] = $this->user;
224762f4807SAndreas Gohr            $params[] = $group;
225762f4807SAndreas Gohr        }
226762f4807SAndreas Gohr        $this->db->exec($sql, $params);
227762f4807SAndreas Gohr    }
228762f4807SAndreas Gohr
229762f4807SAndreas Gohr    /**
230*4a163f50SAndreas Gohr     * Log email domain
2310b8b7abaSAnna Dabrowska     *
232*4a163f50SAndreas Gohr     * @todo maybe this should be done only once per session?
2330b8b7abaSAnna Dabrowska     */
234*4a163f50SAndreas Gohr    protected function logDomain(): void
2350b8b7abaSAnna Dabrowska    {
236*4a163f50SAndreas Gohr        global $USERINFO;
237*4a163f50SAndreas Gohr        if (!$this->user) return;
238*4a163f50SAndreas Gohr        if (!isset($USERINFO['mail'])) return;
239*4a163f50SAndreas Gohr        $mail = $USERINFO['mail'];
2400b8b7abaSAnna Dabrowska
2410b8b7abaSAnna Dabrowska        $pos = strrpos($mail, '@');
2420b8b7abaSAnna Dabrowska        if (!$pos) return;
2430b8b7abaSAnna Dabrowska        $domain = substr($mail, $pos + 1);
2440b8b7abaSAnna Dabrowska        if (empty($domain)) return;
2450b8b7abaSAnna Dabrowska
246*4a163f50SAndreas Gohr        $sql = 'UPDATE users SET domain = ? WHERE user = ?';
247*4a163f50SAndreas Gohr        $this->db->exec($sql, [$domain, $this->user]);
2480b8b7abaSAnna Dabrowska    }
2490b8b7abaSAnna Dabrowska
250*4a163f50SAndreas Gohr    // endregion
251*4a163f50SAndreas Gohr    // region internal loggers called by the dispatchers
252*4a163f50SAndreas Gohr
2530b8b7abaSAnna Dabrowska    /**
254*4a163f50SAndreas Gohr     * Log the given referer URL
255762f4807SAndreas Gohr     *
256*4a163f50SAndreas Gohr     * @param $referer
257*4a163f50SAndreas Gohr     * @return int|null The referer ID or null if no referer was given
258762f4807SAndreas Gohr     */
259*4a163f50SAndreas Gohr    public function logReferer($referer): ?int
260762f4807SAndreas Gohr    {
261*4a163f50SAndreas Gohr        if (!$referer) return null;
262762f4807SAndreas Gohr
263*4a163f50SAndreas Gohr        // FIXME we could check against a blacklist here
264762f4807SAndreas Gohr
265*4a163f50SAndreas Gohr        $se = new SearchEngines($referer);
266*4a163f50SAndreas Gohr        $type = $se->isSearchEngine() ? 'search' : 'external';
267762f4807SAndreas Gohr
268*4a163f50SAndreas Gohr        $sql = '
269*4a163f50SAndreas Gohr            INSERT INTO referers (url, type, dt)
270*4a163f50SAndreas Gohr                 VALUES (?, ?, CURRENT_TIMESTAMP)
271*4a163f50SAndreas Gohr            ON CONFLICT (url)
272*4a163f50SAndreas Gohr              DO UPDATE
273*4a163f50SAndreas Gohr                    SET type = excluded.type, dt = excluded.dt;
274*4a163f50SAndreas Gohr        ';
275*4a163f50SAndreas Gohr        return $this->db->exec($sql, [$referer, $type]);
276762f4807SAndreas Gohr    }
277762f4807SAndreas Gohr
278762f4807SAndreas Gohr    /**
279762f4807SAndreas Gohr     * Resolve IP to country/city and store in database
280762f4807SAndreas Gohr     *
281*4a163f50SAndreas Gohr     * @return string The IP address as stored
282762f4807SAndreas Gohr     */
283*4a163f50SAndreas Gohr    public function logIp(): string
284762f4807SAndreas Gohr    {
285*4a163f50SAndreas Gohr        $ip = clientIP(true);
286*4a163f50SAndreas Gohr        $hash = $ip; // @todo we could anonymize here
287*4a163f50SAndreas Gohr
288762f4807SAndreas Gohr        // check if IP already known and up-to-date
289762f4807SAndreas Gohr        $result = $this->db->queryValue(
290762f4807SAndreas Gohr            "SELECT ip
291762f4807SAndreas Gohr             FROM   iplocation
292762f4807SAndreas Gohr             WHERE  ip = ?
293762f4807SAndreas Gohr               AND  lastupd > date('now', '-30 days')",
294*4a163f50SAndreas Gohr            $hash
295762f4807SAndreas Gohr        );
296*4a163f50SAndreas Gohr        if ($result) return $hash; // already known and up-to-date
297762f4807SAndreas Gohr
298c7cad24dSAndreas Gohr        $http = $this->httpClient ?: new DokuHTTPClient();
299*4a163f50SAndreas Gohr        $http->timeout = 7;
300762f4807SAndreas Gohr        $json = $http->get('http://ip-api.com/json/' . $ip); // yes, it's HTTP only
301762f4807SAndreas Gohr
302*4a163f50SAndreas Gohr        if (!$json) {
303*4a163f50SAndreas Gohr            \dokuwiki\Logger::error('Statistics Plugin - Failed talk to ip-api.com.');
304*4a163f50SAndreas Gohr            return $hash;
305*4a163f50SAndreas Gohr        }
306762f4807SAndreas Gohr        try {
307762f4807SAndreas Gohr            $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
308762f4807SAndreas Gohr        } catch (\JsonException $e) {
309*4a163f50SAndreas Gohr            \dokuwiki\Logger::error('Statistics Plugin - Failed to decode JSON from ip-api.com.', $e);
310*4a163f50SAndreas Gohr            return $hash;
311762f4807SAndreas Gohr        }
312a10aed88SAndreas Gohr        if (!isset($data['status']) || $data['status'] !== 'success') {
313*4a163f50SAndreas Gohr            \dokuwiki\Logger::error('Statistics Plugin - IP location lookup failed for ' . $ip, $data);
314*4a163f50SAndreas Gohr            return $hash;
315a10aed88SAndreas Gohr        }
316762f4807SAndreas Gohr
317*4a163f50SAndreas Gohr        $host = gethostbyaddr($ip); // @todo if we anonymize the IP, we should not do this
318762f4807SAndreas Gohr        $this->db->exec(
319762f4807SAndreas Gohr            'INSERT OR REPLACE INTO iplocation (
320762f4807SAndreas Gohr                    ip, country, code, city, host, lastupd
321762f4807SAndreas Gohr                 ) VALUES (
322762f4807SAndreas Gohr                    ?, ?, ?, ?, ?, CURRENT_TIMESTAMP
323762f4807SAndreas Gohr                 )',
324*4a163f50SAndreas Gohr            $hash,
3252adee4c6SAndreas Gohr            $data['country'],
3262adee4c6SAndreas Gohr            $data['countryCode'],
3272adee4c6SAndreas Gohr            $data['city'],
3282adee4c6SAndreas Gohr            $host
329762f4807SAndreas Gohr        );
330*4a163f50SAndreas Gohr
331*4a163f50SAndreas Gohr        return $hash;
332*4a163f50SAndreas Gohr    }
333*4a163f50SAndreas Gohr
334*4a163f50SAndreas Gohr    // endregion
335*4a163f50SAndreas Gohr    // region log dispatchers
336*4a163f50SAndreas Gohr
337*4a163f50SAndreas Gohr    public function logPageView(): void
338*4a163f50SAndreas Gohr    {
339*4a163f50SAndreas Gohr        global $INPUT;
340*4a163f50SAndreas Gohr
341*4a163f50SAndreas Gohr        if (!$INPUT->str('p')) return;
342*4a163f50SAndreas Gohr
343*4a163f50SAndreas Gohr
344*4a163f50SAndreas Gohr        $referer = $INPUT->filter('trim')->str('r');
345*4a163f50SAndreas Gohr        $ip = $this->logIp(); // resolve the IP address
346*4a163f50SAndreas Gohr
347*4a163f50SAndreas Gohr        $data = [
348*4a163f50SAndreas Gohr            'page' => $INPUT->filter('cleanID')->str('p'),
349*4a163f50SAndreas Gohr            'ip' => $ip,
350*4a163f50SAndreas Gohr            'ref_id' => $this->logReferer($referer),
351*4a163f50SAndreas Gohr            'sx' => $INPUT->int('sx'),
352*4a163f50SAndreas Gohr            'sy' => $INPUT->int('sy'),
353*4a163f50SAndreas Gohr            'vx' => $INPUT->int('vx'),
354*4a163f50SAndreas Gohr            'vy' => $INPUT->int('vy'),
355*4a163f50SAndreas Gohr            'session' => $this->session,
356*4a163f50SAndreas Gohr        ];
357*4a163f50SAndreas Gohr
358*4a163f50SAndreas Gohr        $this->db->exec('
359*4a163f50SAndreas Gohr        INSERT INTO pageviews (
360*4a163f50SAndreas Gohr            dt, page, ip, ref_id, screen_x, screen_y, view_x, view_y, session
361*4a163f50SAndreas Gohr        ) VALUES (
362*4a163f50SAndreas Gohr            CURRENT_TIMESTAMP, :page, :ip, :ref_id, :sx, :sy, :vx, :vy, :session
363*4a163f50SAndreas Gohr        )
364*4a163f50SAndreas Gohr        ',
365*4a163f50SAndreas Gohr            $data
366*4a163f50SAndreas Gohr        );
367762f4807SAndreas Gohr    }
368762f4807SAndreas Gohr
369762f4807SAndreas Gohr    /**
370762f4807SAndreas Gohr     * Log a click on an external link
371762f4807SAndreas Gohr     *
372762f4807SAndreas Gohr     * Called from log.php
373762f4807SAndreas Gohr     */
374762f4807SAndreas Gohr    public function logOutgoing(): void
375762f4807SAndreas Gohr    {
376762f4807SAndreas Gohr        global $INPUT;
377762f4807SAndreas Gohr
378762f4807SAndreas Gohr        if (!$INPUT->str('ol')) return;
379762f4807SAndreas Gohr
380*4a163f50SAndreas Gohr        $link = $INPUT->filter('trim')->str('ol');
381*4a163f50SAndreas Gohr        $session = $this->session;
382*4a163f50SAndreas Gohr        $page = $INPUT->filter('cleanID')->str('p');
383762f4807SAndreas Gohr
384762f4807SAndreas Gohr        $this->db->exec(
385762f4807SAndreas Gohr            'INSERT INTO outlinks (
386*4a163f50SAndreas Gohr                dt, session, page, link
387762f4807SAndreas Gohr             ) VALUES (
388762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?
389762f4807SAndreas Gohr             )',
3902adee4c6SAndreas Gohr            $session,
3912adee4c6SAndreas Gohr            $page,
3922adee4c6SAndreas Gohr            $link
393762f4807SAndreas Gohr        );
394762f4807SAndreas Gohr    }
395762f4807SAndreas Gohr
396762f4807SAndreas Gohr    /**
397762f4807SAndreas Gohr     * Log access to a media file
398762f4807SAndreas Gohr     *
399762f4807SAndreas Gohr     * Called from action.php
400762f4807SAndreas Gohr     *
401762f4807SAndreas Gohr     * @param string $media The media ID
402762f4807SAndreas Gohr     * @param string $mime The media's mime type
403762f4807SAndreas Gohr     * @param bool $inline Is this displayed inline?
404762f4807SAndreas Gohr     * @param int $size Size of the media file
405762f4807SAndreas Gohr     */
406762f4807SAndreas Gohr    public function logMedia(string $media, string $mime, bool $inline, int $size): void
407762f4807SAndreas Gohr    {
408762f4807SAndreas Gohr        [$mime1, $mime2] = explode('/', strtolower($mime));
409762f4807SAndreas Gohr        $inline = $inline ? 1 : 0;
410762f4807SAndreas Gohr
411762f4807SAndreas Gohr
412*4a163f50SAndreas Gohr        $data = [
413*4a163f50SAndreas Gohr            'media' => cleanID($media),
414*4a163f50SAndreas Gohr            'ip' => $this->logIp(), // resolve the IP address
415*4a163f50SAndreas Gohr            'session' => $this->session,
416*4a163f50SAndreas Gohr            'size' => $size,
417*4a163f50SAndreas Gohr            'mime1' => $mime1,
418*4a163f50SAndreas Gohr            'mime2' => $mime2,
419*4a163f50SAndreas Gohr            'inline' => $inline,
420*4a163f50SAndreas Gohr        ];
421*4a163f50SAndreas Gohr
422*4a163f50SAndreas Gohr        $this->db->exec('
423*4a163f50SAndreas Gohr                INSERT INTO media ( dt, media, ip, session, size, mime1, mime2, inline )
424*4a163f50SAndreas Gohr                     VALUES (CURRENT_TIMESTAMP, :media, :ip, :session, :size, :mime1, :mime2, :inline)
425*4a163f50SAndreas Gohr            ',
426*4a163f50SAndreas Gohr            $data
427762f4807SAndreas Gohr        );
428762f4807SAndreas Gohr    }
429762f4807SAndreas Gohr
430762f4807SAndreas Gohr    /**
431762f4807SAndreas Gohr     * Log page edits
432762f4807SAndreas Gohr     *
433*4a163f50SAndreas Gohr     * called from action.php
434*4a163f50SAndreas Gohr     *
435762f4807SAndreas Gohr     * @param string $page The page that was edited
436762f4807SAndreas Gohr     * @param string $type The type of edit (create, edit, etc.)
437762f4807SAndreas Gohr     */
438762f4807SAndreas Gohr    public function logEdit(string $page, string $type): void
439762f4807SAndreas Gohr    {
440*4a163f50SAndreas Gohr        $data = [
441*4a163f50SAndreas Gohr            'page' => cleanID($page),
442*4a163f50SAndreas Gohr            'type' => $type,
443*4a163f50SAndreas Gohr            'ip' => $this->logIp(), // resolve the IP address
444*4a163f50SAndreas Gohr            'session' => $this->session
445*4a163f50SAndreas Gohr        ];
446762f4807SAndreas Gohr
4470b8b7abaSAnna Dabrowska        $editId = $this->db->exec(
448762f4807SAndreas Gohr            'INSERT INTO edits (
449*4a163f50SAndreas Gohr                dt, page, type, ip, session
450762f4807SAndreas Gohr             ) VALUES (
451*4a163f50SAndreas Gohr                CURRENT_TIMESTAMP, :page, :type, :ip, :session
452762f4807SAndreas Gohr             )',
453*4a163f50SAndreas Gohr            $data
454762f4807SAndreas Gohr        );
455762f4807SAndreas Gohr    }
456762f4807SAndreas Gohr
457762f4807SAndreas Gohr    /**
458762f4807SAndreas Gohr     * Log login/logoffs and user creations
459762f4807SAndreas Gohr     *
460762f4807SAndreas Gohr     * @param string $type The type of login event (login, logout, create)
461762f4807SAndreas Gohr     * @param string $user The username (optional, will use current user if empty)
462*4a163f50SAndreas Gohr     * @fixme this is still broken, I need to figure out the session handling first
463762f4807SAndreas Gohr     */
464762f4807SAndreas Gohr    public function logLogin(string $type, string $user = ''): void
465762f4807SAndreas Gohr    {
466762f4807SAndreas Gohr        global $INPUT;
467762f4807SAndreas Gohr
468762f4807SAndreas Gohr        if (!$user) $user = $INPUT->server->str('REMOTE_USER');
469762f4807SAndreas Gohr
470762f4807SAndreas Gohr        $ip = clientIP(true);
471*4a163f50SAndreas Gohr        $session = $this->session;
472762f4807SAndreas Gohr
473762f4807SAndreas Gohr        $this->db->exec(
474762f4807SAndreas Gohr            'INSERT INTO logins (
475*4a163f50SAndreas Gohr                dt, type, ip, session
476762f4807SAndreas Gohr             ) VALUES (
477762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?
478762f4807SAndreas Gohr             )',
4792adee4c6SAndreas Gohr            $type,
4802adee4c6SAndreas Gohr            $ip,
4812adee4c6SAndreas Gohr            $user,
4822adee4c6SAndreas Gohr            $session,
4832adee4c6SAndreas Gohr            $this->uid
484762f4807SAndreas Gohr        );
485762f4807SAndreas Gohr    }
486762f4807SAndreas Gohr
487762f4807SAndreas Gohr    /**
488762f4807SAndreas Gohr     * Log the current page count and size as today's history entry
489762f4807SAndreas Gohr     */
490762f4807SAndreas Gohr    public function logHistoryPages(): void
491762f4807SAndreas Gohr    {
492762f4807SAndreas Gohr        global $conf;
493762f4807SAndreas Gohr
494762f4807SAndreas Gohr        // use the popularity plugin's search method to find the wanted data
495762f4807SAndreas Gohr        /** @var helper_plugin_popularity $pop */
496762f4807SAndreas Gohr        $pop = plugin_load('helper', 'popularity');
497b188870fSAndreas Gohr        $list = $this->initEmptySearchList();
498762f4807SAndreas Gohr        search($list, $conf['datadir'], [$pop, 'searchCountCallback'], ['all' => false], '');
499762f4807SAndreas Gohr        $page_count = $list['file_count'];
500762f4807SAndreas Gohr        $page_size = $list['file_size'];
501762f4807SAndreas Gohr
502762f4807SAndreas Gohr        $this->db->exec(
503762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
504762f4807SAndreas Gohr                info, value, dt
505762f4807SAndreas Gohr             ) VALUES (
506483101d3SAndreas Gohr                ?, ?, CURRENT_TIMESTAMP
507762f4807SAndreas Gohr             )',
5082adee4c6SAndreas Gohr            'page_count',
5092adee4c6SAndreas Gohr            $page_count
510762f4807SAndreas Gohr        );
511762f4807SAndreas Gohr        $this->db->exec(
512762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
513762f4807SAndreas Gohr                info, value, dt
514762f4807SAndreas Gohr             ) VALUES (
515483101d3SAndreas Gohr                ?, ?, CURRENT_TIMESTAMP
516762f4807SAndreas Gohr             )',
5172adee4c6SAndreas Gohr            'page_size',
5182adee4c6SAndreas Gohr            $page_size
519762f4807SAndreas Gohr        );
520762f4807SAndreas Gohr    }
521762f4807SAndreas Gohr
522762f4807SAndreas Gohr    /**
523762f4807SAndreas Gohr     * Log the current media count and size as today's history entry
524762f4807SAndreas Gohr     */
525762f4807SAndreas Gohr    public function logHistoryMedia(): void
526762f4807SAndreas Gohr    {
527762f4807SAndreas Gohr        global $conf;
528762f4807SAndreas Gohr
529762f4807SAndreas Gohr        // use the popularity plugin's search method to find the wanted data
530762f4807SAndreas Gohr        /** @var helper_plugin_popularity $pop */
531762f4807SAndreas Gohr        $pop = plugin_load('helper', 'popularity');
532b188870fSAndreas Gohr        $list = $this->initEmptySearchList();
533762f4807SAndreas Gohr        search($list, $conf['mediadir'], [$pop, 'searchCountCallback'], ['all' => true], '');
534762f4807SAndreas Gohr        $media_count = $list['file_count'];
535762f4807SAndreas Gohr        $media_size = $list['file_size'];
536762f4807SAndreas Gohr
537762f4807SAndreas Gohr        $this->db->exec(
538762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
539762f4807SAndreas Gohr                info, value, dt
540762f4807SAndreas Gohr             ) VALUES (
541483101d3SAndreas Gohr                ?, ?, CURRENT_TIMESTAMP
542762f4807SAndreas Gohr             )',
5432adee4c6SAndreas Gohr            'media_count',
5442adee4c6SAndreas Gohr            $media_count
545762f4807SAndreas Gohr        );
546762f4807SAndreas Gohr        $this->db->exec(
547762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
548762f4807SAndreas Gohr                info, value, dt
549762f4807SAndreas Gohr             ) VALUES (
550483101d3SAndreas Gohr                ?, ?, CURRENT_TIMESTAMP
551762f4807SAndreas Gohr             )',
5522adee4c6SAndreas Gohr            'media_size',
5532adee4c6SAndreas Gohr            $media_size
554762f4807SAndreas Gohr        );
555762f4807SAndreas Gohr    }
556b188870fSAndreas Gohr
557*4a163f50SAndreas Gohr    // endregion
558*4a163f50SAndreas Gohr
559b188870fSAndreas Gohr    /**
560b188870fSAndreas Gohr     * @todo can be dropped in favor of helper_plugin_popularity::initEmptySearchList() once it's public
561b188870fSAndreas Gohr     * @return array
562b188870fSAndreas Gohr     */
563b188870fSAndreas Gohr    protected function initEmptySearchList()
564b188870fSAndreas Gohr    {
565b188870fSAndreas Gohr        return array_fill_keys([
566b188870fSAndreas Gohr            'file_count',
567b188870fSAndreas Gohr            'file_size',
568b188870fSAndreas Gohr            'file_max',
569b188870fSAndreas Gohr            'file_min',
570b188870fSAndreas Gohr            'dir_count',
571b188870fSAndreas Gohr            'dir_nest',
572b188870fSAndreas Gohr            'file_oldest'
573b188870fSAndreas Gohr        ], 0);
574b188870fSAndreas Gohr    }
575762f4807SAndreas Gohr}
576