xref: /plugin/botmon/action.php (revision 19b69b647da12be70cee3430601d5917bad20dca)
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
16*19b69b64SSascha Leib	public function __construct() {
17*19b69b64SSascha Leib
18*19b69b64SSascha Leib		// determine if a captcha should be loaded:
19*19b69b64SSascha Leib		$this->showCaptcha = 'Z';
20*19b69b64SSascha Leib
21*19b69b64SSascha Leib		$useCaptcha = $this->getConf('useCaptcha'); // should we show a captcha?
22*19b69b64SSascha Leib
23*19b69b64SSascha Leib		if ($useCaptcha !== 'disabled') {
24*19b69b64SSascha Leib			if ($_SERVER['REQUEST_METHOD'] == 'HEAD') {
25*19b69b64SSascha Leib				$this->showCaptcha = 'H'; // Method is HEAD, no need for captcha
26*19b69b64SSascha Leib			} elseif ($this->captchaWhitelisted()) {
27*19b69b64SSascha Leib				$this->showCaptcha = 'W'; // IP is whitelisted, no captcha
28*19b69b64SSascha Leib			} elseif ($this->hasCaptchaCookie()) {
29*19b69b64SSascha Leib				$this->showCaptcha = 'N'; // No, user already has a cookie, don't show the captcha
30*19b69b64SSascha Leib			} else {
31*19b69b64SSascha Leib				$this->showCaptcha = 'Y'; // Yes, show the captcha
32*19b69b64SSascha Leib			}
33*19b69b64SSascha Leib		}
34*19b69b64SSascha Leib	}
35*19b69b64SSascha Leib
366980370bSSascha Leib	/**
376980370bSSascha Leib	 * Registers a callback functions
386980370bSSascha Leib	 *
396980370bSSascha Leib	 * @param EventHandler $controller DokuWiki's event controller object
406980370bSSascha Leib	 * @return void
416980370bSSascha Leib	 */
426980370bSSascha Leib	public function register(EventHandler $controller) {
435f2c1759SSascha Leib
44e56d7b71SSascha Leib		global $ACT;
45e56d7b71SSascha Leib
46447b8b4fSSascha Leib		// populate the session id and type:
47447b8b4fSSascha Leib		$this->setSessionInfo();
48d49ab213SSascha Leib
495f2c1759SSascha Leib		// insert header data into the page:
50d49ab213SSascha Leib		if ($ACT == 'show' || $ACT == 'edit' || $ACT == 'media') {
516980370bSSascha Leib			$controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertHeader');
52d49ab213SSascha Leib
53d49ab213SSascha Leib			// Override the page rendering, if a captcha needs to be displayed:
54393de67cSSascha Leib			$controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'insertCaptchaCode');
55d49ab213SSascha Leib
56e56d7b71SSascha Leib		} else if ($ACT == 'admin' && isset($_REQUEST['page']) && $_REQUEST['page'] == 'botmon') {
57e56d7b71SSascha Leib			$controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertAdminHeader');
58e56d7b71SSascha Leib		}
595f2c1759SSascha Leib
60d49ab213SSascha Leib		// also show a captcha before the image preview
61d49ab213SSascha Leib		$controller->register_hook('TPL_IMG_DISPLAY', 'BEFORE', $this, 'showImageCaptcha');
62f5f4ca13SSascha Leib
635f2c1759SSascha Leib		// write to the log after the page content was displayed:
645f2c1759SSascha Leib		$controller->register_hook('TPL_CONTENT_DISPLAY', 'AFTER', $this, 'writeServerLog');
655f2c1759SSascha Leib
666980370bSSascha Leib	}
676980370bSSascha Leib
68b148c85eSSascha Leib	/* session information */
69f6a7ebc1SSascha Leib	private $sessionId = null;
70f6a7ebc1SSascha Leib	private $sessionType = '';
71393de67cSSascha Leib	private $showCaptcha = 'X';
72b148c85eSSascha Leib
736980370bSSascha Leib	/**
746980370bSSascha Leib	 * Inserts tracking code to the page header
75e56d7b71SSascha Leib	 * (only called on 'show' actions)
766980370bSSascha Leib	 *
776980370bSSascha Leib	 * @param Event $event event object by reference
786980370bSSascha Leib	 * @return void
796980370bSSascha Leib	 */
806980370bSSascha Leib	public function insertHeader(Event $event, $param) {
816980370bSSascha Leib
826980370bSSascha Leib		global $INFO;
836980370bSSascha Leib
84b148c85eSSascha Leib
856980370bSSascha Leib		// build the tracker code:
86d49ab213SSascha Leib		$code = $this->getBMHeader();
876980370bSSascha Leib
885f2c1759SSascha Leib		// add the deferred script loader::
89e56d7b71SSascha Leib		$code .= DOKU_TAB . DOKU_TAB . "addEventListener('DOMContentLoaded', function(){" . NL;
90e56d7b71SSascha Leib		$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "const e=document.createElement('script');" . NL;
91e56d7b71SSascha Leib		$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "e.async=true;e.defer=true;" . NL;
92e56d7b71SSascha Leib		$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "e.src='".DOKU_BASE."lib/plugins/botmon/client.js';" . NL;
93e56d7b71SSascha Leib		$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "document.getElementsByTagName('head')[0].appendChild(e);" . NL;
94e56d7b71SSascha Leib		$code .= DOKU_TAB . DOKU_TAB . "});";
955f2c1759SSascha Leib		$event->data['script'][] = ['_data' => $code];
96451abfadSSascha Leib	}
97451abfadSSascha Leib
98d49ab213SSascha Leib	/* create the BM object code for insertion into a script element: */
99d49ab213SSascha Leib	private function getBMHeader() {
100d49ab213SSascha Leib
101d49ab213SSascha Leib		// build the tracker code:
102d49ab213SSascha Leib		$code = DOKU_TAB . DOKU_TAB . "document._botmon = {t0: Date.now(), session: " . json_encode($this->sessionId) . ", seed: " . json_encode($this->getConf('captchaSeed')) . ", ip: " . json_encode($_SERVER['REMOTE_ADDR']) . "};" . NL;
103d49ab213SSascha Leib
104d49ab213SSascha Leib		// is there a user logged in?
105d49ab213SSascha Leib		$username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name']) ?  $INFO['userinfo']['name'] : '');
106d49ab213SSascha Leib		if ($username) {
107d49ab213SSascha Leib			$code .= DOKU_TAB . DOKU_TAB . 'document._botmon.user = "' . $username . '";'. NL;
108d49ab213SSascha Leib		}
109d49ab213SSascha Leib
110d49ab213SSascha Leib		return $code;
111d49ab213SSascha Leib
112d49ab213SSascha Leib	}
113d49ab213SSascha Leib
114451abfadSSascha Leib	/**
115e56d7b71SSascha Leib	 * Inserts tracking code to the page header
116e56d7b71SSascha Leib	 * (only called on 'show' actions)
117e56d7b71SSascha Leib	 *
118e56d7b71SSascha Leib	 * @param Event $event event object by reference
119e56d7b71SSascha Leib	 * @return void
120e56d7b71SSascha Leib	 */
121e56d7b71SSascha Leib	public function insertAdminHeader(Event $event, $param) {
122e56d7b71SSascha Leib
123e56d7b71SSascha Leib		$event->data['link'][] = ['rel' => 'stylesheet', 'href' => DOKU_BASE.'lib/plugins/botmon/admin.css', 'defer' => 'defer'];
1240edf1a56SSascha Leib		$event->data['script'][] = ['src' => DOKU_BASE.'lib/plugins/botmon/admin.js', 'defer' => 'defer', '_data' => ''];
125e56d7b71SSascha Leib	}
126e56d7b71SSascha Leib
127e56d7b71SSascha Leib	/**
128451abfadSSascha Leib	 * Writes data to the server log.
129451abfadSSascha Leib	 *
130451abfadSSascha Leib	 * @return void
131451abfadSSascha Leib	 */
1325f2c1759SSascha Leib	public function writeServerLog(Event $event, $param) {
133451abfadSSascha Leib
134451abfadSSascha Leib		global $conf;
135451abfadSSascha Leib		global $INFO;
136091b5998SSascha Leib
1375f2c1759SSascha Leib		// is there a user logged in?
1385f2c1759SSascha Leib		$username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name'])
1395f2c1759SSascha Leib					?  $INFO['userinfo']['name'] : '');
1405f2c1759SSascha Leib
141b2e3bd8bSSascha Leib		// clean the page ID
142b2e3bd8bSSascha Leib		$pageId = preg_replace('/[\x00-\x1F]/', "\u{FFFD}", $INFO['id'] ?? '');
143b2e3bd8bSSascha Leib
144451abfadSSascha Leib		// create the log array:
145cf9f7fe8SSascha Leib		$logArr = Array(
146f5f4ca13SSascha Leib			$_SERVER['REMOTE_ADDR'], /* remote IP */
147b2e3bd8bSSascha Leib			$pageId, /* page ID */
148b148c85eSSascha Leib			$this->sessionId, /* Session ID */
149b148c85eSSascha Leib			$this->sessionType, /* session ID type */
1505f2c1759SSascha Leib			$username, /* user name */
1512f2bc93aSSascha Leib			$_SERVER['HTTP_USER_AGENT'] ?? '', /* User agent */
152451abfadSSascha Leib			$_SERVER['HTTP_REFERER'] ?? '', /* HTTP Referrer */
153451abfadSSascha Leib			substr($conf['lang'],0,2), /* page language */
154a93de874SSascha Leib			implode(',', array_unique(array_map( function($it) { return substr(trim($it),0,2); }, explode(',',trim($_SERVER['HTTP_ACCEPT_LANGUAGE'], " \t;,*"))))), /* accepted client languages */
1552c641262SSascha Leib			$this->getCountryCode(), /* GeoIP country code */
156*19b69b64SSascha Leib			$this->showCaptcha /* show captcha? */
157d49ab213SSascha Leib		);
158cf9f7fe8SSascha Leib
159cf9f7fe8SSascha Leib		//* create the log line */
1604cddc661SSascha Leib		$filename = __DIR__ .'/logs/' . gmdate('Y-m-d') . '.srv.txt'; /* use GMT date for filename */
161cf9f7fe8SSascha Leib		$logline = gmdate('Y-m-d H:i:s'); /* use GMT time for log entries */
162cf9f7fe8SSascha Leib		foreach ($logArr as $tab) {
163cf9f7fe8SSascha Leib			$logline .= "\t" . $tab;
164cf9f7fe8SSascha Leib		};
165cf9f7fe8SSascha Leib
166cf9f7fe8SSascha Leib		/* write the log line to the file */
167cf9f7fe8SSascha Leib		$logfile = fopen($filename, 'a');
168cf9f7fe8SSascha Leib		if (!$logfile) die();
169cf9f7fe8SSascha Leib		if (fwrite($logfile, $logline . "\n") === false) {
170cf9f7fe8SSascha Leib			fclose($logfile);
171cf9f7fe8SSascha Leib			die();
1726980370bSSascha Leib		}
173cf9f7fe8SSascha Leib
174cf9f7fe8SSascha Leib		/* Done */
175cf9f7fe8SSascha Leib		fclose($logfile);
176cf9f7fe8SSascha Leib	}
177b148c85eSSascha Leib
1785f2c1759SSascha Leib	private function getCountryCode() {
1795f2c1759SSascha Leib
180f5f4ca13SSascha Leib		$country = ( $_SERVER['REMOTE_ADDR'] == '127.0.0.1' ? 'local' : 'ZZ' ); // default if no geoip is available!
1815f2c1759SSascha Leib
1825f2c1759SSascha Leib		$lib = $this->getConf('geoiplib'); /* which library to use? (can only be phpgeoip or disabled) */
1835f2c1759SSascha Leib
1845f2c1759SSascha Leib		try {
1855f2c1759SSascha Leib
1865f2c1759SSascha Leib			// use GeoIP module?
1875f2c1759SSascha Leib			if ($lib == 'phpgeoip' && extension_loaded('geoip') && geoip_db_avail(GEOIP_COUNTRY_EDITION)) { // Use PHP GeoIP module
1885f2c1759SSascha Leib				$result = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);
1895f2c1759SSascha Leib				$country = ($result ? $result : $country);
1905f2c1759SSascha Leib			}
1915f2c1759SSascha Leib		} catch (Exception $e) {
1925f2c1759SSascha Leib			Logger::error('BotMon Plugin: GeoIP Error', $e->getMessage());
1935f2c1759SSascha Leib		}
1945f2c1759SSascha Leib
1955f2c1759SSascha Leib		return $country;
1965f2c1759SSascha Leib	}
1975f2c1759SSascha Leib
198447b8b4fSSascha Leib	private function setSessionInfo() {
199b148c85eSSascha Leib
200b148c85eSSascha Leib		// what is the session identifier?
201b148c85eSSascha Leib		if (isset($_SESSION)) {
202b148c85eSSascha Leib			$sesKeys = array_keys($_SESSION); /* DokuWiki Session ID preferred */
203b148c85eSSascha Leib			foreach ($sesKeys as $key) {
204b148c85eSSascha Leib				if (substr($key, 0, 2) == 'DW') {
205b148c85eSSascha Leib					$this->sessionId = $key;
206b148c85eSSascha Leib					$this->sessionType = 'dw';
207b148c85eSSascha Leib					return;
208b148c85eSSascha Leib				}
209b148c85eSSascha Leib			}
210b148c85eSSascha Leib		}
211f6a7ebc1SSascha Leib		if (!$this->sessionId) { /* no DokuWiki Session ID, try PHP session ID */
212b148c85eSSascha Leib			$this->sessionId = session_id();
213b148c85eSSascha Leib			$this->sessionType = 'php';
214b148c85eSSascha Leib		}
215f5f4ca13SSascha Leib		if (!$this->sessionId) { /* no PHP session ID, try IP address */
216f5f4ca13SSascha Leib			$this->sessionId = $_SERVER['REMOTE_ADDR'];
217b148c85eSSascha Leib			$this->sessionType = 'ip';
218b148c85eSSascha Leib		}
219447b8b4fSSascha Leib
220447b8b4fSSascha Leib		if (!$this->sessionId) { /* if all fails, use random data */
221447b8b4fSSascha Leib			$this->sessionId = rand(100000000, 999999999);
222447b8b4fSSascha Leib			$this->sessionType = 'rnd';
223447b8b4fSSascha Leib		}
224447b8b4fSSascha Leib
225b148c85eSSascha Leib	}
226f5f4ca13SSascha Leib
227393de67cSSascha Leib	public function insertCaptchaCode(Event $event) {
228f5f4ca13SSascha Leib
229*19b69b64SSascha Leib		$useCaptcha = $this->getConf('useCaptcha'); // which background to show?
230f5f4ca13SSascha Leib
231*19b69b64SSascha Leib		// only if we previously determined that we need a captcha:
232*19b69b64SSascha Leib		if ($this->showCaptcha == 'Y') {
2332c641262SSascha Leib
23412993035SSascha Leib			echo '<h1 class="sectionedit1">'; tpl_pagetitle(); echo "</h1>\n"; // always show the original page title
235f5f4ca13SSascha Leib			$event->preventDefault(); // don't show normal content
23612993035SSascha Leib			switch ($useCaptcha) {
237393de67cSSascha Leib				case 'loremipsum':
238393de67cSSascha Leib					$this->insertLoremIpsum();  // show dada filler instead of text
23912993035SSascha Leib					break;
24012993035SSascha Leib				case 'dada':
24112993035SSascha Leib					$this->insertDadaFiller();  // show dada filler instead of text
24212993035SSascha Leib					break;
24312993035SSascha Leib			}
244d49ab213SSascha Leib
245*19b69b64SSascha Leib			// insert the captcha loader code:
246*19b69b64SSascha Leib			echo '<script>' . NL;
247*19b69b64SSascha Leib
248*19b69b64SSascha Leib			// add the deferred script loader::
249*19b69b64SSascha Leib			echo  DOKU_TAB . "addEventListener('DOMContentLoaded', function(){" . NL;
250*19b69b64SSascha Leib			echo  DOKU_TAB . DOKU_TAB . "const cj=document.createElement('script');" . NL;
251*19b69b64SSascha Leib			echo  DOKU_TAB . DOKU_TAB . "cj.async=true;cj.defer=true;cj.type='text/javascript';" . NL;
252*19b69b64SSascha Leib			echo  DOKU_TAB . DOKU_TAB . "cj.src='".DOKU_BASE."lib/plugins/botmon/captcha.js';" . NL;
253*19b69b64SSascha Leib			echo  DOKU_TAB . DOKU_TAB . "document.getElementsByTagName('head')[0].appendChild(cj);" . NL;
254*19b69b64SSascha Leib			echo  DOKU_TAB . "});";
255*19b69b64SSascha Leib
256*19b69b64SSascha Leib			// add the translated strings for the captcha:
257*19b69b64SSascha Leib			echo  DOKU_TAB . '$BMLocales = {' . NL;
258*19b69b64SSascha Leib			echo  DOKU_TAB . DOKU_TAB . '"dlgTitle": ' . json_encode($this->getLang('bm_dlgTitle')) . ',' . NL;
259*19b69b64SSascha Leib			echo  DOKU_TAB . DOKU_TAB . '"dlgSubtitle": ' . json_encode($this->getLang('bm_dlgSubtitle')) . ',' . NL;
260*19b69b64SSascha Leib			echo  DOKU_TAB . DOKU_TAB . '"dlgConfirm": ' . json_encode($this->getLang('bm_dlgConfirm')) . ',' . NL;
261*19b69b64SSascha Leib			echo  DOKU_TAB . DOKU_TAB . '"dlgChecking": ' . json_encode($this->getLang('bm_dlgChecking')) . ',' . NL;
262*19b69b64SSascha Leib			echo  DOKU_TAB . DOKU_TAB . '"dlgLoading": ' . json_encode($this->getLang('bm_dlgLoading')) . ',' . NL;
263*19b69b64SSascha Leib			echo  DOKU_TAB . DOKU_TAB . '"dlgError": ' . json_encode($this->getLang('bm_dlgError')) . ',' . NL;
264*19b69b64SSascha Leib			echo  DOKU_TAB . '};' . NL;
265*19b69b64SSascha Leib
266*19b69b64SSascha Leib			echo '</script>' . NL;
267*19b69b64SSascha Leib		}
268d49ab213SSascha Leib	}
269f5f4ca13SSascha Leib
270d49ab213SSascha Leib	public function showImageCaptcha(Event $event, $param) {
271d49ab213SSascha Leib
272d49ab213SSascha Leib		$useCaptcha = $this->getConf('useCaptcha');
273d49ab213SSascha Leib
274d49ab213SSascha Leib		echo '<script>' . $this->getBMHeader($event, $param) . '</script>';
275d49ab213SSascha Leib
276d49ab213SSascha Leib		$cCode = '-';
277d49ab213SSascha Leib		if ($useCaptcha !== 'disabled') {
278d49ab213SSascha Leib			if ($this->captchaWhitelisted()) {
279d49ab213SSascha Leib				$cCode = 'W'; // whitelisted
280d49ab213SSascha Leib			}
281d49ab213SSascha Leib			elseif ($this->hasCaptchaCookie()) {
282d49ab213SSascha Leib				$cCode  = 'N'; // user already has a cookie
283d49ab213SSascha Leib			}
284d49ab213SSascha Leib			else {
285d49ab213SSascha Leib				$cCode  = 'Y'; // show the captcha
286d49ab213SSascha Leib
287d49ab213SSascha Leib				echo '<svg width="100%" height="100%" viewBox="0 0 800 400" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M1,1l798,398" style="fill:none;stroke:#f00;stroke-width:1px;"/><path d="M1,399l798,-398" style="fill:none;stroke:#f00;stroke-width:1px;"/><rect x="1" y="1" width="798" height="398" style="fill:none;stroke:#000;stroke-width:1px;"/></svg>'; // placeholder image
288d49ab213SSascha Leib				$event->preventDefault(); // don't show normal content
289d49ab213SSascha Leib
290d49ab213SSascha Leib				// TODO Insert dummy image
291d49ab213SSascha Leib				$this->insertCaptchaLoader(); // and load the captcha
292d49ab213SSascha Leib			}
293d49ab213SSascha Leib		};
294d49ab213SSascha Leib
295d49ab213SSascha Leib		$this->showCaptcha = $cCode; // store the captcha code for the logfile
296d49ab213SSascha Leib	}
297d49ab213SSascha Leib
298d49ab213SSascha Leib	private function hasCaptchaCookie() {
299f5f4ca13SSascha Leib
300cdc02cd4SSascha Leib		$cookieVal = isset($_COOKIE['DWConfirm']) ? $_COOKIE['DWConfirm'] : null;
301f5f4ca13SSascha Leib
302cdc02cd4SSascha Leib		$today = substr((new DateTime())->format('c'), 0, 10);
30312993035SSascha Leib
304cdc02cd4SSascha Leib		$raw = $this->getConf('captchaSeed') . '|' . $_SERVER['SERVER_NAME'] . '|' . $_SERVER['REMOTE_ADDR'] . '|' . $today;
305cdc02cd4SSascha Leib		$expected = hash('sha256', $raw);
30612993035SSascha Leib
307cdc02cd4SSascha Leib		//echo '<ul><li>cookie: ' . $cookieVal . '</li><li>expected: ' . $expected . '</li><li>matches: ' .($cookieVal == $expected ? 'true' : 'false') . '</li></ul>';
308cdc02cd4SSascha Leib
309d49ab213SSascha Leib		return $cookieVal == $expected;
310f5f4ca13SSascha Leib	}
311f5f4ca13SSascha Leib
3122c641262SSascha Leib	// check if the visitor's IP is on a whitelist:
3132c641262SSascha Leib	private function captchaWhitelisted() {
3142c641262SSascha Leib
3152c641262SSascha Leib		// normalise IP address:
3162c641262SSascha Leib		$ip = inet_pton($_SERVER['REMOTE_ADDR']);
3172c641262SSascha Leib
3182c641262SSascha Leib		// find which file to open:
3192c641262SSascha Leib		$prefixes = ['user', 'default'];
3202c641262SSascha Leib		foreach ($prefixes as $pre) {
3212c641262SSascha Leib			$filename = __DIR__ .'/config/' . $pre . '-whitelist.txt';
3222c641262SSascha Leib			if (file_exists($filename)) {
3232c641262SSascha Leib				break;
3242c641262SSascha Leib			}
3252c641262SSascha Leib		}
3262c641262SSascha Leib
3272c641262SSascha Leib		if (file_exists($filename)) {
3282c641262SSascha Leib			$lines = file($filename, FILE_SKIP_EMPTY_LINES);
3292c641262SSascha Leib			foreach ($lines as $line) {
3302c641262SSascha Leib				if (trim($line) !== '' && !str_starts_with($line, '#')) {
3312c641262SSascha Leib					$col = explode("\t", $line);
3322c641262SSascha Leib					if (count($col) >= 2) {
3332c641262SSascha Leib						$from = inet_pton($col[0]);
3342c641262SSascha Leib						$to = inet_pton($col[1]);
3352c641262SSascha Leib
3362c641262SSascha Leib						if ($ip >= $from && $ip <= $to) {
337d49ab213SSascha Leib							return true; /* IP whitelisted */
3382c641262SSascha Leib						}
3392c641262SSascha Leib					}
3402c641262SSascha Leib				}
3412c641262SSascha Leib			}
3422c641262SSascha Leib		}
343d49ab213SSascha Leib		return false; /* IP not found in whitelist */
3442c641262SSascha Leib	}
3452c641262SSascha Leib
34612993035SSascha Leib	// inserts a blank box to ensure there is enough space for the captcha:
347393de67cSSascha Leib	private function insertLoremIpsum() {
34812993035SSascha Leib
349393de67cSSascha Leib		echo '<div class="level1">' . NL;
350393de67cSSascha Leib		echo '<p>' . NL . 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'. NL . '</p>' . NL;
351393de67cSSascha Leib		echo '<p>' . NL . 'At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga.'. NL . '</p>' . NL;
352393de67cSSascha Leib		echo '</div>' . NL;
353393de67cSSascha Leib
35412993035SSascha Leib	}
35512993035SSascha Leib
35612993035SSascha Leib	/* Generates a few paragraphs of Dada text to show instead of the article content */
357f5f4ca13SSascha Leib	private function insertDadaFiller() {
358f5f4ca13SSascha Leib
35912993035SSascha Leib		global $conf;
36012993035SSascha Leib		global $TOC;
36112993035SSascha Leib		global $ID;
362f5f4ca13SSascha Leib
36312993035SSascha Leib		// list of languages to search for the wordlist
36412993035SSascha Leib		$langs = array_unique([$conf['lang'], 'la']);
365f5f4ca13SSascha Leib
36612993035SSascha Leib		// find path to the first available wordlist:
36712993035SSascha Leib		foreach ($langs as $lang) {
36812993035SSascha Leib			$filename = __DIR__ .'/lang/' . $lang . '/wordlist.txt'; /* language-specific wordlist */
36912993035SSascha Leib			if (file_exists($filename)) {
37012993035SSascha Leib				break;
37112993035SSascha Leib			}
37212993035SSascha Leib		}
373f5f4ca13SSascha Leib
37412993035SSascha Leib		// load the wordlist file:
37512993035SSascha Leib		if (file_exists($filename)) {
37612993035SSascha Leib			$words = array();
37712993035SSascha Leib			$totalWeight = 0;
37812993035SSascha Leib			$lines = file($filename, FILE_SKIP_EMPTY_LINES);
37912993035SSascha Leib			foreach ($lines as $line) {
38012993035SSascha Leib				$arr = explode("\t", $line);
38112993035SSascha Leib				$arr[1] = ( count($arr) > 1 ? (int) trim($arr[1]) : 1 );
38212993035SSascha Leib				$totalWeight += (int) $arr[1];
38312993035SSascha Leib				array_push($words, $arr);
38412993035SSascha Leib			}
38512993035SSascha Leib		} else {
38612993035SSascha Leib			echo '<script> console.log("Can’t generate filler text: wordlist file not found!"); </script>';
38712993035SSascha Leib			return;
38812993035SSascha Leib		}
389f5f4ca13SSascha Leib
39012993035SSascha Leib		// If a TOC exists, use it for the headlines:
39112993035SSascha Leib		if(is_array($TOC)) {
39212993035SSascha Leib			$toc = $TOC;
39312993035SSascha Leib		} else {
39412993035SSascha Leib			$meta = p_get_metadata($ID, '', METADATA_RENDER_USING_CACHE);
39512993035SSascha Leib			//$tocok = (isset($meta['internal']['toc']) ? $meta['internal']['toc'] : $tocok = true);
39612993035SSascha Leib			$toc = isset($meta['description']['tableofcontents']) ? $meta['description']['tableofcontents'] : null;
39712993035SSascha Leib		}
39812993035SSascha Leib		if (!$toc) { // no TOC, generate my own:
39912993035SSascha Leib			$hlCount = mt_rand(0, (int) $conf['tocminheads']);
40012993035SSascha Leib			$toc = array();
40112993035SSascha Leib			for ($i=0; $i<$hlCount; $i++) {
40212993035SSascha Leib				array_push($toc, $this->dadaMakeHeadline($words, $totalWeight)); // $toc
40312993035SSascha Leib			}
40412993035SSascha Leib		}
40512993035SSascha Leib
40612993035SSascha Leib		// if H1 heading is not in the TOC, add a chappeau section:
40712993035SSascha Leib		$chapeauCount = mt_rand(1, 3);
40812993035SSascha Leib		if ((int) $conf['toptoclevel'] > 1) {
40912993035SSascha Leib			echo "<div class=\"level1\">\n";
41012993035SSascha Leib			for ($i=0; $i<$chapeauCount; $i++) {
41112993035SSascha Leib				echo $this->dadaMakeParagraph($words, $totalWeight);
41212993035SSascha Leib			}
41312993035SSascha Leib			echo "</div>\n";
41412993035SSascha Leib		}
41512993035SSascha Leib
41612993035SSascha Leib		//  text sections for each sub-headline:
41712993035SSascha Leib		foreach ($toc as $hl) {
41812993035SSascha Leib			echo $this->dadaMakeSection($words, $totalWeight, $hl);
41912993035SSascha Leib		}
42012993035SSascha Leib	}
42112993035SSascha Leib
42212993035SSascha Leib	private function dadaMakeSection($words, $totalWeight, $hl) {
42312993035SSascha Leib
42412993035SSascha Leib		global $conf;
42512993035SSascha Leib
42612993035SSascha Leib		// how many paragraphs?
42712993035SSascha Leib		$paragraphCount = mt_rand(1, 4);
42812993035SSascha Leib
42912993035SSascha Leib		// section level
43012993035SSascha Leib		$topTocLevel = (int) $conf['toptoclevel'];
43112993035SSascha Leib		$secLevel = $hl['level'] + 1;;
43212993035SSascha Leib
43312993035SSascha Leib		// return value:
43412993035SSascha Leib		$sec = "";
43512993035SSascha Leib
43612993035SSascha Leib		// make a headline:
43712993035SSascha Leib		if ($topTocLevel > 1 || $secLevel > 1) {
43812993035SSascha Leib			$sec .= "<h{$secLevel} id=\"{$hl['hid']}\">{$hl['title']}</h{$secLevel}>\n";
43912993035SSascha Leib		}
44012993035SSascha Leib
44112993035SSascha Leib		// add the paragraphs:
44212993035SSascha Leib		$sec .= "<div class=\"level{$secLevel}\">\n";
44312993035SSascha Leib		for ($i=0; $i<$paragraphCount; $i++) {
44412993035SSascha Leib			$sec .= $this->dadaMakeParagraph($words, $totalWeight);
44512993035SSascha Leib		}
44612993035SSascha Leib		$sec .= "</div>\n";
44712993035SSascha Leib
44812993035SSascha Leib		return $sec;
44912993035SSascha Leib	}
45012993035SSascha Leib
45112993035SSascha Leib	private function dadaMakeHeadline($words, $totalWeight) {
45212993035SSascha Leib
45312993035SSascha Leib		// how many words to generate?
45412993035SSascha Leib		$wordCount = mt_rand(2, 5);
45512993035SSascha Leib
45612993035SSascha Leib		// function returns an array:
45712993035SSascha Leib		$r = Array();
45812993035SSascha Leib
45912993035SSascha Leib		// generate the headline:
46012993035SSascha Leib		$hlArr = array();
46112993035SSascha Leib		for ($i=0; $i<$wordCount; $i++) {
46212993035SSascha Leib			array_push($hlArr, $this->dadaSelectRandomWord($words, $totalWeight));
46312993035SSascha Leib		}
46412993035SSascha Leib
46512993035SSascha Leib		$r['title'] =  ucfirst(implode(' ', $hlArr));
46612993035SSascha Leib
46712993035SSascha Leib		$r['hid'] = preg_replace('/[^\w\d\-]+/i', '_', strtolower($r['title']));
46812993035SSascha Leib		$r['type'] = 'ul'; // always ul!
46912993035SSascha Leib		$r['level'] = 1; // always level 1 for now
47012993035SSascha Leib
47112993035SSascha Leib		return $r;
47212993035SSascha Leib	}
47312993035SSascha Leib
47412993035SSascha Leib	private function dadaMakeParagraph($words, $totalWeight) {
47512993035SSascha Leib
47612993035SSascha Leib		// how many words to generate?
47712993035SSascha Leib		$sentenceCount = mt_rand(2, 5);
47812993035SSascha Leib
47912993035SSascha Leib		$paragraph = array();
48012993035SSascha Leib		for ($i=0; $i<$sentenceCount; $i++) {
48112993035SSascha Leib			array_push($paragraph, $this->dadaMakeSentence($words, $totalWeight));
48212993035SSascha Leib		}
48312993035SSascha Leib
48412993035SSascha Leib		return "<p>\n" . implode(' ', $paragraph) . "\n</p>\n";
48512993035SSascha Leib
48612993035SSascha Leib	}
48712993035SSascha Leib
48812993035SSascha Leib	private function dadaMakeSentence($words, $totalWeight) {
48912993035SSascha Leib
49012993035SSascha Leib		// how many words to generate?
49112993035SSascha Leib		$wordCount = mt_rand(4, 20);
49212993035SSascha Leib
49312993035SSascha Leib		// generate the sentence:
49412993035SSascha Leib		$sentence = array();
49512993035SSascha Leib		for ($i=0; $i<$wordCount; $i++) {
49612993035SSascha Leib			array_push($sentence, $this->dadaSelectRandomWord($words, $totalWeight));
49712993035SSascha Leib		}
49812993035SSascha Leib
49912993035SSascha Leib		return ucfirst(implode(' ', $sentence)) . '.';
50012993035SSascha Leib
50112993035SSascha Leib	}
50212993035SSascha Leib
50312993035SSascha Leib	private function dadaSelectRandomWord($list, $totalWeight) {
50412993035SSascha Leib
50512993035SSascha Leib		// get a random selection:
50612993035SSascha Leib		$rand = mt_rand(0, $totalWeight);
50712993035SSascha Leib
50812993035SSascha Leib		// match the selection to the weighted list:
50912993035SSascha Leib		$cumulativeWeight = 0;
51012993035SSascha Leib		for ($i=0; $i<count($list); $i++) {
51112993035SSascha Leib			$cumulativeWeight += $list[$i][1];
51212993035SSascha Leib			if ($cumulativeWeight >= $rand) {
51312993035SSascha Leib				return $list[$i][0];
51412993035SSascha Leib			}
51512993035SSascha Leib		}
51612993035SSascha Leib		return '***';
517f5f4ca13SSascha Leib	}
518f5f4ca13SSascha Leib
5196980370bSSascha Leib}