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 helper_plugin_popularity; 12762f4807SAndreas Gohruse helper_plugin_statistics; 13762f4807SAndreas Gohr 14762f4807SAndreas Gohrclass Logger 15762f4807SAndreas Gohr{ 16762f4807SAndreas Gohr /** @var helper_plugin_statistics The statistics helper plugin instance */ 17762f4807SAndreas Gohr protected helper_plugin_statistics $hlp; 18762f4807SAndreas Gohr 19762f4807SAndreas Gohr /** @var SQLiteDB The SQLite database instance */ 20762f4807SAndreas Gohr protected SQLiteDB $db; 21762f4807SAndreas Gohr 22762f4807SAndreas Gohr /** @var string The full user agent string */ 23762f4807SAndreas Gohr protected string $uaAgent; 24762f4807SAndreas Gohr 25762f4807SAndreas Gohr /** @var string The type of user agent (browser, robot, feedreader) */ 26762f4807SAndreas Gohr protected string $uaType = 'browser'; 27762f4807SAndreas Gohr 28762f4807SAndreas Gohr /** @var string The browser/client name */ 29762f4807SAndreas Gohr protected string $uaName; 30762f4807SAndreas Gohr 31762f4807SAndreas Gohr /** @var string The browser/client version */ 32762f4807SAndreas Gohr protected string $uaVersion; 33762f4807SAndreas Gohr 34762f4807SAndreas Gohr /** @var string The operating system/platform */ 35762f4807SAndreas Gohr protected string $uaPlatform; 36762f4807SAndreas Gohr 374a163f50SAndreas Gohr /** @var string|null The user name, if available */ 384a163f50SAndreas Gohr protected ?string $user = null; 394a163f50SAndreas Gohr 40762f4807SAndreas Gohr /** @var string The unique user identifier */ 41762f4807SAndreas Gohr protected string $uid; 42762f4807SAndreas Gohr 434a163f50SAndreas Gohr /** @var string The session identifier */ 444a163f50SAndreas Gohr protected string $session; 454a163f50SAndreas Gohr 464a163f50SAndreas Gohr /** @var int|null The ID of the main access log entry if any */ 474a163f50SAndreas Gohr protected ?int $hit = null; 484a163f50SAndreas Gohr 49c7cad24dSAndreas Gohr /** @var DokuHTTPClient|null The HTTP client instance for testing */ 50c7cad24dSAndreas Gohr protected ?DokuHTTPClient $httpClient = null; 51c7cad24dSAndreas Gohr 524a163f50SAndreas Gohr // region lifecycle 53762f4807SAndreas Gohr 54762f4807SAndreas Gohr /** 55762f4807SAndreas Gohr * Constructor 56762f4807SAndreas Gohr * 57762f4807SAndreas Gohr * Parses browser info and set internal vars 58762f4807SAndreas Gohr */ 59c7cad24dSAndreas Gohr public function __construct(helper_plugin_statistics $hlp, ?DokuHTTPClient $httpClient = null) 60762f4807SAndreas Gohr { 61762f4807SAndreas Gohr global $INPUT; 62762f4807SAndreas Gohr 63762f4807SAndreas Gohr $this->hlp = $hlp; 64762f4807SAndreas Gohr $this->db = $this->hlp->getDB(); 65c7cad24dSAndreas Gohr $this->httpClient = $httpClient; 66762f4807SAndreas Gohr 674a163f50SAndreas Gohr // FIXME if we already have a session, we should not re-parse the user agent 68762f4807SAndreas Gohr 694a163f50SAndreas Gohr $ua = trim($INPUT->server->str('HTTP_USER_AGENT')); 70762f4807SAndreas Gohr AbstractDeviceParser::setVersionTruncation(AbstractDeviceParser::VERSION_TRUNCATION_MAJOR); 71762f4807SAndreas Gohr $dd = new DeviceDetector($ua); // FIXME we could use client hints, but need to add headers 72762f4807SAndreas Gohr $dd->discardBotInformation(); 73762f4807SAndreas Gohr $dd->parse(); 74762f4807SAndreas Gohr 7500f786d8SAndreas Gohr if ($dd->isFeedReader()) { 7600f786d8SAndreas Gohr $this->uaType = 'feedreader'; 7700f786d8SAndreas Gohr } elseif ($dd->isBot()) { 78762f4807SAndreas Gohr $this->uaType = 'robot'; 79762f4807SAndreas Gohr // for now ignore bots 80c5d2f052SAndreas Gohr throw new IgnoreException('Bot detected, not logging'); 81762f4807SAndreas Gohr } 82762f4807SAndreas Gohr 83762f4807SAndreas Gohr $this->uaAgent = $ua; 8405786d83SAndreas Gohr $this->uaName = Browser::getBrowserFamily($dd->getClient('name')) ?: 'Unknown'; 8500f786d8SAndreas Gohr $this->uaVersion = $dd->getClient('version') ?: '0'; 8605786d83SAndreas Gohr $this->uaPlatform = OperatingSystem::getOsFamily($dd->getOs('name')) ?: 'Unknown'; 87762f4807SAndreas Gohr $this->uid = $this->getUID(); 884a163f50SAndreas Gohr $this->session = $this->getSession(); 894a163f50SAndreas Gohr $this->user = $INPUT->server->str('REMOTE_USER') ?: null; 90762f4807SAndreas Gohr } 91762f4807SAndreas Gohr 92762f4807SAndreas Gohr /** 93762f4807SAndreas Gohr * Should be called before logging 94762f4807SAndreas Gohr * 954a163f50SAndreas Gohr * This starts a transaction, so all logging is done in one go. It also logs the user and session data. 96762f4807SAndreas Gohr */ 97762f4807SAndreas Gohr public function begin(): void 98762f4807SAndreas Gohr { 99762f4807SAndreas Gohr $this->hlp->getDB()->getPdo()->beginTransaction(); 1004a163f50SAndreas Gohr 1014a163f50SAndreas Gohr $this->logUser(); 1024a163f50SAndreas Gohr $this->logGroups(); 1034a163f50SAndreas Gohr $this->logDomain(); 1044a163f50SAndreas Gohr $this->logSession(); 105762f4807SAndreas Gohr } 106762f4807SAndreas Gohr 107762f4807SAndreas Gohr /** 108762f4807SAndreas Gohr * Should be called after logging 109762f4807SAndreas Gohr * 110762f4807SAndreas Gohr * This commits the transaction started in begin() 111762f4807SAndreas Gohr */ 112762f4807SAndreas Gohr public function end(): void 113762f4807SAndreas Gohr { 114762f4807SAndreas Gohr $this->hlp->getDB()->getPdo()->commit(); 115762f4807SAndreas Gohr } 116762f4807SAndreas Gohr 1174a163f50SAndreas Gohr // endregion 1184a163f50SAndreas Gohr // region data gathering 1194a163f50SAndreas Gohr 120762f4807SAndreas Gohr /** 121762f4807SAndreas Gohr * Get the unique user ID 122762f4807SAndreas Gohr * 123*04928db4SAndreas Gohr * The user ID is stored in the user preferences and should stay there forever. 124762f4807SAndreas Gohr * @return string The unique user identifier 125762f4807SAndreas Gohr */ 126762f4807SAndreas Gohr protected function getUID(): string 127762f4807SAndreas Gohr { 128*04928db4SAndreas Gohr if(!isset($_SESSION[DOKU_COOKIE]['statistics']['uid'])) { 129*04928db4SAndreas Gohr // when there is no session UID set, we assume this was deliberate and we simply abort all logging 130*04928db4SAndreas Gohr // @todo we may later make UID generation optional 131*04928db4SAndreas Gohr throw new IgnoreException('No user ID found'); 132*04928db4SAndreas Gohr } 133762f4807SAndreas Gohr 134*04928db4SAndreas Gohr return $_SESSION[DOKU_COOKIE]['statistics']['uid']; 135762f4807SAndreas Gohr } 136762f4807SAndreas Gohr 137762f4807SAndreas Gohr /** 138762f4807SAndreas Gohr * Return the user's session ID 139762f4807SAndreas Gohr * 140762f4807SAndreas Gohr * @return string The session identifier 141762f4807SAndreas Gohr */ 142762f4807SAndreas Gohr protected function getSession(): string 143762f4807SAndreas Gohr { 144*04928db4SAndreas Gohr if(!isset($_SESSION[DOKU_COOKIE]['statistics']['id'])) { 145*04928db4SAndreas Gohr // when there is no session ID set, we assume this was deliberate and we simply abort all logging 146*04928db4SAndreas Gohr throw new IgnoreException('No session ID found'); 147*04928db4SAndreas Gohr } 148762f4807SAndreas Gohr 149*04928db4SAndreas Gohr return $_SESSION[DOKU_COOKIE]['statistics']['id']; 150762f4807SAndreas Gohr } 151762f4807SAndreas Gohr 1524a163f50SAndreas Gohr // endregion 1534a163f50SAndreas Gohr // region automatic logging 154762f4807SAndreas Gohr 1554a163f50SAndreas Gohr /** 1564a163f50SAndreas Gohr * Log the user was seen 1574a163f50SAndreas Gohr */ 1584a163f50SAndreas Gohr protected function logUser(): void 1594a163f50SAndreas Gohr { 1604a163f50SAndreas Gohr if (!$this->user) return; 161762f4807SAndreas Gohr 162762f4807SAndreas Gohr $this->db->exec( 1634a163f50SAndreas Gohr 'INSERT INTO users (user, dt) 1644a163f50SAndreas Gohr VALUES (?, CURRENT_TIMESTAMP) 1654a163f50SAndreas Gohr ON CONFLICT (user) DO UPDATE SET 1664a163f50SAndreas Gohr dt = CURRENT_TIMESTAMP 1674a163f50SAndreas Gohr WHERE excluded.user = users.user 1684a163f50SAndreas Gohr ', 1694a163f50SAndreas Gohr $this->user 1704a163f50SAndreas Gohr ); 1714a163f50SAndreas Gohr 1724a163f50SAndreas Gohr } 1734a163f50SAndreas Gohr 1744a163f50SAndreas Gohr /** 1754a163f50SAndreas Gohr * Log the session and user agent information 1764a163f50SAndreas Gohr */ 1774a163f50SAndreas Gohr protected function logSession(): void 1784a163f50SAndreas Gohr { 1794a163f50SAndreas Gohr $this->db->exec( 1804a163f50SAndreas Gohr 'INSERT INTO sessions (session, dt, end, uid, user, ua, ua_info, ua_type, ua_ver, os) 1814a163f50SAndreas Gohr VALUES (?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?, ?) 1824a163f50SAndreas Gohr ON CONFLICT (session) DO UPDATE SET 1834a163f50SAndreas Gohr end = CURRENT_TIMESTAMP 1844a163f50SAndreas Gohr WHERE excluded.session = sessions.session 1854a163f50SAndreas Gohr ', 1864a163f50SAndreas Gohr $this->session, 1874a163f50SAndreas Gohr $this->uid, 1884a163f50SAndreas Gohr $this->user, 1894a163f50SAndreas Gohr $this->uaAgent, 1904a163f50SAndreas Gohr $this->uaName, 1914a163f50SAndreas Gohr $this->uaType, 1924a163f50SAndreas Gohr $this->uaVersion, 1934a163f50SAndreas Gohr $this->uaPlatform 194762f4807SAndreas Gohr ); 195762f4807SAndreas Gohr } 196762f4807SAndreas Gohr 197762f4807SAndreas Gohr /** 1984a163f50SAndreas Gohr * Log all groups for the user 199762f4807SAndreas Gohr * 2004a163f50SAndreas Gohr * @todo maybe this should be done only once per session? 201762f4807SAndreas Gohr */ 2024a163f50SAndreas Gohr protected function logGroups(): void 203762f4807SAndreas Gohr { 2044a163f50SAndreas Gohr global $USERINFO; 205762f4807SAndreas Gohr 2064a163f50SAndreas Gohr if (!$this->user) return; 2074a163f50SAndreas Gohr if (!isset($USERINFO['grps'])) return; 2084a163f50SAndreas Gohr if (!is_array($USERINFO['grps'])) return; 2094a163f50SAndreas Gohr $groups = $USERINFO['grps']; 210fced2f86SAnna Dabrowska 2114a163f50SAndreas Gohr $this->db->exec('DELETE FROM groups WHERE user = ?', $this->user); 212762f4807SAndreas Gohr 21302aa9b73SAndreas Gohr $placeholders = implode(',', array_fill(0, count($groups), '(?, ?)')); 214762f4807SAndreas Gohr $params = []; 2154a163f50SAndreas Gohr $sql = "INSERT INTO groups (`user`, `group`) VALUES $placeholders"; 216762f4807SAndreas Gohr foreach ($groups as $group) { 2174a163f50SAndreas Gohr $params[] = $this->user; 218762f4807SAndreas Gohr $params[] = $group; 219762f4807SAndreas Gohr } 220762f4807SAndreas Gohr $this->db->exec($sql, $params); 221762f4807SAndreas Gohr } 222762f4807SAndreas Gohr 223762f4807SAndreas Gohr /** 2244a163f50SAndreas Gohr * Log email domain 2250b8b7abaSAnna Dabrowska * 2264a163f50SAndreas Gohr * @todo maybe this should be done only once per session? 2270b8b7abaSAnna Dabrowska */ 2284a163f50SAndreas Gohr protected function logDomain(): void 2290b8b7abaSAnna Dabrowska { 2304a163f50SAndreas Gohr global $USERINFO; 2314a163f50SAndreas Gohr if (!$this->user) return; 2324a163f50SAndreas Gohr if (!isset($USERINFO['mail'])) return; 2334a163f50SAndreas Gohr $mail = $USERINFO['mail']; 2340b8b7abaSAnna Dabrowska 2350b8b7abaSAnna Dabrowska $pos = strrpos($mail, '@'); 2360b8b7abaSAnna Dabrowska if (!$pos) return; 2370b8b7abaSAnna Dabrowska $domain = substr($mail, $pos + 1); 2380b8b7abaSAnna Dabrowska if (empty($domain)) return; 2390b8b7abaSAnna Dabrowska 2404a163f50SAndreas Gohr $sql = 'UPDATE users SET domain = ? WHERE user = ?'; 2414a163f50SAndreas Gohr $this->db->exec($sql, [$domain, $this->user]); 2420b8b7abaSAnna Dabrowska } 2430b8b7abaSAnna Dabrowska 2444a163f50SAndreas Gohr // endregion 2454a163f50SAndreas Gohr // region internal loggers called by the dispatchers 2464a163f50SAndreas Gohr 2470b8b7abaSAnna Dabrowska /** 2484a163f50SAndreas Gohr * Log the given referer URL 249762f4807SAndreas Gohr * 2504a163f50SAndreas Gohr * @param $referer 2514a163f50SAndreas Gohr * @return int|null The referer ID or null if no referer was given 252762f4807SAndreas Gohr */ 2534a163f50SAndreas Gohr public function logReferer($referer): ?int 254762f4807SAndreas Gohr { 2554a163f50SAndreas Gohr if (!$referer) return null; 256762f4807SAndreas Gohr 2574a163f50SAndreas Gohr // FIXME we could check against a blacklist here 258762f4807SAndreas Gohr 2594a163f50SAndreas Gohr $se = new SearchEngines($referer); 2604a163f50SAndreas Gohr $type = $se->isSearchEngine() ? 'search' : 'external'; 261762f4807SAndreas Gohr 26202aa9b73SAndreas Gohr $sql = 'INSERT OR IGNORE INTO referers (url, type, dt) VALUES (?, ?, CURRENT_TIMESTAMP)'; 26302aa9b73SAndreas Gohr return $this->db->exec($sql, [$referer, $type]); // returns ID even if the insert was ignored 264762f4807SAndreas Gohr } 265762f4807SAndreas Gohr 266762f4807SAndreas Gohr /** 267762f4807SAndreas Gohr * Resolve IP to country/city and store in database 268762f4807SAndreas Gohr * 2694a163f50SAndreas Gohr * @return string The IP address as stored 270762f4807SAndreas Gohr */ 2714a163f50SAndreas Gohr public function logIp(): string 272762f4807SAndreas Gohr { 2734a163f50SAndreas Gohr $ip = clientIP(true); 2744a163f50SAndreas Gohr $hash = $ip; // @todo we could anonymize here 2754a163f50SAndreas Gohr 276762f4807SAndreas Gohr // check if IP already known and up-to-date 277762f4807SAndreas Gohr $result = $this->db->queryValue( 278762f4807SAndreas Gohr "SELECT ip 279762f4807SAndreas Gohr FROM iplocation 280762f4807SAndreas Gohr WHERE ip = ? 281762f4807SAndreas Gohr AND lastupd > date('now', '-30 days')", 2824a163f50SAndreas Gohr $hash 283762f4807SAndreas Gohr ); 2844a163f50SAndreas Gohr if ($result) return $hash; // already known and up-to-date 285762f4807SAndreas Gohr 286c7cad24dSAndreas Gohr $http = $this->httpClient ?: new DokuHTTPClient(); 2874a163f50SAndreas Gohr $http->timeout = 7; 288762f4807SAndreas Gohr $json = $http->get('http://ip-api.com/json/' . $ip); // yes, it's HTTP only 289762f4807SAndreas Gohr 2904a163f50SAndreas Gohr if (!$json) { 2914a163f50SAndreas Gohr \dokuwiki\Logger::error('Statistics Plugin - Failed talk to ip-api.com.'); 2924a163f50SAndreas Gohr return $hash; 2934a163f50SAndreas Gohr } 294762f4807SAndreas Gohr try { 295762f4807SAndreas Gohr $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); 296762f4807SAndreas Gohr } catch (\JsonException $e) { 2974a163f50SAndreas Gohr \dokuwiki\Logger::error('Statistics Plugin - Failed to decode JSON from ip-api.com.', $e); 2984a163f50SAndreas Gohr return $hash; 299762f4807SAndreas Gohr } 30002aa9b73SAndreas Gohr if (!isset($data['status'])) { 30102aa9b73SAndreas Gohr \dokuwiki\Logger::error('Statistics Plugin - Invalid ip-api.com result' . $ip, $data); 3024a163f50SAndreas Gohr return $hash; 30302aa9b73SAndreas Gohr }; 30402aa9b73SAndreas Gohr 30502aa9b73SAndreas Gohr // we do not check for 'success' status here. when the API can't resolve the IP we still log it 30602aa9b73SAndreas Gohr // without location data, so we won't re-query it in the next 30 days. 307762f4807SAndreas Gohr 3084a163f50SAndreas Gohr $host = gethostbyaddr($ip); // @todo if we anonymize the IP, we should not do this 309762f4807SAndreas Gohr $this->db->exec( 310762f4807SAndreas Gohr 'INSERT OR REPLACE INTO iplocation ( 311762f4807SAndreas Gohr ip, country, code, city, host, lastupd 312762f4807SAndreas Gohr ) VALUES ( 313762f4807SAndreas Gohr ?, ?, ?, ?, ?, CURRENT_TIMESTAMP 314762f4807SAndreas Gohr )', 3154a163f50SAndreas Gohr $hash, 31602aa9b73SAndreas Gohr $data['country'] ?? '', 31702aa9b73SAndreas Gohr $data['countryCode'] ?? '', 31802aa9b73SAndreas Gohr $data['city'] ?? '', 3192adee4c6SAndreas Gohr $host 320762f4807SAndreas Gohr ); 3214a163f50SAndreas Gohr 3224a163f50SAndreas Gohr return $hash; 3234a163f50SAndreas Gohr } 3244a163f50SAndreas Gohr 3254a163f50SAndreas Gohr // endregion 3264a163f50SAndreas Gohr // region log dispatchers 3274a163f50SAndreas Gohr 3284a163f50SAndreas Gohr public function logPageView(): void 3294a163f50SAndreas Gohr { 3304a163f50SAndreas Gohr global $INPUT; 3314a163f50SAndreas Gohr 3324a163f50SAndreas Gohr if (!$INPUT->str('p')) return; 3334a163f50SAndreas Gohr 3344a163f50SAndreas Gohr 3354a163f50SAndreas Gohr $referer = $INPUT->filter('trim')->str('r'); 3364a163f50SAndreas Gohr $ip = $this->logIp(); // resolve the IP address 3374a163f50SAndreas Gohr 3384a163f50SAndreas Gohr $data = [ 3394a163f50SAndreas Gohr 'page' => $INPUT->filter('cleanID')->str('p'), 3404a163f50SAndreas Gohr 'ip' => $ip, 3414a163f50SAndreas Gohr 'ref_id' => $this->logReferer($referer), 3424a163f50SAndreas Gohr 'sx' => $INPUT->int('sx'), 3434a163f50SAndreas Gohr 'sy' => $INPUT->int('sy'), 3444a163f50SAndreas Gohr 'vx' => $INPUT->int('vx'), 3454a163f50SAndreas Gohr 'vy' => $INPUT->int('vy'), 3464a163f50SAndreas Gohr 'session' => $this->session, 3474a163f50SAndreas Gohr ]; 3484a163f50SAndreas Gohr 3494a163f50SAndreas Gohr $this->db->exec(' 3504a163f50SAndreas Gohr INSERT INTO pageviews ( 3514a163f50SAndreas Gohr dt, page, ip, ref_id, screen_x, screen_y, view_x, view_y, session 3524a163f50SAndreas Gohr ) VALUES ( 3534a163f50SAndreas Gohr CURRENT_TIMESTAMP, :page, :ip, :ref_id, :sx, :sy, :vx, :vy, :session 3544a163f50SAndreas Gohr ) 3554a163f50SAndreas Gohr ', 3564a163f50SAndreas Gohr $data 3574a163f50SAndreas Gohr ); 358762f4807SAndreas Gohr } 359762f4807SAndreas Gohr 360762f4807SAndreas Gohr /** 361762f4807SAndreas Gohr * Log a click on an external link 362762f4807SAndreas Gohr * 363762f4807SAndreas Gohr * Called from log.php 364762f4807SAndreas Gohr */ 365762f4807SAndreas Gohr public function logOutgoing(): void 366762f4807SAndreas Gohr { 367762f4807SAndreas Gohr global $INPUT; 368762f4807SAndreas Gohr 369762f4807SAndreas Gohr if (!$INPUT->str('ol')) return; 370762f4807SAndreas Gohr 3714a163f50SAndreas Gohr $link = $INPUT->filter('trim')->str('ol'); 3724a163f50SAndreas Gohr $session = $this->session; 3734a163f50SAndreas Gohr $page = $INPUT->filter('cleanID')->str('p'); 374762f4807SAndreas Gohr 375762f4807SAndreas Gohr $this->db->exec( 376762f4807SAndreas Gohr 'INSERT INTO outlinks ( 3774a163f50SAndreas Gohr dt, session, page, link 378762f4807SAndreas Gohr ) VALUES ( 379762f4807SAndreas Gohr CURRENT_TIMESTAMP, ?, ?, ?, ? 380762f4807SAndreas Gohr )', 3812adee4c6SAndreas Gohr $session, 3822adee4c6SAndreas Gohr $page, 3832adee4c6SAndreas Gohr $link 384762f4807SAndreas Gohr ); 385762f4807SAndreas Gohr } 386762f4807SAndreas Gohr 387762f4807SAndreas Gohr /** 388762f4807SAndreas Gohr * Log access to a media file 389762f4807SAndreas Gohr * 390762f4807SAndreas Gohr * Called from action.php 391762f4807SAndreas Gohr * 392762f4807SAndreas Gohr * @param string $media The media ID 393762f4807SAndreas Gohr * @param string $mime The media's mime type 394762f4807SAndreas Gohr * @param bool $inline Is this displayed inline? 395762f4807SAndreas Gohr * @param int $size Size of the media file 396762f4807SAndreas Gohr */ 397762f4807SAndreas Gohr public function logMedia(string $media, string $mime, bool $inline, int $size): void 398762f4807SAndreas Gohr { 399762f4807SAndreas Gohr [$mime1, $mime2] = explode('/', strtolower($mime)); 400762f4807SAndreas Gohr $inline = $inline ? 1 : 0; 401762f4807SAndreas Gohr 402762f4807SAndreas Gohr 4034a163f50SAndreas Gohr $data = [ 4044a163f50SAndreas Gohr 'media' => cleanID($media), 4054a163f50SAndreas Gohr 'ip' => $this->logIp(), // resolve the IP address 4064a163f50SAndreas Gohr 'session' => $this->session, 4074a163f50SAndreas Gohr 'size' => $size, 4084a163f50SAndreas Gohr 'mime1' => $mime1, 4094a163f50SAndreas Gohr 'mime2' => $mime2, 4104a163f50SAndreas Gohr 'inline' => $inline, 4114a163f50SAndreas Gohr ]; 4124a163f50SAndreas Gohr 4134a163f50SAndreas Gohr $this->db->exec(' 4144a163f50SAndreas Gohr INSERT INTO media ( dt, media, ip, session, size, mime1, mime2, inline ) 4154a163f50SAndreas Gohr VALUES (CURRENT_TIMESTAMP, :media, :ip, :session, :size, :mime1, :mime2, :inline) 4164a163f50SAndreas Gohr ', 4174a163f50SAndreas Gohr $data 418762f4807SAndreas Gohr ); 419762f4807SAndreas Gohr } 420762f4807SAndreas Gohr 421762f4807SAndreas Gohr /** 422762f4807SAndreas Gohr * Log page edits 423762f4807SAndreas Gohr * 4244a163f50SAndreas Gohr * called from action.php 4254a163f50SAndreas Gohr * 426762f4807SAndreas Gohr * @param string $page The page that was edited 427762f4807SAndreas Gohr * @param string $type The type of edit (create, edit, etc.) 428762f4807SAndreas Gohr */ 429762f4807SAndreas Gohr public function logEdit(string $page, string $type): void 430762f4807SAndreas Gohr { 4314a163f50SAndreas Gohr $data = [ 4324a163f50SAndreas Gohr 'page' => cleanID($page), 4334a163f50SAndreas Gohr 'type' => $type, 4344a163f50SAndreas Gohr 'ip' => $this->logIp(), // resolve the IP address 4354a163f50SAndreas Gohr 'session' => $this->session 4364a163f50SAndreas Gohr ]; 437762f4807SAndreas Gohr 4380b8b7abaSAnna Dabrowska $editId = $this->db->exec( 439762f4807SAndreas Gohr 'INSERT INTO edits ( 4404a163f50SAndreas Gohr dt, page, type, ip, session 441762f4807SAndreas Gohr ) VALUES ( 4424a163f50SAndreas Gohr CURRENT_TIMESTAMP, :page, :type, :ip, :session 443762f4807SAndreas Gohr )', 4444a163f50SAndreas Gohr $data 445762f4807SAndreas Gohr ); 446762f4807SAndreas Gohr } 447762f4807SAndreas Gohr 448762f4807SAndreas Gohr /** 449762f4807SAndreas Gohr * Log login/logoffs and user creations 450762f4807SAndreas Gohr * 451762f4807SAndreas Gohr * @param string $type The type of login event (login, logout, create) 452762f4807SAndreas Gohr * @param string $user The username (optional, will use current user if empty) 4534a163f50SAndreas Gohr * @fixme this is still broken, I need to figure out the session handling first 454762f4807SAndreas Gohr */ 455762f4807SAndreas Gohr public function logLogin(string $type, string $user = ''): void 456762f4807SAndreas Gohr { 457762f4807SAndreas Gohr global $INPUT; 458762f4807SAndreas Gohr 459762f4807SAndreas Gohr if (!$user) $user = $INPUT->server->str('REMOTE_USER'); 460762f4807SAndreas Gohr 461762f4807SAndreas Gohr $ip = clientIP(true); 4624a163f50SAndreas Gohr $session = $this->session; 463762f4807SAndreas Gohr 464762f4807SAndreas Gohr $this->db->exec( 465762f4807SAndreas Gohr 'INSERT INTO logins ( 4664a163f50SAndreas Gohr dt, type, ip, session 467762f4807SAndreas Gohr ) VALUES ( 468762f4807SAndreas Gohr CURRENT_TIMESTAMP, ?, ?, ?, ?, ? 469762f4807SAndreas Gohr )', 4702adee4c6SAndreas Gohr $type, 4712adee4c6SAndreas Gohr $ip, 4722adee4c6SAndreas Gohr $user, 4732adee4c6SAndreas Gohr $session, 4742adee4c6SAndreas Gohr $this->uid 475762f4807SAndreas Gohr ); 476762f4807SAndreas Gohr } 477762f4807SAndreas Gohr 478762f4807SAndreas Gohr /** 47902aa9b73SAndreas Gohr * Log search data to the search related tables 48002aa9b73SAndreas Gohr * 48102aa9b73SAndreas Gohr * @param string $query The search query 48202aa9b73SAndreas Gohr * @param string[] $words The query split into words 48302aa9b73SAndreas Gohr */ 48402aa9b73SAndreas Gohr public function logSearch(string $query, array $words): void 48502aa9b73SAndreas Gohr { 48602aa9b73SAndreas Gohr if (!$query) return; 48702aa9b73SAndreas Gohr 48802aa9b73SAndreas Gohr $sid = $this->db->exec( 48902aa9b73SAndreas Gohr 'INSERT INTO search (dt, ip, session, query) VALUES (CURRENT_TIMESTAMP, ?, ? , ?)', 49002aa9b73SAndreas Gohr $this->logIp(), // resolve the IP address 49102aa9b73SAndreas Gohr $this->session, 49202aa9b73SAndreas Gohr $query, 49302aa9b73SAndreas Gohr ); 49402aa9b73SAndreas Gohr 49502aa9b73SAndreas Gohr foreach ($words as $word) { 49602aa9b73SAndreas Gohr if (!$word) continue; 49702aa9b73SAndreas Gohr $this->db->exec( 49802aa9b73SAndreas Gohr 'INSERT INTO searchwords (sid, word) VALUES (?, ?)', 49902aa9b73SAndreas Gohr $sid, 50002aa9b73SAndreas Gohr $word 50102aa9b73SAndreas Gohr ); 50202aa9b73SAndreas Gohr } 50302aa9b73SAndreas Gohr } 50402aa9b73SAndreas Gohr 50502aa9b73SAndreas Gohr /** 506762f4807SAndreas Gohr * Log the current page count and size as today's history entry 507762f4807SAndreas Gohr */ 508762f4807SAndreas Gohr public function logHistoryPages(): void 509762f4807SAndreas Gohr { 510762f4807SAndreas Gohr global $conf; 511762f4807SAndreas Gohr 512762f4807SAndreas Gohr // use the popularity plugin's search method to find the wanted data 513762f4807SAndreas Gohr /** @var helper_plugin_popularity $pop */ 514762f4807SAndreas Gohr $pop = plugin_load('helper', 'popularity'); 515b188870fSAndreas Gohr $list = $this->initEmptySearchList(); 516762f4807SAndreas Gohr search($list, $conf['datadir'], [$pop, 'searchCountCallback'], ['all' => false], ''); 517762f4807SAndreas Gohr $page_count = $list['file_count']; 518762f4807SAndreas Gohr $page_size = $list['file_size']; 519762f4807SAndreas Gohr 520762f4807SAndreas Gohr $this->db->exec( 521762f4807SAndreas Gohr 'INSERT OR REPLACE INTO history ( 522762f4807SAndreas Gohr info, value, dt 523762f4807SAndreas Gohr ) VALUES ( 524483101d3SAndreas Gohr ?, ?, CURRENT_TIMESTAMP 525762f4807SAndreas Gohr )', 5262adee4c6SAndreas Gohr 'page_count', 5272adee4c6SAndreas Gohr $page_count 528762f4807SAndreas Gohr ); 529762f4807SAndreas Gohr $this->db->exec( 530762f4807SAndreas Gohr 'INSERT OR REPLACE INTO history ( 531762f4807SAndreas Gohr info, value, dt 532762f4807SAndreas Gohr ) VALUES ( 533483101d3SAndreas Gohr ?, ?, CURRENT_TIMESTAMP 534762f4807SAndreas Gohr )', 5352adee4c6SAndreas Gohr 'page_size', 5362adee4c6SAndreas Gohr $page_size 537762f4807SAndreas Gohr ); 538762f4807SAndreas Gohr } 539762f4807SAndreas Gohr 540762f4807SAndreas Gohr /** 541762f4807SAndreas Gohr * Log the current media count and size as today's history entry 542762f4807SAndreas Gohr */ 543762f4807SAndreas Gohr public function logHistoryMedia(): void 544762f4807SAndreas Gohr { 545762f4807SAndreas Gohr global $conf; 546762f4807SAndreas Gohr 547762f4807SAndreas Gohr // use the popularity plugin's search method to find the wanted data 548762f4807SAndreas Gohr /** @var helper_plugin_popularity $pop */ 549762f4807SAndreas Gohr $pop = plugin_load('helper', 'popularity'); 550b188870fSAndreas Gohr $list = $this->initEmptySearchList(); 551762f4807SAndreas Gohr search($list, $conf['mediadir'], [$pop, 'searchCountCallback'], ['all' => true], ''); 552762f4807SAndreas Gohr $media_count = $list['file_count']; 553762f4807SAndreas Gohr $media_size = $list['file_size']; 554762f4807SAndreas Gohr 555762f4807SAndreas Gohr $this->db->exec( 556762f4807SAndreas Gohr 'INSERT OR REPLACE INTO history ( 557762f4807SAndreas Gohr info, value, dt 558762f4807SAndreas Gohr ) VALUES ( 559483101d3SAndreas Gohr ?, ?, CURRENT_TIMESTAMP 560762f4807SAndreas Gohr )', 5612adee4c6SAndreas Gohr 'media_count', 5622adee4c6SAndreas Gohr $media_count 563762f4807SAndreas Gohr ); 564762f4807SAndreas Gohr $this->db->exec( 565762f4807SAndreas Gohr 'INSERT OR REPLACE INTO history ( 566762f4807SAndreas Gohr info, value, dt 567762f4807SAndreas Gohr ) VALUES ( 568483101d3SAndreas Gohr ?, ?, CURRENT_TIMESTAMP 569762f4807SAndreas Gohr )', 5702adee4c6SAndreas Gohr 'media_size', 5712adee4c6SAndreas Gohr $media_size 572762f4807SAndreas Gohr ); 573762f4807SAndreas Gohr } 574b188870fSAndreas Gohr 5754a163f50SAndreas Gohr // endregion 5764a163f50SAndreas Gohr 577b188870fSAndreas Gohr /** 578b188870fSAndreas Gohr * @todo can be dropped in favor of helper_plugin_popularity::initEmptySearchList() once it's public 579b188870fSAndreas Gohr * @return array 580b188870fSAndreas Gohr */ 581b188870fSAndreas Gohr protected function initEmptySearchList() 582b188870fSAndreas Gohr { 583b188870fSAndreas Gohr return array_fill_keys([ 584b188870fSAndreas Gohr 'file_count', 585b188870fSAndreas Gohr 'file_size', 586b188870fSAndreas Gohr 'file_max', 587b188870fSAndreas Gohr 'file_min', 588b188870fSAndreas Gohr 'dir_count', 589b188870fSAndreas Gohr 'dir_nest', 590b188870fSAndreas Gohr 'file_oldest' 591b188870fSAndreas Gohr ], 0); 592b188870fSAndreas Gohr } 593762f4807SAndreas Gohr} 594