1<?php 2 3use dokuwiki\Extension\ActionPlugin; 4use dokuwiki\Extension\Event; 5use dokuwiki\Extension\EventHandler; 6 7/** 8 * 9 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 10 * @author Andreas Gohr <gohr@cosmocode.de> 11 */ 12class action_plugin_statistics extends ActionPlugin 13{ 14 /** 15 * register the eventhandlers and initialize some options 16 */ 17 public function register(EventHandler $controller) 18 { 19 global $JSINFO; 20 global $ACT; 21 $JSINFO['act'] = $ACT; 22 23 $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'initSession', []); 24 // FIXME new save event might be better: 25 $controller->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, 'logedits', []); 26 $controller->register_hook('SEARCH_QUERY_FULLPAGE', 'AFTER', $this, 'logsearch', []); 27 $controller->register_hook('FETCH_MEDIA_STATUS', 'BEFORE', $this, 'logmedia', []); 28 $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'loghistory', []); 29 $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'retention', []); 30 31 // log registration and login/logout actionsonly when user tracking is enabled 32 if (!$this->getConf('nousers')) { 33 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'loglogins', []); 34 $controller->register_hook('AUTH_USER_CHANGE', 'AFTER', $this, 'logregistration', []); 35 } 36 } 37 38 /** 39 * This ensures we have a session for the statistics plugin 40 * 41 * We reset this when the user agent changes or the session is too old 42 * (15 minutes). 43 */ 44 public function initSession() 45 { 46 global $INPUT; 47 48 // load session data 49 $session = $_SESSION[DOKU_COOKIE]['statistics'] ?? []; 50 51 // reset if session is too old 52 if (time() - ($session['time'] ?? 0) > 60 * 15) { 53 $session = []; 54 } 55 // reset if user agent changed 56 if ($INPUT->server->str('HTTP_USER_AGENT') != ($session['user_agent'] ?? '')) { 57 $session = []; 58 } 59 60 // update session data 61 $session['time'] = time(); 62 $session['user_agent'] = $INPUT->server->str('HTTP_USER_AGENT'); 63 $session['uid'] = get_doku_pref('plgstats', bin2hex(random_bytes(16))); 64 if (!isset($session['id'])) { 65 // generate a new session id if not set 66 $session['id'] = bin2hex(random_bytes(16)); 67 } 68 69 // store session and cookie data 70 $_SESSION[DOKU_COOKIE]['statistics'] = $session; 71 set_doku_pref('plgstats', $session['uid']); 72 } 73 74 /** 75 * @fixme call this in the webbug call 76 */ 77 public function putpixel() 78 { 79 global $ID, $INPUT; 80 $url = DOKU_BASE . 'lib/plugins/statistics/dispatch.php?p=' . rawurlencode($ID) . 81 '&r=' . rawurlencode($INPUT->server->str('HTTP_REFERER')) . '&rnd=' . time(); 82 83 echo '<noscript><img alt="" src="' . $url . '" width="1" height="1" /></noscript>'; 84 } 85 86 /** 87 * Log page edits actions 88 */ 89 public function logedits(Event $event, $param) 90 { 91 if ($event->data[3]) return; // no revision 92 93 if (file_exists($event->data[0][0])) { 94 if ($event->data[0][1] == '') { 95 $type = 'D'; 96 } else { 97 $type = 'E'; 98 } 99 } else { 100 $type = 'C'; 101 } 102 /** @var helper_plugin_statistics $hlp */ 103 $hlp = plugin_load('helper', 'statistics'); 104 $hlp->getLogger()->logEdit($event->data[1] . ':' . $event->data[2], $type); 105 } 106 107 /** 108 * Log internal search 109 */ 110 public function logsearch(Event $event, $param) 111 { 112 /** @var helper_plugin_statistics $hlp */ 113 $hlp = plugin_load('helper', 'statistics'); 114 $hlp->getLogger()->logSearch($event->data['query'], $event->data['highlight']); 115 } 116 117 /** 118 * Log login/logouts 119 */ 120 public function loglogins(Event $event, $param) 121 { 122 global $INPUT; 123 124 $type = ''; 125 $act = $this->actClean($event->data); 126 $user = $INPUT->server->str('REMOTE_USER'); 127 if ($act == 'logout') { 128 // logout 129 $type = 'o'; 130 } elseif ($INPUT->server->str('REMOTE_USER') && $act == 'login') { 131 if ($INPUT->str('r')) { 132 // permanent login 133 $type = 'p'; 134 } else { 135 // normal login 136 $type = 'l'; 137 } 138 } elseif ($INPUT->str('u') && !$INPUT->str('http_credentials') && !$INPUT->server->str('REMOTE_USER')) { 139 // failed attempt 140 $user = $INPUT->str('u'); 141 $type = 'f'; 142 } 143 if (!$type) return; 144 145 /** @var helper_plugin_statistics $hlp */ 146 $hlp = plugin_load('helper', 'statistics'); 147 $hlp->getLogger()->logLogin($type, $user); 148 } 149 150 /** 151 * Log user creations 152 */ 153 public function logregistration(Event $event, $param) 154 { 155 if ($event->data['type'] == 'create') { 156 /** @var helper_plugin_statistics $hlp */ 157 $hlp = plugin_load('helper', 'statistics'); 158 $hlp->getLogger()->logLogin('C', $event->data['params'][0]); 159 } 160 } 161 162 /** 163 * Log media access 164 */ 165 public function logmedia(Event $event, $param) 166 { 167 if ($event->data['status'] < 200) return; 168 if ($event->data['status'] >= 400) return; 169 if (preg_match('/^\w+:\/\//', $event->data['media'])) return; 170 171 // no size for redirect/not modified 172 if ($event->data['status'] >= 300) { 173 $size = 0; 174 } else { 175 $size = @filesize($event->data['file']); 176 } 177 178 /** @var helper_plugin_statistics $hlp */ 179 $hlp = plugin_load('helper', 'statistics'); 180 $hlp->getLogger()->logMedia( 181 $event->data['media'], 182 $event->data['mime'], 183 !$event->data['download'], 184 $size 185 ); 186 } 187 188 /** 189 * Log the daily page and media counts for the history 190 */ 191 public function loghistory(Event $event, $param) 192 { 193 echo 'Plugin Statistics: started' . DOKU_LF; 194 195 /** @var helper_plugin_statistics $hlp */ 196 $hlp = plugin_load('helper', 'statistics'); 197 $db = $hlp->getDB(); 198 199 // check if a history was gathered already today 200 $result = $db->queryAll( 201 "SELECT info FROM history WHERE date(dt) = date('now')" 202 ); 203 204 $page_ran = false; 205 $media_ran = false; 206 foreach ($result as $row) { 207 if ($row['info'] == 'page_count') $page_ran = true; 208 if ($row['info'] == 'media_count') $media_ran = true; 209 } 210 211 if ($page_ran && $media_ran) { 212 echo 'Plugin Statistics: nothing to do - finished' . DOKU_LF; 213 return; 214 } 215 216 $event->stopPropagation(); 217 $event->preventDefault(); 218 219 if ($page_ran) { 220 echo 'Plugin Statistics: logging media' . DOKU_LF; 221 $hlp->getLogger()->logHistoryMedia(); 222 } else { 223 echo 'Plugin Statistics: logging pages' . DOKU_LF; 224 $hlp->getLogger()->logHistoryPages(); 225 } 226 echo 'Plugin Statistics: finished' . DOKU_LF; 227 } 228 229 /** 230 * Prune old data 231 * 232 * This is run once a day and removes all data older than the configured 233 * retention time. 234 */ 235 public function retention(Event $event, $param) 236 { 237 $retention = (int)$this->getConf('retention'); 238 if ($retention <= 0) return; 239 // pruning is only done once a day 240 $touch = getCacheName('statistics_retention', '.statistics-retention'); 241 if (file_exists($touch) && time() - filemtime($touch) < 24 * 3600) { 242 return; 243 } 244 245 $event->stopPropagation(); 246 $event->preventDefault(); 247 248 // these are the tables to be pruned 249 $tables = [ 250 'edits', 251 'history', 252 'iplocation', 253 'logins', 254 'media', 255 'outlinks', 256 'pageviews', 257 'referers', 258 'search', 259 'sessions', 260 ]; 261 262 /** @var helper_plugin_statistics $hlp */ 263 $hlp = plugin_load('helper', 'statistics'); 264 $db = $hlp->getDB(); 265 266 $db->getPdo()->beginTransaction(); 267 foreach ($tables as $table) { 268 echo "Plugin Statistics: pruning $table" . DOKU_LF; 269 $db->exec( 270 "DELETE FROM $table WHERE dt < datetime('now', '-$retention days')" 271 ); 272 } 273 $db->getPdo()->commit(); 274 275 echo "Plugin Statistics: Optimizing" . DOKU_LF; 276 $db->exec('VACUUM'); 277 278 // touch the retention file to prevent multiple runs 279 io_saveFile($touch, dformat()); 280 } 281 282 /** 283 * Pre-Sanitize the action command 284 * 285 * Similar to act_clean in action.php but simplified and without 286 * error messages 287 */ 288 protected function actClean($act) 289 { 290 // check if the action was given as array key 291 if (is_array($act)) { 292 [$act] = array_keys($act); 293 } 294 295 //remove all bad chars 296 $act = strtolower($act); 297 $act = preg_replace('/[^a-z_]+/', '', $act); 298 299 return $act; 300 } 301} 302