xref: /plugin/statistics/Logger.php (revision 762f48070f8d46fc261c987fad5da39924f2b19d)
1*762f4807SAndreas Gohr<?php
2*762f4807SAndreas Gohr
3*762f4807SAndreas Gohrnamespace dokuwiki\plugin\statistics;
4*762f4807SAndreas Gohr
5*762f4807SAndreas Gohruse DeviceDetector\DeviceDetector;
6*762f4807SAndreas Gohruse DeviceDetector\Parser\Client\Browser;
7*762f4807SAndreas Gohruse DeviceDetector\Parser\Device\AbstractDeviceParser;
8*762f4807SAndreas Gohruse DeviceDetector\Parser\OperatingSystem;
9*762f4807SAndreas Gohruse dokuwiki\HTTP\DokuHTTPClient;
10*762f4807SAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB;
11*762f4807SAndreas Gohruse dokuwiki\Utf8\Clean;
12*762f4807SAndreas Gohruse dokuwiki\Utf8\PhpString;
13*762f4807SAndreas Gohruse helper_plugin_popularity;
14*762f4807SAndreas Gohruse helper_plugin_statistics;
15*762f4807SAndreas Gohr
16*762f4807SAndreas Gohr
17*762f4807SAndreas Gohrclass Logger
18*762f4807SAndreas Gohr{
19*762f4807SAndreas Gohr    /** @var helper_plugin_statistics The statistics helper plugin instance */
20*762f4807SAndreas Gohr    protected helper_plugin_statistics $hlp;
21*762f4807SAndreas Gohr
22*762f4807SAndreas Gohr    /** @var SQLiteDB The SQLite database instance */
23*762f4807SAndreas Gohr    protected SQLiteDB $db;
24*762f4807SAndreas Gohr
25*762f4807SAndreas Gohr    /** @var string The full user agent string */
26*762f4807SAndreas Gohr    protected string $uaAgent;
27*762f4807SAndreas Gohr
28*762f4807SAndreas Gohr    /** @var string The type of user agent (browser, robot, feedreader) */
29*762f4807SAndreas Gohr    protected string $uaType = 'browser';
30*762f4807SAndreas Gohr
31*762f4807SAndreas Gohr    /** @var string The browser/client name */
32*762f4807SAndreas Gohr    protected string $uaName;
33*762f4807SAndreas Gohr
34*762f4807SAndreas Gohr    /** @var string The browser/client version */
35*762f4807SAndreas Gohr    protected string $uaVersion;
36*762f4807SAndreas Gohr
37*762f4807SAndreas Gohr    /** @var string The operating system/platform */
38*762f4807SAndreas Gohr    protected string $uaPlatform;
39*762f4807SAndreas Gohr
40*762f4807SAndreas Gohr    /** @var string The unique user identifier */
41*762f4807SAndreas Gohr    protected string $uid;
42*762f4807SAndreas Gohr
43*762f4807SAndreas Gohr
44*762f4807SAndreas Gohr    /**
45*762f4807SAndreas Gohr     * Constructor
46*762f4807SAndreas Gohr     *
47*762f4807SAndreas Gohr     * Parses browser info and set internal vars
48*762f4807SAndreas Gohr     */
49*762f4807SAndreas Gohr    public function __construct(helper_plugin_statistics $hlp)
50*762f4807SAndreas Gohr    {
51*762f4807SAndreas Gohr        global $INPUT;
52*762f4807SAndreas Gohr
53*762f4807SAndreas Gohr        $this->hlp = $hlp;
54*762f4807SAndreas Gohr        $this->db = $this->hlp->getDB();
55*762f4807SAndreas Gohr
56*762f4807SAndreas Gohr        $ua = trim($INPUT->server->str('HTTP_USER_AGENT'));
57*762f4807SAndreas Gohr
58*762f4807SAndreas Gohr        AbstractDeviceParser::setVersionTruncation(AbstractDeviceParser::VERSION_TRUNCATION_MAJOR);
59*762f4807SAndreas Gohr        $dd = new DeviceDetector($ua); // FIXME we could use client hints, but need to add headers
60*762f4807SAndreas Gohr        $dd->discardBotInformation();
61*762f4807SAndreas Gohr        $dd->parse();
62*762f4807SAndreas Gohr
63*762f4807SAndreas Gohr        if ($dd->isBot()) {
64*762f4807SAndreas Gohr            $this->uaType = 'robot';
65*762f4807SAndreas Gohr
66*762f4807SAndreas Gohr            // for now ignore bots
67*762f4807SAndreas Gohr            throw new \RuntimeException('Bot detected, not logging');
68*762f4807SAndreas Gohr        }
69*762f4807SAndreas Gohr
70*762f4807SAndreas Gohr        $this->uaAgent = $ua;
71*762f4807SAndreas Gohr        $this->uaName = Browser::getBrowserFamily($dd->getClient('name'));
72*762f4807SAndreas Gohr        $this->uaVersion = $dd->getClient('version');
73*762f4807SAndreas Gohr        $this->uaPlatform = OperatingSystem::getOsFamily($dd->getOs('name'));
74*762f4807SAndreas Gohr        $this->uid = $this->getUID();
75*762f4807SAndreas Gohr
76*762f4807SAndreas Gohr        if ($dd->isFeedReader()) {
77*762f4807SAndreas Gohr            $this->uaType = 'feedreader';
78*762f4807SAndreas Gohr        }
79*762f4807SAndreas Gohr
80*762f4807SAndreas Gohr        $this->logLastseen();
81*762f4807SAndreas Gohr    }
82*762f4807SAndreas Gohr
83*762f4807SAndreas Gohr    /**
84*762f4807SAndreas Gohr     * Should be called before logging
85*762f4807SAndreas Gohr     *
86*762f4807SAndreas Gohr     * This starts a transaction, so all logging is done in one go
87*762f4807SAndreas Gohr     */
88*762f4807SAndreas Gohr    public function begin(): void
89*762f4807SAndreas Gohr    {
90*762f4807SAndreas Gohr        $this->hlp->getDB()->getPdo()->beginTransaction();
91*762f4807SAndreas Gohr    }
92*762f4807SAndreas Gohr
93*762f4807SAndreas Gohr    /**
94*762f4807SAndreas Gohr     * Should be called after logging
95*762f4807SAndreas Gohr     *
96*762f4807SAndreas Gohr     * This commits the transaction started in begin()
97*762f4807SAndreas Gohr     */
98*762f4807SAndreas Gohr    public function end(): void
99*762f4807SAndreas Gohr    {
100*762f4807SAndreas Gohr        $this->hlp->getDB()->getPdo()->commit();
101*762f4807SAndreas Gohr    }
102*762f4807SAndreas Gohr
103*762f4807SAndreas Gohr    /**
104*762f4807SAndreas Gohr     * Get the unique user ID
105*762f4807SAndreas Gohr     *
106*762f4807SAndreas Gohr     * @return string The unique user identifier
107*762f4807SAndreas Gohr     */
108*762f4807SAndreas Gohr    protected function getUID(): string
109*762f4807SAndreas Gohr    {
110*762f4807SAndreas Gohr        global $INPUT;
111*762f4807SAndreas Gohr
112*762f4807SAndreas Gohr        $uid = $INPUT->str('uid');
113*762f4807SAndreas Gohr        if (!$uid) $uid = get_doku_pref('plgstats', false);
114*762f4807SAndreas Gohr        if (!$uid) $uid = session_id();
115*762f4807SAndreas Gohr        return $uid;
116*762f4807SAndreas Gohr    }
117*762f4807SAndreas Gohr
118*762f4807SAndreas Gohr    /**
119*762f4807SAndreas Gohr     * Return the user's session ID
120*762f4807SAndreas Gohr     *
121*762f4807SAndreas Gohr     * This is usually our own managed session, not a PHP session (only in fallback)
122*762f4807SAndreas Gohr     *
123*762f4807SAndreas Gohr     * @return string The session identifier
124*762f4807SAndreas Gohr     */
125*762f4807SAndreas Gohr    protected function getSession(): string
126*762f4807SAndreas Gohr    {
127*762f4807SAndreas Gohr        global $INPUT;
128*762f4807SAndreas Gohr
129*762f4807SAndreas Gohr        $ses = $INPUT->str('ses');
130*762f4807SAndreas Gohr        if (!$ses) $ses = get_doku_pref('plgstatsses', false);
131*762f4807SAndreas Gohr        if (!$ses) $ses = session_id();
132*762f4807SAndreas Gohr        return $ses;
133*762f4807SAndreas Gohr    }
134*762f4807SAndreas Gohr
135*762f4807SAndreas Gohr    /**
136*762f4807SAndreas Gohr     * Log that we've seen the user (authenticated only)
137*762f4807SAndreas Gohr     */
138*762f4807SAndreas Gohr    public function logLastseen(): void
139*762f4807SAndreas Gohr    {
140*762f4807SAndreas Gohr        global $INPUT;
141*762f4807SAndreas Gohr
142*762f4807SAndreas Gohr        if (empty($INPUT->server->str('REMOTE_USER'))) return;
143*762f4807SAndreas Gohr
144*762f4807SAndreas Gohr        $this->db->exec(
145*762f4807SAndreas Gohr            'REPLACE INTO lastseen (user, dt) VALUES (?, CURRENT_TIMESTAMP)',
146*762f4807SAndreas Gohr            $INPUT->server->str('REMOTE_USER'),
147*762f4807SAndreas Gohr        );
148*762f4807SAndreas Gohr    }
149*762f4807SAndreas Gohr
150*762f4807SAndreas Gohr    /**
151*762f4807SAndreas Gohr     * Log actions by groups
152*762f4807SAndreas Gohr     *
153*762f4807SAndreas Gohr     * @param string $type The type of access to log ('view','edit')
154*762f4807SAndreas Gohr     * @param array $groups The groups to log
155*762f4807SAndreas Gohr     */
156*762f4807SAndreas Gohr    public function logGroups(string $type, array $groups): void
157*762f4807SAndreas Gohr    {
158*762f4807SAndreas Gohr        if (!is_array($groups)) {
159*762f4807SAndreas Gohr            return;
160*762f4807SAndreas Gohr        }
161*762f4807SAndreas Gohr
162*762f4807SAndreas Gohr        $tolog = (array)$this->hlp->getConf('loggroups');
163*762f4807SAndreas Gohr        $groups = array_intersect($groups, $tolog);
164*762f4807SAndreas Gohr        if ($groups === []) {
165*762f4807SAndreas Gohr            return;
166*762f4807SAndreas Gohr        }
167*762f4807SAndreas Gohr
168*762f4807SAndreas Gohr
169*762f4807SAndreas Gohr        $params = [];
170*762f4807SAndreas Gohr        $sql = "INSERT INTO groups (`type`, `group`) VALUES ";
171*762f4807SAndreas Gohr        foreach ($groups as $group) {
172*762f4807SAndreas Gohr            $sql .= '(?, ?),';
173*762f4807SAndreas Gohr            $params[] = $type;
174*762f4807SAndreas Gohr            $params[] = $group;
175*762f4807SAndreas Gohr        }
176*762f4807SAndreas Gohr        $sql = rtrim($sql, ',');
177*762f4807SAndreas Gohr        $this->db->exec($sql, $params);
178*762f4807SAndreas Gohr    }
179*762f4807SAndreas Gohr
180*762f4807SAndreas Gohr    /**
181*762f4807SAndreas Gohr     * Log external search queries
182*762f4807SAndreas Gohr     *
183*762f4807SAndreas Gohr     * Will not write anything if the referer isn't a search engine
184*762f4807SAndreas Gohr     *
185*762f4807SAndreas Gohr     * @param string $referer The HTTP referer URL
186*762f4807SAndreas Gohr     * @param string $type Reference to the type variable that will be modified
187*762f4807SAndreas Gohr     */
188*762f4807SAndreas Gohr    public function logExternalSearch(string $referer, string &$type): void
189*762f4807SAndreas Gohr    {
190*762f4807SAndreas Gohr        global $INPUT;
191*762f4807SAndreas Gohr
192*762f4807SAndreas Gohr        $searchEngine = new SearchEngines($referer);
193*762f4807SAndreas Gohr
194*762f4807SAndreas Gohr        if (!$searchEngine->isSearchEngine()) {
195*762f4807SAndreas Gohr            return; // not a search engine
196*762f4807SAndreas Gohr        }
197*762f4807SAndreas Gohr
198*762f4807SAndreas Gohr        $type = 'search';
199*762f4807SAndreas Gohr        $query = $searchEngine->getQuery();
200*762f4807SAndreas Gohr
201*762f4807SAndreas Gohr        // log it!
202*762f4807SAndreas Gohr        $words = explode(' ', Clean::stripspecials($query, ' ', '\._\-:\*'));
203*762f4807SAndreas Gohr        $this->logSearch($INPUT->str('p'), $query, $words, $searchEngine->getEngine());
204*762f4807SAndreas Gohr    }
205*762f4807SAndreas Gohr
206*762f4807SAndreas Gohr    /**
207*762f4807SAndreas Gohr     * Log search data to the search related tables
208*762f4807SAndreas Gohr     *
209*762f4807SAndreas Gohr     * @param string $page The page being searched from
210*762f4807SAndreas Gohr     * @param string $query The search query
211*762f4807SAndreas Gohr     * @param array $words Array of search words
212*762f4807SAndreas Gohr     * @param string $engine The search engine name
213*762f4807SAndreas Gohr     */
214*762f4807SAndreas Gohr    public function logSearch(string $page, string $query, array $words, string $engine): void
215*762f4807SAndreas Gohr    {
216*762f4807SAndreas Gohr        $sid = $this->db->exec(
217*762f4807SAndreas Gohr            'INSERT INTO search (dt, page, query, engine) VALUES (CURRENT_TIMESTAMP, ?, ?, ?)',
218*762f4807SAndreas Gohr            $page, $query, $engine
219*762f4807SAndreas Gohr        );
220*762f4807SAndreas Gohr        if (!$sid) return;
221*762f4807SAndreas Gohr
222*762f4807SAndreas Gohr        foreach ($words as $word) {
223*762f4807SAndreas Gohr            if (!$word) continue;
224*762f4807SAndreas Gohr            $this->db->exec(
225*762f4807SAndreas Gohr                'INSERT INTO searchwords (sid, word) VALUES (?, ?)',
226*762f4807SAndreas Gohr                $sid, $word
227*762f4807SAndreas Gohr            );
228*762f4807SAndreas Gohr        }
229*762f4807SAndreas Gohr    }
230*762f4807SAndreas Gohr
231*762f4807SAndreas Gohr    /**
232*762f4807SAndreas Gohr     * Log that the session was seen
233*762f4807SAndreas Gohr     *
234*762f4807SAndreas Gohr     * This is used to calculate the time people spend on the whole site
235*762f4807SAndreas Gohr     * during their session
236*762f4807SAndreas Gohr     *
237*762f4807SAndreas Gohr     * Viewcounts are used for bounce calculation
238*762f4807SAndreas Gohr     *
239*762f4807SAndreas Gohr     * @param int $addview set to 1 to count a view
240*762f4807SAndreas Gohr     */
241*762f4807SAndreas Gohr    public function logSession(int $addview = 0): void
242*762f4807SAndreas Gohr    {
243*762f4807SAndreas Gohr        // only log browser sessions
244*762f4807SAndreas Gohr        if ($this->uaType != 'browser') return;
245*762f4807SAndreas Gohr
246*762f4807SAndreas Gohr        $session = $this->getSession();
247*762f4807SAndreas Gohr        $this->db->exec(
248*762f4807SAndreas Gohr            'INSERT OR REPLACE INTO session (
249*762f4807SAndreas Gohr                session, dt, end, views, uid
250*762f4807SAndreas Gohr             ) VALUES (
251*762f4807SAndreas Gohr                ?,
252*762f4807SAndreas Gohr                CURRENT_TIMESTAMP,
253*762f4807SAndreas Gohr                CURRENT_TIMESTAMP,
254*762f4807SAndreas Gohr                COALESCE((SELECT views FROM session WHERE session = ?) + ?, ?),
255*762f4807SAndreas Gohr                ?
256*762f4807SAndreas Gohr             )',
257*762f4807SAndreas Gohr            $session, $session, $addview, $addview, $this->uid
258*762f4807SAndreas Gohr        );
259*762f4807SAndreas Gohr    }
260*762f4807SAndreas Gohr
261*762f4807SAndreas Gohr    /**
262*762f4807SAndreas Gohr     * Resolve IP to country/city and store in database
263*762f4807SAndreas Gohr     *
264*762f4807SAndreas Gohr     * @param string $ip The IP address to resolve
265*762f4807SAndreas Gohr     */
266*762f4807SAndreas Gohr    public function logIp(string $ip): void
267*762f4807SAndreas Gohr    {
268*762f4807SAndreas Gohr        // check if IP already known and up-to-date
269*762f4807SAndreas Gohr        $result = $this->db->queryValue(
270*762f4807SAndreas Gohr            "SELECT ip
271*762f4807SAndreas Gohr             FROM   iplocation
272*762f4807SAndreas Gohr             WHERE  ip = ?
273*762f4807SAndreas Gohr               AND  lastupd > date('now', '-30 days')",
274*762f4807SAndreas Gohr            $ip
275*762f4807SAndreas Gohr        );
276*762f4807SAndreas Gohr        if ($result) return;
277*762f4807SAndreas Gohr
278*762f4807SAndreas Gohr        $http = new DokuHTTPClient();
279*762f4807SAndreas Gohr        $http->timeout = 10;
280*762f4807SAndreas Gohr        $json = $http->get('http://ip-api.com/json/' . $ip); // yes, it's HTTP only
281*762f4807SAndreas Gohr
282*762f4807SAndreas Gohr        if (!$json) return; // FIXME log error
283*762f4807SAndreas Gohr        try {
284*762f4807SAndreas Gohr            $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
285*762f4807SAndreas Gohr        } catch (\JsonException $e) {
286*762f4807SAndreas Gohr            return; // FIXME log error
287*762f4807SAndreas Gohr        }
288*762f4807SAndreas Gohr
289*762f4807SAndreas Gohr        $host = gethostbyaddr($ip);
290*762f4807SAndreas Gohr        $this->db->exec(
291*762f4807SAndreas Gohr            'INSERT OR REPLACE INTO iplocation (
292*762f4807SAndreas Gohr                    ip, country, code, city, host, lastupd
293*762f4807SAndreas Gohr                 ) VALUES (
294*762f4807SAndreas Gohr                    ?, ?, ?, ?, ?, CURRENT_TIMESTAMP
295*762f4807SAndreas Gohr                 )',
296*762f4807SAndreas Gohr            $ip, $data['country'], $data['countryCode'], $data['city'], $host
297*762f4807SAndreas Gohr        );
298*762f4807SAndreas Gohr    }
299*762f4807SAndreas Gohr
300*762f4807SAndreas Gohr    /**
301*762f4807SAndreas Gohr     * Log a click on an external link
302*762f4807SAndreas Gohr     *
303*762f4807SAndreas Gohr     * Called from log.php
304*762f4807SAndreas Gohr     */
305*762f4807SAndreas Gohr    public function logOutgoing(): void
306*762f4807SAndreas Gohr    {
307*762f4807SAndreas Gohr        global $INPUT;
308*762f4807SAndreas Gohr
309*762f4807SAndreas Gohr        if (!$INPUT->str('ol')) return;
310*762f4807SAndreas Gohr
311*762f4807SAndreas Gohr        $link = $INPUT->str('ol');
312*762f4807SAndreas Gohr        $link_md5 = md5($link);
313*762f4807SAndreas Gohr        $session = $this->getSession();
314*762f4807SAndreas Gohr        $page = $INPUT->str('p');
315*762f4807SAndreas Gohr
316*762f4807SAndreas Gohr        $this->db->exec(
317*762f4807SAndreas Gohr            'INSERT INTO outlinks (
318*762f4807SAndreas Gohr                dt, session, page, link_md5, link
319*762f4807SAndreas Gohr             ) VALUES (
320*762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?
321*762f4807SAndreas Gohr             )',
322*762f4807SAndreas Gohr            $session, $page, $link_md5, $link
323*762f4807SAndreas Gohr        );
324*762f4807SAndreas Gohr    }
325*762f4807SAndreas Gohr
326*762f4807SAndreas Gohr    /**
327*762f4807SAndreas Gohr     * Log a page access
328*762f4807SAndreas Gohr     *
329*762f4807SAndreas Gohr     * Called from log.php
330*762f4807SAndreas Gohr     */
331*762f4807SAndreas Gohr    public function logAccess(): void
332*762f4807SAndreas Gohr    {
333*762f4807SAndreas Gohr        global $INPUT, $USERINFO;
334*762f4807SAndreas Gohr
335*762f4807SAndreas Gohr        if (!$INPUT->str('p')) return;
336*762f4807SAndreas Gohr
337*762f4807SAndreas Gohr        # FIXME check referer against blacklist and drop logging for bad boys
338*762f4807SAndreas Gohr
339*762f4807SAndreas Gohr        // handle referer
340*762f4807SAndreas Gohr        $referer = trim($INPUT->str('r'));
341*762f4807SAndreas Gohr        if ($referer) {
342*762f4807SAndreas Gohr            $ref = $referer;
343*762f4807SAndreas Gohr            $ref_md5 = md5($referer);
344*762f4807SAndreas Gohr            if (str_starts_with($referer, DOKU_URL)) {
345*762f4807SAndreas Gohr                $ref_type = 'internal';
346*762f4807SAndreas Gohr            } else {
347*762f4807SAndreas Gohr                $ref_type = 'external';
348*762f4807SAndreas Gohr                $this->logExternalSearch($referer, $ref_type);
349*762f4807SAndreas Gohr            }
350*762f4807SAndreas Gohr        } else {
351*762f4807SAndreas Gohr            $ref = '';
352*762f4807SAndreas Gohr            $ref_md5 = '';
353*762f4807SAndreas Gohr            $ref_type = '';
354*762f4807SAndreas Gohr        }
355*762f4807SAndreas Gohr
356*762f4807SAndreas Gohr        $page = $INPUT->str('p');
357*762f4807SAndreas Gohr        $ip = clientIP(true);
358*762f4807SAndreas Gohr        $sx = $INPUT->int('sx');
359*762f4807SAndreas Gohr        $sy = $INPUT->int('sy');
360*762f4807SAndreas Gohr        $vx = $INPUT->int('vx');
361*762f4807SAndreas Gohr        $vy = $INPUT->int('vy');
362*762f4807SAndreas Gohr        $js = $INPUT->int('js');
363*762f4807SAndreas Gohr        $user = $INPUT->server->str('REMOTE_USER');
364*762f4807SAndreas Gohr        $session = $this->getSession();
365*762f4807SAndreas Gohr
366*762f4807SAndreas Gohr        $this->db->exec(
367*762f4807SAndreas Gohr            'INSERT INTO access (
368*762f4807SAndreas Gohr                dt, page, ip, ua, ua_info, ua_type, ua_ver, os, ref, ref_md5, ref_type,
369*762f4807SAndreas Gohr                screen_x, screen_y, view_x, view_y, js, user, session, uid
370*762f4807SAndreas Gohr             ) VALUES (
371*762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
372*762f4807SAndreas Gohr                ?, ?, ?, ?, ?, ?, ?, ?
373*762f4807SAndreas Gohr             )',
374*762f4807SAndreas Gohr            $page, $ip, $this->uaAgent, $this->uaName, $this->uaType, $this->uaVersion, $this->uaPlatform,
375*762f4807SAndreas Gohr            $ref, $ref_md5, $ref_type, $sx, $sy, $vx, $vy, $js, $user, $session, $this->uid
376*762f4807SAndreas Gohr        );
377*762f4807SAndreas Gohr
378*762f4807SAndreas Gohr        if ($ref_md5) {
379*762f4807SAndreas Gohr            $this->db->exec(
380*762f4807SAndreas Gohr                'INSERT OR IGNORE INTO refseen (
381*762f4807SAndreas Gohr                    ref_md5, dt
382*762f4807SAndreas Gohr                 ) VALUES (
383*762f4807SAndreas Gohr                    ?, CURRENT_TIMESTAMP
384*762f4807SAndreas Gohr                 )',
385*762f4807SAndreas Gohr                $ref_md5
386*762f4807SAndreas Gohr            );
387*762f4807SAndreas Gohr        }
388*762f4807SAndreas Gohr
389*762f4807SAndreas Gohr        // log group access
390*762f4807SAndreas Gohr        if (isset($USERINFO['grps'])) {
391*762f4807SAndreas Gohr            $this->logGroups('view', $USERINFO['grps']);
392*762f4807SAndreas Gohr        }
393*762f4807SAndreas Gohr
394*762f4807SAndreas Gohr        // resolve the IP
395*762f4807SAndreas Gohr        $this->logIp(clientIP(true));
396*762f4807SAndreas Gohr    }
397*762f4807SAndreas Gohr
398*762f4807SAndreas Gohr    /**
399*762f4807SAndreas Gohr     * Log access to a media file
400*762f4807SAndreas Gohr     *
401*762f4807SAndreas Gohr     * Called from action.php
402*762f4807SAndreas Gohr     *
403*762f4807SAndreas Gohr     * @param string $media The media ID
404*762f4807SAndreas Gohr     * @param string $mime The media's mime type
405*762f4807SAndreas Gohr     * @param bool $inline Is this displayed inline?
406*762f4807SAndreas Gohr     * @param int $size Size of the media file
407*762f4807SAndreas Gohr     */
408*762f4807SAndreas Gohr    public function logMedia(string $media, string $mime, bool $inline, int $size): void
409*762f4807SAndreas Gohr    {
410*762f4807SAndreas Gohr        global $INPUT;
411*762f4807SAndreas Gohr
412*762f4807SAndreas Gohr        [$mime1, $mime2] = explode('/', strtolower($mime));
413*762f4807SAndreas Gohr        $inline = $inline ? 1 : 0;
414*762f4807SAndreas Gohr        $size = (int)$size;
415*762f4807SAndreas Gohr
416*762f4807SAndreas Gohr        $ip = clientIP(true);
417*762f4807SAndreas Gohr        $user = $INPUT->server->str('REMOTE_USER');
418*762f4807SAndreas Gohr        $session = $this->getSession();
419*762f4807SAndreas Gohr
420*762f4807SAndreas Gohr        $this->db->exec(
421*762f4807SAndreas Gohr            'INSERT INTO media (
422*762f4807SAndreas Gohr                dt, media, ip, ua, ua_info, ua_type, ua_ver, os, user, session, uid,
423*762f4807SAndreas Gohr                size, mime1, mime2, inline
424*762f4807SAndreas Gohr             ) VALUES (
425*762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
426*762f4807SAndreas Gohr                ?, ?, ?, ?
427*762f4807SAndreas Gohr             )',
428*762f4807SAndreas Gohr            $media, $ip, $this->uaAgent, $this->uaName, $this->uaType, $this->uaVersion, $this->uaPlatform,
429*762f4807SAndreas Gohr            $user, $session, $this->uid, $size, $mime1, $mime2, $inline
430*762f4807SAndreas Gohr        );
431*762f4807SAndreas Gohr    }
432*762f4807SAndreas Gohr
433*762f4807SAndreas Gohr    /**
434*762f4807SAndreas Gohr     * Log page edits
435*762f4807SAndreas Gohr     *
436*762f4807SAndreas Gohr     * @param string $page The page that was edited
437*762f4807SAndreas Gohr     * @param string $type The type of edit (create, edit, etc.)
438*762f4807SAndreas Gohr     */
439*762f4807SAndreas Gohr    public function logEdit(string $page, string $type): void
440*762f4807SAndreas Gohr    {
441*762f4807SAndreas Gohr        global $INPUT, $USERINFO;
442*762f4807SAndreas Gohr
443*762f4807SAndreas Gohr        $ip = clientIP(true);
444*762f4807SAndreas Gohr        $user = $INPUT->server->str('REMOTE_USER');
445*762f4807SAndreas Gohr        $session = $this->getSession();
446*762f4807SAndreas Gohr
447*762f4807SAndreas Gohr        $this->db->exec(
448*762f4807SAndreas Gohr            'INSERT INTO edits (
449*762f4807SAndreas Gohr                dt, page, type, ip, user, session, uid
450*762f4807SAndreas Gohr             ) VALUES (
451*762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?
452*762f4807SAndreas Gohr             )',
453*762f4807SAndreas Gohr            $page, $type, $ip, $user, $session, $this->uid
454*762f4807SAndreas Gohr        );
455*762f4807SAndreas Gohr
456*762f4807SAndreas Gohr        // log group access
457*762f4807SAndreas Gohr        if (isset($USERINFO['grps'])) {
458*762f4807SAndreas Gohr            $this->logGroups('edit', $USERINFO['grps']);
459*762f4807SAndreas Gohr        }
460*762f4807SAndreas Gohr    }
461*762f4807SAndreas Gohr
462*762f4807SAndreas Gohr    /**
463*762f4807SAndreas Gohr     * Log login/logoffs and user creations
464*762f4807SAndreas Gohr     *
465*762f4807SAndreas Gohr     * @param string $type The type of login event (login, logout, create)
466*762f4807SAndreas Gohr     * @param string $user The username (optional, will use current user if empty)
467*762f4807SAndreas Gohr     */
468*762f4807SAndreas Gohr    public function logLogin(string $type, string $user = ''): void
469*762f4807SAndreas Gohr    {
470*762f4807SAndreas Gohr        global $INPUT;
471*762f4807SAndreas Gohr
472*762f4807SAndreas Gohr        if (!$user) $user = $INPUT->server->str('REMOTE_USER');
473*762f4807SAndreas Gohr
474*762f4807SAndreas Gohr        $ip = clientIP(true);
475*762f4807SAndreas Gohr        $session = $this->getSession();
476*762f4807SAndreas Gohr
477*762f4807SAndreas Gohr        $this->db->exec(
478*762f4807SAndreas Gohr            'INSERT INTO logins (
479*762f4807SAndreas Gohr                dt, type, ip, user, session, uid
480*762f4807SAndreas Gohr             ) VALUES (
481*762f4807SAndreas Gohr                CURRENT_TIMESTAMP, ?, ?, ?, ?, ?
482*762f4807SAndreas Gohr             )',
483*762f4807SAndreas Gohr            $type, $ip, $user, $session, $this->uid
484*762f4807SAndreas Gohr        );
485*762f4807SAndreas Gohr    }
486*762f4807SAndreas Gohr
487*762f4807SAndreas Gohr    /**
488*762f4807SAndreas Gohr     * Log the current page count and size as today's history entry
489*762f4807SAndreas Gohr     */
490*762f4807SAndreas Gohr    public function logHistoryPages(): void
491*762f4807SAndreas Gohr    {
492*762f4807SAndreas Gohr        global $conf;
493*762f4807SAndreas Gohr
494*762f4807SAndreas Gohr        // use the popularity plugin's search method to find the wanted data
495*762f4807SAndreas Gohr        /** @var helper_plugin_popularity $pop */
496*762f4807SAndreas Gohr        $pop = plugin_load('helper', 'popularity');
497*762f4807SAndreas Gohr        $list = [];
498*762f4807SAndreas Gohr        search($list, $conf['datadir'], [$pop, 'searchCountCallback'], ['all' => false], '');
499*762f4807SAndreas Gohr        $page_count = $list['file_count'];
500*762f4807SAndreas Gohr        $page_size = $list['file_size'];
501*762f4807SAndreas Gohr
502*762f4807SAndreas Gohr        $this->db->exec(
503*762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
504*762f4807SAndreas Gohr                info, value, dt
505*762f4807SAndreas Gohr             ) VALUES (
506*762f4807SAndreas Gohr                ?, ?, date("now")
507*762f4807SAndreas Gohr             )',
508*762f4807SAndreas Gohr            'page_count', $page_count
509*762f4807SAndreas Gohr        );
510*762f4807SAndreas Gohr        $this->db->exec(
511*762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
512*762f4807SAndreas Gohr                info, value, dt
513*762f4807SAndreas Gohr             ) VALUES (
514*762f4807SAndreas Gohr                ?, ?, date("now")
515*762f4807SAndreas Gohr             )',
516*762f4807SAndreas Gohr            'page_size', $page_size
517*762f4807SAndreas Gohr        );
518*762f4807SAndreas Gohr    }
519*762f4807SAndreas Gohr
520*762f4807SAndreas Gohr    /**
521*762f4807SAndreas Gohr     * Log the current media count and size as today's history entry
522*762f4807SAndreas Gohr     */
523*762f4807SAndreas Gohr    public function logHistoryMedia(): void
524*762f4807SAndreas Gohr    {
525*762f4807SAndreas Gohr        global $conf;
526*762f4807SAndreas Gohr
527*762f4807SAndreas Gohr        // use the popularity plugin's search method to find the wanted data
528*762f4807SAndreas Gohr        /** @var helper_plugin_popularity $pop */
529*762f4807SAndreas Gohr        $pop = plugin_load('helper', 'popularity');
530*762f4807SAndreas Gohr        $list = [];
531*762f4807SAndreas Gohr        search($list, $conf['mediadir'], [$pop, 'searchCountCallback'], ['all' => true], '');
532*762f4807SAndreas Gohr        $media_count = $list['file_count'];
533*762f4807SAndreas Gohr        $media_size = $list['file_size'];
534*762f4807SAndreas Gohr
535*762f4807SAndreas Gohr        $this->db->exec(
536*762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
537*762f4807SAndreas Gohr                info, value, dt
538*762f4807SAndreas Gohr             ) VALUES (
539*762f4807SAndreas Gohr                ?, ?, date("now")
540*762f4807SAndreas Gohr             )',
541*762f4807SAndreas Gohr            'media_count', $media_count
542*762f4807SAndreas Gohr        );
543*762f4807SAndreas Gohr        $this->db->exec(
544*762f4807SAndreas Gohr            'INSERT OR REPLACE INTO history (
545*762f4807SAndreas Gohr                info, value, dt
546*762f4807SAndreas Gohr             ) VALUES (
547*762f4807SAndreas Gohr                ?, ?, date("now")
548*762f4807SAndreas Gohr             )',
549*762f4807SAndreas Gohr            'media_size', $media_size
550*762f4807SAndreas Gohr        );
551*762f4807SAndreas Gohr    }
552*762f4807SAndreas Gohr}
553