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', []); 297a1a7c58SAndreas 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 49*523da372SAndreas Gohr $session = $_SESSION[DOKU_COOKIE]['statistics'] ?? []; 50*523da372SAndreas Gohr 5104928db4SAndreas Gohr // reset if session is too old 5204928db4SAndreas Gohr if (time() - ($session['time'] ?? 0) > 60 * 15) { 5304928db4SAndreas Gohr $session = []; 5404928db4SAndreas Gohr } 5504928db4SAndreas Gohr // reset if user agent changed 5604928db4SAndreas Gohr if ($INPUT->server->str('HTTP_USER_AGENT') != ($session['user_agent'] ?? '')) { 5704928db4SAndreas Gohr $session = []; 5804928db4SAndreas Gohr } 5904928db4SAndreas Gohr 6004928db4SAndreas Gohr // update session data 6104928db4SAndreas Gohr $session['time'] = time(); 6204928db4SAndreas Gohr $session['user_agent'] = $INPUT->server->str('HTTP_USER_AGENT'); 6304928db4SAndreas Gohr $session['uid'] = get_doku_pref('plgstats', bin2hex(random_bytes(16))); 6404928db4SAndreas Gohr if (!isset($session['id'])) { 6504928db4SAndreas Gohr // generate a new session id if not set 6604928db4SAndreas Gohr $session['id'] = bin2hex(random_bytes(16)); 6704928db4SAndreas Gohr } 6804928db4SAndreas Gohr 6904928db4SAndreas Gohr // store session and cookie data 7004928db4SAndreas Gohr $_SESSION[DOKU_COOKIE]['statistics'] = $session; 7104928db4SAndreas Gohr set_doku_pref('plgstats', $session['uid']); 7204928db4SAndreas Gohr } 7304928db4SAndreas Gohr 7404928db4SAndreas Gohr /** 7514d99ec0SAndreas Gohr * @fixme call this in the webbug call 7614d99ec0SAndreas Gohr */ 77a8acb244SAndreas Gohr public function putpixel() 78a8acb244SAndreas Gohr { 79ed6e7cc1SAndreas Gohr (aider) global $ID, $INPUT; 8087e0f0b1SAndreas Gohr $url = DOKU_BASE . 'lib/plugins/statistics/dispatch.php?p=' . rawurlencode($ID) . 81ed6e7cc1SAndreas Gohr (aider) '&r=' . rawurlencode($INPUT->server->str('HTTP_REFERER')) . '&rnd=' . time(); 8214d99ec0SAndreas Gohr 832257e39bSAndreas Gohr echo '<noscript><img alt="" src="' . $url . '" width="1" height="1" /></noscript>'; 8414d99ec0SAndreas Gohr } 8558511ae8SAndreas Gohr 8658511ae8SAndreas Gohr /** 875bccfe87SAndreas Gohr * Log page edits actions 8858511ae8SAndreas Gohr */ 89a8acb244SAndreas Gohr public function logedits(Event $event, $param) 90a8acb244SAndreas Gohr { 9158511ae8SAndreas Gohr if ($event->data[3]) return; // no revision 9258511ae8SAndreas Gohr 9358511ae8SAndreas Gohr if (file_exists($event->data[0][0])) { 9458511ae8SAndreas Gohr if ($event->data[0][1] == '') { 9558511ae8SAndreas Gohr $type = 'D'; 9658511ae8SAndreas Gohr } else { 9758511ae8SAndreas Gohr $type = 'E'; 9858511ae8SAndreas Gohr } 9958511ae8SAndreas Gohr } else { 10058511ae8SAndreas Gohr $type = 'C'; 10158511ae8SAndreas Gohr } 1021664ba1dSAndreas Gohr /** @var helper_plugin_statistics $hlp */ 10358511ae8SAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 10402aa9b73SAndreas Gohr $hlp->getLogger()->logEdit($event->data[1] . ':' . $event->data[2], $type); 10558511ae8SAndreas Gohr } 1065bccfe87SAndreas Gohr 1075bccfe87SAndreas Gohr /** 1085bccfe87SAndreas Gohr * Log internal search 1095bccfe87SAndreas Gohr */ 110a8acb244SAndreas Gohr public function logsearch(Event $event, $param) 111a8acb244SAndreas Gohr { 1121664ba1dSAndreas Gohr /** @var helper_plugin_statistics $hlp */ 1135bccfe87SAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 11402aa9b73SAndreas Gohr $hlp->getLogger()->logSearch($event->data['query'], $event->data['highlight']); 1155bccfe87SAndreas Gohr } 116b5e880bdSAndreas Gohr 117b5e880bdSAndreas Gohr /** 118b5e880bdSAndreas Gohr * Log login/logouts 119b5e880bdSAndreas Gohr */ 120a8acb244SAndreas Gohr public function loglogins(Event $event, $param) 121a8acb244SAndreas Gohr { 122ed6e7cc1SAndreas Gohr (aider) global $INPUT; 123ed6e7cc1SAndreas Gohr (aider) 124b5e880bdSAndreas Gohr $type = ''; 125a5dadbc1SAndreas Gohr $act = $this->actClean($event->data); 126af93d154SAndreas Gohr $user = $INPUT->server->str('REMOTE_USER'); 127b5e880bdSAndreas Gohr if ($act == 'logout') { 128af93d154SAndreas Gohr // logout 129b5e880bdSAndreas Gohr $type = 'o'; 130ed6e7cc1SAndreas Gohr (aider) } elseif ($INPUT->server->str('REMOTE_USER') && $act == 'login') { 131ed6e7cc1SAndreas Gohr (aider) if ($INPUT->str('r')) { 132af93d154SAndreas Gohr // permanent login 133b5e880bdSAndreas Gohr $type = 'p'; 134b5e880bdSAndreas Gohr } else { 135af93d154SAndreas Gohr // normal login 136b5e880bdSAndreas Gohr $type = 'l'; 137b5e880bdSAndreas Gohr } 138ed6e7cc1SAndreas Gohr (aider) } elseif ($INPUT->str('u') && !$INPUT->str('http_credentials') && !$INPUT->server->str('REMOTE_USER')) { 139af93d154SAndreas Gohr // failed attempt 140af93d154SAndreas Gohr $user = $INPUT->str('u'); 141b5e880bdSAndreas Gohr $type = 'f'; 142b5e880bdSAndreas Gohr } 143b5e880bdSAndreas Gohr if (!$type) return; 144b5e880bdSAndreas Gohr 1451664ba1dSAndreas Gohr /** @var helper_plugin_statistics $hlp */ 146b5e880bdSAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 147af93d154SAndreas Gohr $hlp->getLogger()->logLogin($type, $user); 14814d99ec0SAndreas Gohr } 14914d99ec0SAndreas Gohr 150535aeea1SAndreas Gohr /** 151535aeea1SAndreas Gohr * Log user creations 152535aeea1SAndreas Gohr */ 153a8acb244SAndreas Gohr public function logregistration(Event $event, $param) 154a8acb244SAndreas Gohr { 155535aeea1SAndreas Gohr if ($event->data['type'] == 'create') { 1561664ba1dSAndreas Gohr /** @var helper_plugin_statistics $hlp */ 157535aeea1SAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 158211caa5dSAndreas Gohr $hlp->getLogger()->logLogin('C', $event->data['params'][0]); 159535aeea1SAndreas Gohr } 160535aeea1SAndreas Gohr } 161b5e880bdSAndreas Gohr 162b5e880bdSAndreas Gohr /** 1631664ba1dSAndreas Gohr * Log media access 1641664ba1dSAndreas Gohr */ 165a8acb244SAndreas Gohr public function logmedia(Event $event, $param) 166a8acb244SAndreas Gohr { 1671664ba1dSAndreas Gohr if ($event->data['status'] < 200) return; 1681664ba1dSAndreas Gohr if ($event->data['status'] >= 400) return; 1691664ba1dSAndreas Gohr if (preg_match('/^\w+:\/\//', $event->data['media'])) return; 1701664ba1dSAndreas Gohr 1711664ba1dSAndreas Gohr // no size for redirect/not modified 1721664ba1dSAndreas Gohr if ($event->data['status'] >= 300) { 1731664ba1dSAndreas Gohr $size = 0; 1741664ba1dSAndreas Gohr } else { 17517978b38SAndreas Gohr $size = @filesize($event->data['file']); 1761664ba1dSAndreas Gohr } 1771664ba1dSAndreas Gohr 1781664ba1dSAndreas Gohr /** @var helper_plugin_statistics $hlp */ 1791664ba1dSAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 180211caa5dSAndreas Gohr $hlp->getLogger()->logMedia( 1811664ba1dSAndreas Gohr $event->data['media'], 1821664ba1dSAndreas Gohr $event->data['mime'], 1831664ba1dSAndreas Gohr !$event->data['download'], 1841664ba1dSAndreas Gohr $size 1851664ba1dSAndreas Gohr ); 1861664ba1dSAndreas Gohr } 1871664ba1dSAndreas Gohr 1881664ba1dSAndreas Gohr /** 189cae4a1c5SAndreas Gohr * Log the daily page and media counts for the history 190cae4a1c5SAndreas Gohr */ 191a8acb244SAndreas Gohr public function loghistory(Event $event, $param) 192a8acb244SAndreas Gohr { 193cae4a1c5SAndreas Gohr echo 'Plugin Statistics: started' . DOKU_LF; 194cae4a1c5SAndreas Gohr 195cae4a1c5SAndreas Gohr /** @var helper_plugin_statistics $hlp */ 196cae4a1c5SAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 197a9509e05SAndreas Gohr (aider) $db = $hlp->getDB(); 198cae4a1c5SAndreas Gohr 199cae4a1c5SAndreas Gohr // check if a history was gathered already today 200a9509e05SAndreas Gohr (aider) $result = $db->queryAll( 20102aa9b73SAndreas Gohr "SELECT info FROM history WHERE date(dt) = date('now')" 202a9509e05SAndreas Gohr (aider) ); 203cae4a1c5SAndreas Gohr 204cae4a1c5SAndreas Gohr $page_ran = false; 205cae4a1c5SAndreas Gohr $media_ran = false; 206cae4a1c5SAndreas Gohr foreach ($result as $row) { 207cae4a1c5SAndreas Gohr if ($row['info'] == 'page_count') $page_ran = true; 208cae4a1c5SAndreas Gohr if ($row['info'] == 'media_count') $media_ran = true; 209cae4a1c5SAndreas Gohr } 210cae4a1c5SAndreas Gohr 211cae4a1c5SAndreas Gohr if ($page_ran && $media_ran) { 212cae4a1c5SAndreas Gohr echo 'Plugin Statistics: nothing to do - finished' . DOKU_LF; 213cae4a1c5SAndreas Gohr return; 214cae4a1c5SAndreas Gohr } 215cae4a1c5SAndreas Gohr 216cae4a1c5SAndreas Gohr $event->stopPropagation(); 217cae4a1c5SAndreas Gohr $event->preventDefault(); 218cae4a1c5SAndreas Gohr 219cae4a1c5SAndreas Gohr if ($page_ran) { 220cae4a1c5SAndreas Gohr echo 'Plugin Statistics: logging media' . DOKU_LF; 221211caa5dSAndreas Gohr $hlp->getLogger()->logHistoryMedia(); 222cae4a1c5SAndreas Gohr } else { 223cae4a1c5SAndreas Gohr echo 'Plugin Statistics: logging pages' . DOKU_LF; 224211caa5dSAndreas Gohr $hlp->getLogger()->logHistoryPages(); 225cae4a1c5SAndreas Gohr } 226cae4a1c5SAndreas Gohr echo 'Plugin Statistics: finished' . DOKU_LF; 227cae4a1c5SAndreas Gohr } 228cae4a1c5SAndreas Gohr 229cae4a1c5SAndreas Gohr /** 2307a1a7c58SAndreas Gohr * Prune old data 2317a1a7c58SAndreas Gohr * 2327a1a7c58SAndreas Gohr * This is run once a day and removes all data older than the configured 2337a1a7c58SAndreas Gohr * retention time. 2347a1a7c58SAndreas Gohr */ 2357a1a7c58SAndreas Gohr public function retention(Event $event, $param) 2367a1a7c58SAndreas Gohr { 2377a1a7c58SAndreas Gohr $retention = (int)$this->getConf('retention'); 2387a1a7c58SAndreas Gohr if ($retention <= 0) return; 2397a1a7c58SAndreas Gohr // pruning is only done once a day 2407a1a7c58SAndreas Gohr $touch = getCacheName('statistics_retention', '.statistics-retention'); 2417a1a7c58SAndreas Gohr if (file_exists($touch) && time() - filemtime($touch) < 24 * 3600) { 2427a1a7c58SAndreas Gohr return; 2437a1a7c58SAndreas Gohr } 2447a1a7c58SAndreas Gohr 2457a1a7c58SAndreas Gohr $event->stopPropagation(); 2467a1a7c58SAndreas Gohr $event->preventDefault(); 2477a1a7c58SAndreas Gohr 2487a1a7c58SAndreas Gohr // these are the tables to be pruned 2497a1a7c58SAndreas Gohr $tables = [ 2507a1a7c58SAndreas Gohr 'edits', 2517a1a7c58SAndreas Gohr 'history', 2527a1a7c58SAndreas Gohr 'iplocation', 2537a1a7c58SAndreas Gohr 'logins', 2547a1a7c58SAndreas Gohr 'media', 2557a1a7c58SAndreas Gohr 'outlinks', 2567a1a7c58SAndreas Gohr 'pageviews', 2577a1a7c58SAndreas Gohr 'referers', 2587a1a7c58SAndreas Gohr 'search', 2597a1a7c58SAndreas Gohr 'sessions', 2607a1a7c58SAndreas Gohr ]; 2617a1a7c58SAndreas Gohr 2627a1a7c58SAndreas Gohr /** @var helper_plugin_statistics $hlp */ 2637a1a7c58SAndreas Gohr $hlp = plugin_load('helper', 'statistics'); 2647a1a7c58SAndreas Gohr $db = $hlp->getDB(); 2657a1a7c58SAndreas Gohr 2667a1a7c58SAndreas Gohr $db->getPdo()->beginTransaction(); 2677a1a7c58SAndreas Gohr foreach ($tables as $table) { 2687a1a7c58SAndreas Gohr echo "Plugin Statistics: pruning $table" . DOKU_LF; 2697a1a7c58SAndreas Gohr $db->exec( 2707a1a7c58SAndreas Gohr "DELETE FROM $table WHERE dt < datetime('now', '-$retention days')" 2717a1a7c58SAndreas Gohr ); 2727a1a7c58SAndreas Gohr } 2737a1a7c58SAndreas Gohr $db->getPdo()->commit(); 2747a1a7c58SAndreas Gohr 2757a1a7c58SAndreas Gohr echo "Plugin Statistics: Optimizing" . DOKU_LF; 2767a1a7c58SAndreas Gohr $db->exec('VACUUM'); 2777a1a7c58SAndreas Gohr 2787a1a7c58SAndreas Gohr // touch the retention file to prevent multiple runs 2797a1a7c58SAndreas Gohr io_saveFile($touch, dformat()); 2807a1a7c58SAndreas Gohr } 2817a1a7c58SAndreas Gohr 2827a1a7c58SAndreas Gohr /** 283b5e880bdSAndreas Gohr * Pre-Sanitize the action command 284b5e880bdSAndreas Gohr * 285b5e880bdSAndreas Gohr * Similar to act_clean in action.php but simplified and without 286b5e880bdSAndreas Gohr * error messages 287b5e880bdSAndreas Gohr */ 288a5dadbc1SAndreas Gohr protected function actClean($act) 289a8acb244SAndreas Gohr { 290b5e880bdSAndreas Gohr // check if the action was given as array key 291b5e880bdSAndreas Gohr if (is_array($act)) { 292a8acb244SAndreas Gohr [$act] = array_keys($act); 293b5e880bdSAndreas Gohr } 294b5e880bdSAndreas Gohr 295b5e880bdSAndreas Gohr //remove all bad chars 296b5e880bdSAndreas Gohr $act = strtolower($act); 297b5e880bdSAndreas Gohr $act = preg_replace('/[^a-z_]+/', '', $act); 298b5e880bdSAndreas Gohr 299b5e880bdSAndreas Gohr return $act; 300b5e880bdSAndreas Gohr } 301b5e880bdSAndreas Gohr} 302