xref: /plugin/botmon/action.php (revision 393de67c2219bde8f8a5693bcbdc0755b6d8e697)
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) {
235f2c1759SSascha Leib
24e56d7b71SSascha Leib		global $ACT;
25e56d7b71SSascha Leib
26447b8b4fSSascha Leib		// populate the session id and type:
27447b8b4fSSascha Leib		$this->setSessionInfo();
28d49ab213SSascha Leib
29*393de67cSSascha Leib		// temporary fix: save the method of the request:
30*393de67cSSascha Leib		$this->tempMethod = $_SERVER['REQUEST_METHOD'];
31*393de67cSSascha Leib
325f2c1759SSascha Leib		// insert header data into the page:
33d49ab213SSascha Leib		if ($ACT == 'show' || $ACT == 'edit' || $ACT == 'media') {
346980370bSSascha Leib			$controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertHeader');
35d49ab213SSascha Leib
36d49ab213SSascha Leib			// Override the page rendering, if a captcha needs to be displayed:
37*393de67cSSascha Leib			$controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'insertCaptchaCode');
38d49ab213SSascha Leib
39e56d7b71SSascha Leib		} else if ($ACT == 'admin' && isset($_REQUEST['page']) && $_REQUEST['page'] == 'botmon') {
40e56d7b71SSascha Leib			$controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertAdminHeader');
41e56d7b71SSascha Leib		}
425f2c1759SSascha Leib
43d49ab213SSascha Leib		// also show a captcha before the image preview
44d49ab213SSascha Leib		$controller->register_hook('TPL_IMG_DISPLAY', 'BEFORE', $this, 'showImageCaptcha');
45f5f4ca13SSascha Leib
465f2c1759SSascha Leib		// write to the log after the page content was displayed:
475f2c1759SSascha Leib		$controller->register_hook('TPL_CONTENT_DISPLAY', 'AFTER', $this, 'writeServerLog');
485f2c1759SSascha Leib
496980370bSSascha Leib	}
506980370bSSascha Leib
51b148c85eSSascha Leib	/* session information */
52f6a7ebc1SSascha Leib	private $sessionId = null;
53f6a7ebc1SSascha Leib	private $sessionType = '';
54*393de67cSSascha Leib	private $showCaptcha = 'X';
55*393de67cSSascha Leib	private $tempMethod = '';
56b148c85eSSascha Leib
576980370bSSascha Leib	/**
586980370bSSascha Leib	 * Inserts tracking code to the page header
59e56d7b71SSascha Leib	 * (only called on 'show' actions)
606980370bSSascha Leib	 *
616980370bSSascha Leib	 * @param Event $event event object by reference
626980370bSSascha Leib	 * @return void
636980370bSSascha Leib	 */
646980370bSSascha Leib	public function insertHeader(Event $event, $param) {
656980370bSSascha Leib
666980370bSSascha Leib		global $INFO;
676980370bSSascha Leib
68b148c85eSSascha Leib
696980370bSSascha Leib		// build the tracker code:
70d49ab213SSascha Leib		$code = $this->getBMHeader();
716980370bSSascha Leib
725f2c1759SSascha Leib		// add the deferred script loader::
73e56d7b71SSascha Leib		$code .= DOKU_TAB . DOKU_TAB . "addEventListener('DOMContentLoaded', function(){" . NL;
74e56d7b71SSascha Leib		$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "const e=document.createElement('script');" . NL;
75e56d7b71SSascha Leib		$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "e.async=true;e.defer=true;" . NL;
76e56d7b71SSascha Leib		$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "e.src='".DOKU_BASE."lib/plugins/botmon/client.js';" . NL;
77e56d7b71SSascha Leib		$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "document.getElementsByTagName('head')[0].appendChild(e);" . NL;
78e56d7b71SSascha Leib		$code .= DOKU_TAB . DOKU_TAB . "});";
795f2c1759SSascha Leib		$event->data['script'][] = ['_data' => $code];
80451abfadSSascha Leib	}
81451abfadSSascha Leib
82d49ab213SSascha Leib	/* create the BM object code for insertion into a script element: */
83d49ab213SSascha Leib	private function getBMHeader() {
84d49ab213SSascha Leib
85d49ab213SSascha Leib		// build the tracker code:
86d49ab213SSascha 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;
87d49ab213SSascha Leib
88d49ab213SSascha Leib		// is there a user logged in?
89d49ab213SSascha Leib		$username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name']) ?  $INFO['userinfo']['name'] : '');
90d49ab213SSascha Leib		if ($username) {
91d49ab213SSascha Leib			$code .= DOKU_TAB . DOKU_TAB . 'document._botmon.user = "' . $username . '";'. NL;
92d49ab213SSascha Leib		}
93d49ab213SSascha Leib
94d49ab213SSascha Leib		return $code;
95d49ab213SSascha Leib
96d49ab213SSascha Leib	}
97d49ab213SSascha Leib
98451abfadSSascha Leib	/**
99e56d7b71SSascha Leib	 * Inserts tracking code to the page header
100e56d7b71SSascha Leib	 * (only called on 'show' actions)
101e56d7b71SSascha Leib	 *
102e56d7b71SSascha Leib	 * @param Event $event event object by reference
103e56d7b71SSascha Leib	 * @return void
104e56d7b71SSascha Leib	 */
105e56d7b71SSascha Leib	public function insertAdminHeader(Event $event, $param) {
106e56d7b71SSascha Leib
107e56d7b71SSascha Leib		$event->data['link'][] = ['rel' => 'stylesheet', 'href' => DOKU_BASE.'lib/plugins/botmon/admin.css', 'defer' => 'defer'];
1080edf1a56SSascha Leib		$event->data['script'][] = ['src' => DOKU_BASE.'lib/plugins/botmon/admin.js', 'defer' => 'defer', '_data' => ''];
109e56d7b71SSascha Leib	}
110e56d7b71SSascha Leib
111e56d7b71SSascha Leib	/**
112451abfadSSascha Leib	 * Writes data to the server log.
113451abfadSSascha Leib	 *
114451abfadSSascha Leib	 * @return void
115451abfadSSascha Leib	 */
1165f2c1759SSascha Leib	public function writeServerLog(Event $event, $param) {
117451abfadSSascha Leib
118451abfadSSascha Leib		global $conf;
119451abfadSSascha Leib		global $INFO;
120091b5998SSascha Leib
1215f2c1759SSascha Leib		// is there a user logged in?
1225f2c1759SSascha Leib		$username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name'])
1235f2c1759SSascha Leib					?  $INFO['userinfo']['name'] : '');
1245f2c1759SSascha Leib
125b2e3bd8bSSascha Leib		// clean the page ID
126b2e3bd8bSSascha Leib		$pageId = preg_replace('/[\x00-\x1F]/', "\u{FFFD}", $INFO['id'] ?? '');
127b2e3bd8bSSascha Leib
128451abfadSSascha Leib		// create the log array:
129cf9f7fe8SSascha Leib		$logArr = Array(
130f5f4ca13SSascha Leib			$_SERVER['REMOTE_ADDR'], /* remote IP */
131b2e3bd8bSSascha Leib			$pageId, /* page ID */
132b148c85eSSascha Leib			$this->sessionId, /* Session ID */
133b148c85eSSascha Leib			$this->sessionType, /* session ID type */
1345f2c1759SSascha Leib			$username, /* user name */
1352f2bc93aSSascha Leib			$_SERVER['HTTP_USER_AGENT'] ?? '', /* User agent */
136451abfadSSascha Leib			$_SERVER['HTTP_REFERER'] ?? '', /* HTTP Referrer */
137451abfadSSascha Leib			substr($conf['lang'],0,2), /* page language */
138a93de874SSascha Leib			implode(',', array_unique(array_map( function($it) { return substr(trim($it),0,2); }, explode(',',trim($_SERVER['HTTP_ACCEPT_LANGUAGE'], " \t;,*"))))), /* accepted client languages */
1392c641262SSascha Leib			$this->getCountryCode(), /* GeoIP country code */
140*393de67cSSascha Leib			$this->showCaptcha, /* show captcha? */
141*393de67cSSascha Leib			$this->tempMethod /* show captcha? */
142d49ab213SSascha Leib		);
143cf9f7fe8SSascha Leib
144cf9f7fe8SSascha Leib		//* create the log line */
1454cddc661SSascha Leib		$filename = __DIR__ .'/logs/' . gmdate('Y-m-d') . '.srv.txt'; /* use GMT date for filename */
146cf9f7fe8SSascha Leib		$logline = gmdate('Y-m-d H:i:s'); /* use GMT time for log entries */
147cf9f7fe8SSascha Leib		foreach ($logArr as $tab) {
148cf9f7fe8SSascha Leib			$logline .= "\t" . $tab;
149cf9f7fe8SSascha Leib		};
150cf9f7fe8SSascha Leib
151cf9f7fe8SSascha Leib		/* write the log line to the file */
152cf9f7fe8SSascha Leib		$logfile = fopen($filename, 'a');
153cf9f7fe8SSascha Leib		if (!$logfile) die();
154cf9f7fe8SSascha Leib		if (fwrite($logfile, $logline . "\n") === false) {
155cf9f7fe8SSascha Leib			fclose($logfile);
156cf9f7fe8SSascha Leib			die();
1576980370bSSascha Leib		}
158cf9f7fe8SSascha Leib
159cf9f7fe8SSascha Leib		/* Done */
160cf9f7fe8SSascha Leib		fclose($logfile);
161cf9f7fe8SSascha Leib	}
162b148c85eSSascha Leib
1635f2c1759SSascha Leib	private function getCountryCode() {
1645f2c1759SSascha Leib
165f5f4ca13SSascha Leib		$country = ( $_SERVER['REMOTE_ADDR'] == '127.0.0.1' ? 'local' : 'ZZ' ); // default if no geoip is available!
1665f2c1759SSascha Leib
1675f2c1759SSascha Leib		$lib = $this->getConf('geoiplib'); /* which library to use? (can only be phpgeoip or disabled) */
1685f2c1759SSascha Leib
1695f2c1759SSascha Leib		try {
1705f2c1759SSascha Leib
1715f2c1759SSascha Leib			// use GeoIP module?
1725f2c1759SSascha Leib			if ($lib == 'phpgeoip' && extension_loaded('geoip') && geoip_db_avail(GEOIP_COUNTRY_EDITION)) { // Use PHP GeoIP module
1735f2c1759SSascha Leib				$result = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);
1745f2c1759SSascha Leib				$country = ($result ? $result : $country);
1755f2c1759SSascha Leib			}
1765f2c1759SSascha Leib		} catch (Exception $e) {
1775f2c1759SSascha Leib			Logger::error('BotMon Plugin: GeoIP Error', $e->getMessage());
1785f2c1759SSascha Leib		}
1795f2c1759SSascha Leib
1805f2c1759SSascha Leib		return $country;
1815f2c1759SSascha Leib	}
1825f2c1759SSascha Leib
183447b8b4fSSascha Leib	private function setSessionInfo() {
184b148c85eSSascha Leib
185b148c85eSSascha Leib		// what is the session identifier?
186b148c85eSSascha Leib		if (isset($_SESSION)) {
187b148c85eSSascha Leib			$sesKeys = array_keys($_SESSION); /* DokuWiki Session ID preferred */
188b148c85eSSascha Leib			foreach ($sesKeys as $key) {
189b148c85eSSascha Leib				if (substr($key, 0, 2) == 'DW') {
190b148c85eSSascha Leib					$this->sessionId = $key;
191b148c85eSSascha Leib					$this->sessionType = 'dw';
192b148c85eSSascha Leib					return;
193b148c85eSSascha Leib				}
194b148c85eSSascha Leib			}
195b148c85eSSascha Leib		}
196f6a7ebc1SSascha Leib		if (!$this->sessionId) { /* no DokuWiki Session ID, try PHP session ID */
197b148c85eSSascha Leib			$this->sessionId = session_id();
198b148c85eSSascha Leib			$this->sessionType = 'php';
199b148c85eSSascha Leib		}
200f5f4ca13SSascha Leib		if (!$this->sessionId) { /* no PHP session ID, try IP address */
201f5f4ca13SSascha Leib			$this->sessionId = $_SERVER['REMOTE_ADDR'];
202b148c85eSSascha Leib			$this->sessionType = 'ip';
203b148c85eSSascha Leib		}
204447b8b4fSSascha Leib
205447b8b4fSSascha Leib		if (!$this->sessionId) { /* if all fails, use random data */
206447b8b4fSSascha Leib			$this->sessionId = rand(100000000, 999999999);
207447b8b4fSSascha Leib			$this->sessionType = 'rnd';
208447b8b4fSSascha Leib		}
209447b8b4fSSascha Leib
210b148c85eSSascha Leib	}
211f5f4ca13SSascha Leib
212*393de67cSSascha Leib	public function insertCaptchaCode(Event $event) {
213f5f4ca13SSascha Leib
21412993035SSascha Leib		$useCaptcha = $this->getConf('useCaptcha');
215f5f4ca13SSascha Leib
216d49ab213SSascha Leib		$cCode = '-';
217d49ab213SSascha Leib		if ($useCaptcha !== 'disabled') {
218d49ab213SSascha Leib			if ($this->captchaWhitelisted()) {
219d49ab213SSascha Leib				$cCode = 'W'; // whitelisted
220d49ab213SSascha Leib			} elseif ($this->hasCaptchaCookie()) {
221d49ab213SSascha Leib				$cCode  = 'N'; // user already has a cookie
222d49ab213SSascha Leib			} else {
223d49ab213SSascha Leib				$cCode  = 'Y'; // show the captcha
2242c641262SSascha Leib
2252c641262SSascha Leib
22612993035SSascha Leib				echo '<h1 class="sectionedit1">'; tpl_pagetitle(); echo "</h1>\n"; // always show the original page title
227f5f4ca13SSascha Leib				$event->preventDefault(); // don't show normal content
22812993035SSascha Leib				switch ($useCaptcha) {
229*393de67cSSascha Leib					case 'loremipsum':
230*393de67cSSascha Leib						$this->insertLoremIpsum();  // show dada filler instead of text
23112993035SSascha Leib						break;
23212993035SSascha Leib					case 'dada':
23312993035SSascha Leib						$this->insertDadaFiller();  // show dada filler instead of text
23412993035SSascha Leib						break;
23512993035SSascha Leib				}
236f5f4ca13SSascha Leib				$this->insertCaptchaLoader(); // and load the captcha
237f5f4ca13SSascha Leib			}
238f5f4ca13SSascha Leib		}
239d49ab213SSascha Leib		$this->showCaptcha = $cCode; // store the captcha code for the logfile
240d49ab213SSascha Leib
241d49ab213SSascha Leib	}
242f5f4ca13SSascha Leib
243d49ab213SSascha Leib	public function showImageCaptcha(Event $event, $param) {
244d49ab213SSascha Leib
245d49ab213SSascha Leib		$useCaptcha = $this->getConf('useCaptcha');
246d49ab213SSascha Leib
247d49ab213SSascha Leib		echo '<script>' . $this->getBMHeader($event, $param) . '</script>';
248d49ab213SSascha Leib
249d49ab213SSascha Leib		$cCode = '-';
250d49ab213SSascha Leib		if ($useCaptcha !== 'disabled') {
251d49ab213SSascha Leib			if ($this->captchaWhitelisted()) {
252d49ab213SSascha Leib				$cCode = 'W'; // whitelisted
253d49ab213SSascha Leib			}
254d49ab213SSascha Leib			elseif ($this->hasCaptchaCookie()) {
255d49ab213SSascha Leib				$cCode  = 'N'; // user already has a cookie
256d49ab213SSascha Leib			}
257d49ab213SSascha Leib			else {
258d49ab213SSascha Leib				$cCode  = 'Y'; // show the captcha
259d49ab213SSascha Leib
260d49ab213SSascha 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
261d49ab213SSascha Leib				$event->preventDefault(); // don't show normal content
262d49ab213SSascha Leib
263d49ab213SSascha Leib				// TODO Insert dummy image
264d49ab213SSascha Leib				$this->insertCaptchaLoader(); // and load the captcha
265d49ab213SSascha Leib			}
266d49ab213SSascha Leib		};
267d49ab213SSascha Leib
268d49ab213SSascha Leib		$this->showCaptcha = $cCode; // store the captcha code for the logfile
269d49ab213SSascha Leib	}
270d49ab213SSascha Leib
271d49ab213SSascha Leib	private function hasCaptchaCookie() {
272f5f4ca13SSascha Leib
273cdc02cd4SSascha Leib		$cookieVal = isset($_COOKIE['DWConfirm']) ? $_COOKIE['DWConfirm'] : null;
274f5f4ca13SSascha Leib
275cdc02cd4SSascha Leib		$today = substr((new DateTime())->format('c'), 0, 10);
27612993035SSascha Leib
277cdc02cd4SSascha Leib		$raw = $this->getConf('captchaSeed') . '|' . $_SERVER['SERVER_NAME'] . '|' . $_SERVER['REMOTE_ADDR'] . '|' . $today;
278cdc02cd4SSascha Leib		$expected = hash('sha256', $raw);
27912993035SSascha Leib
280cdc02cd4SSascha Leib		//echo '<ul><li>cookie: ' . $cookieVal . '</li><li>expected: ' . $expected . '</li><li>matches: ' .($cookieVal == $expected ? 'true' : 'false') . '</li></ul>';
281cdc02cd4SSascha Leib
282d49ab213SSascha Leib		return $cookieVal == $expected;
283f5f4ca13SSascha Leib	}
284f5f4ca13SSascha Leib
2852c641262SSascha Leib	// check if the visitor's IP is on a whitelist:
2862c641262SSascha Leib	private function captchaWhitelisted() {
2872c641262SSascha Leib
2882c641262SSascha Leib		// normalise IP address:
2892c641262SSascha Leib		$ip = inet_pton($_SERVER['REMOTE_ADDR']);
2902c641262SSascha Leib
2912c641262SSascha Leib		// find which file to open:
2922c641262SSascha Leib		$prefixes = ['user', 'default'];
2932c641262SSascha Leib		foreach ($prefixes as $pre) {
2942c641262SSascha Leib			$filename = __DIR__ .'/config/' . $pre . '-whitelist.txt';
2952c641262SSascha Leib			if (file_exists($filename)) {
2962c641262SSascha Leib				break;
2972c641262SSascha Leib			}
2982c641262SSascha Leib		}
2992c641262SSascha Leib
3002c641262SSascha Leib		if (file_exists($filename)) {
3012c641262SSascha Leib			$lines = file($filename, FILE_SKIP_EMPTY_LINES);
3022c641262SSascha Leib			foreach ($lines as $line) {
3032c641262SSascha Leib				if (trim($line) !== '' && !str_starts_with($line, '#')) {
3042c641262SSascha Leib					$col = explode("\t", $line);
3052c641262SSascha Leib					if (count($col) >= 2) {
3062c641262SSascha Leib						$from = inet_pton($col[0]);
3072c641262SSascha Leib						$to = inet_pton($col[1]);
3082c641262SSascha Leib
3092c641262SSascha Leib						if ($ip >= $from && $ip <= $to) {
310d49ab213SSascha Leib							return true; /* IP whitelisted */
3112c641262SSascha Leib						}
3122c641262SSascha Leib					}
3132c641262SSascha Leib				}
3142c641262SSascha Leib			}
3152c641262SSascha Leib		}
316d49ab213SSascha Leib		return false; /* IP not found in whitelist */
3172c641262SSascha Leib	}
3182c641262SSascha Leib
319f5f4ca13SSascha Leib	private function insertCaptchaLoader() {
320620d9253SSascha Leib
32112993035SSascha Leib		echo '<script>' . NL;
32212993035SSascha Leib
32312993035SSascha Leib		// add the deferred script loader::
32412993035SSascha Leib		echo  DOKU_TAB . "addEventListener('DOMContentLoaded', function(){" . NL;
32512993035SSascha Leib		echo  DOKU_TAB . DOKU_TAB . "const cj=document.createElement('script');" . NL;
32612993035SSascha Leib		echo  DOKU_TAB . DOKU_TAB . "cj.async=true;cj.defer=true;cj.type='text/javascript';" . NL;
32712993035SSascha Leib		echo  DOKU_TAB . DOKU_TAB . "cj.src='".DOKU_BASE."lib/plugins/botmon/captcha.js';" . NL;
32812993035SSascha Leib		echo  DOKU_TAB . DOKU_TAB . "document.getElementsByTagName('head')[0].appendChild(cj);" . NL;
32912993035SSascha Leib		echo  DOKU_TAB . "});";
330620d9253SSascha Leib
331620d9253SSascha Leib		// add the locales for the captcha:
332620d9253SSascha Leib		echo  DOKU_TAB . '$BMLocales = {' . NL;
333620d9253SSascha Leib		echo  DOKU_TAB . DOKU_TAB . '"dlgTitle": ' . json_encode($this->getLang('bm_dlgTitle')) . ',' . NL;
334620d9253SSascha Leib		echo  DOKU_TAB . DOKU_TAB . '"dlgSubtitle": ' . json_encode($this->getLang('bm_dlgSubtitle')) . ',' . NL;
335620d9253SSascha Leib		echo  DOKU_TAB . DOKU_TAB . '"dlgConfirm": ' . json_encode($this->getLang('bm_dlgConfirm')) . ',' . NL;
336620d9253SSascha Leib		echo  DOKU_TAB . DOKU_TAB . '"dlgChecking": ' . json_encode($this->getLang('bm_dlgChecking')) . ',' . NL;
337620d9253SSascha Leib		echo  DOKU_TAB . DOKU_TAB . '"dlgLoading": ' . json_encode($this->getLang('bm_dlgLoading')) . ',' . NL;
338620d9253SSascha Leib		echo  DOKU_TAB . DOKU_TAB . '"dlgError": ' . json_encode($this->getLang('bm_dlgError')) . ',' . NL;
339620d9253SSascha Leib		echo  DOKU_TAB . '};' . NL;
340620d9253SSascha Leib
34112993035SSascha Leib		echo '</script>' . NL;
342f5f4ca13SSascha Leib
343f5f4ca13SSascha Leib	}
344f5f4ca13SSascha Leib
34512993035SSascha Leib	// inserts a blank box to ensure there is enough space for the captcha:
346*393de67cSSascha Leib	private function insertLoremIpsum() {
34712993035SSascha Leib
348*393de67cSSascha Leib		echo '<div class="level1">' . NL;
349*393de67cSSascha 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;
350*393de67cSSascha 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;
351*393de67cSSascha Leib		echo '</div>' . NL;
352*393de67cSSascha Leib
35312993035SSascha Leib	}
35412993035SSascha Leib
35512993035SSascha Leib	/* Generates a few paragraphs of Dada text to show instead of the article content */
356f5f4ca13SSascha Leib	private function insertDadaFiller() {
357f5f4ca13SSascha Leib
35812993035SSascha Leib		global $conf;
35912993035SSascha Leib		global $TOC;
36012993035SSascha Leib		global $ID;
361f5f4ca13SSascha Leib
36212993035SSascha Leib		// list of languages to search for the wordlist
36312993035SSascha Leib		$langs = array_unique([$conf['lang'], 'la']);
364f5f4ca13SSascha Leib
36512993035SSascha Leib		// find path to the first available wordlist:
36612993035SSascha Leib		foreach ($langs as $lang) {
36712993035SSascha Leib			$filename = __DIR__ .'/lang/' . $lang . '/wordlist.txt'; /* language-specific wordlist */
36812993035SSascha Leib			if (file_exists($filename)) {
36912993035SSascha Leib				break;
37012993035SSascha Leib			}
37112993035SSascha Leib		}
372f5f4ca13SSascha Leib
37312993035SSascha Leib		// load the wordlist file:
37412993035SSascha Leib		if (file_exists($filename)) {
37512993035SSascha Leib			$words = array();
37612993035SSascha Leib			$totalWeight = 0;
37712993035SSascha Leib			$lines = file($filename, FILE_SKIP_EMPTY_LINES);
37812993035SSascha Leib			foreach ($lines as $line) {
37912993035SSascha Leib				$arr = explode("\t", $line);
38012993035SSascha Leib				$arr[1] = ( count($arr) > 1 ? (int) trim($arr[1]) : 1 );
38112993035SSascha Leib				$totalWeight += (int) $arr[1];
38212993035SSascha Leib				array_push($words, $arr);
38312993035SSascha Leib			}
38412993035SSascha Leib		} else {
38512993035SSascha Leib			echo '<script> console.log("Can’t generate filler text: wordlist file not found!"); </script>';
38612993035SSascha Leib			return;
38712993035SSascha Leib		}
388f5f4ca13SSascha Leib
38912993035SSascha Leib		// If a TOC exists, use it for the headlines:
39012993035SSascha Leib		if(is_array($TOC)) {
39112993035SSascha Leib			$toc = $TOC;
39212993035SSascha Leib		} else {
39312993035SSascha Leib			$meta = p_get_metadata($ID, '', METADATA_RENDER_USING_CACHE);
39412993035SSascha Leib			//$tocok = (isset($meta['internal']['toc']) ? $meta['internal']['toc'] : $tocok = true);
39512993035SSascha Leib			$toc = isset($meta['description']['tableofcontents']) ? $meta['description']['tableofcontents'] : null;
39612993035SSascha Leib		}
39712993035SSascha Leib		if (!$toc) { // no TOC, generate my own:
39812993035SSascha Leib			$hlCount = mt_rand(0, (int) $conf['tocminheads']);
39912993035SSascha Leib			$toc = array();
40012993035SSascha Leib			for ($i=0; $i<$hlCount; $i++) {
40112993035SSascha Leib				array_push($toc, $this->dadaMakeHeadline($words, $totalWeight)); // $toc
40212993035SSascha Leib			}
40312993035SSascha Leib		}
40412993035SSascha Leib
40512993035SSascha Leib		// if H1 heading is not in the TOC, add a chappeau section:
40612993035SSascha Leib		$chapeauCount = mt_rand(1, 3);
40712993035SSascha Leib		if ((int) $conf['toptoclevel'] > 1) {
40812993035SSascha Leib			echo "<div class=\"level1\">\n";
40912993035SSascha Leib			for ($i=0; $i<$chapeauCount; $i++) {
41012993035SSascha Leib				echo $this->dadaMakeParagraph($words, $totalWeight);
41112993035SSascha Leib			}
41212993035SSascha Leib			echo "</div>\n";
41312993035SSascha Leib		}
41412993035SSascha Leib
41512993035SSascha Leib		//  text sections for each sub-headline:
41612993035SSascha Leib		foreach ($toc as $hl) {
41712993035SSascha Leib			echo $this->dadaMakeSection($words, $totalWeight, $hl);
41812993035SSascha Leib		}
41912993035SSascha Leib	}
42012993035SSascha Leib
42112993035SSascha Leib	private function dadaMakeSection($words, $totalWeight, $hl) {
42212993035SSascha Leib
42312993035SSascha Leib		global $conf;
42412993035SSascha Leib
42512993035SSascha Leib		// how many paragraphs?
42612993035SSascha Leib		$paragraphCount = mt_rand(1, 4);
42712993035SSascha Leib
42812993035SSascha Leib		// section level
42912993035SSascha Leib		$topTocLevel = (int) $conf['toptoclevel'];
43012993035SSascha Leib		$secLevel = $hl['level'] + 1;;
43112993035SSascha Leib
43212993035SSascha Leib		// return value:
43312993035SSascha Leib		$sec = "";
43412993035SSascha Leib
43512993035SSascha Leib		// make a headline:
43612993035SSascha Leib		if ($topTocLevel > 1 || $secLevel > 1) {
43712993035SSascha Leib			$sec .= "<h{$secLevel} id=\"{$hl['hid']}\">{$hl['title']}</h{$secLevel}>\n";
43812993035SSascha Leib		}
43912993035SSascha Leib
44012993035SSascha Leib		// add the paragraphs:
44112993035SSascha Leib		$sec .= "<div class=\"level{$secLevel}\">\n";
44212993035SSascha Leib		for ($i=0; $i<$paragraphCount; $i++) {
44312993035SSascha Leib			$sec .= $this->dadaMakeParagraph($words, $totalWeight);
44412993035SSascha Leib		}
44512993035SSascha Leib		$sec .= "</div>\n";
44612993035SSascha Leib
44712993035SSascha Leib		return $sec;
44812993035SSascha Leib	}
44912993035SSascha Leib
45012993035SSascha Leib	private function dadaMakeHeadline($words, $totalWeight) {
45112993035SSascha Leib
45212993035SSascha Leib		// how many words to generate?
45312993035SSascha Leib		$wordCount = mt_rand(2, 5);
45412993035SSascha Leib
45512993035SSascha Leib		// function returns an array:
45612993035SSascha Leib		$r = Array();
45712993035SSascha Leib
45812993035SSascha Leib		// generate the headline:
45912993035SSascha Leib		$hlArr = array();
46012993035SSascha Leib		for ($i=0; $i<$wordCount; $i++) {
46112993035SSascha Leib			array_push($hlArr, $this->dadaSelectRandomWord($words, $totalWeight));
46212993035SSascha Leib		}
46312993035SSascha Leib
46412993035SSascha Leib		$r['title'] =  ucfirst(implode(' ', $hlArr));
46512993035SSascha Leib
46612993035SSascha Leib		$r['hid'] = preg_replace('/[^\w\d\-]+/i', '_', strtolower($r['title']));
46712993035SSascha Leib		$r['type'] = 'ul'; // always ul!
46812993035SSascha Leib		$r['level'] = 1; // always level 1 for now
46912993035SSascha Leib
47012993035SSascha Leib		return $r;
47112993035SSascha Leib	}
47212993035SSascha Leib
47312993035SSascha Leib	private function dadaMakeParagraph($words, $totalWeight) {
47412993035SSascha Leib
47512993035SSascha Leib		// how many words to generate?
47612993035SSascha Leib		$sentenceCount = mt_rand(2, 5);
47712993035SSascha Leib
47812993035SSascha Leib		$paragraph = array();
47912993035SSascha Leib		for ($i=0; $i<$sentenceCount; $i++) {
48012993035SSascha Leib			array_push($paragraph, $this->dadaMakeSentence($words, $totalWeight));
48112993035SSascha Leib		}
48212993035SSascha Leib
48312993035SSascha Leib		return "<p>\n" . implode(' ', $paragraph) . "\n</p>\n";
48412993035SSascha Leib
48512993035SSascha Leib	}
48612993035SSascha Leib
48712993035SSascha Leib	private function dadaMakeSentence($words, $totalWeight) {
48812993035SSascha Leib
48912993035SSascha Leib		// how many words to generate?
49012993035SSascha Leib		$wordCount = mt_rand(4, 20);
49112993035SSascha Leib
49212993035SSascha Leib		// generate the sentence:
49312993035SSascha Leib		$sentence = array();
49412993035SSascha Leib		for ($i=0; $i<$wordCount; $i++) {
49512993035SSascha Leib			array_push($sentence, $this->dadaSelectRandomWord($words, $totalWeight));
49612993035SSascha Leib		}
49712993035SSascha Leib
49812993035SSascha Leib		return ucfirst(implode(' ', $sentence)) . '.';
49912993035SSascha Leib
50012993035SSascha Leib	}
50112993035SSascha Leib
50212993035SSascha Leib	private function dadaSelectRandomWord($list, $totalWeight) {
50312993035SSascha Leib
50412993035SSascha Leib		// get a random selection:
50512993035SSascha Leib		$rand = mt_rand(0, $totalWeight);
50612993035SSascha Leib
50712993035SSascha Leib		// match the selection to the weighted list:
50812993035SSascha Leib		$cumulativeWeight = 0;
50912993035SSascha Leib		for ($i=0; $i<count($list); $i++) {
51012993035SSascha Leib			$cumulativeWeight += $list[$i][1];
51112993035SSascha Leib			if ($cumulativeWeight >= $rand) {
51212993035SSascha Leib				return $list[$i][0];
51312993035SSascha Leib			}
51412993035SSascha Leib		}
51512993035SSascha Leib		return '***';
516f5f4ca13SSascha Leib	}
517f5f4ca13SSascha Leib
5186980370bSSascha Leib}