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; 94a163f50SAndreas 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 394a163f50SAndreas Gohr /** @var string|null The user name, if available */ 404a163f50SAndreas Gohr protected ?string $user = null; 414a163f50SAndreas Gohr 42762f4807SAndreas Gohr /** @var string The unique user identifier */ 43762f4807SAndreas Gohr protected string $uid; 44762f4807SAndreas Gohr 454a163f50SAndreas Gohr /** @var string The session identifier */ 464a163f50SAndreas Gohr protected string $session; 474a163f50SAndreas Gohr 484a163f50SAndreas Gohr /** @var int|null The ID of the main access log entry if any */ 494a163f50SAndreas Gohr protected ?int $hit = null; 504a163f50SAndreas Gohr 51c7cad24dSAndreas Gohr /** @var DokuHTTPClient|null The HTTP client instance for testing */ 52c7cad24dSAndreas Gohr protected ?DokuHTTPClient $httpClient = null; 53c7cad24dSAndreas Gohr 544a163f50SAndreas 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 694a163f50SAndreas Gohr // FIXME if we already have a session, we should not re-parse the user agent 70762f4807SAndreas Gohr 714a163f50SAndreas 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(); 904a163f50SAndreas Gohr $this->session = $this->getSession(); 914a163f50SAndreas Gohr $this->user = $INPUT->server->str('REMOTE_USER') ?: null; 92762f4807SAndreas Gohr } 93762f4807SAndreas Gohr 94762f4807SAndreas Gohr /** 95762f4807SAndreas Gohr * Should be called before logging 96762f4807SAndreas Gohr * 974a163f50SAndreas 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(); 1024a163f50SAndreas Gohr 1034a163f50SAndreas Gohr $this->logUser(); 1044a163f50SAndreas Gohr $this->logGroups(); 1054a163f50SAndreas Gohr $this->logDomain(); 1064a163f50SAndreas 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 1194a163f50SAndreas Gohr // endregion 1204a163f50SAndreas Gohr // region data gathering 1214a163f50SAndreas 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*02aa9b73SAndreas Gohr 150*02aa9b73SAndreas Gohr 151*02aa9b73SAndreas Gohr 1524a163f50SAndreas Gohr // FIXME session setting needs work. It should be reset on user change, maybe we do rely on the PHP session? 1534a163f50SAndreas Gohr // We also want to store the user agent in the session table, so this needs also change the session ID 154762f4807SAndreas Gohr $ses = $INPUT->str('ses'); 155762f4807SAndreas Gohr if (!$ses) $ses = get_doku_pref('plgstatsses', false); 156762f4807SAndreas Gohr if (!$ses) $ses = session_id(); 157b188870fSAndreas Gohr set_doku_pref('plgstatsses', $ses); 158762f4807SAndreas Gohr return $ses; 159762f4807SAndreas Gohr } 160762f4807SAndreas Gohr 1614a163f50SAndreas Gohr // endregion 1624a163f50SAndreas Gohr // region automatic logging 163762f4807SAndreas Gohr 1644a163f50SAndreas Gohr /** 1654a163f50SAndreas Gohr * Log the user was seen 1664a163f50SAndreas Gohr */ 1674a163f50SAndreas Gohr protected function logUser(): void 1684a163f50SAndreas Gohr { 1694a163f50SAndreas Gohr if (!$this->user) return; 170762f4807SAndreas Gohr 171762f4807SAndreas Gohr $this->db->exec( 1724a163f50SAndreas Gohr 'INSERT INTO users (user, dt) 1734a163f50SAndreas Gohr VALUES (?, CURRENT_TIMESTAMP) 1744a163f50SAndreas Gohr ON CONFLICT (user) DO UPDATE SET 1754a163f50SAndreas Gohr dt = CURRENT_TIMESTAMP 1764a163f50SAndreas Gohr WHERE excluded.user = users.user 1774a163f50SAndreas Gohr ', 1784a163f50SAndreas Gohr $this->user 1794a163f50SAndreas Gohr ); 1804a163f50SAndreas Gohr 1814a163f50SAndreas Gohr } 1824a163f50SAndreas Gohr 1834a163f50SAndreas Gohr /** 1844a163f50SAndreas Gohr * Log the session and user agent information 1854a163f50SAndreas Gohr */ 1864a163f50SAndreas Gohr protected function logSession(): void 1874a163f50SAndreas Gohr { 1884a163f50SAndreas Gohr $this->db->exec( 1894a163f50SAndreas Gohr 'INSERT INTO sessions (session, dt, end, uid, user, ua, ua_info, ua_type, ua_ver, os) 1904a163f50SAndreas Gohr VALUES (?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?, ?) 1914a163f50SAndreas Gohr ON CONFLICT (session) DO UPDATE SET 1924a163f50SAndreas Gohr end = CURRENT_TIMESTAMP 1934a163f50SAndreas Gohr WHERE excluded.session = sessions.session 1944a163f50SAndreas Gohr ', 1954a163f50SAndreas Gohr $this->session, 1964a163f50SAndreas Gohr $this->uid, 1974a163f50SAndreas Gohr $this->user, 1984a163f50SAndreas Gohr $this->uaAgent, 1994a163f50SAndreas Gohr $this->uaName, 2004a163f50SAndreas Gohr $this->uaType, 2014a163f50SAndreas Gohr $this->uaVersion, 2024a163f50SAndreas Gohr $this->uaPlatform 203762f4807SAndreas Gohr ); 204762f4807SAndreas Gohr } 205762f4807SAndreas Gohr 206762f4807SAndreas Gohr /** 2074a163f50SAndreas Gohr * Log all groups for the user 208762f4807SAndreas Gohr * 2094a163f50SAndreas Gohr * @todo maybe this should be done only once per session? 210762f4807SAndreas Gohr */ 2114a163f50SAndreas Gohr protected function logGroups(): void 212762f4807SAndreas Gohr { 2134a163f50SAndreas Gohr global $USERINFO; 214762f4807SAndreas Gohr 2154a163f50SAndreas Gohr if (!$this->user) return; 2164a163f50SAndreas Gohr if (!isset($USERINFO['grps'])) return; 2174a163f50SAndreas Gohr if (!is_array($USERINFO['grps'])) return; 2184a163f50SAndreas Gohr $groups = $USERINFO['grps']; 219fced2f86SAnna Dabrowska 2204a163f50SAndreas Gohr $this->db->exec('DELETE FROM groups WHERE user = ?', $this->user); 221762f4807SAndreas Gohr 222*02aa9b73SAndreas Gohr $placeholders = implode(',', array_fill(0, count($groups), '(?, ?)')); 223762f4807SAndreas Gohr $params = []; 2244a163f50SAndreas Gohr $sql = "INSERT INTO groups (`user`, `group`) VALUES $placeholders"; 225762f4807SAndreas Gohr foreach ($groups as $group) { 2264a163f50SAndreas Gohr $params[] = $this->user; 227762f4807SAndreas Gohr $params[] = $group; 228762f4807SAndreas Gohr } 229762f4807SAndreas Gohr $this->db->exec($sql, $params); 230762f4807SAndreas Gohr } 231762f4807SAndreas Gohr 232762f4807SAndreas Gohr /** 2334a163f50SAndreas Gohr * Log email domain 2340b8b7abaSAnna Dabrowska * 2354a163f50SAndreas Gohr * @todo maybe this should be done only once per session? 2360b8b7abaSAnna Dabrowska */ 2374a163f50SAndreas Gohr protected function logDomain(): void 2380b8b7abaSAnna Dabrowska { 2394a163f50SAndreas Gohr global $USERINFO; 2404a163f50SAndreas Gohr if (!$this->user) return; 2414a163f50SAndreas Gohr if (!isset($USERINFO['mail'])) return; 2424a163f50SAndreas Gohr $mail = $USERINFO['mail']; 2430b8b7abaSAnna Dabrowska 2440b8b7abaSAnna Dabrowska $pos = strrpos($mail, '@'); 2450b8b7abaSAnna Dabrowska if (!$pos) return; 2460b8b7abaSAnna Dabrowska $domain = substr($mail, $pos + 1); 2470b8b7abaSAnna Dabrowska if (empty($domain)) return; 2480b8b7abaSAnna Dabrowska 2494a163f50SAndreas Gohr $sql = 'UPDATE users SET domain = ? WHERE user = ?'; 2504a163f50SAndreas Gohr $this->db->exec($sql, [$domain, $this->user]); 2510b8b7abaSAnna Dabrowska } 2520b8b7abaSAnna Dabrowska 2534a163f50SAndreas Gohr // endregion 2544a163f50SAndreas Gohr // region internal loggers called by the dispatchers 2554a163f50SAndreas Gohr 2560b8b7abaSAnna Dabrowska /** 2574a163f50SAndreas Gohr * Log the given referer URL 258762f4807SAndreas Gohr * 2594a163f50SAndreas Gohr * @param $referer 2604a163f50SAndreas Gohr * @return int|null The referer ID or null if no referer was given 261762f4807SAndreas Gohr */ 2624a163f50SAndreas Gohr public function logReferer($referer): ?int 263762f4807SAndreas Gohr { 2644a163f50SAndreas Gohr if (!$referer) return null; 265762f4807SAndreas Gohr 2664a163f50SAndreas Gohr // FIXME we could check against a blacklist here 267762f4807SAndreas Gohr 2684a163f50SAndreas Gohr $se = new SearchEngines($referer); 2694a163f50SAndreas Gohr $type = $se->isSearchEngine() ? 'search' : 'external'; 270762f4807SAndreas Gohr 271*02aa9b73SAndreas Gohr $sql = 'INSERT OR IGNORE INTO referers (url, type, dt) VALUES (?, ?, CURRENT_TIMESTAMP)'; 272*02aa9b73SAndreas Gohr return $this->db->exec($sql, [$referer, $type]); // returns ID even if the insert was ignored 273762f4807SAndreas Gohr } 274762f4807SAndreas Gohr 275762f4807SAndreas Gohr /** 276762f4807SAndreas Gohr * Resolve IP to country/city and store in database 277762f4807SAndreas Gohr * 2784a163f50SAndreas Gohr * @return string The IP address as stored 279762f4807SAndreas Gohr */ 2804a163f50SAndreas Gohr public function logIp(): string 281762f4807SAndreas Gohr { 2824a163f50SAndreas Gohr $ip = clientIP(true); 2834a163f50SAndreas Gohr $hash = $ip; // @todo we could anonymize here 2844a163f50SAndreas Gohr 285762f4807SAndreas Gohr // check if IP already known and up-to-date 286762f4807SAndreas Gohr $result = $this->db->queryValue( 287762f4807SAndreas Gohr "SELECT ip 288762f4807SAndreas Gohr FROM iplocation 289762f4807SAndreas Gohr WHERE ip = ? 290762f4807SAndreas Gohr AND lastupd > date('now', '-30 days')", 2914a163f50SAndreas Gohr $hash 292762f4807SAndreas Gohr ); 2934a163f50SAndreas Gohr if ($result) return $hash; // already known and up-to-date 294762f4807SAndreas Gohr 295c7cad24dSAndreas Gohr $http = $this->httpClient ?: new DokuHTTPClient(); 2964a163f50SAndreas Gohr $http->timeout = 7; 297762f4807SAndreas Gohr $json = $http->get('http://ip-api.com/json/' . $ip); // yes, it's HTTP only 298762f4807SAndreas Gohr 2994a163f50SAndreas Gohr if (!$json) { 3004a163f50SAndreas Gohr \dokuwiki\Logger::error('Statistics Plugin - Failed talk to ip-api.com.'); 3014a163f50SAndreas Gohr return $hash; 3024a163f50SAndreas Gohr } 303762f4807SAndreas Gohr try { 304762f4807SAndreas Gohr $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); 305762f4807SAndreas Gohr } catch (\JsonException $e) { 3064a163f50SAndreas Gohr \dokuwiki\Logger::error('Statistics Plugin - Failed to decode JSON from ip-api.com.', $e); 3074a163f50SAndreas Gohr return $hash; 308762f4807SAndreas Gohr } 309*02aa9b73SAndreas Gohr if (!isset($data['status'])) { 310*02aa9b73SAndreas Gohr \dokuwiki\Logger::error('Statistics Plugin - Invalid ip-api.com result' . $ip, $data); 3114a163f50SAndreas Gohr return $hash; 312*02aa9b73SAndreas Gohr }; 313*02aa9b73SAndreas Gohr 314*02aa9b73SAndreas Gohr // we do not check for 'success' status here. when the API can't resolve the IP we still log it 315*02aa9b73SAndreas Gohr // without location data, so we won't re-query it in the next 30 days. 316762f4807SAndreas Gohr 3174a163f50SAndreas 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 )', 3244a163f50SAndreas Gohr $hash, 325*02aa9b73SAndreas Gohr $data['country'] ?? '', 326*02aa9b73SAndreas Gohr $data['countryCode'] ?? '', 327*02aa9b73SAndreas Gohr $data['city'] ?? '', 3282adee4c6SAndreas Gohr $host 329762f4807SAndreas Gohr ); 3304a163f50SAndreas Gohr 3314a163f50SAndreas Gohr return $hash; 3324a163f50SAndreas Gohr } 3334a163f50SAndreas Gohr 3344a163f50SAndreas Gohr // endregion 3354a163f50SAndreas Gohr // region log dispatchers 3364a163f50SAndreas Gohr 3374a163f50SAndreas Gohr public function logPageView(): void 3384a163f50SAndreas Gohr { 3394a163f50SAndreas Gohr global $INPUT; 3404a163f50SAndreas Gohr 3414a163f50SAndreas Gohr if (!$INPUT->str('p')) return; 3424a163f50SAndreas Gohr 3434a163f50SAndreas Gohr 3444a163f50SAndreas Gohr $referer = $INPUT->filter('trim')->str('r'); 3454a163f50SAndreas Gohr $ip = $this->logIp(); // resolve the IP address 3464a163f50SAndreas Gohr 3474a163f50SAndreas Gohr $data = [ 3484a163f50SAndreas Gohr 'page' => $INPUT->filter('cleanID')->str('p'), 3494a163f50SAndreas Gohr 'ip' => $ip, 3504a163f50SAndreas Gohr 'ref_id' => $this->logReferer($referer), 3514a163f50SAndreas Gohr 'sx' => $INPUT->int('sx'), 3524a163f50SAndreas Gohr 'sy' => $INPUT->int('sy'), 3534a163f50SAndreas Gohr 'vx' => $INPUT->int('vx'), 3544a163f50SAndreas Gohr 'vy' => $INPUT->int('vy'), 3554a163f50SAndreas Gohr 'session' => $this->session, 3564a163f50SAndreas Gohr ]; 3574a163f50SAndreas Gohr 3584a163f50SAndreas Gohr $this->db->exec(' 3594a163f50SAndreas Gohr INSERT INTO pageviews ( 3604a163f50SAndreas Gohr dt, page, ip, ref_id, screen_x, screen_y, view_x, view_y, session 3614a163f50SAndreas Gohr ) VALUES ( 3624a163f50SAndreas Gohr CURRENT_TIMESTAMP, :page, :ip, :ref_id, :sx, :sy, :vx, :vy, :session 3634a163f50SAndreas Gohr ) 3644a163f50SAndreas Gohr ', 3654a163f50SAndreas Gohr $data 3664a163f50SAndreas 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 3804a163f50SAndreas Gohr $link = $INPUT->filter('trim')->str('ol'); 3814a163f50SAndreas Gohr $session = $this->session; 3824a163f50SAndreas Gohr $page = $INPUT->filter('cleanID')->str('p'); 383762f4807SAndreas Gohr 384762f4807SAndreas Gohr $this->db->exec( 385762f4807SAndreas Gohr 'INSERT INTO outlinks ( 3864a163f50SAndreas 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 4124a163f50SAndreas Gohr $data = [ 4134a163f50SAndreas Gohr 'media' => cleanID($media), 4144a163f50SAndreas Gohr 'ip' => $this->logIp(), // resolve the IP address 4154a163f50SAndreas Gohr 'session' => $this->session, 4164a163f50SAndreas Gohr 'size' => $size, 4174a163f50SAndreas Gohr 'mime1' => $mime1, 4184a163f50SAndreas Gohr 'mime2' => $mime2, 4194a163f50SAndreas Gohr 'inline' => $inline, 4204a163f50SAndreas Gohr ]; 4214a163f50SAndreas Gohr 4224a163f50SAndreas Gohr $this->db->exec(' 4234a163f50SAndreas Gohr INSERT INTO media ( dt, media, ip, session, size, mime1, mime2, inline ) 4244a163f50SAndreas Gohr VALUES (CURRENT_TIMESTAMP, :media, :ip, :session, :size, :mime1, :mime2, :inline) 4254a163f50SAndreas Gohr ', 4264a163f50SAndreas Gohr $data 427762f4807SAndreas Gohr ); 428762f4807SAndreas Gohr } 429762f4807SAndreas Gohr 430762f4807SAndreas Gohr /** 431762f4807SAndreas Gohr * Log page edits 432762f4807SAndreas Gohr * 4334a163f50SAndreas Gohr * called from action.php 4344a163f50SAndreas 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 { 4404a163f50SAndreas Gohr $data = [ 4414a163f50SAndreas Gohr 'page' => cleanID($page), 4424a163f50SAndreas Gohr 'type' => $type, 4434a163f50SAndreas Gohr 'ip' => $this->logIp(), // resolve the IP address 4444a163f50SAndreas Gohr 'session' => $this->session 4454a163f50SAndreas Gohr ]; 446762f4807SAndreas Gohr 4470b8b7abaSAnna Dabrowska $editId = $this->db->exec( 448762f4807SAndreas Gohr 'INSERT INTO edits ( 4494a163f50SAndreas Gohr dt, page, type, ip, session 450762f4807SAndreas Gohr ) VALUES ( 4514a163f50SAndreas Gohr CURRENT_TIMESTAMP, :page, :type, :ip, :session 452762f4807SAndreas Gohr )', 4534a163f50SAndreas 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) 4624a163f50SAndreas 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); 4714a163f50SAndreas Gohr $session = $this->session; 472762f4807SAndreas Gohr 473762f4807SAndreas Gohr $this->db->exec( 474762f4807SAndreas Gohr 'INSERT INTO logins ( 4754a163f50SAndreas 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 /** 488*02aa9b73SAndreas Gohr * Log search data to the search related tables 489*02aa9b73SAndreas Gohr * 490*02aa9b73SAndreas Gohr * @param string $query The search query 491*02aa9b73SAndreas Gohr * @param string[] $words The query split into words 492*02aa9b73SAndreas Gohr */ 493*02aa9b73SAndreas Gohr public function logSearch(string $query, array $words): void 494*02aa9b73SAndreas Gohr { 495*02aa9b73SAndreas Gohr if(!$query) return; 496*02aa9b73SAndreas Gohr 497*02aa9b73SAndreas Gohr $sid = $this->db->exec( 498*02aa9b73SAndreas Gohr 'INSERT INTO search (dt, ip, session, query) VALUES (CURRENT_TIMESTAMP, ?, ? , ?)', 499*02aa9b73SAndreas Gohr $this->logIp(), // resolve the IP address 500*02aa9b73SAndreas Gohr $this->session, 501*02aa9b73SAndreas Gohr $query, 502*02aa9b73SAndreas Gohr ); 503*02aa9b73SAndreas Gohr 504*02aa9b73SAndreas Gohr foreach ($words as $word) { 505*02aa9b73SAndreas Gohr if (!$word) continue; 506*02aa9b73SAndreas Gohr $this->db->exec( 507*02aa9b73SAndreas Gohr 'INSERT INTO searchwords (sid, word) VALUES (?, ?)', 508*02aa9b73SAndreas Gohr $sid, 509*02aa9b73SAndreas Gohr $word 510*02aa9b73SAndreas Gohr ); 511*02aa9b73SAndreas Gohr } 512*02aa9b73SAndreas Gohr } 513*02aa9b73SAndreas Gohr 514*02aa9b73SAndreas Gohr /** 515762f4807SAndreas Gohr * Log the current page count and size as today's history entry 516762f4807SAndreas Gohr */ 517762f4807SAndreas Gohr public function logHistoryPages(): void 518762f4807SAndreas Gohr { 519762f4807SAndreas Gohr global $conf; 520762f4807SAndreas Gohr 521762f4807SAndreas Gohr // use the popularity plugin's search method to find the wanted data 522762f4807SAndreas Gohr /** @var helper_plugin_popularity $pop */ 523762f4807SAndreas Gohr $pop = plugin_load('helper', 'popularity'); 524b188870fSAndreas Gohr $list = $this->initEmptySearchList(); 525762f4807SAndreas Gohr search($list, $conf['datadir'], [$pop, 'searchCountCallback'], ['all' => false], ''); 526762f4807SAndreas Gohr $page_count = $list['file_count']; 527762f4807SAndreas Gohr $page_size = $list['file_size']; 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_count', 5362adee4c6SAndreas Gohr $page_count 537762f4807SAndreas Gohr ); 538762f4807SAndreas Gohr $this->db->exec( 539762f4807SAndreas Gohr 'INSERT OR REPLACE INTO history ( 540762f4807SAndreas Gohr info, value, dt 541762f4807SAndreas Gohr ) VALUES ( 542483101d3SAndreas Gohr ?, ?, CURRENT_TIMESTAMP 543762f4807SAndreas Gohr )', 5442adee4c6SAndreas Gohr 'page_size', 5452adee4c6SAndreas Gohr $page_size 546762f4807SAndreas Gohr ); 547762f4807SAndreas Gohr } 548762f4807SAndreas Gohr 549762f4807SAndreas Gohr /** 550762f4807SAndreas Gohr * Log the current media count and size as today's history entry 551762f4807SAndreas Gohr */ 552762f4807SAndreas Gohr public function logHistoryMedia(): void 553762f4807SAndreas Gohr { 554762f4807SAndreas Gohr global $conf; 555762f4807SAndreas Gohr 556762f4807SAndreas Gohr // use the popularity plugin's search method to find the wanted data 557762f4807SAndreas Gohr /** @var helper_plugin_popularity $pop */ 558762f4807SAndreas Gohr $pop = plugin_load('helper', 'popularity'); 559b188870fSAndreas Gohr $list = $this->initEmptySearchList(); 560762f4807SAndreas Gohr search($list, $conf['mediadir'], [$pop, 'searchCountCallback'], ['all' => true], ''); 561762f4807SAndreas Gohr $media_count = $list['file_count']; 562762f4807SAndreas Gohr $media_size = $list['file_size']; 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_count', 5712adee4c6SAndreas Gohr $media_count 572762f4807SAndreas Gohr ); 573762f4807SAndreas Gohr $this->db->exec( 574762f4807SAndreas Gohr 'INSERT OR REPLACE INTO history ( 575762f4807SAndreas Gohr info, value, dt 576762f4807SAndreas Gohr ) VALUES ( 577483101d3SAndreas Gohr ?, ?, CURRENT_TIMESTAMP 578762f4807SAndreas Gohr )', 5792adee4c6SAndreas Gohr 'media_size', 5802adee4c6SAndreas Gohr $media_size 581762f4807SAndreas Gohr ); 582762f4807SAndreas Gohr } 583b188870fSAndreas Gohr 5844a163f50SAndreas Gohr // endregion 5854a163f50SAndreas Gohr 586b188870fSAndreas Gohr /** 587b188870fSAndreas Gohr * @todo can be dropped in favor of helper_plugin_popularity::initEmptySearchList() once it's public 588b188870fSAndreas Gohr * @return array 589b188870fSAndreas Gohr */ 590b188870fSAndreas Gohr protected function initEmptySearchList() 591b188870fSAndreas Gohr { 592b188870fSAndreas Gohr return array_fill_keys([ 593b188870fSAndreas Gohr 'file_count', 594b188870fSAndreas Gohr 'file_size', 595b188870fSAndreas Gohr 'file_max', 596b188870fSAndreas Gohr 'file_min', 597b188870fSAndreas Gohr 'dir_count', 598b188870fSAndreas Gohr 'dir_nest', 599b188870fSAndreas Gohr 'file_oldest' 600b188870fSAndreas Gohr ], 0); 601b188870fSAndreas Gohr } 602762f4807SAndreas Gohr} 603