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 1619b69b64SSascha Leib public function __construct() { 1719b69b64SSascha Leib 1819b69b64SSascha Leib // determine if a captcha should be loaded: 19ad279a21SSascha Leib $this->showCaptcha = 'Z'; // Captcha unknown 2019b69b64SSascha Leib 2119b69b64SSascha Leib $useCaptcha = $this->getConf('useCaptcha'); // should we show a captcha? 2219b69b64SSascha Leib 2319b69b64SSascha Leib if ($useCaptcha !== 'disabled') { 2419b69b64SSascha Leib if ($_SERVER['REQUEST_METHOD'] == 'HEAD') { 2519b69b64SSascha Leib $this->showCaptcha = 'H'; // Method is HEAD, no need for captcha 2619b69b64SSascha Leib } elseif ($this->captchaWhitelisted()) { 2719b69b64SSascha Leib $this->showCaptcha = 'W'; // IP is whitelisted, no captcha 2819b69b64SSascha Leib } elseif ($this->hasCaptchaCookie()) { 2919b69b64SSascha Leib $this->showCaptcha = 'N'; // No, user already has a cookie, don't show the captcha 3019b69b64SSascha Leib } else { 3119b69b64SSascha Leib $this->showCaptcha = 'Y'; // Yes, show the captcha 3219b69b64SSascha Leib } 3319b69b64SSascha Leib } 3419b69b64SSascha Leib } 3519b69b64SSascha 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 */ 156ad279a21SSascha Leib $this->showCaptcha, /* show captcha? */ 157ad279a21SSascha Leib $_SERVER['REQUEST_METHOD'] ?? '' /* request method */ 158d49ab213SSascha Leib ); 159cf9f7fe8SSascha Leib 160cf9f7fe8SSascha Leib //* create the log line */ 1614cddc661SSascha Leib $filename = __DIR__ .'/logs/' . gmdate('Y-m-d') . '.srv.txt'; /* use GMT date for filename */ 162cf9f7fe8SSascha Leib $logline = gmdate('Y-m-d H:i:s'); /* use GMT time for log entries */ 163cf9f7fe8SSascha Leib foreach ($logArr as $tab) { 164cf9f7fe8SSascha Leib $logline .= "\t" . $tab; 165cf9f7fe8SSascha Leib }; 166cf9f7fe8SSascha Leib 167cf9f7fe8SSascha Leib /* write the log line to the file */ 168cf9f7fe8SSascha Leib $logfile = fopen($filename, 'a'); 169cf9f7fe8SSascha Leib if (!$logfile) die(); 170cf9f7fe8SSascha Leib if (fwrite($logfile, $logline . "\n") === false) { 171cf9f7fe8SSascha Leib fclose($logfile); 172cf9f7fe8SSascha Leib die(); 1736980370bSSascha Leib } 174cf9f7fe8SSascha Leib 175cf9f7fe8SSascha Leib /* Done */ 176cf9f7fe8SSascha Leib fclose($logfile); 177cf9f7fe8SSascha Leib } 178b148c85eSSascha Leib 1795f2c1759SSascha Leib private function getCountryCode() { 1805f2c1759SSascha Leib 181f5f4ca13SSascha Leib $country = ( $_SERVER['REMOTE_ADDR'] == '127.0.0.1' ? 'local' : 'ZZ' ); // default if no geoip is available! 1825f2c1759SSascha Leib 1835f2c1759SSascha Leib $lib = $this->getConf('geoiplib'); /* which library to use? (can only be phpgeoip or disabled) */ 1845f2c1759SSascha Leib 1855f2c1759SSascha Leib try { 1865f2c1759SSascha Leib 1875f2c1759SSascha Leib // use GeoIP module? 1885f2c1759SSascha Leib if ($lib == 'phpgeoip' && extension_loaded('geoip') && geoip_db_avail(GEOIP_COUNTRY_EDITION)) { // Use PHP GeoIP module 1895f2c1759SSascha Leib $result = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']); 1905f2c1759SSascha Leib $country = ($result ? $result : $country); 1915f2c1759SSascha Leib } 1925f2c1759SSascha Leib } catch (Exception $e) { 1935f2c1759SSascha Leib Logger::error('BotMon Plugin: GeoIP Error', $e->getMessage()); 1945f2c1759SSascha Leib } 1955f2c1759SSascha Leib 1965f2c1759SSascha Leib return $country; 1975f2c1759SSascha Leib } 1985f2c1759SSascha Leib 199447b8b4fSSascha Leib private function setSessionInfo() { 200b148c85eSSascha Leib 201b148c85eSSascha Leib // what is the session identifier? 202b148c85eSSascha Leib if (isset($_SESSION)) { 203b148c85eSSascha Leib $sesKeys = array_keys($_SESSION); /* DokuWiki Session ID preferred */ 204b148c85eSSascha Leib foreach ($sesKeys as $key) { 205b148c85eSSascha Leib if (substr($key, 0, 2) == 'DW') { 206b148c85eSSascha Leib $this->sessionId = $key; 207b148c85eSSascha Leib $this->sessionType = 'dw'; 208b148c85eSSascha Leib return; 209b148c85eSSascha Leib } 210b148c85eSSascha Leib } 211b148c85eSSascha Leib } 212f6a7ebc1SSascha Leib if (!$this->sessionId) { /* no DokuWiki Session ID, try PHP session ID */ 213b148c85eSSascha Leib $this->sessionId = session_id(); 214b148c85eSSascha Leib $this->sessionType = 'php'; 215b148c85eSSascha Leib } 216f5f4ca13SSascha Leib if (!$this->sessionId) { /* no PHP session ID, try IP address */ 217f5f4ca13SSascha Leib $this->sessionId = $_SERVER['REMOTE_ADDR']; 218b148c85eSSascha Leib $this->sessionType = 'ip'; 219b148c85eSSascha Leib } 220447b8b4fSSascha Leib 221447b8b4fSSascha Leib if (!$this->sessionId) { /* if all fails, use random data */ 222447b8b4fSSascha Leib $this->sessionId = rand(100000000, 999999999); 223447b8b4fSSascha Leib $this->sessionType = 'rnd'; 224447b8b4fSSascha Leib } 225447b8b4fSSascha Leib 226b148c85eSSascha Leib } 227f5f4ca13SSascha Leib 228393de67cSSascha Leib public function insertCaptchaCode(Event $event) { 229f5f4ca13SSascha Leib 23019b69b64SSascha Leib $useCaptcha = $this->getConf('useCaptcha'); // which background to show? 231f5f4ca13SSascha Leib 23219b69b64SSascha Leib // only if we previously determined that we need a captcha: 23319b69b64SSascha Leib if ($this->showCaptcha == 'Y') { 2342c641262SSascha Leib 23512993035SSascha Leib echo '<h1 class="sectionedit1">'; tpl_pagetitle(); echo "</h1>\n"; // always show the original page title 236f5f4ca13SSascha Leib $event->preventDefault(); // don't show normal content 23712993035SSascha Leib switch ($useCaptcha) { 238393de67cSSascha Leib case 'loremipsum': 239393de67cSSascha Leib $this->insertLoremIpsum(); // show dada filler instead of text 24012993035SSascha Leib break; 24112993035SSascha Leib case 'dada': 24212993035SSascha Leib $this->insertDadaFiller(); // show dada filler instead of text 24312993035SSascha Leib break; 24412993035SSascha Leib } 245d49ab213SSascha Leib 24619b69b64SSascha Leib // insert the captcha loader code: 24719b69b64SSascha Leib echo '<script>' . NL; 24819b69b64SSascha Leib 24919b69b64SSascha Leib // add the deferred script loader:: 25019b69b64SSascha Leib echo DOKU_TAB . "addEventListener('DOMContentLoaded', function(){" . NL; 25119b69b64SSascha Leib echo DOKU_TAB . DOKU_TAB . "const cj=document.createElement('script');" . NL; 25219b69b64SSascha Leib echo DOKU_TAB . DOKU_TAB . "cj.async=true;cj.defer=true;cj.type='text/javascript';" . NL; 25319b69b64SSascha Leib echo DOKU_TAB . DOKU_TAB . "cj.src='".DOKU_BASE."lib/plugins/botmon/captcha.js';" . NL; 25419b69b64SSascha Leib echo DOKU_TAB . DOKU_TAB . "document.getElementsByTagName('head')[0].appendChild(cj);" . NL; 2556b6cd387SSascha Leib echo DOKU_TAB . "});" . NL; 25619b69b64SSascha Leib 25719b69b64SSascha Leib // add the translated strings for the captcha: 25819b69b64SSascha Leib echo DOKU_TAB . '$BMLocales = {' . NL; 25919b69b64SSascha Leib echo DOKU_TAB . DOKU_TAB . '"dlgTitle": ' . json_encode($this->getLang('bm_dlgTitle')) . ',' . NL; 26019b69b64SSascha Leib echo DOKU_TAB . DOKU_TAB . '"dlgSubtitle": ' . json_encode($this->getLang('bm_dlgSubtitle')) . ',' . NL; 26119b69b64SSascha Leib echo DOKU_TAB . DOKU_TAB . '"dlgConfirm": ' . json_encode($this->getLang('bm_dlgConfirm')) . ',' . NL; 26219b69b64SSascha Leib echo DOKU_TAB . DOKU_TAB . '"dlgChecking": ' . json_encode($this->getLang('bm_dlgChecking')) . ',' . NL; 26319b69b64SSascha Leib echo DOKU_TAB . DOKU_TAB . '"dlgLoading": ' . json_encode($this->getLang('bm_dlgLoading')) . ',' . NL; 26419b69b64SSascha Leib echo DOKU_TAB . DOKU_TAB . '"dlgError": ' . json_encode($this->getLang('bm_dlgError')) . ',' . NL; 26519b69b64SSascha Leib echo DOKU_TAB . '};' . NL; 26619b69b64SSascha Leib 267*fb281ca1SSascha Leib // captcha configuration options 268*fb281ca1SSascha Leib echo DOKU_TAB . '$BMConfig = {' . NL; 269*fb281ca1SSascha Leib echo DOKU_TAB . DOKU_TAB . '"captchaBypass": ' . json_encode($this->getConf('captchaBypass')) . NL; 270*fb281ca1SSascha Leib echo DOKU_TAB . '};' . NL; 271*fb281ca1SSascha Leib 27219b69b64SSascha Leib echo '</script>' . NL; 27319b69b64SSascha Leib } 274d49ab213SSascha Leib } 275f5f4ca13SSascha Leib 276d49ab213SSascha Leib public function showImageCaptcha(Event $event, $param) { 277d49ab213SSascha Leib 278d49ab213SSascha Leib $useCaptcha = $this->getConf('useCaptcha'); 279d49ab213SSascha Leib 280d49ab213SSascha Leib echo '<script>' . $this->getBMHeader($event, $param) . '</script>'; 281d49ab213SSascha Leib 282d49ab213SSascha Leib $cCode = '-'; 283d49ab213SSascha Leib if ($useCaptcha !== 'disabled') { 284d49ab213SSascha Leib if ($this->captchaWhitelisted()) { 285d49ab213SSascha Leib $cCode = 'W'; // whitelisted 286d49ab213SSascha Leib } 287d49ab213SSascha Leib elseif ($this->hasCaptchaCookie()) { 288d49ab213SSascha Leib $cCode = 'N'; // user already has a cookie 289d49ab213SSascha Leib } 290d49ab213SSascha Leib else { 291d49ab213SSascha Leib $cCode = 'Y'; // show the captcha 292d49ab213SSascha Leib 293d49ab213SSascha 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 294d49ab213SSascha Leib $event->preventDefault(); // don't show normal content 295d49ab213SSascha Leib 296d49ab213SSascha Leib // TODO Insert dummy image 297d49ab213SSascha Leib $this->insertCaptchaLoader(); // and load the captcha 298d49ab213SSascha Leib } 299d49ab213SSascha Leib }; 300d49ab213SSascha Leib 301d49ab213SSascha Leib $this->showCaptcha = $cCode; // store the captcha code for the logfile 302d49ab213SSascha Leib } 303d49ab213SSascha Leib 304d49ab213SSascha Leib private function hasCaptchaCookie() { 305f5f4ca13SSascha Leib 306cdc02cd4SSascha Leib $cookieVal = isset($_COOKIE['DWConfirm']) ? $_COOKIE['DWConfirm'] : null; 307f5f4ca13SSascha Leib 308cdc02cd4SSascha Leib $today = substr((new DateTime())->format('c'), 0, 10); 30912993035SSascha Leib 310cdc02cd4SSascha Leib $raw = $this->getConf('captchaSeed') . '|' . $_SERVER['SERVER_NAME'] . '|' . $_SERVER['REMOTE_ADDR'] . '|' . $today; 311cdc02cd4SSascha Leib $expected = hash('sha256', $raw); 31212993035SSascha Leib 313cdc02cd4SSascha Leib //echo '<ul><li>cookie: ' . $cookieVal . '</li><li>expected: ' . $expected . '</li><li>matches: ' .($cookieVal == $expected ? 'true' : 'false') . '</li></ul>'; 314cdc02cd4SSascha Leib 315d49ab213SSascha Leib return $cookieVal == $expected; 316f5f4ca13SSascha Leib } 317f5f4ca13SSascha Leib 3182c641262SSascha Leib // check if the visitor's IP is on a whitelist: 3192c641262SSascha Leib private function captchaWhitelisted() { 3202c641262SSascha Leib 3212c641262SSascha Leib // normalise IP address: 3222c641262SSascha Leib $ip = inet_pton($_SERVER['REMOTE_ADDR']); 3232c641262SSascha Leib 3242c641262SSascha Leib // find which file to open: 3252c641262SSascha Leib $prefixes = ['user', 'default']; 3262c641262SSascha Leib foreach ($prefixes as $pre) { 3272c641262SSascha Leib $filename = __DIR__ .'/config/' . $pre . '-whitelist.txt'; 3282c641262SSascha Leib if (file_exists($filename)) { 3292c641262SSascha Leib break; 3302c641262SSascha Leib } 3312c641262SSascha Leib } 3322c641262SSascha Leib 3332c641262SSascha Leib if (file_exists($filename)) { 3342c641262SSascha Leib $lines = file($filename, FILE_SKIP_EMPTY_LINES); 3352c641262SSascha Leib foreach ($lines as $line) { 3362c641262SSascha Leib if (trim($line) !== '' && !str_starts_with($line, '#')) { 3372c641262SSascha Leib $col = explode("\t", $line); 3382c641262SSascha Leib if (count($col) >= 2) { 3392c641262SSascha Leib $from = inet_pton($col[0]); 3402c641262SSascha Leib $to = inet_pton($col[1]); 3412c641262SSascha Leib 3422c641262SSascha Leib if ($ip >= $from && $ip <= $to) { 343d49ab213SSascha Leib return true; /* IP whitelisted */ 3442c641262SSascha Leib } 3452c641262SSascha Leib } 3462c641262SSascha Leib } 3472c641262SSascha Leib } 3482c641262SSascha Leib } 349d49ab213SSascha Leib return false; /* IP not found in whitelist */ 3502c641262SSascha Leib } 3512c641262SSascha Leib 35212993035SSascha Leib // inserts a blank box to ensure there is enough space for the captcha: 353393de67cSSascha Leib private function insertLoremIpsum() { 35412993035SSascha Leib 355393de67cSSascha Leib echo '<div class="level1">' . NL; 356393de67cSSascha 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; 357393de67cSSascha 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; 358393de67cSSascha Leib echo '</div>' . NL; 359393de67cSSascha Leib 36012993035SSascha Leib } 36112993035SSascha Leib 36212993035SSascha Leib /* Generates a few paragraphs of Dada text to show instead of the article content */ 363f5f4ca13SSascha Leib private function insertDadaFiller() { 364f5f4ca13SSascha Leib 36512993035SSascha Leib global $conf; 36612993035SSascha Leib global $TOC; 36712993035SSascha Leib global $ID; 368f5f4ca13SSascha Leib 36912993035SSascha Leib // list of languages to search for the wordlist 37012993035SSascha Leib $langs = array_unique([$conf['lang'], 'la']); 371f5f4ca13SSascha Leib 37212993035SSascha Leib // find path to the first available wordlist: 37312993035SSascha Leib foreach ($langs as $lang) { 37412993035SSascha Leib $filename = __DIR__ .'/lang/' . $lang . '/wordlist.txt'; /* language-specific wordlist */ 37512993035SSascha Leib if (file_exists($filename)) { 37612993035SSascha Leib break; 37712993035SSascha Leib } 37812993035SSascha Leib } 379f5f4ca13SSascha Leib 38012993035SSascha Leib // load the wordlist file: 38112993035SSascha Leib if (file_exists($filename)) { 38212993035SSascha Leib $words = array(); 38312993035SSascha Leib $totalWeight = 0; 38412993035SSascha Leib $lines = file($filename, FILE_SKIP_EMPTY_LINES); 38512993035SSascha Leib foreach ($lines as $line) { 38612993035SSascha Leib $arr = explode("\t", $line); 38712993035SSascha Leib $arr[1] = ( count($arr) > 1 ? (int) trim($arr[1]) : 1 ); 38812993035SSascha Leib $totalWeight += (int) $arr[1]; 38912993035SSascha Leib array_push($words, $arr); 39012993035SSascha Leib } 39112993035SSascha Leib } else { 39212993035SSascha Leib echo '<script> console.log("Can’t generate filler text: wordlist file not found!"); </script>'; 39312993035SSascha Leib return; 39412993035SSascha Leib } 395f5f4ca13SSascha Leib 39612993035SSascha Leib // If a TOC exists, use it for the headlines: 39712993035SSascha Leib if(is_array($TOC)) { 39812993035SSascha Leib $toc = $TOC; 39912993035SSascha Leib } else { 40012993035SSascha Leib $meta = p_get_metadata($ID, '', METADATA_RENDER_USING_CACHE); 40112993035SSascha Leib //$tocok = (isset($meta['internal']['toc']) ? $meta['internal']['toc'] : $tocok = true); 40212993035SSascha Leib $toc = isset($meta['description']['tableofcontents']) ? $meta['description']['tableofcontents'] : null; 40312993035SSascha Leib } 40412993035SSascha Leib if (!$toc) { // no TOC, generate my own: 40512993035SSascha Leib $hlCount = mt_rand(0, (int) $conf['tocminheads']); 40612993035SSascha Leib $toc = array(); 40712993035SSascha Leib for ($i=0; $i<$hlCount; $i++) { 40812993035SSascha Leib array_push($toc, $this->dadaMakeHeadline($words, $totalWeight)); // $toc 40912993035SSascha Leib } 41012993035SSascha Leib } 41112993035SSascha Leib 41212993035SSascha Leib // if H1 heading is not in the TOC, add a chappeau section: 41312993035SSascha Leib $chapeauCount = mt_rand(1, 3); 41412993035SSascha Leib if ((int) $conf['toptoclevel'] > 1) { 41512993035SSascha Leib echo "<div class=\"level1\">\n"; 41612993035SSascha Leib for ($i=0; $i<$chapeauCount; $i++) { 41712993035SSascha Leib echo $this->dadaMakeParagraph($words, $totalWeight); 41812993035SSascha Leib } 41912993035SSascha Leib echo "</div>\n"; 42012993035SSascha Leib } 42112993035SSascha Leib 42212993035SSascha Leib // text sections for each sub-headline: 42312993035SSascha Leib foreach ($toc as $hl) { 42412993035SSascha Leib echo $this->dadaMakeSection($words, $totalWeight, $hl); 42512993035SSascha Leib } 42612993035SSascha Leib } 42712993035SSascha Leib 42812993035SSascha Leib private function dadaMakeSection($words, $totalWeight, $hl) { 42912993035SSascha Leib 43012993035SSascha Leib global $conf; 43112993035SSascha Leib 43212993035SSascha Leib // how many paragraphs? 43312993035SSascha Leib $paragraphCount = mt_rand(1, 4); 43412993035SSascha Leib 43512993035SSascha Leib // section level 43612993035SSascha Leib $topTocLevel = (int) $conf['toptoclevel']; 43712993035SSascha Leib $secLevel = $hl['level'] + 1;; 43812993035SSascha Leib 43912993035SSascha Leib // return value: 44012993035SSascha Leib $sec = ""; 44112993035SSascha Leib 44212993035SSascha Leib // make a headline: 44312993035SSascha Leib if ($topTocLevel > 1 || $secLevel > 1) { 44412993035SSascha Leib $sec .= "<h{$secLevel} id=\"{$hl['hid']}\">{$hl['title']}</h{$secLevel}>\n"; 44512993035SSascha Leib } 44612993035SSascha Leib 44712993035SSascha Leib // add the paragraphs: 44812993035SSascha Leib $sec .= "<div class=\"level{$secLevel}\">\n"; 44912993035SSascha Leib for ($i=0; $i<$paragraphCount; $i++) { 45012993035SSascha Leib $sec .= $this->dadaMakeParagraph($words, $totalWeight); 45112993035SSascha Leib } 45212993035SSascha Leib $sec .= "</div>\n"; 45312993035SSascha Leib 45412993035SSascha Leib return $sec; 45512993035SSascha Leib } 45612993035SSascha Leib 45712993035SSascha Leib private function dadaMakeHeadline($words, $totalWeight) { 45812993035SSascha Leib 45912993035SSascha Leib // how many words to generate? 46012993035SSascha Leib $wordCount = mt_rand(2, 5); 46112993035SSascha Leib 46212993035SSascha Leib // function returns an array: 46312993035SSascha Leib $r = Array(); 46412993035SSascha Leib 46512993035SSascha Leib // generate the headline: 46612993035SSascha Leib $hlArr = array(); 46712993035SSascha Leib for ($i=0; $i<$wordCount; $i++) { 46812993035SSascha Leib array_push($hlArr, $this->dadaSelectRandomWord($words, $totalWeight)); 46912993035SSascha Leib } 47012993035SSascha Leib 47112993035SSascha Leib $r['title'] = ucfirst(implode(' ', $hlArr)); 47212993035SSascha Leib 47312993035SSascha Leib $r['hid'] = preg_replace('/[^\w\d\-]+/i', '_', strtolower($r['title'])); 47412993035SSascha Leib $r['type'] = 'ul'; // always ul! 47512993035SSascha Leib $r['level'] = 1; // always level 1 for now 47612993035SSascha Leib 47712993035SSascha Leib return $r; 47812993035SSascha Leib } 47912993035SSascha Leib 48012993035SSascha Leib private function dadaMakeParagraph($words, $totalWeight) { 48112993035SSascha Leib 48212993035SSascha Leib // how many words to generate? 48312993035SSascha Leib $sentenceCount = mt_rand(2, 5); 48412993035SSascha Leib 48512993035SSascha Leib $paragraph = array(); 48612993035SSascha Leib for ($i=0; $i<$sentenceCount; $i++) { 48712993035SSascha Leib array_push($paragraph, $this->dadaMakeSentence($words, $totalWeight)); 48812993035SSascha Leib } 48912993035SSascha Leib 49012993035SSascha Leib return "<p>\n" . implode(' ', $paragraph) . "\n</p>\n"; 49112993035SSascha Leib 49212993035SSascha Leib } 49312993035SSascha Leib 49412993035SSascha Leib private function dadaMakeSentence($words, $totalWeight) { 49512993035SSascha Leib 49612993035SSascha Leib // how many words to generate? 49712993035SSascha Leib $wordCount = mt_rand(4, 20); 49812993035SSascha Leib 49912993035SSascha Leib // generate the sentence: 50012993035SSascha Leib $sentence = array(); 50112993035SSascha Leib for ($i=0; $i<$wordCount; $i++) { 50212993035SSascha Leib array_push($sentence, $this->dadaSelectRandomWord($words, $totalWeight)); 50312993035SSascha Leib } 50412993035SSascha Leib 50512993035SSascha Leib return ucfirst(implode(' ', $sentence)) . '.'; 50612993035SSascha Leib 50712993035SSascha Leib } 50812993035SSascha Leib 50912993035SSascha Leib private function dadaSelectRandomWord($list, $totalWeight) { 51012993035SSascha Leib 51112993035SSascha Leib // get a random selection: 51212993035SSascha Leib $rand = mt_rand(0, $totalWeight); 51312993035SSascha Leib 51412993035SSascha Leib // match the selection to the weighted list: 51512993035SSascha Leib $cumulativeWeight = 0; 51612993035SSascha Leib for ($i=0; $i<count($list); $i++) { 51712993035SSascha Leib $cumulativeWeight += $list[$i][1]; 51812993035SSascha Leib if ($cumulativeWeight >= $rand) { 51912993035SSascha Leib return $list[$i][0]; 52012993035SSascha Leib } 52112993035SSascha Leib } 52212993035SSascha Leib return '***'; 523f5f4ca13SSascha Leib } 524f5f4ca13SSascha Leib 5256980370bSSascha Leib}