114d99ec0SAndreas Gohr<?php 2a8acb244SAndreas Gohr 3a8acb244SAndreas Gohruse dokuwiki\Extension\ActionPlugin; 4a8acb244SAndreas Gohruse dokuwiki\Extension\Event; 504928db4SAndreas Gohruse dokuwiki\Extension\EventHandler; 6a8acb244SAndreas Gohr 714d99ec0SAndreas Gohr/** 814d99ec0SAndreas Gohr * 914d99ec0SAndreas Gohr * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 1014d99ec0SAndreas Gohr * @author Andreas Gohr <gohr@cosmocode.de> 1114d99ec0SAndreas Gohr */ 12a8acb244SAndreas Gohrclass action_plugin_statistics extends ActionPlugin 13a8acb244SAndreas Gohr{ 1414d99ec0SAndreas Gohr /** 1514d99ec0SAndreas Gohr * register the eventhandlers and initialize some options 1614d99ec0SAndreas Gohr */ 17a8acb244SAndreas Gohr public function register(EventHandler $controller) 18a8acb244SAndreas Gohr { 19eabe0d07SAndreas Gohr global $JSINFO; 20eabe0d07SAndreas Gohr global $ACT; 21eabe0d07SAndreas Gohr $JSINFO['act'] = $ACT; 2214d99ec0SAndreas Gohr 2304928db4SAndreas Gohr $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'initSession', []); 2402aa9b73SAndreas Gohr // FIXME new save event might be better: 252257e39bSAndreas Gohr $controller->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, 'logedits', []); 262257e39bSAndreas Gohr $controller->register_hook('SEARCH_QUERY_FULLPAGE', 'AFTER', $this, 'logsearch', []); 272257e39bSAndreas Gohr $controller->register_hook('FETCH_MEDIA_STATUS', 'BEFORE', $this, 'logmedia', []); 282257e39bSAndreas Gohr $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'loghistory', []); 29*7a1a7c58SAndreas Gohr $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'retention', []); 30d550a4adSAndreas Gohr 31d550a4adSAndreas Gohr // log registration and login/logout actionsonly when user tracking is enabled 32d550a4adSAndreas Gohr if (!$this->getConf('nousers')) { 33d550a4adSAndreas Gohr $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'loglogins', []); 34d550a4adSAndreas Gohr $controller->register_hook('AUTH_USER_CHANGE', 'AFTER', $this, 'logregistration', []); 35d550a4adSAndreas Gohr } 3614d99ec0SAndreas Gohr } 3714d99ec0SAndreas Gohr 3814d99ec0SAndreas Gohr /** 3904928db4SAndreas Gohr * This ensures we have a session for the statistics plugin 4004928db4SAndreas Gohr * 4104928db4SAndreas Gohr * We reset this when the user agent changes or the session is too old 4204928db4SAndreas Gohr * (15 minutes). 4304928db4SAndreas Gohr */ 4404928db4SAndreas Gohr public function initSession() 4504928db4SAndreas Gohr { 4604928db4SAndreas Gohr global $INPUT; 4704928db4SAndreas Gohr 4804928db4SAndreas Gohr // load session data 4904928db4SAndreas Gohr if (isset($_SESSION[DOKU_COOKIE]['statistics'])) { 5004928db4SAndreas Gohr $session = $_SESSION[DOKU_COOKIE]['statistics']; 5104928db4SAndreas Gohr } else { 5204928db4SAndreas Gohr $session = []; 5304928db4SAndreas Gohr } 5404928db4SAndreas Gohr // reset if session is too old 5504928db4SAndreas Gohr if (time() - ($session['time'] ?? 0) > 60 * 15) { 5604928db4SAndreas Gohr $session = []; 5704928db4SAndreas Gohr } 5804928db4SAndreas Gohr // reset if user agent changed 5904928db4SAndreas Gohr if ($INPUT->server->str('HTTP_USER_AGENT') != ($session['user_agent'] ?? '')) { 6004928db4SAndreas Gohr $session = []; 6104928db4SAndreas Gohr } 6204928db4SAndreas Gohr 6304928db4SAndreas Gohr // update session data 6404928db4SAndreas Gohr $session['time'] = time(); 6504928db4SAndreas Gohr $session['user_agent'] = $INPUT->server->str('HTTP_USER_AGENT'); 6604928db4SAndreas Gohr $session['uid'] = get_doku_pref('plgstats', bin2hex(random_bytes(16))); 6704928db4SAndreas Gohr if (!isset($session['id'])) { 6804928db4SAndreas Gohr // generate a new session id if not set 6904928db4SAndreas Gohr $session['id'] = bin2hex(random_bytes(16)); 7004928db4SAndreas Gohr } 7104928db4SAndreas Gohr 7204928db4SAndreas Gohr // store session and cookie data 7304928db4SAndreas Gohr $_SESSION[DOKU_COOKIE]['statistics'] = $session; 7404928db4SAndreas Gohr set_doku_pref('plgstats', $session['uid']); 7504928db4SAndreas Gohr } 7604928db4SAndreas Gohr 7704928db4SAndreas Gohr /** 7814d99ec0SAndreas Gohr * @fixme call this in the webbug call 7914d99ec0SAndreas Gohr */ 80a8acb244SAndreas Gohr public function putpixel() 81a8acb244SAndreas Gohr { 82ed6e7cc1SAndreas Gohr (aider) global $ID, $INPUT; 8387e0f0b1SAndreas Gohr $url = DOKU_BASE . 'lib/plugins/statistics/dispatch.php?p=' . rawurlencode($ID) . 84ed6e7cc1SAndreas Gohr (aider) '&r=' . rawurlencode($INPUT->server->str('HTTP_REFERER')) . '&rnd=' . time(); 8514d99ec0SAndreas Gohr 862257e39bSAndreas Gohr echo '<noscript><img alt="" src="' . $url . '" width="1" height="1" /></noscript>'; 8714d99ec0SAndreas Gohr } 8858511ae8SAndreas Gohr 8958511ae8SAndreas Gohr /** 905bccfe87SAndreas Gohr * Log page edits actions 9158511ae8SAndreas Gohr */ 92a8acb244SAndreas Gohr public function logedits(Event $event, $param) 93a8acb244SAndreas Gohr { 9458511ae8SAndreas Gohr if ($event->data[3]) return; // no revision 9558511ae8SAndreas Gohr 9658511ae8SAndreas Gohr if (file_exists($event->data[0][0])) { 9758511ae8SAndreas Gohr if ($event->data[0][1] == '') { 9858511ae8SAndreas Gohr $type = 'D'; 9958511ae8SAndreas Gohr } else { 10058511ae8SAndreas Gohr $type = 'E'; 10158511ae8SAndreas Gohr } 10258511ae8SAndreas Gohr } else { 10358511ae8SAndreas Gohr $type = 'C'; 10458511ae8SAndreas Gohr } 1051664ba1dSAndreas Gohr /** @var helper_plugin_statistics $hlp */ 10658511ae8SAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 10702aa9b73SAndreas Gohr $hlp->getLogger()->logEdit($event->data[1] . ':' . $event->data[2], $type); 10858511ae8SAndreas Gohr } 1095bccfe87SAndreas Gohr 1105bccfe87SAndreas Gohr /** 1115bccfe87SAndreas Gohr * Log internal search 1125bccfe87SAndreas Gohr */ 113a8acb244SAndreas Gohr public function logsearch(Event $event, $param) 114a8acb244SAndreas Gohr { 1151664ba1dSAndreas Gohr /** @var helper_plugin_statistics $hlp */ 1165bccfe87SAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 11702aa9b73SAndreas Gohr $hlp->getLogger()->logSearch($event->data['query'], $event->data['highlight']); 1185bccfe87SAndreas Gohr } 119b5e880bdSAndreas Gohr 120b5e880bdSAndreas Gohr /** 121b5e880bdSAndreas Gohr * Log login/logouts 122b5e880bdSAndreas Gohr */ 123a8acb244SAndreas Gohr public function loglogins(Event $event, $param) 124a8acb244SAndreas Gohr { 125ed6e7cc1SAndreas Gohr (aider) global $INPUT; 126ed6e7cc1SAndreas Gohr (aider) 127b5e880bdSAndreas Gohr $type = ''; 128a5dadbc1SAndreas Gohr $act = $this->actClean($event->data); 129af93d154SAndreas Gohr $user = $INPUT->server->str('REMOTE_USER'); 130b5e880bdSAndreas Gohr if ($act == 'logout') { 131af93d154SAndreas Gohr // logout 132b5e880bdSAndreas Gohr $type = 'o'; 133ed6e7cc1SAndreas Gohr (aider) } elseif ($INPUT->server->str('REMOTE_USER') && $act == 'login') { 134ed6e7cc1SAndreas Gohr (aider) if ($INPUT->str('r')) { 135af93d154SAndreas Gohr // permanent login 136b5e880bdSAndreas Gohr $type = 'p'; 137b5e880bdSAndreas Gohr } else { 138af93d154SAndreas Gohr // normal login 139b5e880bdSAndreas Gohr $type = 'l'; 140b5e880bdSAndreas Gohr } 141ed6e7cc1SAndreas Gohr (aider) } elseif ($INPUT->str('u') && !$INPUT->str('http_credentials') && !$INPUT->server->str('REMOTE_USER')) { 142af93d154SAndreas Gohr // failed attempt 143af93d154SAndreas Gohr $user = $INPUT->str('u'); 144b5e880bdSAndreas Gohr $type = 'f'; 145b5e880bdSAndreas Gohr } 146b5e880bdSAndreas Gohr if (!$type) return; 147b5e880bdSAndreas Gohr 1481664ba1dSAndreas Gohr /** @var helper_plugin_statistics $hlp */ 149b5e880bdSAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 150af93d154SAndreas Gohr $hlp->getLogger()->logLogin($type, $user); 15114d99ec0SAndreas Gohr } 15214d99ec0SAndreas Gohr 153535aeea1SAndreas Gohr /** 154535aeea1SAndreas Gohr * Log user creations 155535aeea1SAndreas Gohr */ 156a8acb244SAndreas Gohr public function logregistration(Event $event, $param) 157a8acb244SAndreas Gohr { 158535aeea1SAndreas Gohr if ($event->data['type'] == 'create') { 1591664ba1dSAndreas Gohr /** @var helper_plugin_statistics $hlp */ 160535aeea1SAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 161211caa5dSAndreas Gohr $hlp->getLogger()->logLogin('C', $event->data['params'][0]); 162535aeea1SAndreas Gohr } 163535aeea1SAndreas Gohr } 164b5e880bdSAndreas Gohr 165b5e880bdSAndreas Gohr /** 1661664ba1dSAndreas Gohr * Log media access 1671664ba1dSAndreas Gohr */ 168a8acb244SAndreas Gohr public function logmedia(Event $event, $param) 169a8acb244SAndreas Gohr { 1701664ba1dSAndreas Gohr if ($event->data['status'] < 200) return; 1711664ba1dSAndreas Gohr if ($event->data['status'] >= 400) return; 1721664ba1dSAndreas Gohr if (preg_match('/^\w+:\/\//', $event->data['media'])) return; 1731664ba1dSAndreas Gohr 1741664ba1dSAndreas Gohr // no size for redirect/not modified 1751664ba1dSAndreas Gohr if ($event->data['status'] >= 300) { 1761664ba1dSAndreas Gohr $size = 0; 1771664ba1dSAndreas Gohr } else { 17817978b38SAndreas Gohr $size = @filesize($event->data['file']); 1791664ba1dSAndreas Gohr } 1801664ba1dSAndreas Gohr 1811664ba1dSAndreas Gohr /** @var helper_plugin_statistics $hlp */ 1821664ba1dSAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 183211caa5dSAndreas Gohr $hlp->getLogger()->logMedia( 1841664ba1dSAndreas Gohr $event->data['media'], 1851664ba1dSAndreas Gohr $event->data['mime'], 1861664ba1dSAndreas Gohr !$event->data['download'], 1871664ba1dSAndreas Gohr $size 1881664ba1dSAndreas Gohr ); 1891664ba1dSAndreas Gohr } 1901664ba1dSAndreas Gohr 1911664ba1dSAndreas Gohr /** 192cae4a1c5SAndreas Gohr * Log the daily page and media counts for the history 193cae4a1c5SAndreas Gohr */ 194a8acb244SAndreas Gohr public function loghistory(Event $event, $param) 195a8acb244SAndreas Gohr { 196cae4a1c5SAndreas Gohr echo 'Plugin Statistics: started' . DOKU_LF; 197cae4a1c5SAndreas Gohr 198cae4a1c5SAndreas Gohr /** @var helper_plugin_statistics $hlp */ 199cae4a1c5SAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 200a9509e05SAndreas Gohr (aider) $db = $hlp->getDB(); 201cae4a1c5SAndreas Gohr 202cae4a1c5SAndreas Gohr // check if a history was gathered already today 203a9509e05SAndreas Gohr (aider) $result = $db->queryAll( 20402aa9b73SAndreas Gohr "SELECT info FROM history WHERE date(dt) = date('now')" 205a9509e05SAndreas Gohr (aider) ); 206cae4a1c5SAndreas Gohr 207cae4a1c5SAndreas Gohr $page_ran = false; 208cae4a1c5SAndreas Gohr $media_ran = false; 209cae4a1c5SAndreas Gohr foreach ($result as $row) { 210cae4a1c5SAndreas Gohr if ($row['info'] == 'page_count') $page_ran = true; 211cae4a1c5SAndreas Gohr if ($row['info'] == 'media_count') $media_ran = true; 212cae4a1c5SAndreas Gohr } 213cae4a1c5SAndreas Gohr 214cae4a1c5SAndreas Gohr if ($page_ran && $media_ran) { 215cae4a1c5SAndreas Gohr echo 'Plugin Statistics: nothing to do - finished' . DOKU_LF; 216cae4a1c5SAndreas Gohr return; 217cae4a1c5SAndreas Gohr } 218cae4a1c5SAndreas Gohr 219cae4a1c5SAndreas Gohr $event->stopPropagation(); 220cae4a1c5SAndreas Gohr $event->preventDefault(); 221cae4a1c5SAndreas Gohr 222cae4a1c5SAndreas Gohr if ($page_ran) { 223cae4a1c5SAndreas Gohr echo 'Plugin Statistics: logging media' . DOKU_LF; 224211caa5dSAndreas Gohr $hlp->getLogger()->logHistoryMedia(); 225cae4a1c5SAndreas Gohr } else { 226cae4a1c5SAndreas Gohr echo 'Plugin Statistics: logging pages' . DOKU_LF; 227211caa5dSAndreas Gohr $hlp->getLogger()->logHistoryPages(); 228cae4a1c5SAndreas Gohr } 229cae4a1c5SAndreas Gohr echo 'Plugin Statistics: finished' . DOKU_LF; 230cae4a1c5SAndreas Gohr } 231cae4a1c5SAndreas Gohr 232cae4a1c5SAndreas Gohr /** 233*7a1a7c58SAndreas Gohr * Prune old data 234*7a1a7c58SAndreas Gohr * 235*7a1a7c58SAndreas Gohr * This is run once a day and removes all data older than the configured 236*7a1a7c58SAndreas Gohr * retention time. 237*7a1a7c58SAndreas Gohr */ 238*7a1a7c58SAndreas Gohr public function retention(Event $event, $param) 239*7a1a7c58SAndreas Gohr { 240*7a1a7c58SAndreas Gohr $retention = (int)$this->getConf('retention'); 241*7a1a7c58SAndreas Gohr if ($retention <= 0) return; 242*7a1a7c58SAndreas Gohr // pruning is only done once a day 243*7a1a7c58SAndreas Gohr $touch = getCacheName('statistics_retention', '.statistics-retention'); 244*7a1a7c58SAndreas Gohr if (file_exists($touch) && time() - filemtime($touch) < 24 * 3600) { 245*7a1a7c58SAndreas Gohr return; 246*7a1a7c58SAndreas Gohr } 247*7a1a7c58SAndreas Gohr 248*7a1a7c58SAndreas Gohr $event->stopPropagation(); 249*7a1a7c58SAndreas Gohr $event->preventDefault(); 250*7a1a7c58SAndreas Gohr 251*7a1a7c58SAndreas Gohr // these are the tables to be pruned 252*7a1a7c58SAndreas Gohr $tables = [ 253*7a1a7c58SAndreas Gohr 'edits', 254*7a1a7c58SAndreas Gohr 'history', 255*7a1a7c58SAndreas Gohr 'iplocation', 256*7a1a7c58SAndreas Gohr 'logins', 257*7a1a7c58SAndreas Gohr 'media', 258*7a1a7c58SAndreas Gohr 'outlinks', 259*7a1a7c58SAndreas Gohr 'pageviews', 260*7a1a7c58SAndreas Gohr 'referers', 261*7a1a7c58SAndreas Gohr 'search', 262*7a1a7c58SAndreas Gohr 'sessions', 263*7a1a7c58SAndreas Gohr ]; 264*7a1a7c58SAndreas Gohr 265*7a1a7c58SAndreas Gohr /** @var helper_plugin_statistics $hlp */ 266*7a1a7c58SAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 267*7a1a7c58SAndreas Gohr $db = $hlp->getDB(); 268*7a1a7c58SAndreas Gohr 269*7a1a7c58SAndreas Gohr $db->getPdo()->beginTransaction(); 270*7a1a7c58SAndreas Gohr foreach ($tables as $table) { 271*7a1a7c58SAndreas Gohr echo "Plugin Statistics: pruning $table" . DOKU_LF; 272*7a1a7c58SAndreas Gohr $db->exec( 273*7a1a7c58SAndreas Gohr "DELETE FROM $table WHERE dt < datetime('now', '-$retention days')" 274*7a1a7c58SAndreas Gohr ); 275*7a1a7c58SAndreas Gohr } 276*7a1a7c58SAndreas Gohr $db->getPdo()->commit(); 277*7a1a7c58SAndreas Gohr 278*7a1a7c58SAndreas Gohr echo "Plugin Statistics: Optimizing" . DOKU_LF; 279*7a1a7c58SAndreas Gohr $db->exec('VACUUM'); 280*7a1a7c58SAndreas Gohr 281*7a1a7c58SAndreas Gohr // touch the retention file to prevent multiple runs 282*7a1a7c58SAndreas Gohr io_saveFile($touch, dformat()); 283*7a1a7c58SAndreas Gohr } 284*7a1a7c58SAndreas Gohr 285*7a1a7c58SAndreas Gohr /** 286b5e880bdSAndreas Gohr * Pre-Sanitize the action command 287b5e880bdSAndreas Gohr * 288b5e880bdSAndreas Gohr * Similar to act_clean in action.php but simplified and without 289b5e880bdSAndreas Gohr * error messages 290b5e880bdSAndreas Gohr */ 291a5dadbc1SAndreas Gohr protected function actClean($act) 292a8acb244SAndreas Gohr { 293b5e880bdSAndreas Gohr // check if the action was given as array key 294b5e880bdSAndreas Gohr if (is_array($act)) { 295a8acb244SAndreas Gohr [$act] = array_keys($act); 296b5e880bdSAndreas Gohr } 297b5e880bdSAndreas Gohr 298b5e880bdSAndreas Gohr //remove all bad chars 299b5e880bdSAndreas Gohr $act = strtolower($act); 300b5e880bdSAndreas Gohr $act = preg_replace('/[^a-z_]+/', '', $act); 301b5e880bdSAndreas Gohr 302b5e880bdSAndreas Gohr return $act; 303b5e880bdSAndreas Gohr } 304b5e880bdSAndreas Gohr} 305