xref: /plugin/botmon/action.php (revision 0edf1a5674fab8bbda861d077231c188a562e705)
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}