16980370bSSascha Leib<?php 26980370bSSascha Leib 36980370bSSascha Leibuse dokuwiki\Extension\EventHandler; 46980370bSSascha Leibuse dokuwiki\Extension\Event; 55fbe88f7SSascha Leibuse dokuwiki\Logger; 66980370bSSascha Leib 76980370bSSascha Leib/** 87bd08c30SSascha Leib * Action Component for the Bot Monitoring Plugin 96980370bSSascha Leib * 106980370bSSascha Leib * @license GPL 3 (http://www.gnu.org/licenses/gpl.html) 116980370bSSascha Leib * @author Sascha Leib <sascha.leib(at)kolmio.com> 126980370bSSascha Leib */ 136980370bSSascha Leib 147bd08c30SSascha Leibclass action_plugin_botmon extends DokuWiki_Action_Plugin { 156980370bSSascha Leib 166980370bSSascha Leib /** 176980370bSSascha Leib * Registers a callback functions 186980370bSSascha Leib * 196980370bSSascha Leib * @param EventHandler $controller DokuWiki's event controller object 206980370bSSascha Leib * @return void 216980370bSSascha Leib */ 226980370bSSascha Leib public function register(EventHandler $controller) { 23*5f2c1759SSascha Leib 24*5f2c1759SSascha Leib // insert header data into the page: 256980370bSSascha Leib $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertHeader'); 26*5f2c1759SSascha Leib 27*5f2c1759SSascha Leib // write to the log after the page content was displayed: 28*5f2c1759SSascha Leib $controller->register_hook('TPL_CONTENT_DISPLAY', 'AFTER', $this, 'writeServerLog'); 29*5f2c1759SSascha Leib 306980370bSSascha Leib } 316980370bSSascha Leib 32b148c85eSSascha Leib /* session information */ 33f6a7ebc1SSascha Leib private $sessionId = null; 34f6a7ebc1SSascha Leib private $sessionType = ''; 35*5f2c1759SSascha Leib private $ipAddress = null; 36b148c85eSSascha Leib 376980370bSSascha Leib /** 386980370bSSascha Leib * Inserts tracking code to the page header 396980370bSSascha Leib * 406980370bSSascha Leib * @param Event $event event object by reference 416980370bSSascha Leib * @return void 426980370bSSascha Leib */ 436980370bSSascha Leib public function insertHeader(Event $event, $param) { 446980370bSSascha Leib 456980370bSSascha Leib global $INFO; 466980370bSSascha Leib 47b148c85eSSascha Leib // populate the session id and type: 48b148c85eSSascha Leib $this->getSessionInfo(); 49b148c85eSSascha Leib 506980370bSSascha Leib // is there a user logged in? 51*5f2c1759SSascha Leib $username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name']) ? $INFO['userinfo']['name'] : ''); 526980370bSSascha Leib 536980370bSSascha Leib // build the tracker code: 54b148c85eSSascha Leib $code = NL . DOKU_TAB . "document._botmon = {'t0': Date.now(), 'session': '" . json_encode($this->sessionId) . "'};" . NL; 556980370bSSascha Leib if ($username) { 567bd08c30SSascha Leib $code .= DOKU_TAB . 'document._botmon.user = "' . $username . '";'. NL; 576980370bSSascha Leib } 586980370bSSascha Leib 59*5f2c1759SSascha Leib // add the deferred script loader:: 60*5f2c1759SSascha Leib $code .= DOKU_TAB . "addEventListener('load', function(){" . NL; 616980370bSSascha Leib $code .= DOKU_TAB . DOKU_TAB . "const e=document.createElement('script');" . NL; 626980370bSSascha Leib $code .= DOKU_TAB . DOKU_TAB . "e.async=true;e.defer=true;" . NL; 637bd08c30SSascha Leib $code .= DOKU_TAB . DOKU_TAB . "e.src='".DOKU_BASE."lib/plugins/botmon/client.js';" . NL; 646980370bSSascha Leib $code .= DOKU_TAB . DOKU_TAB . "document.getElementsByTagName('head')[0].appendChild(e);" . NL; 651c16f1b7SSascha Leib $code .= DOKU_TAB . "});" . NL . DOKU_TAB; 666980370bSSascha Leib 67*5f2c1759SSascha Leib $event->data['script'][] = ['_data' => $code]; 68451abfadSSascha Leib } 69451abfadSSascha Leib 70451abfadSSascha Leib /** 71451abfadSSascha Leib * Writes data to the server log. 72451abfadSSascha Leib * 73451abfadSSascha Leib * @return void 74451abfadSSascha Leib */ 75*5f2c1759SSascha Leib public function writeServerLog(Event $event, $param) { 76451abfadSSascha Leib 77451abfadSSascha Leib global $conf; 78451abfadSSascha Leib global $INFO; 79091b5998SSascha Leib 80*5f2c1759SSascha Leib // is there a user logged in? 81*5f2c1759SSascha Leib $username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name']) 82*5f2c1759SSascha Leib ? $INFO['userinfo']['name'] : ''); 83*5f2c1759SSascha Leib 84*5f2c1759SSascha Leib 85*5f2c1759SSascha Leib 86b2e3bd8bSSascha Leib // clean the page ID 87b2e3bd8bSSascha Leib $pageId = preg_replace('/[\x00-\x1F]/', "\u{FFFD}", $INFO['id'] ?? ''); 88b2e3bd8bSSascha Leib 89451abfadSSascha Leib // create the log array: 90cf9f7fe8SSascha Leib $logArr = Array( 91*5f2c1759SSascha Leib $this->ipAddress, /* remote IP */ 92b2e3bd8bSSascha Leib $pageId, /* page ID */ 93b148c85eSSascha Leib $this->sessionId, /* Session ID */ 94b148c85eSSascha Leib $this->sessionType, /* session ID type */ 95*5f2c1759SSascha Leib $username, /* user name */ 962f2bc93aSSascha Leib $_SERVER['HTTP_USER_AGENT'] ?? '', /* User agent */ 97451abfadSSascha Leib $_SERVER['HTTP_REFERER'] ?? '', /* HTTP Referrer */ 98451abfadSSascha Leib substr($conf['lang'],0,2), /* page language */ 995fbe88f7SSascha Leib implode(',', array_unique(array_map( function($it) { return substr($it,0,2); }, explode(',',trim($_SERVER['HTTP_ACCEPT_LANGUAGE'], " \t;,*"))))), /* accepted client languages */ 100*5f2c1759SSascha Leib $this->getCountryCode() /* GeoIP country code */ 101cf9f7fe8SSascha Leib ); 102cf9f7fe8SSascha Leib 103cf9f7fe8SSascha Leib //* create the log line */ 1044cddc661SSascha Leib $filename = __DIR__ .'/logs/' . gmdate('Y-m-d') . '.srv.txt'; /* use GMT date for filename */ 105cf9f7fe8SSascha Leib $logline = gmdate('Y-m-d H:i:s'); /* use GMT time for log entries */ 106cf9f7fe8SSascha Leib foreach ($logArr as $tab) { 107cf9f7fe8SSascha Leib $logline .= "\t" . $tab; 108cf9f7fe8SSascha Leib }; 109cf9f7fe8SSascha Leib 110cf9f7fe8SSascha Leib /* write the log line to the file */ 111cf9f7fe8SSascha Leib $logfile = fopen($filename, 'a'); 112cf9f7fe8SSascha Leib if (!$logfile) die(); 113cf9f7fe8SSascha Leib if (fwrite($logfile, $logline . "\n") === false) { 114cf9f7fe8SSascha Leib fclose($logfile); 115cf9f7fe8SSascha Leib die(); 1166980370bSSascha Leib } 117cf9f7fe8SSascha Leib 118cf9f7fe8SSascha Leib /* Done */ 119cf9f7fe8SSascha Leib fclose($logfile); 120cf9f7fe8SSascha Leib } 121b148c85eSSascha Leib 122*5f2c1759SSascha Leib private function getCountryCode() { 123*5f2c1759SSascha Leib 124*5f2c1759SSascha Leib $country = ( $this->ipAddress == 'localhost' ? 'AA' : 'ZZ' ); // default if no geoip is available! 125*5f2c1759SSascha Leib 126*5f2c1759SSascha Leib $lib = $this->getConf('geoiplib'); /* which library to use? (can only be phpgeoip or disabled) */ 127*5f2c1759SSascha Leib 128*5f2c1759SSascha Leib try { 129*5f2c1759SSascha Leib 130*5f2c1759SSascha Leib // use GeoIP module? 131*5f2c1759SSascha Leib if ($lib == 'phpgeoip' && extension_loaded('geoip') && geoip_db_avail(GEOIP_COUNTRY_EDITION)) { // Use PHP GeoIP module 132*5f2c1759SSascha Leib $result = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']); 133*5f2c1759SSascha Leib $country = ($result ? $result : $country); 134*5f2c1759SSascha Leib } 135*5f2c1759SSascha Leib } catch (Exception $e) { 136*5f2c1759SSascha Leib Logger::error('BotMon Plugin: GeoIP Error', $e->getMessage()); 137*5f2c1759SSascha Leib } 138*5f2c1759SSascha Leib 139*5f2c1759SSascha Leib return $country; 140*5f2c1759SSascha Leib } 141*5f2c1759SSascha Leib 142b148c85eSSascha Leib private function getSessionInfo() { 143b148c85eSSascha Leib 144*5f2c1759SSascha Leib $this->ipAddress = $_SERVER['REMOTE_ADDR'] ?? null; 145*5f2c1759SSascha Leib if ($this->ipAddress == '127.0.0.1' || $this->ipAddress == '::1') $this->ipAddress = 'localhost'; 146*5f2c1759SSascha Leib 147b148c85eSSascha Leib // what is the session identifier? 148b148c85eSSascha Leib if (isset($_SESSION)) { 149b148c85eSSascha Leib $sesKeys = array_keys($_SESSION); /* DokuWiki Session ID preferred */ 150b148c85eSSascha Leib foreach ($sesKeys as $key) { 151b148c85eSSascha Leib if (substr($key, 0, 2) == 'DW') { 152b148c85eSSascha Leib $this->sessionId = $key; 153b148c85eSSascha Leib $this->sessionType = 'dw'; 154b148c85eSSascha Leib return; 155b148c85eSSascha Leib } 156b148c85eSSascha Leib } 157b148c85eSSascha Leib } 158f6a7ebc1SSascha Leib if (!$this->sessionId) { /* no DokuWiki Session ID, try PHP session ID */ 159b148c85eSSascha Leib $this->sessionId = session_id(); 160b148c85eSSascha Leib $this->sessionType = 'php'; 161b148c85eSSascha Leib } 162*5f2c1759SSascha Leib if (!$this->sessionId && $this->ipAddress) { /* no PHP session ID, try IP address */ 163*5f2c1759SSascha Leib $this->sessionId = $this->ipAddress; 164b148c85eSSascha Leib $this->sessionType = 'ip'; 165b148c85eSSascha Leib } 166f6a7ebc1SSascha Leib if (!$this->sessionId) { /* if everything else fails, just us a random ID */ 167b148c85eSSascha Leib $this->sessionId = rand(1000000, 9999999); 168b148c85eSSascha Leib $this->sessionType = 'rand'; 169b148c85eSSascha Leib } 170b148c85eSSascha Leib } 1716980370bSSascha Leib}