1762f4807SAndreas Gohr<?php 2762f4807SAndreas Gohr 3762f4807SAndreas Gohrnamespace dokuwiki\plugin\statistics; 4762f4807SAndreas Gohr 5762f4807SAndreas Gohruse DeviceDetector\DeviceDetector; 6762f4807SAndreas Gohruse DeviceDetector\Parser\Client\Browser; 7762f4807SAndreas Gohruse DeviceDetector\Parser\Device\AbstractDeviceParser; 8762f4807SAndreas Gohruse DeviceDetector\Parser\OperatingSystem; 9762f4807SAndreas Gohruse dokuwiki\HTTP\DokuHTTPClient; 10762f4807SAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB; 11762f4807SAndreas Gohruse dokuwiki\Utf8\Clean; 12762f4807SAndreas Gohruse helper_plugin_popularity; 13762f4807SAndreas Gohruse helper_plugin_statistics; 14762f4807SAndreas Gohr 15762f4807SAndreas Gohrclass Logger 16762f4807SAndreas Gohr{ 17762f4807SAndreas Gohr /** @var helper_plugin_statistics The statistics helper plugin instance */ 18762f4807SAndreas Gohr protected helper_plugin_statistics $hlp; 19762f4807SAndreas Gohr 20762f4807SAndreas Gohr /** @var SQLiteDB The SQLite database instance */ 21762f4807SAndreas Gohr protected SQLiteDB $db; 22762f4807SAndreas Gohr 23762f4807SAndreas Gohr /** @var string The full user agent string */ 24762f4807SAndreas Gohr protected string $uaAgent; 25762f4807SAndreas Gohr 26762f4807SAndreas Gohr /** @var string The type of user agent (browser, robot, feedreader) */ 27762f4807SAndreas Gohr protected string $uaType = 'browser'; 28762f4807SAndreas Gohr 29762f4807SAndreas Gohr /** @var string The browser/client name */ 30762f4807SAndreas Gohr protected string $uaName; 31762f4807SAndreas Gohr 32762f4807SAndreas Gohr /** @var string The browser/client version */ 33762f4807SAndreas Gohr protected string $uaVersion; 34762f4807SAndreas Gohr 35762f4807SAndreas Gohr /** @var string The operating system/platform */ 36762f4807SAndreas Gohr protected string $uaPlatform; 37762f4807SAndreas Gohr 38762f4807SAndreas Gohr /** @var string The unique user identifier */ 39762f4807SAndreas Gohr protected string $uid; 40762f4807SAndreas Gohr 41c7cad24dSAndreas Gohr /** @var DokuHTTPClient|null The HTTP client instance for testing */ 42c7cad24dSAndreas Gohr protected ?DokuHTTPClient $httpClient = null; 43c7cad24dSAndreas Gohr 44762f4807SAndreas Gohr 45762f4807SAndreas Gohr /** 46762f4807SAndreas Gohr * Constructor 47762f4807SAndreas Gohr * 48762f4807SAndreas Gohr * Parses browser info and set internal vars 49762f4807SAndreas Gohr */ 50c7cad24dSAndreas Gohr public function __construct(helper_plugin_statistics $hlp, ?DokuHTTPClient $httpClient = null) 51762f4807SAndreas Gohr { 52762f4807SAndreas Gohr global $INPUT; 53762f4807SAndreas Gohr 54762f4807SAndreas Gohr $this->hlp = $hlp; 55762f4807SAndreas Gohr $this->db = $this->hlp->getDB(); 56c7cad24dSAndreas Gohr $this->httpClient = $httpClient; 57762f4807SAndreas Gohr 58762f4807SAndreas Gohr $ua = trim($INPUT->server->str('HTTP_USER_AGENT')); 59762f4807SAndreas Gohr 60762f4807SAndreas Gohr AbstractDeviceParser::setVersionTruncation(AbstractDeviceParser::VERSION_TRUNCATION_MAJOR); 61762f4807SAndreas Gohr $dd = new DeviceDetector($ua); // FIXME we could use client hints, but need to add headers 62762f4807SAndreas Gohr $dd->discardBotInformation(); 63762f4807SAndreas Gohr $dd->parse(); 64762f4807SAndreas Gohr 6500f786d8SAndreas Gohr if ($dd->isFeedReader()) { 6600f786d8SAndreas Gohr $this->uaType = 'feedreader'; 6700f786d8SAndreas Gohr } elseif ($dd->isBot()) { 68762f4807SAndreas Gohr $this->uaType = 'robot'; 69762f4807SAndreas Gohr // for now ignore bots 70762f4807SAndreas Gohr throw new \RuntimeException('Bot detected, not logging'); 71762f4807SAndreas Gohr } 72762f4807SAndreas Gohr 73762f4807SAndreas Gohr $this->uaAgent = $ua; 7405786d83SAndreas Gohr $this->uaName = Browser::getBrowserFamily($dd->getClient('name')) ?: 'Unknown'; 7500f786d8SAndreas Gohr $this->uaVersion = $dd->getClient('version') ?: '0'; 7605786d83SAndreas Gohr $this->uaPlatform = OperatingSystem::getOsFamily($dd->getOs('name')) ?: 'Unknown'; 77762f4807SAndreas Gohr $this->uid = $this->getUID(); 78762f4807SAndreas Gohr 79762f4807SAndreas Gohr 80762f4807SAndreas Gohr $this->logLastseen(); 81762f4807SAndreas Gohr } 82762f4807SAndreas Gohr 83762f4807SAndreas Gohr /** 84762f4807SAndreas Gohr * Should be called before logging 85762f4807SAndreas Gohr * 86762f4807SAndreas Gohr * This starts a transaction, so all logging is done in one go 87762f4807SAndreas Gohr */ 88762f4807SAndreas Gohr public function begin(): void 89762f4807SAndreas Gohr { 90762f4807SAndreas Gohr $this->hlp->getDB()->getPdo()->beginTransaction(); 91762f4807SAndreas Gohr } 92762f4807SAndreas Gohr 93762f4807SAndreas Gohr /** 94762f4807SAndreas Gohr * Should be called after logging 95762f4807SAndreas Gohr * 96762f4807SAndreas Gohr * This commits the transaction started in begin() 97762f4807SAndreas Gohr */ 98762f4807SAndreas Gohr public function end(): void 99762f4807SAndreas Gohr { 100762f4807SAndreas Gohr $this->hlp->getDB()->getPdo()->commit(); 101762f4807SAndreas Gohr } 102762f4807SAndreas Gohr 103762f4807SAndreas Gohr /** 104762f4807SAndreas Gohr * Get the unique user ID 105762f4807SAndreas Gohr * 106762f4807SAndreas Gohr * @return string The unique user identifier 107762f4807SAndreas Gohr */ 108762f4807SAndreas Gohr protected function getUID(): string 109762f4807SAndreas Gohr { 110762f4807SAndreas Gohr global $INPUT; 111762f4807SAndreas Gohr 112762f4807SAndreas Gohr $uid = $INPUT->str('uid'); 113762f4807SAndreas Gohr if (!$uid) $uid = get_doku_pref('plgstats', false); 114762f4807SAndreas Gohr if (!$uid) $uid = session_id(); 115b188870fSAndreas Gohr set_doku_pref('plgstats', $uid); 116762f4807SAndreas Gohr return $uid; 117762f4807SAndreas Gohr } 118762f4807SAndreas Gohr 119762f4807SAndreas Gohr /** 120762f4807SAndreas Gohr * Return the user's session ID 121762f4807SAndreas Gohr * 122762f4807SAndreas Gohr * This is usually our own managed session, not a PHP session (only in fallback) 123762f4807SAndreas Gohr * 124762f4807SAndreas Gohr * @return string The session identifier 125762f4807SAndreas Gohr */ 126762f4807SAndreas Gohr protected function getSession(): string 127762f4807SAndreas Gohr { 128762f4807SAndreas Gohr global $INPUT; 129762f4807SAndreas Gohr 130762f4807SAndreas Gohr $ses = $INPUT->str('ses'); 131762f4807SAndreas Gohr if (!$ses) $ses = get_doku_pref('plgstatsses', false); 132762f4807SAndreas Gohr if (!$ses) $ses = session_id(); 133b188870fSAndreas Gohr set_doku_pref('plgstatsses', $ses); 134762f4807SAndreas Gohr return $ses; 135762f4807SAndreas Gohr } 136762f4807SAndreas Gohr 137762f4807SAndreas Gohr /** 138762f4807SAndreas Gohr * Log that we've seen the user (authenticated only) 139762f4807SAndreas Gohr */ 140762f4807SAndreas Gohr public function logLastseen(): void 141762f4807SAndreas Gohr { 142762f4807SAndreas Gohr global $INPUT; 143762f4807SAndreas Gohr 144762f4807SAndreas Gohr if (empty($INPUT->server->str('REMOTE_USER'))) return; 145762f4807SAndreas Gohr 146762f4807SAndreas Gohr $this->db->exec( 147762f4807SAndreas Gohr 'REPLACE INTO lastseen (user, dt) VALUES (?, CURRENT_TIMESTAMP)', 148762f4807SAndreas Gohr $INPUT->server->str('REMOTE_USER'), 149762f4807SAndreas Gohr ); 150762f4807SAndreas Gohr } 151762f4807SAndreas Gohr 152762f4807SAndreas Gohr /** 153762f4807SAndreas Gohr * Log actions by groups 154762f4807SAndreas Gohr * 1550b8b7abaSAnna Dabrowska * @param int $pid Id of access data row (foreign key) 156762f4807SAndreas Gohr * @param string $type The type of access to log ('view','edit') 157762f4807SAndreas Gohr * @param array $groups The groups to log 158762f4807SAndreas Gohr */ 1590b8b7abaSAnna Dabrowska public function logGroups(int $pid, string $type, array $groups): void 160762f4807SAndreas Gohr { 161*2adee4c6SAndreas Gohr if ($groups === [] || !$pid) return; 162762f4807SAndreas Gohr 163483101d3SAndreas Gohr $toLog = (array)$this->hlp->getConf('loggroups'); 164fced2f86SAnna Dabrowska 165fced2f86SAnna Dabrowska // if specific groups are configured, limit logging to them only 166*2adee4c6SAndreas Gohr $groups = empty(array_filter($toLog)) ? $groups : array_intersect($groups, $toLog); 167483101d3SAndreas Gohr if (!$groups) return; 168762f4807SAndreas Gohr 169*2adee4c6SAndreas Gohr $placeholders = implode(',', array_fill(0, count($groups), '(?, ?, ?)')); 170762f4807SAndreas Gohr $params = []; 1710b8b7abaSAnna Dabrowska $sql = "INSERT INTO groups (`pid`, `type`, `group`) VALUES $placeholders"; 172762f4807SAndreas Gohr foreach ($groups as $group) { 1730b8b7abaSAnna Dabrowska $params[] = $pid; 174762f4807SAndreas Gohr $params[] = $type; 175762f4807SAndreas Gohr $params[] = $group; 176762f4807SAndreas Gohr } 177762f4807SAndreas Gohr $sql = rtrim($sql, ','); 178762f4807SAndreas Gohr $this->db->exec($sql, $params); 179762f4807SAndreas Gohr } 180762f4807SAndreas Gohr 181762f4807SAndreas Gohr /** 1820b8b7abaSAnna Dabrowska * Log email domain, skip logging if no domain is found 1830b8b7abaSAnna Dabrowska * 1840b8b7abaSAnna Dabrowska * @param int $pid Id of access data row (foreign key) 1850b8b7abaSAnna Dabrowska * @param string $type The type of access to log ('view','edit') 1860b8b7abaSAnna Dabrowska * @param string $mail The email to extract the domain from 1870b8b7abaSAnna Dabrowska */ 1880b8b7abaSAnna Dabrowska public function logDomain(int $pid, string $type, string $mail): void 1890b8b7abaSAnna Dabrowska { 1900b8b7abaSAnna Dabrowska if (!$pid) return; 1910b8b7abaSAnna Dabrowska 1920b8b7abaSAnna Dabrowska $pos = strrpos($mail, '@'); 1930b8b7abaSAnna Dabrowska if (!$pos) return; 1940b8b7abaSAnna Dabrowska $domain = substr($mail, $pos + 1); 1950b8b7abaSAnna Dabrowska if (empty($domain)) return; 1960b8b7abaSAnna Dabrowska 1970b8b7abaSAnna Dabrowska $sql = "INSERT INTO domain (`pid`, `type`, `domain`) VALUES (?, ?, ?)"; 1980b8b7abaSAnna Dabrowska $this->db->exec($sql, [$pid, $type, $domain]); 1990b8b7abaSAnna Dabrowska } 2000b8b7abaSAnna Dabrowska 2010b8b7abaSAnna Dabrowska /** 202762f4807SAndreas Gohr * Log external search queries 203762f4807SAndreas Gohr * 204762f4807SAndreas Gohr * Will not write anything if the referer isn't a search engine 205762f4807SAndreas Gohr * 206762f4807SAndreas Gohr * @param string $referer The HTTP referer URL 207762f4807SAndreas Gohr * @param string $type Reference to the type variable that will be modified 208762f4807SAndreas Gohr */ 209762f4807SAndreas Gohr public function logExternalSearch(string $referer, string &$type): void 210762f4807SAndreas Gohr { 211762f4807SAndreas Gohr global $INPUT; 212762f4807SAndreas Gohr 213762f4807SAndreas Gohr $searchEngine = new SearchEngines($referer); 214762f4807SAndreas Gohr 215762f4807SAndreas Gohr if (!$searchEngine->isSearchEngine()) { 216762f4807SAndreas Gohr return; // not a search engine 217762f4807SAndreas Gohr } 218762f4807SAndreas Gohr 219762f4807SAndreas Gohr $type = 'search'; 220762f4807SAndreas Gohr $query = $searchEngine->getQuery(); 221762f4807SAndreas Gohr 222762f4807SAndreas Gohr // log it! 223d40a6291SAnna Dabrowska $words = []; 224d40a6291SAnna Dabrowska if ($query) { 225762f4807SAndreas Gohr $words = explode(' ', Clean::stripspecials($query, ' ', '\._\-:\*')); 226d40a6291SAnna Dabrowska } 227d40a6291SAnna Dabrowska $this->logSearch($INPUT->str('p'), $searchEngine->getEngine(), $query, $words); 228762f4807SAndreas Gohr } 229762f4807SAndreas Gohr 230762f4807SAndreas Gohr /** 231762f4807SAndreas Gohr * Log search data to the search related tables 232762f4807SAndreas Gohr * 233762f4807SAndreas Gohr * @param string $page The page being searched from 234762f4807SAndreas Gohr * @param string $engine The search engine name 235d40a6291SAnna Dabrowska * @param string|null $query The search query 236d40a6291SAnna Dabrowska * @param array|null $words Array of search words 237762f4807SAndreas Gohr */ 238d40a6291SAnna Dabrowska public function logSearch(string $page, string $engine, ?string $query, ?array $words): void 239762f4807SAndreas Gohr { 240762f4807SAndreas Gohr $sid = $this->db->exec( 241762f4807SAndreas Gohr 'INSERT INTO search (dt, page, query, engine) VALUES (CURRENT_TIMESTAMP, ?, ?, ?)', 242*2adee4c6SAndreas Gohr $page, 243*2adee4c6SAndreas Gohr $query ?? '', 244*2adee4c6SAndreas Gohr $engine 245762f4807SAndreas Gohr ); 246762f4807SAndreas Gohr if (!$sid) return; 247762f4807SAndreas Gohr 248762f4807SAndreas Gohr foreach ($words as $word) { 249762f4807SAndreas Gohr if (!$word) continue; 250762f4807SAndreas Gohr $this->db->exec( 251762f4807SAndreas Gohr 'INSERT INTO searchwords (sid, word) VALUES (?, ?)', 252*2adee4c6SAndreas Gohr $sid, 253*2adee4c6SAndreas Gohr $word 254762f4807SAndreas Gohr ); 255762f4807SAndreas Gohr } 256762f4807SAndreas Gohr } 257762f4807SAndreas Gohr 258762f4807SAndreas Gohr /** 259762f4807SAndreas Gohr * Log that the session was seen 260762f4807SAndreas Gohr * 261762f4807SAndreas Gohr * This is used to calculate the time people spend on the whole site 262762f4807SAndreas Gohr * during their session 263762f4807SAndreas Gohr * 264762f4807SAndreas Gohr * Viewcounts are used for bounce calculation 265762f4807SAndreas Gohr * 266762f4807SAndreas Gohr * @param int $addview set to 1 to count a view 267762f4807SAndreas Gohr */ 268762f4807SAndreas Gohr public function logSession(int $addview = 0): void 269762f4807SAndreas Gohr { 270762f4807SAndreas Gohr // only log browser sessions 271762f4807SAndreas Gohr if ($this->uaType != 'browser') return; 272762f4807SAndreas Gohr 273762f4807SAndreas Gohr $session = $this->getSession(); 274762f4807SAndreas Gohr $this->db->exec( 275762f4807SAndreas Gohr 'INSERT OR REPLACE INTO session ( 276762f4807SAndreas Gohr session, dt, end, views, uid 277762f4807SAndreas Gohr ) VALUES ( 278762f4807SAndreas Gohr ?, 279762f4807SAndreas Gohr CURRENT_TIMESTAMP, 280762f4807SAndreas Gohr CURRENT_TIMESTAMP, 281762f4807SAndreas Gohr COALESCE((SELECT views FROM session WHERE session = ?) + ?, ?), 282762f4807SAndreas Gohr ? 283762f4807SAndreas Gohr )', 284*2adee4c6SAndreas Gohr $session, 285*2adee4c6SAndreas Gohr $session, 286*2adee4c6SAndreas Gohr $addview, 287*2adee4c6SAndreas Gohr $addview, 288*2adee4c6SAndreas Gohr $this->uid 289762f4807SAndreas Gohr ); 290762f4807SAndreas Gohr } 291762f4807SAndreas Gohr 292762f4807SAndreas Gohr /** 293762f4807SAndreas Gohr * Resolve IP to country/city and store in database 294762f4807SAndreas Gohr * 295762f4807SAndreas Gohr * @param string $ip The IP address to resolve 296762f4807SAndreas Gohr */ 297762f4807SAndreas Gohr public function logIp(string $ip): void 298762f4807SAndreas Gohr { 299762f4807SAndreas Gohr // check if IP already known and up-to-date 300762f4807SAndreas Gohr $result = $this->db->queryValue( 301762f4807SAndreas Gohr "SELECT ip 302762f4807SAndreas Gohr FROM iplocation 303762f4807SAndreas Gohr WHERE ip = ? 304762f4807SAndreas Gohr AND lastupd > date('now', '-30 days')", 305762f4807SAndreas Gohr $ip 306762f4807SAndreas Gohr ); 307762f4807SAndreas Gohr if ($result) return; 308762f4807SAndreas Gohr 309c7cad24dSAndreas Gohr $http = $this->httpClient ?: new DokuHTTPClient(); 310762f4807SAndreas Gohr $http->timeout = 10; 311762f4807SAndreas Gohr $json = $http->get('http://ip-api.com/json/' . $ip); // yes, it's HTTP only 312762f4807SAndreas Gohr 313762f4807SAndreas Gohr if (!$json) return; // FIXME log error 314762f4807SAndreas Gohr try { 315762f4807SAndreas Gohr $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); 316762f4807SAndreas Gohr } catch (\JsonException $e) { 317762f4807SAndreas Gohr return; // FIXME log error 318762f4807SAndreas Gohr } 319a10aed88SAndreas Gohr if (!isset($data['status']) || $data['status'] !== 'success') { 320a10aed88SAndreas Gohr return; // FIXME log error 321a10aed88SAndreas Gohr } 322762f4807SAndreas Gohr 323762f4807SAndreas Gohr $host = gethostbyaddr($ip); 324762f4807SAndreas Gohr $this->db->exec( 325762f4807SAndreas Gohr 'INSERT OR REPLACE INTO iplocation ( 326762f4807SAndreas Gohr ip, country, code, city, host, lastupd 327762f4807SAndreas Gohr ) VALUES ( 328762f4807SAndreas Gohr ?, ?, ?, ?, ?, CURRENT_TIMESTAMP 329762f4807SAndreas Gohr )', 330*2adee4c6SAndreas Gohr $ip, 331*2adee4c6SAndreas Gohr $data['country'], 332*2adee4c6SAndreas Gohr $data['countryCode'], 333*2adee4c6SAndreas Gohr $data['city'], 334*2adee4c6SAndreas Gohr $host 335762f4807SAndreas Gohr ); 336762f4807SAndreas Gohr } 337762f4807SAndreas Gohr 338762f4807SAndreas Gohr /** 339762f4807SAndreas Gohr * Log a click on an external link 340762f4807SAndreas Gohr * 341762f4807SAndreas Gohr * Called from log.php 342762f4807SAndreas Gohr */ 343762f4807SAndreas Gohr public function logOutgoing(): void 344762f4807SAndreas Gohr { 345762f4807SAndreas Gohr global $INPUT; 346762f4807SAndreas Gohr 347762f4807SAndreas Gohr if (!$INPUT->str('ol')) return; 348762f4807SAndreas Gohr 349762f4807SAndreas Gohr $link = $INPUT->str('ol'); 350762f4807SAndreas Gohr $link_md5 = md5($link); 351762f4807SAndreas Gohr $session = $this->getSession(); 352762f4807SAndreas Gohr $page = $INPUT->str('p'); 353762f4807SAndreas Gohr 354762f4807SAndreas Gohr $this->db->exec( 355762f4807SAndreas Gohr 'INSERT INTO outlinks ( 356762f4807SAndreas Gohr dt, session, page, link_md5, link 357762f4807SAndreas Gohr ) VALUES ( 358762f4807SAndreas Gohr CURRENT_TIMESTAMP, ?, ?, ?, ? 359762f4807SAndreas Gohr )', 360*2adee4c6SAndreas Gohr $session, 361*2adee4c6SAndreas Gohr $page, 362*2adee4c6SAndreas Gohr $link_md5, 363*2adee4c6SAndreas Gohr $link 364762f4807SAndreas Gohr ); 365762f4807SAndreas Gohr } 366762f4807SAndreas Gohr 367762f4807SAndreas Gohr /** 368762f4807SAndreas Gohr * Log a page access 369762f4807SAndreas Gohr * 370762f4807SAndreas Gohr * Called from log.php 371762f4807SAndreas Gohr */ 372762f4807SAndreas Gohr public function logAccess(): void 373762f4807SAndreas Gohr { 374762f4807SAndreas Gohr global $INPUT, $USERINFO; 375762f4807SAndreas Gohr 376762f4807SAndreas Gohr if (!$INPUT->str('p')) return; 377762f4807SAndreas Gohr 378762f4807SAndreas Gohr # FIXME check referer against blacklist and drop logging for bad boys 379762f4807SAndreas Gohr 380762f4807SAndreas Gohr // handle referer 381762f4807SAndreas Gohr $referer = trim($INPUT->str('r')); 382762f4807SAndreas Gohr if ($referer) { 383762f4807SAndreas Gohr $ref = $referer; 384762f4807SAndreas Gohr $ref_md5 = md5($referer); 385762f4807SAndreas Gohr if (str_starts_with($referer, DOKU_URL)) { 386762f4807SAndreas Gohr $ref_type = 'internal'; 387762f4807SAndreas Gohr } else { 388762f4807SAndreas Gohr $ref_type = 'external'; 389762f4807SAndreas Gohr $this->logExternalSearch($referer, $ref_type); 390762f4807SAndreas Gohr } 391762f4807SAndreas Gohr } else { 392762f4807SAndreas Gohr $ref = ''; 393762f4807SAndreas Gohr $ref_md5 = ''; 394762f4807SAndreas Gohr $ref_type = ''; 395762f4807SAndreas Gohr } 396762f4807SAndreas Gohr 397762f4807SAndreas Gohr $page = $INPUT->str('p'); 398762f4807SAndreas Gohr $ip = clientIP(true); 399762f4807SAndreas Gohr $sx = $INPUT->int('sx'); 400762f4807SAndreas Gohr $sy = $INPUT->int('sy'); 401762f4807SAndreas Gohr $vx = $INPUT->int('vx'); 402762f4807SAndreas Gohr $vy = $INPUT->int('vy'); 403762f4807SAndreas Gohr $js = $INPUT->int('js'); 404762f4807SAndreas Gohr $user = $INPUT->server->str('REMOTE_USER'); 405762f4807SAndreas Gohr $session = $this->getSession(); 406762f4807SAndreas Gohr 4070b8b7abaSAnna Dabrowska $accessId = $this->db->exec( 408762f4807SAndreas Gohr 'INSERT INTO access ( 409762f4807SAndreas Gohr dt, page, ip, ua, ua_info, ua_type, ua_ver, os, ref, ref_md5, ref_type, 410762f4807SAndreas Gohr screen_x, screen_y, view_x, view_y, js, user, session, uid 411762f4807SAndreas Gohr ) VALUES ( 412762f4807SAndreas Gohr CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 413762f4807SAndreas Gohr ?, ?, ?, ?, ?, ?, ?, ? 414762f4807SAndreas Gohr )', 415*2adee4c6SAndreas Gohr $page, 416*2adee4c6SAndreas Gohr $ip, 417*2adee4c6SAndreas Gohr $this->uaAgent, 418*2adee4c6SAndreas Gohr $this->uaName, 419*2adee4c6SAndreas Gohr $this->uaType, 420*2adee4c6SAndreas Gohr $this->uaVersion, 421*2adee4c6SAndreas Gohr $this->uaPlatform, 422*2adee4c6SAndreas Gohr $ref, 423*2adee4c6SAndreas Gohr $ref_md5, 424*2adee4c6SAndreas Gohr $ref_type, 425*2adee4c6SAndreas Gohr $sx, 426*2adee4c6SAndreas Gohr $sy, 427*2adee4c6SAndreas Gohr $vx, 428*2adee4c6SAndreas Gohr $vy, 429*2adee4c6SAndreas Gohr $js, 430*2adee4c6SAndreas Gohr $user, 431*2adee4c6SAndreas Gohr $session, 432*2adee4c6SAndreas Gohr $this->uid 433762f4807SAndreas Gohr ); 434762f4807SAndreas Gohr 435762f4807SAndreas Gohr if ($ref_md5) { 436762f4807SAndreas Gohr $this->db->exec( 437762f4807SAndreas Gohr 'INSERT OR IGNORE INTO refseen ( 438762f4807SAndreas Gohr ref_md5, dt 439762f4807SAndreas Gohr ) VALUES ( 440762f4807SAndreas Gohr ?, CURRENT_TIMESTAMP 441762f4807SAndreas Gohr )', 442762f4807SAndreas Gohr $ref_md5 443762f4807SAndreas Gohr ); 444762f4807SAndreas Gohr } 445762f4807SAndreas Gohr 446762f4807SAndreas Gohr // log group access 447762f4807SAndreas Gohr if (isset($USERINFO['grps'])) { 4480b8b7abaSAnna Dabrowska $this->logGroups($accessId, 'view', $USERINFO['grps']); 4490b8b7abaSAnna Dabrowska } 4500b8b7abaSAnna Dabrowska // log email domain 4510b8b7abaSAnna Dabrowska if (!empty($USERINFO['mail'])) { 4520b8b7abaSAnna Dabrowska $this->logDomain($accessId, 'view', $USERINFO['mail']); 453762f4807SAndreas Gohr } 454762f4807SAndreas Gohr 455762f4807SAndreas Gohr // resolve the IP 456762f4807SAndreas Gohr $this->logIp(clientIP(true)); 457762f4807SAndreas Gohr } 458762f4807SAndreas Gohr 459762f4807SAndreas Gohr /** 460762f4807SAndreas Gohr * Log access to a media file 461762f4807SAndreas Gohr * 462762f4807SAndreas Gohr * Called from action.php 463762f4807SAndreas Gohr * 464762f4807SAndreas Gohr * @param string $media The media ID 465762f4807SAndreas Gohr * @param string $mime The media's mime type 466762f4807SAndreas Gohr * @param bool $inline Is this displayed inline? 467762f4807SAndreas Gohr * @param int $size Size of the media file 468762f4807SAndreas Gohr */ 469762f4807SAndreas Gohr public function logMedia(string $media, string $mime, bool $inline, int $size): void 470762f4807SAndreas Gohr { 471762f4807SAndreas Gohr global $INPUT; 472762f4807SAndreas Gohr 473762f4807SAndreas Gohr [$mime1, $mime2] = explode('/', strtolower($mime)); 474762f4807SAndreas Gohr $inline = $inline ? 1 : 0; 475762f4807SAndreas Gohr 476762f4807SAndreas Gohr $ip = clientIP(true); 477762f4807SAndreas Gohr $user = $INPUT->server->str('REMOTE_USER'); 478762f4807SAndreas Gohr $session = $this->getSession(); 479762f4807SAndreas Gohr 480762f4807SAndreas Gohr $this->db->exec( 481762f4807SAndreas Gohr 'INSERT INTO media ( 482762f4807SAndreas Gohr dt, media, ip, ua, ua_info, ua_type, ua_ver, os, user, session, uid, 483762f4807SAndreas Gohr size, mime1, mime2, inline 484762f4807SAndreas Gohr ) VALUES ( 485762f4807SAndreas Gohr CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 486762f4807SAndreas Gohr ?, ?, ?, ? 487762f4807SAndreas Gohr )', 488*2adee4c6SAndreas Gohr $media, 489*2adee4c6SAndreas Gohr $ip, 490*2adee4c6SAndreas Gohr $this->uaAgent, 491*2adee4c6SAndreas Gohr $this->uaName, 492*2adee4c6SAndreas Gohr $this->uaType, 493*2adee4c6SAndreas Gohr $this->uaVersion, 494*2adee4c6SAndreas Gohr $this->uaPlatform, 495*2adee4c6SAndreas Gohr $user, 496*2adee4c6SAndreas Gohr $session, 497*2adee4c6SAndreas Gohr $this->uid, 498*2adee4c6SAndreas Gohr $size, 499*2adee4c6SAndreas Gohr $mime1, 500*2adee4c6SAndreas Gohr $mime2, 501*2adee4c6SAndreas Gohr $inline 502762f4807SAndreas Gohr ); 503762f4807SAndreas Gohr } 504762f4807SAndreas Gohr 505762f4807SAndreas Gohr /** 506762f4807SAndreas Gohr * Log page edits 507762f4807SAndreas Gohr * 508762f4807SAndreas Gohr * @param string $page The page that was edited 509762f4807SAndreas Gohr * @param string $type The type of edit (create, edit, etc.) 510762f4807SAndreas Gohr */ 511762f4807SAndreas Gohr public function logEdit(string $page, string $type): void 512762f4807SAndreas Gohr { 513762f4807SAndreas Gohr global $INPUT, $USERINFO; 514762f4807SAndreas Gohr 515762f4807SAndreas Gohr $ip = clientIP(true); 516762f4807SAndreas Gohr $user = $INPUT->server->str('REMOTE_USER'); 517762f4807SAndreas Gohr $session = $this->getSession(); 518762f4807SAndreas Gohr 5190b8b7abaSAnna Dabrowska $editId = $this->db->exec( 520762f4807SAndreas Gohr 'INSERT INTO edits ( 521762f4807SAndreas Gohr dt, page, type, ip, user, session, uid 522762f4807SAndreas Gohr ) VALUES ( 523762f4807SAndreas Gohr CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ? 524762f4807SAndreas Gohr )', 525*2adee4c6SAndreas Gohr $page, 526*2adee4c6SAndreas Gohr $type, 527*2adee4c6SAndreas Gohr $ip, 528*2adee4c6SAndreas Gohr $user, 529*2adee4c6SAndreas Gohr $session, 530*2adee4c6SAndreas Gohr $this->uid 531762f4807SAndreas Gohr ); 532762f4807SAndreas Gohr 533762f4807SAndreas Gohr // log group access 534762f4807SAndreas Gohr if (isset($USERINFO['grps'])) { 5350b8b7abaSAnna Dabrowska $this->logGroups($editId, 'edit', $USERINFO['grps']); 5360b8b7abaSAnna Dabrowska } 5370b8b7abaSAnna Dabrowska 5380b8b7abaSAnna Dabrowska // log email domain 5390b8b7abaSAnna Dabrowska if (!empty($USERINFO['mail'])) { 5400b8b7abaSAnna Dabrowska $this->logDomain($editId, 'edit', $USERINFO['mail']); 541762f4807SAndreas Gohr } 542762f4807SAndreas Gohr } 543762f4807SAndreas Gohr 544762f4807SAndreas Gohr /** 545762f4807SAndreas Gohr * Log login/logoffs and user creations 546762f4807SAndreas Gohr * 547762f4807SAndreas Gohr * @param string $type The type of login event (login, logout, create) 548762f4807SAndreas Gohr * @param string $user The username (optional, will use current user if empty) 549762f4807SAndreas Gohr */ 550762f4807SAndreas Gohr public function logLogin(string $type, string $user = ''): void 551762f4807SAndreas Gohr { 552762f4807SAndreas Gohr global $INPUT; 553762f4807SAndreas Gohr 554762f4807SAndreas Gohr if (!$user) $user = $INPUT->server->str('REMOTE_USER'); 555762f4807SAndreas Gohr 556762f4807SAndreas Gohr $ip = clientIP(true); 557762f4807SAndreas Gohr $session = $this->getSession(); 558762f4807SAndreas Gohr 559762f4807SAndreas Gohr $this->db->exec( 560762f4807SAndreas Gohr 'INSERT INTO logins ( 561762f4807SAndreas Gohr dt, type, ip, user, session, uid 562762f4807SAndreas Gohr ) VALUES ( 563762f4807SAndreas Gohr CURRENT_TIMESTAMP, ?, ?, ?, ?, ? 564762f4807SAndreas Gohr )', 565*2adee4c6SAndreas Gohr $type, 566*2adee4c6SAndreas Gohr $ip, 567*2adee4c6SAndreas Gohr $user, 568*2adee4c6SAndreas Gohr $session, 569*2adee4c6SAndreas Gohr $this->uid 570762f4807SAndreas Gohr ); 571762f4807SAndreas Gohr } 572762f4807SAndreas Gohr 573762f4807SAndreas Gohr /** 574762f4807SAndreas Gohr * Log the current page count and size as today's history entry 575762f4807SAndreas Gohr */ 576762f4807SAndreas Gohr public function logHistoryPages(): void 577762f4807SAndreas Gohr { 578762f4807SAndreas Gohr global $conf; 579762f4807SAndreas Gohr 580762f4807SAndreas Gohr // use the popularity plugin's search method to find the wanted data 581762f4807SAndreas Gohr /** @var helper_plugin_popularity $pop */ 582762f4807SAndreas Gohr $pop = plugin_load('helper', 'popularity'); 583b188870fSAndreas Gohr $list = $this->initEmptySearchList(); 584762f4807SAndreas Gohr search($list, $conf['datadir'], [$pop, 'searchCountCallback'], ['all' => false], ''); 585762f4807SAndreas Gohr $page_count = $list['file_count']; 586762f4807SAndreas Gohr $page_size = $list['file_size']; 587762f4807SAndreas Gohr 588762f4807SAndreas Gohr $this->db->exec( 589762f4807SAndreas Gohr 'INSERT OR REPLACE INTO history ( 590762f4807SAndreas Gohr info, value, dt 591762f4807SAndreas Gohr ) VALUES ( 592483101d3SAndreas Gohr ?, ?, CURRENT_TIMESTAMP 593762f4807SAndreas Gohr )', 594*2adee4c6SAndreas Gohr 'page_count', 595*2adee4c6SAndreas Gohr $page_count 596762f4807SAndreas Gohr ); 597762f4807SAndreas Gohr $this->db->exec( 598762f4807SAndreas Gohr 'INSERT OR REPLACE INTO history ( 599762f4807SAndreas Gohr info, value, dt 600762f4807SAndreas Gohr ) VALUES ( 601483101d3SAndreas Gohr ?, ?, CURRENT_TIMESTAMP 602762f4807SAndreas Gohr )', 603*2adee4c6SAndreas Gohr 'page_size', 604*2adee4c6SAndreas Gohr $page_size 605762f4807SAndreas Gohr ); 606762f4807SAndreas Gohr } 607762f4807SAndreas Gohr 608762f4807SAndreas Gohr /** 609762f4807SAndreas Gohr * Log the current media count and size as today's history entry 610762f4807SAndreas Gohr */ 611762f4807SAndreas Gohr public function logHistoryMedia(): void 612762f4807SAndreas Gohr { 613762f4807SAndreas Gohr global $conf; 614762f4807SAndreas Gohr 615762f4807SAndreas Gohr // use the popularity plugin's search method to find the wanted data 616762f4807SAndreas Gohr /** @var helper_plugin_popularity $pop */ 617762f4807SAndreas Gohr $pop = plugin_load('helper', 'popularity'); 618b188870fSAndreas Gohr $list = $this->initEmptySearchList(); 619762f4807SAndreas Gohr search($list, $conf['mediadir'], [$pop, 'searchCountCallback'], ['all' => true], ''); 620762f4807SAndreas Gohr $media_count = $list['file_count']; 621762f4807SAndreas Gohr $media_size = $list['file_size']; 622762f4807SAndreas Gohr 623762f4807SAndreas Gohr $this->db->exec( 624762f4807SAndreas Gohr 'INSERT OR REPLACE INTO history ( 625762f4807SAndreas Gohr info, value, dt 626762f4807SAndreas Gohr ) VALUES ( 627483101d3SAndreas Gohr ?, ?, CURRENT_TIMESTAMP 628762f4807SAndreas Gohr )', 629*2adee4c6SAndreas Gohr 'media_count', 630*2adee4c6SAndreas Gohr $media_count 631762f4807SAndreas Gohr ); 632762f4807SAndreas Gohr $this->db->exec( 633762f4807SAndreas Gohr 'INSERT OR REPLACE INTO history ( 634762f4807SAndreas Gohr info, value, dt 635762f4807SAndreas Gohr ) VALUES ( 636483101d3SAndreas Gohr ?, ?, CURRENT_TIMESTAMP 637762f4807SAndreas Gohr )', 638*2adee4c6SAndreas Gohr 'media_size', 639*2adee4c6SAndreas Gohr $media_size 640762f4807SAndreas Gohr ); 641762f4807SAndreas Gohr } 642b188870fSAndreas Gohr 643b188870fSAndreas Gohr /** 644b188870fSAndreas Gohr * @todo can be dropped in favor of helper_plugin_popularity::initEmptySearchList() once it's public 645b188870fSAndreas Gohr * @return array 646b188870fSAndreas Gohr */ 647b188870fSAndreas Gohr protected function initEmptySearchList() 648b188870fSAndreas Gohr { 649b188870fSAndreas Gohr return array_fill_keys([ 650b188870fSAndreas Gohr 'file_count', 651b188870fSAndreas Gohr 'file_size', 652b188870fSAndreas Gohr 'file_max', 653b188870fSAndreas Gohr 'file_min', 654b188870fSAndreas Gohr 'dir_count', 655b188870fSAndreas Gohr 'dir_nest', 656b188870fSAndreas Gohr 'file_oldest' 657b188870fSAndreas Gohr ], 0); 658b188870fSAndreas Gohr } 659762f4807SAndreas Gohr} 660