*/ class action_plugin_statistics extends ActionPlugin { /** * register the eventhandlers and initialize some options */ public function register(EventHandler $controller) { global $JSINFO; global $ACT; $JSINFO['act'] = $ACT; $controller->register_hook('DOKUWIKI_INIT_DONE', 'AFTER', $this, 'initSession', []); // FIXME new save event might be better: $controller->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, 'logedits', []); $controller->register_hook('SEARCH_QUERY_FULLPAGE', 'AFTER', $this, 'logsearch', []); $controller->register_hook('FETCH_MEDIA_STATUS', 'BEFORE', $this, 'logmedia', []); $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'loghistory', []); $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'retention', []); // log registration and login/logout actionsonly when user tracking is enabled if (!$this->getConf('nousers')) { $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'loglogins', []); $controller->register_hook('AUTH_USER_CHANGE', 'AFTER', $this, 'logregistration', []); } } /** * This ensures we have a session for the statistics plugin * * We reset this when the user agent changes or the session is too old * (15 minutes). */ public function initSession() { global $INPUT; // load session data $session = $_SESSION[DOKU_COOKIE]['statistics'] ?? []; // reset if session is too old if (time() - ($session['time'] ?? 0) > 60 * 15) { $session = []; } // reset if user agent changed if ($INPUT->server->str('HTTP_USER_AGENT') != ($session['user_agent'] ?? '')) { $session = []; } // update session data $session['time'] = time(); $session['user_agent'] = $INPUT->server->str('HTTP_USER_AGENT'); $session['uid'] = get_doku_pref('plgstats', bin2hex(random_bytes(16))); if (!isset($session['id'])) { // generate a new session id if not set $session['id'] = bin2hex(random_bytes(16)); } // store session and cookie data $_SESSION[DOKU_COOKIE]['statistics'] = $session; // Workaround for dokuwiki/dokuwiki#4544 $old = get_doku_pref('plgstats', false); if ($old !== $session['uid']) { set_doku_pref('plgstats', $session['uid']); } } /** * @fixme call this in the webbug call */ public function putpixel() { global $ID, $INPUT; $url = DOKU_BASE . 'lib/plugins/statistics/dispatch.php?p=' . rawurlencode($ID) . '&r=' . rawurlencode($INPUT->server->str('HTTP_REFERER')) . '&rnd=' . time(); echo ''; } /** * Log page edits actions */ public function logedits(Event $event, $param) { if ($event->data[3]) return; // no revision if (file_exists($event->data[0][0])) { if ($event->data[0][1] == '') { $type = 'D'; } else { $type = 'E'; } } else { $type = 'C'; } /** @var helper_plugin_statistics $hlp */ $hlp = plugin_load('helper', 'statistics'); $hlp->getLogger()->logEdit($event->data[1] . ':' . $event->data[2], $type); } /** * Log internal search */ public function logsearch(Event $event, $param) { /** @var helper_plugin_statistics $hlp */ $hlp = plugin_load('helper', 'statistics'); $hlp->getLogger()->logSearch($event->data['query'], $event->data['highlight']); } /** * Log login/logouts */ public function loglogins(Event $event, $param) { global $INPUT; $type = ''; $act = $this->actClean($event->data); $user = $INPUT->server->str('REMOTE_USER'); if ($act == 'logout') { // logout $type = 'o'; } elseif ($INPUT->server->str('REMOTE_USER') && $act == 'login') { if ($INPUT->str('r')) { // permanent login $type = 'p'; } else { // normal login $type = 'l'; } } elseif ($INPUT->str('u') && !$INPUT->str('http_credentials') && !$INPUT->server->str('REMOTE_USER')) { // failed attempt $user = $INPUT->str('u'); $type = 'f'; } if (!$type) return; /** @var helper_plugin_statistics $hlp */ $hlp = plugin_load('helper', 'statistics'); $hlp->getLogger()->logLogin($type, $user); } /** * Log user creations */ public function logregistration(Event $event, $param) { if ($event->data['type'] == 'create') { /** @var helper_plugin_statistics $hlp */ $hlp = plugin_load('helper', 'statistics'); $hlp->getLogger()->logLogin('C', $event->data['params'][0]); } } /** * Log media access */ public function logmedia(Event $event, $param) { if ($event->data['status'] < 200) return; if ($event->data['status'] >= 400) return; if (preg_match('/^\w+:\/\//', $event->data['media'])) return; // no size for redirect/not modified if ($event->data['status'] >= 300) { $size = 0; } else { $size = @filesize($event->data['file']); } /** @var helper_plugin_statistics $hlp */ $hlp = plugin_load('helper', 'statistics'); $hlp->getLogger()->logMedia( $event->data['media'], $event->data['mime'], !$event->data['download'], $size ); } /** * Log the daily page and media counts for the history */ public function loghistory(Event $event, $param) { echo 'Plugin Statistics: started' . DOKU_LF; /** @var helper_plugin_statistics $hlp */ $hlp = plugin_load('helper', 'statistics'); $db = $hlp->getDB(); // check if a history was gathered already today $result = $db->queryAll( "SELECT info FROM history WHERE date(dt) = date('now')" ); $page_ran = false; $media_ran = false; foreach ($result as $row) { if ($row['info'] == 'page_count') $page_ran = true; if ($row['info'] == 'media_count') $media_ran = true; } if ($page_ran && $media_ran) { echo 'Plugin Statistics: nothing to do - finished' . DOKU_LF; return; } $event->stopPropagation(); $event->preventDefault(); if ($page_ran) { echo 'Plugin Statistics: logging media' . DOKU_LF; $hlp->getLogger()->logHistoryMedia(); } else { echo 'Plugin Statistics: logging pages' . DOKU_LF; $hlp->getLogger()->logHistoryPages(); } echo 'Plugin Statistics: finished' . DOKU_LF; } /** * Prune old data * * This is run once a day and removes all data older than the configured * retention time. */ public function retention(Event $event, $param) { $retention = (int)$this->getConf('retention'); if ($retention <= 0) return; // pruning is only done once a day $touch = getCacheName('statistics_retention', '.statistics-retention'); if (file_exists($touch) && time() - filemtime($touch) < 24 * 3600) { return; } $event->stopPropagation(); $event->preventDefault(); // these are the tables to be pruned $tables = [ 'edits', 'history', 'iplocation', 'logins', 'media', 'outlinks', 'pageviews', 'referers', 'search', 'sessions', ]; /** @var helper_plugin_statistics $hlp */ $hlp = plugin_load('helper', 'statistics'); $db = $hlp->getDB(); $db->getPdo()->beginTransaction(); foreach ($tables as $table) { echo "Plugin Statistics: pruning $table" . DOKU_LF; $db->exec( "DELETE FROM $table WHERE dt < datetime('now', '-$retention days')" ); } $db->getPdo()->commit(); echo "Plugin Statistics: Optimizing" . DOKU_LF; $db->exec('VACUUM'); // touch the retention file to prevent multiple runs io_saveFile($touch, dformat()); } /** * Pre-Sanitize the action command * * Similar to act_clean in action.php but simplified and without * error messages */ protected function actClean($act) { // check if the action was given as array key if (is_array($act)) { [$act] = array_keys($act); } //remove all bad chars $act = strtolower($act); $act = preg_replace('/[^a-z_]+/', '', $act); return $act; } }