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