2025-08-20 16:34:20 +03:00
< ? php
use dokuwiki\Extension\EventHandler ;
use dokuwiki\Extension\Event ;
2025-09-10 14:44:08 +02:00
use dokuwiki\Logger ;
2025-08-20 16:34:20 +03:00
/**
2025-09-01 16:25:25 +02:00
* Action Component for the Bot Monitoring Plugin
2025-08-20 16:34:20 +03:00
*
* @ license GPL 3 ( http :// www . gnu . org / licenses / gpl . html )
* @ author Sascha Leib < sascha . leib ( at ) kolmio . com >
*/
2025-09-01 16:25:25 +02:00
class action_plugin_botmon extends DokuWiki_Action_Plugin {
2025-08-20 16:34:20 +03:00
/**
2025-08-20 22:58:42 +03:00
* Registers a callback functions
*
* @ param EventHandler $controller DokuWiki ' s event controller object
* @ return void
*/
public function register ( EventHandler $controller ) {
2025-09-13 23:20:43 +02:00
2025-10-14 20:58:33 +02:00
global $ACT ;
2025-09-13 23:20:43 +02:00
2025-10-26 09:14:45 +01:00
// populate the session id and type:
$this -> setSessionInfo ();
2025-10-25 15:58:32 +02:00
2025-10-14 20:58:33 +02:00
// insert header data into the page:
2025-10-25 15:58:32 +02:00
if ( $ACT == 'show' || $ACT == 'edit' || $ACT == 'media' ) {
2025-10-14 20:58:33 +02:00
$controller -> register_hook ( 'TPL_METAHEADER_OUTPUT' , 'BEFORE' , $this , 'insertHeader' );
2025-10-25 15:58:32 +02:00
// Override the page rendering, if a captcha needs to be displayed:
$controller -> register_hook ( 'TPL_ACT_RENDER' , 'BEFORE' , $this , 'showCaptcha' );
2025-10-14 20:58:33 +02:00
} else if ( $ACT == 'admin' && isset ( $_REQUEST [ 'page' ]) && $_REQUEST [ 'page' ] == 'botmon' ) {
$controller -> register_hook ( 'TPL_METAHEADER_OUTPUT' , 'BEFORE' , $this , 'insertAdminHeader' );
2025-10-25 15:58:32 +02:00
}
// also show a captcha before the image preview
$controller -> register_hook ( 'TPL_IMG_DISPLAY' , 'BEFORE' , $this , 'showImageCaptcha' );
2025-10-20 22:06:36 +02:00
2025-09-13 23:20:43 +02:00
// write to the log after the page content was displayed:
$controller -> register_hook ( 'TPL_CONTENT_DISPLAY' , 'AFTER' , $this , 'writeServerLog' );
2025-08-20 22:58:42 +03:00
}
2025-09-12 15:38:28 +02:00
/* session information */
2025-09-12 17:50:05 +02:00
private $sessionId = null ;
private $sessionType = '' ;
2025-10-24 10:02:56 +02:00
private $showCaptcha = '-' ;
2025-09-12 15:38:28 +02:00
2025-08-20 22:58:42 +03:00
/**
* Inserts tracking code to the page header
2025-10-14 20:58:33 +02:00
* ( only called on 'show' actions )
2025-08-20 22:58:42 +03:00
*
* @ param Event $event event object by reference
* @ return void
*/
2025-08-20 16:34:20 +03:00
public function insertHeader ( Event $event , $param ) {
global $INFO ;
2025-09-12 15:38:28 +02:00
2025-08-20 16:34:20 +03:00
// build the tracker code:
2025-10-25 15:58:32 +02:00
$code = $this -> getBMHeader ();
2025-08-20 16:34:20 +03:00
2025-09-13 23:20:43 +02:00
// add the deferred script loader::
2025-10-14 20:58:33 +02:00
$code .= DOKU_TAB . DOKU_TAB . " addEventListener('DOMContentLoaded', function() { " . NL ;
$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . " const e=document.createElement('script'); " . NL ;
$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . " e.async=true;e.defer=true; " . NL ;
$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . " e.src=' " . DOKU_BASE . " lib/plugins/botmon/client.js'; " . NL ;
$code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . " document.getElementsByTagName('head')[0].appendChild(e); " . NL ;
$code .= DOKU_TAB . DOKU_TAB . " }); " ;
2025-09-13 23:20:43 +02:00
$event -> data [ 'script' ][] = [ '_data' => $code ];
2025-09-06 20:01:03 +02:00
}
2025-10-25 15:58:32 +02:00
/* create the BM object code for insertion into a script element: */
private function getBMHeader () {
// build the tracker code:
$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 ;
// is there a user logged in?
$username = ( ! empty ( $INFO [ 'userinfo' ]) && ! empty ( $INFO [ 'userinfo' ][ 'name' ]) ? $INFO [ 'userinfo' ][ 'name' ] : '' );
if ( $username ) {
$code .= DOKU_TAB . DOKU_TAB . 'document._botmon.user = "' . $username . '";' . NL ;
}
return $code ;
}
2025-10-14 20:58:33 +02:00
/**
* Inserts tracking code to the page header
* ( only called on 'show' actions )
*
* @ param Event $event event object by reference
* @ return void
*/
public function insertAdminHeader ( Event $event , $param ) {
$event -> data [ 'link' ][] = [ 'rel' => 'stylesheet' , 'href' => DOKU_BASE . 'lib/plugins/botmon/admin.css' , 'defer' => 'defer' ];
2025-10-15 20:12:42 +02:00
$event -> data [ 'script' ][] = [ 'src' => DOKU_BASE . 'lib/plugins/botmon/admin.js' , 'defer' => 'defer' , '_data' => '' ];
2025-10-14 20:58:33 +02:00
}
2025-09-06 20:01:03 +02:00
/**
* Writes data to the server log .
*
* @ return void
*/
2025-09-13 23:20:43 +02:00
public function writeServerLog ( Event $event , $param ) {
2025-09-06 20:01:03 +02:00
global $conf ;
global $INFO ;
2025-08-29 23:18:51 +03:00
2025-09-13 23:20:43 +02:00
// is there a user logged in?
$username = ( ! empty ( $INFO [ 'userinfo' ]) && ! empty ( $INFO [ 'userinfo' ][ 'name' ])
? $INFO [ 'userinfo' ][ 'name' ] : '' );
2025-09-05 09:15:08 +02:00
// clean the page ID
$pageId = preg_replace ( '/[\x00-\x1F]/' , " \ u { FFFD} " , $INFO [ 'id' ] ? ? '' );
2025-09-06 20:01:03 +02:00
// create the log array:
2025-08-20 22:58:42 +03:00
$logArr = Array (
2025-10-20 22:06:36 +02:00
$_SERVER [ 'REMOTE_ADDR' ], /* remote IP */
2025-09-05 09:15:08 +02:00
$pageId , /* page ID */
2025-09-12 15:38:28 +02:00
$this -> sessionId , /* Session ID */
$this -> sessionType , /* session ID type */
2025-09-13 23:20:43 +02:00
$username , /* user name */
2025-08-30 13:01:50 +03:00
$_SERVER [ 'HTTP_USER_AGENT' ] ? ? '' , /* User agent */
2025-09-06 20:01:03 +02:00
$_SERVER [ 'HTTP_REFERER' ] ? ? '' , /* HTTP Referrer */
substr ( $conf [ 'lang' ], 0 , 2 ), /* page language */
2025-10-13 14:56:25 +02:00
implode ( ',' , array_unique ( array_map ( function ( $it ) { return substr ( trim ( $it ), 0 , 2 ); }, explode ( ',' , trim ( $_SERVER [ 'HTTP_ACCEPT_LANGUAGE' ], " \t ;,* " ))))), /* accepted client languages */
2025-10-24 10:02:56 +02:00
$this -> getCountryCode (), /* GeoIP country code */
2025-10-25 15:58:32 +02:00
$this -> showCaptcha /* show captcha? */
);
2025-08-20 22:58:42 +03:00
//* create the log line */
2025-09-01 10:15:36 +02:00
$filename = __DIR__ . '/logs/' . gmdate ( 'Y-m-d' ) . '.srv.txt' ; /* use GMT date for filename */
2025-08-20 22:58:42 +03:00
$logline = gmdate ( 'Y-m-d H:i:s' ); /* use GMT time for log entries */
foreach ( $logArr as $tab ) {
$logline .= " \t " . $tab ;
};
/* write the log line to the file */
$logfile = fopen ( $filename , 'a' );
if ( ! $logfile ) die ();
if ( fwrite ( $logfile , $logline . " \n " ) === false ) {
fclose ( $logfile );
die ();
}
/* Done */
fclose ( $logfile );
}
2025-09-12 15:38:28 +02:00
2025-09-13 23:20:43 +02:00
private function getCountryCode () {
2025-10-20 22:06:36 +02:00
$country = ( $_SERVER [ 'REMOTE_ADDR' ] == '127.0.0.1' ? 'local' : 'ZZ' ); // default if no geoip is available!
2025-09-13 23:20:43 +02:00
$lib = $this -> getConf ( 'geoiplib' ); /* which library to use? (can only be phpgeoip or disabled) */
try {
// use GeoIP module?
if ( $lib == 'phpgeoip' && extension_loaded ( 'geoip' ) && geoip_db_avail ( GEOIP_COUNTRY_EDITION )) { // Use PHP GeoIP module
$result = geoip_country_code_by_name ( $_SERVER [ 'REMOTE_ADDR' ]);
$country = ( $result ? $result : $country );
}
} catch ( Exception $e ) {
Logger :: error ( 'BotMon Plugin: GeoIP Error' , $e -> getMessage ());
}
return $country ;
}
2025-10-26 09:14:45 +01:00
private function setSessionInfo () {
2025-09-12 15:38:28 +02:00
// what is the session identifier?
if ( isset ( $_SESSION )) {
$sesKeys = array_keys ( $_SESSION ); /* DokuWiki Session ID preferred */
foreach ( $sesKeys as $key ) {
if ( substr ( $key , 0 , 2 ) == 'DW' ) {
$this -> sessionId = $key ;
$this -> sessionType = 'dw' ;
return ;
}
}
}
2025-09-12 17:50:05 +02:00
if ( ! $this -> sessionId ) { /* no DokuWiki Session ID, try PHP session ID */
2025-09-12 15:38:28 +02:00
$this -> sessionId = session_id ();
$this -> sessionType = 'php' ;
}
2025-10-20 22:06:36 +02:00
if ( ! $this -> sessionId ) { /* no PHP session ID, try IP address */
$this -> sessionId = $_SERVER [ 'REMOTE_ADDR' ];
2025-09-12 15:38:28 +02:00
$this -> sessionType = 'ip' ;
}
2025-10-26 09:14:45 +01:00
if ( ! $this -> sessionId ) { /* if all fails, use random data */
$this -> sessionId = rand ( 100000000 , 999999999 );
$this -> sessionType = 'rnd' ;
}
2025-09-12 15:38:28 +02:00
}
2025-10-20 22:06:36 +02:00
public function showCaptcha ( Event $event ) {
2025-10-23 15:01:36 +02:00
$useCaptcha = $this -> getConf ( 'useCaptcha' );
2025-10-25 15:58:32 +02:00
$cCode = '-' ;
if ( $useCaptcha !== 'disabled' ) {
if ( $this -> captchaWhitelisted ()) {
$cCode = 'W' ; // whitelisted
} elseif ( $this -> hasCaptchaCookie ()) {
$cCode = 'N' ; // user already has a cookie
} else {
$cCode = 'Y' ; // show the captcha
echo '<h1 class="sectionedit1">' ; tpl_pagetitle (); echo " </h1> \n " ; // always show the original page title
$event -> preventDefault (); // don't show normal content
switch ( $useCaptcha ) {
case 'blank' :
$this -> insertBlankBox (); // show dada filler instead of text
break ;
case 'dada' :
$this -> insertDadaFiller (); // show dada filler instead of text
break ;
}
$this -> insertCaptchaLoader (); // and load the captcha
}
}
$this -> showCaptcha = $cCode ; // store the captcha code for the logfile
}
2025-10-24 10:02:56 +02:00
2025-10-25 15:58:32 +02:00
public function showImageCaptcha ( Event $event , $param ) {
$useCaptcha = $this -> getConf ( 'useCaptcha' );
2025-10-24 10:02:56 +02:00
2025-10-25 15:58:32 +02:00
echo '<script>' . $this -> getBMHeader ( $event , $param ) . '</script>' ;
$cCode = '-' ;
if ( $useCaptcha !== 'disabled' ) {
if ( $this -> captchaWhitelisted ()) {
$cCode = 'W' ; // whitelisted
2025-10-23 15:01:36 +02:00
}
2025-10-25 15:58:32 +02:00
elseif ( $this -> hasCaptchaCookie ()) {
$cCode = 'N' ; // user already has a cookie
}
else {
$cCode = 'Y' ; // show the captcha
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
$event -> preventDefault (); // don't show normal content
// TODO Insert dummy image
$this -> insertCaptchaLoader (); // and load the captcha
}
};
$this -> showCaptcha = $cCode ; // store the captcha code for the logfile
2025-10-23 15:01:36 +02:00
}
2025-10-25 15:58:32 +02:00
private function hasCaptchaCookie () {
2025-10-23 15:01:36 +02:00
2025-10-23 20:56:24 +02:00
$cookieVal = isset ( $_COOKIE [ 'DWConfirm' ]) ? $_COOKIE [ 'DWConfirm' ] : null ;
2025-10-23 15:01:36 +02:00
2025-10-23 20:56:24 +02:00
$today = substr (( new DateTime ()) -> format ( 'c' ), 0 , 10 );
2025-10-23 15:01:36 +02:00
2025-10-23 20:56:24 +02:00
$raw = $this -> getConf ( 'captchaSeed' ) . '|' . $_SERVER [ 'SERVER_NAME' ] . '|' . $_SERVER [ 'REMOTE_ADDR' ] . '|' . $today ;
$expected = hash ( 'sha256' , $raw );
2025-10-23 15:01:36 +02:00
2025-10-23 20:56:24 +02:00
//echo '<ul><li>cookie: ' . $cookieVal . '</li><li>expected: ' . $expected . '</li><li>matches: ' .($cookieVal == $expected ? 'true' : 'false') . '</li></ul>';
2025-10-25 15:58:32 +02:00
return $cookieVal == $expected ;
2025-10-23 15:01:36 +02:00
}
2025-10-24 10:02:56 +02:00
// check if the visitor's IP is on a whitelist:
private function captchaWhitelisted () {
// normalise IP address:
$ip = inet_pton ( $_SERVER [ 'REMOTE_ADDR' ]);
// find which file to open:
$prefixes = [ 'user' , 'default' ];
foreach ( $prefixes as $pre ) {
$filename = __DIR__ . '/config/' . $pre . '-whitelist.txt' ;
if ( file_exists ( $filename )) {
break ;
}
}
if ( file_exists ( $filename )) {
$lines = file ( $filename , FILE_SKIP_EMPTY_LINES );
foreach ( $lines as $line ) {
if ( trim ( $line ) !== '' && ! str_starts_with ( $line , '#' )) {
$col = explode ( " \t " , $line );
if ( count ( $col ) >= 2 ) {
$from = inet_pton ( $col [ 0 ]);
$to = inet_pton ( $col [ 1 ]);
if ( $ip >= $from && $ip <= $to ) {
2025-10-25 15:58:32 +02:00
return true ; /* IP whitelisted */
2025-10-24 10:02:56 +02:00
}
}
}
}
}
2025-10-25 15:58:32 +02:00
return false ; /* IP not found in whitelist */
2025-10-24 10:02:56 +02:00
}
2025-10-23 15:01:36 +02:00
private function insertCaptchaLoader () {
2025-10-25 21:14:35 +02:00
2025-10-23 15:01:36 +02:00
echo '<script>' . NL ;
// add the deferred script loader::
echo DOKU_TAB . " addEventListener('DOMContentLoaded', function() { " . NL ;
echo DOKU_TAB . DOKU_TAB . " const cj=document.createElement('script'); " . NL ;
echo DOKU_TAB . DOKU_TAB . " cj.async=true;cj.defer=true;cj.type='text/javascript'; " . NL ;
echo DOKU_TAB . DOKU_TAB . " cj.src=' " . DOKU_BASE . " lib/plugins/botmon/captcha.js'; " . NL ;
echo DOKU_TAB . DOKU_TAB . " document.getElementsByTagName('head')[0].appendChild(cj); " . NL ;
echo DOKU_TAB . " }); " ;
2025-10-25 21:14:35 +02:00
// add the locales for the captcha:
echo DOKU_TAB . '$BMLocales = {' . NL ;
echo DOKU_TAB . DOKU_TAB . '"dlgTitle": ' . json_encode ( $this -> getLang ( 'bm_dlgTitle' )) . ',' . NL ;
echo DOKU_TAB . DOKU_TAB . '"dlgSubtitle": ' . json_encode ( $this -> getLang ( 'bm_dlgSubtitle' )) . ',' . NL ;
echo DOKU_TAB . DOKU_TAB . '"dlgConfirm": ' . json_encode ( $this -> getLang ( 'bm_dlgConfirm' )) . ',' . NL ;
echo DOKU_TAB . DOKU_TAB . '"dlgChecking": ' . json_encode ( $this -> getLang ( 'bm_dlgChecking' )) . ',' . NL ;
echo DOKU_TAB . DOKU_TAB . '"dlgLoading": ' . json_encode ( $this -> getLang ( 'bm_dlgLoading' )) . ',' . NL ;
echo DOKU_TAB . DOKU_TAB . '"dlgError": ' . json_encode ( $this -> getLang ( 'bm_dlgError' )) . ',' . NL ;
echo DOKU_TAB . '};' . NL ;
2025-10-23 15:01:36 +02:00
echo '</script>' . NL ;
}
2025-10-20 22:06:36 +02:00
2025-10-23 15:01:36 +02:00
// inserts a blank box to ensure there is enough space for the captcha:
private function insertBlankBox () {
2025-10-20 22:06:36 +02:00
2025-10-23 15:01:36 +02:00
echo '<p style="min-height: 100px;"> </p>' ;
}
/* Generates a few paragraphs of Dada text to show instead of the article content */
private function insertDadaFiller () {
global $conf ;
global $TOC ;
global $ID ;
// list of languages to search for the wordlist
$langs = array_unique ([ $conf [ 'lang' ], 'la' ]);
// find path to the first available wordlist:
foreach ( $langs as $lang ) {
$filename = __DIR__ . '/lang/' . $lang . '/wordlist.txt' ; /* language-specific wordlist */
if ( file_exists ( $filename )) {
break ;
}
}
// load the wordlist file:
if ( file_exists ( $filename )) {
$words = array ();
$totalWeight = 0 ;
$lines = file ( $filename , FILE_SKIP_EMPTY_LINES );
foreach ( $lines as $line ) {
$arr = explode ( " \t " , $line );
$arr [ 1 ] = ( count ( $arr ) > 1 ? ( int ) trim ( $arr [ 1 ]) : 1 );
$totalWeight += ( int ) $arr [ 1 ];
array_push ( $words , $arr );
}
} else {
echo '<script> console.log("Can’ t generate filler text: wordlist file not found!"); </script>' ;
return ;
}
// If a TOC exists, use it for the headlines:
if ( is_array ( $TOC )) {
$toc = $TOC ;
2025-10-20 22:06:36 +02:00
} else {
2025-10-23 15:01:36 +02:00
$meta = p_get_metadata ( $ID , '' , METADATA_RENDER_USING_CACHE );
//$tocok = (isset($meta['internal']['toc']) ? $meta['internal']['toc'] : $tocok = true);
$toc = isset ( $meta [ 'description' ][ 'tableofcontents' ]) ? $meta [ 'description' ][ 'tableofcontents' ] : null ;
}
if ( ! $toc ) { // no TOC, generate my own:
$hlCount = mt_rand ( 0 , ( int ) $conf [ 'tocminheads' ]);
$toc = array ();
for ( $i = 0 ; $i < $hlCount ; $i ++ ) {
array_push ( $toc , $this -> dadaMakeHeadline ( $words , $totalWeight )); // $toc
}
}
// if H1 heading is not in the TOC, add a chappeau section:
$chapeauCount = mt_rand ( 1 , 3 );
if (( int ) $conf [ 'toptoclevel' ] > 1 ) {
echo " <div class= \" level1 \" > \n " ;
for ( $i = 0 ; $i < $chapeauCount ; $i ++ ) {
echo $this -> dadaMakeParagraph ( $words , $totalWeight );
}
echo " </div> \n " ;
}
// text sections for each sub-headline:
foreach ( $toc as $hl ) {
echo $this -> dadaMakeSection ( $words , $totalWeight , $hl );
2025-10-20 22:06:36 +02:00
}
}
2025-10-23 15:01:36 +02:00
private function dadaMakeSection ( $words , $totalWeight , $hl ) {
global $conf ;
// how many paragraphs?
$paragraphCount = mt_rand ( 1 , 4 );
// section level
$topTocLevel = ( int ) $conf [ 'toptoclevel' ];
$secLevel = $hl [ 'level' ] + 1 ;;
2025-10-20 22:06:36 +02:00
2025-10-23 15:01:36 +02:00
// return value:
$sec = " " ;
2025-10-20 22:06:36 +02:00
2025-10-23 15:01:36 +02:00
// make a headline:
if ( $topTocLevel > 1 || $secLevel > 1 ) {
$sec .= " <h { $secLevel } id= \" { $hl [ 'hid' ] } \" > { $hl [ 'title' ] } </h { $secLevel } > \n " ;
}
// add the paragraphs:
$sec .= " <div class= \" level { $secLevel } \" > \n " ;
for ( $i = 0 ; $i < $paragraphCount ; $i ++ ) {
$sec .= $this -> dadaMakeParagraph ( $words , $totalWeight );
}
$sec .= " </div> \n " ;
return $sec ;
2025-10-20 22:06:36 +02:00
}
2025-10-23 15:01:36 +02:00
private function dadaMakeHeadline ( $words , $totalWeight ) {
// how many words to generate?
$wordCount = mt_rand ( 2 , 5 );
// function returns an array:
$r = Array ();
// generate the headline:
$hlArr = array ();
for ( $i = 0 ; $i < $wordCount ; $i ++ ) {
array_push ( $hlArr , $this -> dadaSelectRandomWord ( $words , $totalWeight ));
}
$r [ 'title' ] = ucfirst ( implode ( ' ' , $hlArr ));
$r [ 'hid' ] = preg_replace ( '/[^\w\d\-]+/i' , '_' , strtolower ( $r [ 'title' ]));
$r [ 'type' ] = 'ul' ; // always ul!
$r [ 'level' ] = 1 ; // always level 1 for now
return $r ;
}
private function dadaMakeParagraph ( $words , $totalWeight ) {
// how many words to generate?
$sentenceCount = mt_rand ( 2 , 5 );
$paragraph = array ();
for ( $i = 0 ; $i < $sentenceCount ; $i ++ ) {
array_push ( $paragraph , $this -> dadaMakeSentence ( $words , $totalWeight ));
}
return " <p> \n " . implode ( ' ' , $paragraph ) . " \n </p> \n " ;
2025-10-20 22:06:36 +02:00
}
2025-10-23 15:01:36 +02:00
private function dadaMakeSentence ( $words , $totalWeight ) {
// how many words to generate?
$wordCount = mt_rand ( 4 , 20 );
// generate the sentence:
$sentence = array ();
for ( $i = 0 ; $i < $wordCount ; $i ++ ) {
array_push ( $sentence , $this -> dadaSelectRandomWord ( $words , $totalWeight ));
}
2025-10-20 22:06:36 +02:00
2025-10-23 15:01:36 +02:00
return ucfirst ( implode ( ' ' , $sentence )) . '.' ;
2025-10-20 22:06:36 +02:00
2025-10-23 15:01:36 +02:00
}
2025-10-20 22:06:36 +02:00
2025-10-23 15:01:36 +02:00
private function dadaSelectRandomWord ( $list , $totalWeight ) {
2025-10-20 22:06:36 +02:00
2025-10-23 15:01:36 +02:00
// get a random selection:
$rand = mt_rand ( 0 , $totalWeight );
2025-10-20 22:06:36 +02:00
2025-10-23 15:01:36 +02:00
// match the selection to the weighted list:
$cumulativeWeight = 0 ;
for ( $i = 0 ; $i < count ( $list ); $i ++ ) {
$cumulativeWeight += $list [ $i ][ 1 ];
if ( $cumulativeWeight >= $rand ) {
return $list [ $i ][ 0 ];
}
}
return '***' ;
2025-10-20 22:06:36 +02:00
}
2025-08-20 16:34:20 +03:00
}