1<?php 2 3use dokuwiki\Extension\EventHandler; 4use dokuwiki\Extension\Event; 5use dokuwiki\Logger; 6 7/** 8 * Action Component for the Bot Monitoring Plugin 9 * 10 * @license GPL 3 (http://www.gnu.org/licenses/gpl.html) 11 * @author Sascha Leib <sascha.leib(at)kolmio.com> 12 */ 13 14class action_plugin_botmon extends DokuWiki_Action_Plugin { 15 16 /** 17 * Registers a callback functions 18 * 19 * @param EventHandler $controller DokuWiki's event controller object 20 * @return void 21 */ 22 public function register(EventHandler $controller) { 23 24 global $ACT; 25 26 // insert header data into the page: 27 if ($ACT == 'show') { 28 $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertHeader'); 29 } else if ($ACT == 'admin' && isset($_REQUEST['page']) && $_REQUEST['page'] == 'botmon') { 30 $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertAdminHeader'); 31 } 32 33 // write to the log after the page content was displayed: 34 $controller->register_hook('TPL_CONTENT_DISPLAY', 'AFTER', $this, 'writeServerLog'); 35 36 } 37 38 /* session information */ 39 private $sessionId = null; 40 private $sessionType = ''; 41 private $ipAddress = null; 42 43 /** 44 * Inserts tracking code to the page header 45 * (only called on 'show' actions) 46 * 47 * @param Event $event event object by reference 48 * @return void 49 */ 50 public function insertHeader(Event $event, $param) { 51 52 global $INFO; 53 54 // populate the session id and type: 55 $this->getSessionInfo(); 56 57 // is there a user logged in? 58 $username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name']) ? $INFO['userinfo']['name'] : ''); 59 60 // build the tracker code: 61 $code = "document._botmon = {'t0': Date.now(), 'session': '" . json_encode($this->sessionId) . "'};" . NL; 62 if ($username) { 63 $code .= DOKU_TAB . DOKU_TAB . 'document._botmon.user = "' . $username . '";'. NL; 64 } 65 66 // add the deferred script loader:: 67 $code .= DOKU_TAB . DOKU_TAB . "addEventListener('DOMContentLoaded', function(){" . NL; 68 $code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "const e=document.createElement('script');" . NL; 69 $code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "e.async=true;e.defer=true;" . NL; 70 $code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "e.src='".DOKU_BASE."lib/plugins/botmon/client.js';" . NL; 71 $code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "document.getElementsByTagName('head')[0].appendChild(e);" . NL; 72 $code .= DOKU_TAB . DOKU_TAB . "});"; 73 74 $event->data['script'][] = ['_data' => $code]; 75 } 76 77 /** 78 * Inserts tracking code to the page header 79 * (only called on 'show' actions) 80 * 81 * @param Event $event event object by reference 82 * @return void 83 */ 84 public function insertAdminHeader(Event $event, $param) { 85 86 $event->data['link'][] = ['rel' => 'stylesheet', 'href' => DOKU_BASE.'lib/plugins/botmon/admin.css', 'defer' => 'defer']; 87 $event->data['script'][] = ['src' => DOKU_BASE.'lib/plugins/botmon/admin.js', 'defer' => 'defer', '_data' => '']; 88 } 89 90 91 /** 92 * Writes data to the server log. 93 * 94 * @return void 95 */ 96 public function writeServerLog(Event $event, $param) { 97 98 global $conf; 99 global $INFO; 100 101 // is there a user logged in? 102 $username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name']) 103 ? $INFO['userinfo']['name'] : ''); 104 105 // clean the page ID 106 $pageId = preg_replace('/[\x00-\x1F]/', "\u{FFFD}", $INFO['id'] ?? ''); 107 108 // create the log array: 109 $logArr = Array( 110 $this->ipAddress, /* remote IP */ 111 $pageId, /* page ID */ 112 $this->sessionId, /* Session ID */ 113 $this->sessionType, /* session ID type */ 114 $username, /* user name */ 115 $_SERVER['HTTP_USER_AGENT'] ?? '', /* User agent */ 116 $_SERVER['HTTP_REFERER'] ?? '', /* HTTP Referrer */ 117 substr($conf['lang'],0,2), /* page language */ 118 implode(',', array_unique(array_map( function($it) { return substr(trim($it),0,2); }, explode(',',trim($_SERVER['HTTP_ACCEPT_LANGUAGE'], " \t;,*"))))), /* accepted client languages */ 119 $this->getCountryCode() /* GeoIP country code */ 120 ); 121 122 //* create the log line */ 123 $filename = __DIR__ .'/logs/' . gmdate('Y-m-d') . '.srv.txt'; /* use GMT date for filename */ 124 $logline = gmdate('Y-m-d H:i:s'); /* use GMT time for log entries */ 125 foreach ($logArr as $tab) { 126 $logline .= "\t" . $tab; 127 }; 128 129 /* write the log line to the file */ 130 $logfile = fopen($filename, 'a'); 131 if (!$logfile) die(); 132 if (fwrite($logfile, $logline . "\n") === false) { 133 fclose($logfile); 134 die(); 135 } 136 137 /* Done */ 138 fclose($logfile); 139 } 140 141 private function getCountryCode() { 142 143 $country = ( $this->ipAddress == 'localhost' ? 'local' : 'ZZ' ); // default if no geoip is available! 144 145 $lib = $this->getConf('geoiplib'); /* which library to use? (can only be phpgeoip or disabled) */ 146 147 try { 148 149 // use GeoIP module? 150 if ($lib == 'phpgeoip' && extension_loaded('geoip') && geoip_db_avail(GEOIP_COUNTRY_EDITION)) { // Use PHP GeoIP module 151 $result = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']); 152 $country = ($result ? $result : $country); 153 } 154 } catch (Exception $e) { 155 Logger::error('BotMon Plugin: GeoIP Error', $e->getMessage()); 156 } 157 158 return $country; 159 } 160 161 private function getSessionInfo() { 162 163 $this->ipAddress = $_SERVER['REMOTE_ADDR'] ?? null; 164 if ($this->ipAddress == '127.0.0.1' || $this->ipAddress == '::1') $this->ipAddress = 'localhost'; 165 166 // what is the session identifier? 167 if (isset($_SESSION)) { 168 $sesKeys = array_keys($_SESSION); /* DokuWiki Session ID preferred */ 169 foreach ($sesKeys as $key) { 170 if (substr($key, 0, 2) == 'DW') { 171 $this->sessionId = $key; 172 $this->sessionType = 'dw'; 173 return; 174 } 175 } 176 } 177 if (!$this->sessionId) { /* no DokuWiki Session ID, try PHP session ID */ 178 $this->sessionId = session_id(); 179 $this->sessionType = 'php'; 180 } 181 if (!$this->sessionId && $this->ipAddress) { /* no PHP session ID, try IP address */ 182 $this->sessionId = $this->ipAddress; 183 $this->sessionType = 'ip'; 184 } 185 if (!$this->sessionId) { /* if everything else fails, just us a random ID */ 186 $this->sessionId = rand(1000000, 9999999); 187 $this->sessionType = 'rand'; 188 } 189 } 190}