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