xref: /plugin/botmon/action.php (revision f5f4ca13af25753dcbd8c359fa62b6a8ad8ce120)
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		// Override the page rendering, if a captcha needs to be displayed:
34		if ($ACT !== 'admin') {
35			$controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'showCaptcha');
36		}
37
38		// write to the log after the page content was displayed:
39		$controller->register_hook('TPL_CONTENT_DISPLAY', 'AFTER', $this, 'writeServerLog');
40
41	}
42
43	/* session information */
44	private $sessionId = null;
45	private $sessionType = '';
46
47	/**
48	 * Inserts tracking code to the page header
49	 * (only called on 'show' actions)
50	 *
51	 * @param Event $event event object by reference
52	 * @return void
53	 */
54	public function insertHeader(Event $event, $param) {
55
56		global $INFO;
57
58		// populate the session id and type:
59		$this->getSessionInfo();
60
61		// is there a user logged in?
62		$username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name']) ?  $INFO['userinfo']['name'] : '');
63
64		// build the tracker code:
65		$code = "document._botmon = {'t0': Date.now(), 'session': '" . json_encode($this->sessionId) . "'};" . NL;
66		if ($username) {
67			$code .= DOKU_TAB . DOKU_TAB . 'document._botmon.user = "' . $username . '";'. NL;
68		}
69
70		// add the deferred script loader::
71		$code .= DOKU_TAB . DOKU_TAB . "addEventListener('DOMContentLoaded', function(){" . NL;
72		$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "const e=document.createElement('script');" . NL;
73		$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "e.async=true;e.defer=true;" . NL;
74		$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "e.src='".DOKU_BASE."lib/plugins/botmon/client.js';" . NL;
75		$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "document.getElementsByTagName('head')[0].appendChild(e);" . NL;
76		$code .= DOKU_TAB . DOKU_TAB . "});";
77
78		$event->data['script'][] = ['_data' => $code];
79	}
80
81	/**
82	 * Inserts tracking code to the page header
83	 * (only called on 'show' actions)
84	 *
85	 * @param Event $event event object by reference
86	 * @return void
87	 */
88	public function insertAdminHeader(Event $event, $param) {
89
90		$event->data['link'][] = ['rel' => 'stylesheet', 'href' => DOKU_BASE.'lib/plugins/botmon/admin.css', 'defer' => 'defer'];
91		$event->data['script'][] = ['src' => DOKU_BASE.'lib/plugins/botmon/admin.js', 'defer' => 'defer', '_data' => ''];
92	}
93
94
95	/**
96	 * Writes data to the server log.
97	 *
98	 * @return void
99	 */
100	public function writeServerLog(Event $event, $param) {
101
102		global $conf;
103		global $INFO;
104
105		// is there a user logged in?
106		$username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name'])
107					?  $INFO['userinfo']['name'] : '');
108
109		// clean the page ID
110		$pageId = preg_replace('/[\x00-\x1F]/', "\u{FFFD}", $INFO['id'] ?? '');
111
112		// create the log array:
113		$logArr = Array(
114			$_SERVER['REMOTE_ADDR'], /* remote IP */
115			$pageId, /* page ID */
116			$this->sessionId, /* Session ID */
117			$this->sessionType, /* session ID type */
118			$username, /* user name */
119			$_SERVER['HTTP_USER_AGENT'] ?? '', /* User agent */
120			$_SERVER['HTTP_REFERER'] ?? '', /* HTTP Referrer */
121			substr($conf['lang'],0,2), /* page language */
122			implode(',', array_unique(array_map( function($it) { return substr(trim($it),0,2); }, explode(',',trim($_SERVER['HTTP_ACCEPT_LANGUAGE'], " \t;,*"))))), /* accepted client languages */
123			$this->getCountryCode() /* GeoIP country code */
124		);
125
126		//* create the log line */
127		$filename = __DIR__ .'/logs/' . gmdate('Y-m-d') . '.srv.txt'; /* use GMT date for filename */
128		$logline = gmdate('Y-m-d H:i:s'); /* use GMT time for log entries */
129		foreach ($logArr as $tab) {
130			$logline .= "\t" . $tab;
131		};
132
133		/* write the log line to the file */
134		$logfile = fopen($filename, 'a');
135		if (!$logfile) die();
136		if (fwrite($logfile, $logline . "\n") === false) {
137			fclose($logfile);
138			die();
139		}
140
141		/* Done */
142		fclose($logfile);
143	}
144
145	private function getCountryCode() {
146
147		$country = ( $_SERVER['REMOTE_ADDR'] == '127.0.0.1' ? 'local' : 'ZZ' ); // default if no geoip is available!
148
149		$lib = $this->getConf('geoiplib'); /* which library to use? (can only be phpgeoip or disabled) */
150
151		try {
152
153			// use GeoIP module?
154			if ($lib == 'phpgeoip' && extension_loaded('geoip') && geoip_db_avail(GEOIP_COUNTRY_EDITION)) { // Use PHP GeoIP module
155				$result = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);
156				$country = ($result ? $result : $country);
157			}
158		} catch (Exception $e) {
159			Logger::error('BotMon Plugin: GeoIP Error', $e->getMessage());
160		}
161
162		return $country;
163	}
164
165	private function getSessionInfo() {
166
167		// what is the session identifier?
168		if (isset($_SESSION)) {
169			$sesKeys = array_keys($_SESSION); /* DokuWiki Session ID preferred */
170			foreach ($sesKeys as $key) {
171				if (substr($key, 0, 2) == 'DW') {
172					$this->sessionId = $key;
173					$this->sessionType = 'dw';
174					return;
175				}
176			}
177		}
178		if (!$this->sessionId) { /* no DokuWiki Session ID, try PHP session ID */
179			$this->sessionId = session_id();
180			$this->sessionType = 'php';
181		}
182		if (!$this->sessionId) { /* no PHP session ID, try IP address */
183			$this->sessionId = $_SERVER['REMOTE_ADDR'];
184			$this->sessionType = 'ip';
185		}
186		if (!$this->sessionId) { /* if everything else fails, just us a random ID */
187			$this->sessionId = rand(1000000, 9999999);
188			$this->sessionType = 'rand';
189		}
190	}
191
192	public function showCaptcha(Event $event) {
193
194		if ($this->getConf('useCaptcha') && $this->checkCaptchaCookie()) {
195
196				$event->preventDefault(); // don't show normal content
197				$this->insertDadaFiller(); // show dada filler instead!
198				$this->insertCaptchaLoader(); // and load the captcha
199
200		} else {
201			echo '<p>Normal page.</p>';
202		}
203	}
204
205	private function checkCaptchaCookie() {
206
207		$cookieVal = isset($_COOKIE['_c_']) ? $_COOKIE['_c_'] : '';
208		$seed = $this->getConf('captchaSeed');
209
210		return ($cookieVal == $seed ? 0 : 1); // #TODO: encrypt with other data
211	}
212
213	private function insertCaptchaLoader() {
214
215	}
216
217	private function insertDadaFiller() {
218		// #TODO: make a dada filler
219
220		echo '<h1>'; tpl_pagetitle(); echo "</h1>\n";
221
222		echo '<script> alert("Hello world!"); </script>';
223
224		echo "<p>Placeholder text while the captcha is being displayed.</p>\n";
225
226
227	}
228
229}