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 265f2c1759SSascha Leib // insert header data into the page: 27e56d7b71SSascha Leib if ($ACT == 'show') { 286980370bSSascha Leib $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertHeader'); 29e56d7b71SSascha Leib } else if ($ACT == 'admin' && isset($_REQUEST['page']) && $_REQUEST['page'] == 'botmon') { 30e56d7b71SSascha Leib $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertAdminHeader'); 31e56d7b71SSascha Leib } 325f2c1759SSascha Leib 33f5f4ca13SSascha Leib // Override the page rendering, if a captcha needs to be displayed: 34f5f4ca13SSascha Leib if ($ACT !== 'admin') { 35f5f4ca13SSascha Leib $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'showCaptcha'); 36f5f4ca13SSascha Leib } 37f5f4ca13SSascha Leib 385f2c1759SSascha Leib // write to the log after the page content was displayed: 395f2c1759SSascha Leib $controller->register_hook('TPL_CONTENT_DISPLAY', 'AFTER', $this, 'writeServerLog'); 405f2c1759SSascha Leib 416980370bSSascha Leib } 426980370bSSascha Leib 43b148c85eSSascha Leib /* session information */ 44f6a7ebc1SSascha Leib private $sessionId = null; 45f6a7ebc1SSascha Leib private $sessionType = ''; 46b148c85eSSascha Leib 476980370bSSascha Leib /** 486980370bSSascha Leib * Inserts tracking code to the page header 49e56d7b71SSascha Leib * (only called on 'show' actions) 506980370bSSascha Leib * 516980370bSSascha Leib * @param Event $event event object by reference 526980370bSSascha Leib * @return void 536980370bSSascha Leib */ 546980370bSSascha Leib public function insertHeader(Event $event, $param) { 556980370bSSascha Leib 566980370bSSascha Leib global $INFO; 576980370bSSascha Leib 58b148c85eSSascha Leib // populate the session id and type: 59b148c85eSSascha Leib $this->getSessionInfo(); 60b148c85eSSascha Leib 616980370bSSascha Leib // is there a user logged in? 625f2c1759SSascha Leib $username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name']) ? $INFO['userinfo']['name'] : ''); 636980370bSSascha Leib 646980370bSSascha Leib // build the tracker code: 65*12993035SSascha Leib $code = "document._botmon = {t0: Date.now(), session: " . json_encode($this->sessionId) . ", seed: " . json_encode($this->getConf('captchaSeed')) . ", ip: " . json_encode($_SERVER['REMOTE_ADDR']) . "};" . NL; 666980370bSSascha Leib if ($username) { 67e56d7b71SSascha Leib $code .= DOKU_TAB . DOKU_TAB . 'document._botmon.user = "' . $username . '";'. NL; 686980370bSSascha Leib } 696980370bSSascha Leib 705f2c1759SSascha Leib // add the deferred script loader:: 71e56d7b71SSascha Leib $code .= DOKU_TAB . DOKU_TAB . "addEventListener('DOMContentLoaded', function(){" . NL; 72e56d7b71SSascha Leib $code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "const e=document.createElement('script');" . NL; 73e56d7b71SSascha Leib $code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "e.async=true;e.defer=true;" . NL; 74e56d7b71SSascha Leib $code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "e.src='".DOKU_BASE."lib/plugins/botmon/client.js';" . NL; 75e56d7b71SSascha Leib $code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "document.getElementsByTagName('head')[0].appendChild(e);" . NL; 76e56d7b71SSascha Leib $code .= DOKU_TAB . DOKU_TAB . "});"; 775f2c1759SSascha Leib $event->data['script'][] = ['_data' => $code]; 78451abfadSSascha Leib } 79451abfadSSascha Leib 80451abfadSSascha Leib /** 81e56d7b71SSascha Leib * Inserts tracking code to the page header 82e56d7b71SSascha Leib * (only called on 'show' actions) 83e56d7b71SSascha Leib * 84e56d7b71SSascha Leib * @param Event $event event object by reference 85e56d7b71SSascha Leib * @return void 86e56d7b71SSascha Leib */ 87e56d7b71SSascha Leib public function insertAdminHeader(Event $event, $param) { 88e56d7b71SSascha Leib 89e56d7b71SSascha Leib $event->data['link'][] = ['rel' => 'stylesheet', 'href' => DOKU_BASE.'lib/plugins/botmon/admin.css', 'defer' => 'defer']; 900edf1a56SSascha Leib $event->data['script'][] = ['src' => DOKU_BASE.'lib/plugins/botmon/admin.js', 'defer' => 'defer', '_data' => '']; 91e56d7b71SSascha Leib } 92e56d7b71SSascha Leib 93e56d7b71SSascha Leib /** 94451abfadSSascha Leib * Writes data to the server log. 95451abfadSSascha Leib * 96451abfadSSascha Leib * @return void 97451abfadSSascha Leib */ 985f2c1759SSascha Leib public function writeServerLog(Event $event, $param) { 99451abfadSSascha Leib 100451abfadSSascha Leib global $conf; 101451abfadSSascha Leib global $INFO; 102091b5998SSascha Leib 1035f2c1759SSascha Leib // is there a user logged in? 1045f2c1759SSascha Leib $username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name']) 1055f2c1759SSascha Leib ? $INFO['userinfo']['name'] : ''); 1065f2c1759SSascha Leib 107b2e3bd8bSSascha Leib // clean the page ID 108b2e3bd8bSSascha Leib $pageId = preg_replace('/[\x00-\x1F]/', "\u{FFFD}", $INFO['id'] ?? ''); 109b2e3bd8bSSascha Leib 110451abfadSSascha Leib // create the log array: 111cf9f7fe8SSascha Leib $logArr = Array( 112f5f4ca13SSascha Leib $_SERVER['REMOTE_ADDR'], /* remote IP */ 113b2e3bd8bSSascha Leib $pageId, /* page ID */ 114b148c85eSSascha Leib $this->sessionId, /* Session ID */ 115b148c85eSSascha Leib $this->sessionType, /* session ID type */ 1165f2c1759SSascha Leib $username, /* user name */ 1172f2bc93aSSascha Leib $_SERVER['HTTP_USER_AGENT'] ?? '', /* User agent */ 118451abfadSSascha Leib $_SERVER['HTTP_REFERER'] ?? '', /* HTTP Referrer */ 119451abfadSSascha Leib substr($conf['lang'],0,2), /* page language */ 120a93de874SSascha Leib implode(',', array_unique(array_map( function($it) { return substr(trim($it),0,2); }, explode(',',trim($_SERVER['HTTP_ACCEPT_LANGUAGE'], " \t;,*"))))), /* accepted client languages */ 1215f2c1759SSascha Leib $this->getCountryCode() /* GeoIP country code */ 122cf9f7fe8SSascha Leib ); 123cf9f7fe8SSascha Leib 124cf9f7fe8SSascha Leib //* create the log line */ 1254cddc661SSascha Leib $filename = __DIR__ .'/logs/' . gmdate('Y-m-d') . '.srv.txt'; /* use GMT date for filename */ 126cf9f7fe8SSascha Leib $logline = gmdate('Y-m-d H:i:s'); /* use GMT time for log entries */ 127cf9f7fe8SSascha Leib foreach ($logArr as $tab) { 128cf9f7fe8SSascha Leib $logline .= "\t" . $tab; 129cf9f7fe8SSascha Leib }; 130cf9f7fe8SSascha Leib 131cf9f7fe8SSascha Leib /* write the log line to the file */ 132cf9f7fe8SSascha Leib $logfile = fopen($filename, 'a'); 133cf9f7fe8SSascha Leib if (!$logfile) die(); 134cf9f7fe8SSascha Leib if (fwrite($logfile, $logline . "\n") === false) { 135cf9f7fe8SSascha Leib fclose($logfile); 136cf9f7fe8SSascha Leib die(); 1376980370bSSascha Leib } 138cf9f7fe8SSascha Leib 139cf9f7fe8SSascha Leib /* Done */ 140cf9f7fe8SSascha Leib fclose($logfile); 141cf9f7fe8SSascha Leib } 142b148c85eSSascha Leib 1435f2c1759SSascha Leib private function getCountryCode() { 1445f2c1759SSascha Leib 145f5f4ca13SSascha Leib $country = ( $_SERVER['REMOTE_ADDR'] == '127.0.0.1' ? 'local' : 'ZZ' ); // default if no geoip is available! 1465f2c1759SSascha Leib 1475f2c1759SSascha Leib $lib = $this->getConf('geoiplib'); /* which library to use? (can only be phpgeoip or disabled) */ 1485f2c1759SSascha Leib 1495f2c1759SSascha Leib try { 1505f2c1759SSascha Leib 1515f2c1759SSascha Leib // use GeoIP module? 1525f2c1759SSascha Leib if ($lib == 'phpgeoip' && extension_loaded('geoip') && geoip_db_avail(GEOIP_COUNTRY_EDITION)) { // Use PHP GeoIP module 1535f2c1759SSascha Leib $result = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']); 1545f2c1759SSascha Leib $country = ($result ? $result : $country); 1555f2c1759SSascha Leib } 1565f2c1759SSascha Leib } catch (Exception $e) { 1575f2c1759SSascha Leib Logger::error('BotMon Plugin: GeoIP Error', $e->getMessage()); 1585f2c1759SSascha Leib } 1595f2c1759SSascha Leib 1605f2c1759SSascha Leib return $country; 1615f2c1759SSascha Leib } 1625f2c1759SSascha Leib 163b148c85eSSascha Leib private function getSessionInfo() { 164b148c85eSSascha Leib 165b148c85eSSascha Leib // what is the session identifier? 166b148c85eSSascha Leib if (isset($_SESSION)) { 167b148c85eSSascha Leib $sesKeys = array_keys($_SESSION); /* DokuWiki Session ID preferred */ 168b148c85eSSascha Leib foreach ($sesKeys as $key) { 169b148c85eSSascha Leib if (substr($key, 0, 2) == 'DW') { 170b148c85eSSascha Leib $this->sessionId = $key; 171b148c85eSSascha Leib $this->sessionType = 'dw'; 172b148c85eSSascha Leib return; 173b148c85eSSascha Leib } 174b148c85eSSascha Leib } 175b148c85eSSascha Leib } 176f6a7ebc1SSascha Leib if (!$this->sessionId) { /* no DokuWiki Session ID, try PHP session ID */ 177b148c85eSSascha Leib $this->sessionId = session_id(); 178b148c85eSSascha Leib $this->sessionType = 'php'; 179b148c85eSSascha Leib } 180f5f4ca13SSascha Leib if (!$this->sessionId) { /* no PHP session ID, try IP address */ 181f5f4ca13SSascha Leib $this->sessionId = $_SERVER['REMOTE_ADDR']; 182b148c85eSSascha Leib $this->sessionType = 'ip'; 183b148c85eSSascha Leib } 184f6a7ebc1SSascha Leib if (!$this->sessionId) { /* if everything else fails, just us a random ID */ 185b148c85eSSascha Leib $this->sessionId = rand(1000000, 9999999); 186b148c85eSSascha Leib $this->sessionType = 'rand'; 187b148c85eSSascha Leib } 188b148c85eSSascha Leib } 189f5f4ca13SSascha Leib 190f5f4ca13SSascha Leib public function showCaptcha(Event $event) { 191f5f4ca13SSascha Leib 192*12993035SSascha Leib $useCaptcha = $this->getConf('useCaptcha'); 193f5f4ca13SSascha Leib 194*12993035SSascha Leib if ($useCaptcha !== 'disabled' && $this->checkCaptchaCookie()) { 195*12993035SSascha Leib echo '<h1 class="sectionedit1">'; tpl_pagetitle(); echo "</h1>\n"; // always show the original page title 196f5f4ca13SSascha Leib $event->preventDefault(); // don't show normal content 197*12993035SSascha Leib switch ($useCaptcha) { 198*12993035SSascha Leib case 'blank': 199*12993035SSascha Leib $this->insertBlankBox(); // show dada filler instead of text 200*12993035SSascha Leib break; 201*12993035SSascha Leib case 'dada': 202*12993035SSascha Leib $this->insertDadaFiller(); // show dada filler instead of text 203*12993035SSascha Leib break; 204*12993035SSascha Leib } 205f5f4ca13SSascha Leib $this->insertCaptchaLoader(); // and load the captcha 206f5f4ca13SSascha Leib } 207f5f4ca13SSascha Leib } 208f5f4ca13SSascha Leib 209f5f4ca13SSascha Leib private function checkCaptchaCookie() { 210f5f4ca13SSascha Leib 211*12993035SSascha Leib $cookieVal = isset($_COOKIE['captcha']) ? $_COOKIE['captcha'] : null; 212f5f4ca13SSascha Leib 213*12993035SSascha Leib $today = new DateTime(); 214*12993035SSascha Leib $isodate = substr((new DateTime())->format('c'), 0, 10); 215*12993035SSascha Leib 216*12993035SSascha Leib $raw = $this->getConf('captchaSeed') . '|' . $_SERVER['SERVER_NAME'] . '|' . $_SERVER['REMOTE_ADDR'] . '|' . $isodate; 217*12993035SSascha Leib 218*12993035SSascha Leib return $cookieVal !== hash('sha256', $raw); 219f5f4ca13SSascha Leib } 220f5f4ca13SSascha Leib 221f5f4ca13SSascha Leib private function insertCaptchaLoader() { 222*12993035SSascha Leib echo '<script>' . NL; 223*12993035SSascha Leib 224*12993035SSascha Leib // add the deferred script loader:: 225*12993035SSascha Leib echo DOKU_TAB . "addEventListener('DOMContentLoaded', function(){" . NL; 226*12993035SSascha Leib echo DOKU_TAB . DOKU_TAB . "const cj=document.createElement('script');" . NL; 227*12993035SSascha Leib echo DOKU_TAB . DOKU_TAB . "cj.async=true;cj.defer=true;cj.type='text/javascript';" . NL; 228*12993035SSascha Leib echo DOKU_TAB . DOKU_TAB . "cj.src='".DOKU_BASE."lib/plugins/botmon/captcha.js';" . NL; 229*12993035SSascha Leib echo DOKU_TAB . DOKU_TAB . "document.getElementsByTagName('head')[0].appendChild(cj);" . NL; 230*12993035SSascha Leib echo DOKU_TAB . "});"; 231*12993035SSascha Leib echo '</script>' . NL; 232f5f4ca13SSascha Leib 233f5f4ca13SSascha Leib } 234f5f4ca13SSascha Leib 235*12993035SSascha Leib // inserts a blank box to ensure there is enough space for the captcha: 236*12993035SSascha Leib private function insertBlankBox() { 237*12993035SSascha Leib 238*12993035SSascha Leib echo '<p style="min-height: 100px;"> </p>'; 239*12993035SSascha Leib } 240*12993035SSascha Leib 241*12993035SSascha Leib /* Generates a few paragraphs of Dada text to show instead of the article content */ 242f5f4ca13SSascha Leib private function insertDadaFiller() { 243f5f4ca13SSascha Leib 244*12993035SSascha Leib global $conf; 245*12993035SSascha Leib global $TOC; 246*12993035SSascha Leib global $ID; 247f5f4ca13SSascha Leib 248*12993035SSascha Leib // list of languages to search for the wordlist 249*12993035SSascha Leib $langs = array_unique([$conf['lang'], 'la']); 250f5f4ca13SSascha Leib 251*12993035SSascha Leib // find path to the first available wordlist: 252*12993035SSascha Leib foreach ($langs as $lang) { 253*12993035SSascha Leib $filename = __DIR__ .'/lang/' . $lang . '/wordlist.txt'; /* language-specific wordlist */ 254*12993035SSascha Leib if (file_exists($filename)) { 255*12993035SSascha Leib break; 256*12993035SSascha Leib } 257*12993035SSascha Leib } 258f5f4ca13SSascha Leib 259*12993035SSascha Leib // load the wordlist file: 260*12993035SSascha Leib if (file_exists($filename)) { 261*12993035SSascha Leib $words = array(); 262*12993035SSascha Leib $totalWeight = 0; 263*12993035SSascha Leib $lines = file($filename, FILE_SKIP_EMPTY_LINES); 264*12993035SSascha Leib foreach ($lines as $line) { 265*12993035SSascha Leib $arr = explode("\t", $line); 266*12993035SSascha Leib $arr[1] = ( count($arr) > 1 ? (int) trim($arr[1]) : 1 ); 267*12993035SSascha Leib $totalWeight += (int) $arr[1]; 268*12993035SSascha Leib array_push($words, $arr); 269*12993035SSascha Leib } 270*12993035SSascha Leib } else { 271*12993035SSascha Leib echo '<script> console.log("Can’t generate filler text: wordlist file not found!"); </script>'; 272*12993035SSascha Leib return; 273*12993035SSascha Leib } 274f5f4ca13SSascha Leib 275*12993035SSascha Leib // If a TOC exists, use it for the headlines: 276*12993035SSascha Leib if(is_array($TOC)) { 277*12993035SSascha Leib $toc = $TOC; 278*12993035SSascha Leib } else { 279*12993035SSascha Leib $meta = p_get_metadata($ID, '', METADATA_RENDER_USING_CACHE); 280*12993035SSascha Leib //$tocok = (isset($meta['internal']['toc']) ? $meta['internal']['toc'] : $tocok = true); 281*12993035SSascha Leib $toc = isset($meta['description']['tableofcontents']) ? $meta['description']['tableofcontents'] : null; 282*12993035SSascha Leib } 283*12993035SSascha Leib if (!$toc) { // no TOC, generate my own: 284*12993035SSascha Leib $hlCount = mt_rand(0, (int) $conf['tocminheads']); 285*12993035SSascha Leib $toc = array(); 286*12993035SSascha Leib for ($i=0; $i<$hlCount; $i++) { 287*12993035SSascha Leib array_push($toc, $this->dadaMakeHeadline($words, $totalWeight)); // $toc 288*12993035SSascha Leib } 289*12993035SSascha Leib } 290*12993035SSascha Leib 291*12993035SSascha Leib // if H1 heading is not in the TOC, add a chappeau section: 292*12993035SSascha Leib $chapeauCount = mt_rand(1, 3); 293*12993035SSascha Leib if ((int) $conf['toptoclevel'] > 1) { 294*12993035SSascha Leib echo "<div class=\"level1\">\n"; 295*12993035SSascha Leib for ($i=0; $i<$chapeauCount; $i++) { 296*12993035SSascha Leib echo $this->dadaMakeParagraph($words, $totalWeight); 297*12993035SSascha Leib } 298*12993035SSascha Leib echo "</div>\n"; 299*12993035SSascha Leib } 300*12993035SSascha Leib 301*12993035SSascha Leib // text sections for each sub-headline: 302*12993035SSascha Leib foreach ($toc as $hl) { 303*12993035SSascha Leib echo $this->dadaMakeSection($words, $totalWeight, $hl); 304*12993035SSascha Leib } 305*12993035SSascha Leib } 306*12993035SSascha Leib 307*12993035SSascha Leib private function dadaMakeSection($words, $totalWeight, $hl) { 308*12993035SSascha Leib 309*12993035SSascha Leib global $conf; 310*12993035SSascha Leib 311*12993035SSascha Leib // how many paragraphs? 312*12993035SSascha Leib $paragraphCount = mt_rand(1, 4); 313*12993035SSascha Leib 314*12993035SSascha Leib // section level 315*12993035SSascha Leib $topTocLevel = (int) $conf['toptoclevel']; 316*12993035SSascha Leib $secLevel = $hl['level'] + 1;; 317*12993035SSascha Leib 318*12993035SSascha Leib // return value: 319*12993035SSascha Leib $sec = ""; 320*12993035SSascha Leib 321*12993035SSascha Leib // make a headline: 322*12993035SSascha Leib if ($topTocLevel > 1 || $secLevel > 1) { 323*12993035SSascha Leib $sec .= "<h{$secLevel} id=\"{$hl['hid']}\">{$hl['title']}</h{$secLevel}>\n"; 324*12993035SSascha Leib } 325*12993035SSascha Leib 326*12993035SSascha Leib // add the paragraphs: 327*12993035SSascha Leib $sec .= "<div class=\"level{$secLevel}\">\n"; 328*12993035SSascha Leib for ($i=0; $i<$paragraphCount; $i++) { 329*12993035SSascha Leib $sec .= $this->dadaMakeParagraph($words, $totalWeight); 330*12993035SSascha Leib } 331*12993035SSascha Leib $sec .= "</div>\n"; 332*12993035SSascha Leib 333*12993035SSascha Leib return $sec; 334*12993035SSascha Leib } 335*12993035SSascha Leib 336*12993035SSascha Leib private function dadaMakeHeadline($words, $totalWeight) { 337*12993035SSascha Leib 338*12993035SSascha Leib // how many words to generate? 339*12993035SSascha Leib $wordCount = mt_rand(2, 5); 340*12993035SSascha Leib 341*12993035SSascha Leib // function returns an array: 342*12993035SSascha Leib $r = Array(); 343*12993035SSascha Leib 344*12993035SSascha Leib // generate the headline: 345*12993035SSascha Leib $hlArr = array(); 346*12993035SSascha Leib for ($i=0; $i<$wordCount; $i++) { 347*12993035SSascha Leib array_push($hlArr, $this->dadaSelectRandomWord($words, $totalWeight)); 348*12993035SSascha Leib } 349*12993035SSascha Leib 350*12993035SSascha Leib $r['title'] = ucfirst(implode(' ', $hlArr)); 351*12993035SSascha Leib 352*12993035SSascha Leib $r['hid'] = preg_replace('/[^\w\d\-]+/i', '_', strtolower($r['title'])); 353*12993035SSascha Leib $r['type'] = 'ul'; // always ul! 354*12993035SSascha Leib $r['level'] = 1; // always level 1 for now 355*12993035SSascha Leib 356*12993035SSascha Leib return $r; 357*12993035SSascha Leib } 358*12993035SSascha Leib 359*12993035SSascha Leib private function dadaMakeParagraph($words, $totalWeight) { 360*12993035SSascha Leib 361*12993035SSascha Leib // how many words to generate? 362*12993035SSascha Leib $sentenceCount = mt_rand(2, 5); 363*12993035SSascha Leib 364*12993035SSascha Leib $paragraph = array(); 365*12993035SSascha Leib for ($i=0; $i<$sentenceCount; $i++) { 366*12993035SSascha Leib array_push($paragraph, $this->dadaMakeSentence($words, $totalWeight)); 367*12993035SSascha Leib } 368*12993035SSascha Leib 369*12993035SSascha Leib return "<p>\n" . implode(' ', $paragraph) . "\n</p>\n"; 370*12993035SSascha Leib 371*12993035SSascha Leib } 372*12993035SSascha Leib 373*12993035SSascha Leib private function dadaMakeSentence($words, $totalWeight) { 374*12993035SSascha Leib 375*12993035SSascha Leib // how many words to generate? 376*12993035SSascha Leib $wordCount = mt_rand(4, 20); 377*12993035SSascha Leib 378*12993035SSascha Leib // generate the sentence: 379*12993035SSascha Leib $sentence = array(); 380*12993035SSascha Leib for ($i=0; $i<$wordCount; $i++) { 381*12993035SSascha Leib array_push($sentence, $this->dadaSelectRandomWord($words, $totalWeight)); 382*12993035SSascha Leib } 383*12993035SSascha Leib 384*12993035SSascha Leib return ucfirst(implode(' ', $sentence)) . '.'; 385*12993035SSascha Leib 386*12993035SSascha Leib } 387*12993035SSascha Leib 388*12993035SSascha Leib private function dadaSelectRandomWord($list, $totalWeight) { 389*12993035SSascha Leib 390*12993035SSascha Leib // get a random selection: 391*12993035SSascha Leib $rand = mt_rand(0, $totalWeight); 392*12993035SSascha Leib 393*12993035SSascha Leib // match the selection to the weighted list: 394*12993035SSascha Leib $cumulativeWeight = 0; 395*12993035SSascha Leib for ($i=0; $i<count($list); $i++) { 396*12993035SSascha Leib $cumulativeWeight += $list[$i][1]; 397*12993035SSascha Leib if ($cumulativeWeight >= $rand) { 398*12993035SSascha Leib return $list[$i][0]; 399*12993035SSascha Leib } 400*12993035SSascha Leib } 401*12993035SSascha Leib return '***'; 402f5f4ca13SSascha Leib } 403f5f4ca13SSascha Leib 4046980370bSSascha Leib}