177e00bf9SAndreas Gohr<?php 277e00bf9SAndreas Gohr/** 377e00bf9SAndreas Gohr * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 477e00bf9SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 577e00bf9SAndreas Gohr */ 67218f96cSAndreas Gohr 7a285df67SAndreas Gohr/** 8a285df67SAndreas Gohr * Class helper_plugin_captcha 9a285df67SAndreas Gohr */ 1018622736SAndreas Gohrclass helper_plugin_captcha extends DokuWiki_Plugin 1118622736SAndreas Gohr{ 1277e00bf9SAndreas Gohr 1323d379a8SAndreas Gohr protected $field_in = 'plugin__captcha'; 1423d379a8SAndreas Gohr protected $field_sec = 'plugin__captcha_secret'; 1523d379a8SAndreas Gohr protected $field_hp = 'plugin__captcha_honeypot'; 1623d379a8SAndreas Gohr 1723d379a8SAndreas Gohr /** 1823d379a8SAndreas Gohr * Constructor. Initializes field names 1923d379a8SAndreas Gohr */ 2018622736SAndreas Gohr public function __construct() 2118622736SAndreas Gohr { 2223d379a8SAndreas Gohr $this->field_in = md5($this->_fixedIdent() . $this->field_in); 2323d379a8SAndreas Gohr $this->field_sec = md5($this->_fixedIdent() . $this->field_sec); 2423d379a8SAndreas Gohr $this->field_hp = md5($this->_fixedIdent() . $this->field_hp); 2523d379a8SAndreas Gohr } 2623d379a8SAndreas Gohr 2777e00bf9SAndreas Gohr /** 2877e00bf9SAndreas Gohr * Check if the CAPTCHA should be used. Always check this before using the methods below. 2977e00bf9SAndreas Gohr * 3077e00bf9SAndreas Gohr * @return bool true when the CAPTCHA should be used 3177e00bf9SAndreas Gohr */ 3218622736SAndreas Gohr public function isEnabled() 3318622736SAndreas Gohr { 34*64382f29SAndreas Gohr global $INPUT; 35*64382f29SAndreas Gohr if (!$this->getConf('forusers') && $INPUT->server->str('REMOTE_USER')) return false; 3677e00bf9SAndreas Gohr return true; 3777e00bf9SAndreas Gohr } 3877e00bf9SAndreas Gohr 3977e00bf9SAndreas Gohr /** 4077e00bf9SAndreas Gohr * Returns the HTML to display the CAPTCHA with the chosen method 4177e00bf9SAndreas Gohr */ 4218622736SAndreas Gohr public function getHTML() 4318622736SAndreas Gohr { 4477e00bf9SAndreas Gohr global $ID; 4577e00bf9SAndreas Gohr 4677e00bf9SAndreas Gohr $rand = (float)(rand(0, 10000)) / 10000; 47a285df67SAndreas Gohr $this->storeCaptchaCookie($this->_fixedIdent(), $rand); 48a285df67SAndreas Gohr 499e312724SAndreas Gohr if ($this->getConf('mode') == 'math') { 509e312724SAndreas Gohr $code = $this->_generateMATH($this->_fixedIdent(), $rand); 519e312724SAndreas Gohr $code = $code[0]; 529e312724SAndreas Gohr $text = $this->getLang('fillmath'); 53df8afac4SAndreas Gohr } elseif ($this->getConf('mode') == 'question') { 54a285df67SAndreas Gohr $code = ''; // not used 55df8afac4SAndreas Gohr $text = $this->getConf('question'); 569e312724SAndreas Gohr } else { 5777e00bf9SAndreas Gohr $code = $this->_generateCAPTCHA($this->_fixedIdent(), $rand); 589e312724SAndreas Gohr $text = $this->getLang('fillcaptcha'); 599e312724SAndreas Gohr } 60f044313dSAndreas Gohr $secret = $this->encrypt($rand); 6177e00bf9SAndreas Gohr 6228c14643SAndreas Gohr $txtlen = $this->getConf('lettercount'); 6328c14643SAndreas Gohr 6477e00bf9SAndreas Gohr $out = ''; 6577e00bf9SAndreas Gohr $out .= '<div id="plugin__captcha_wrapper">'; 6623d379a8SAndreas Gohr $out .= '<input type="hidden" name="' . $this->field_sec . '" value="' . hsc($secret) . '" />'; 679e312724SAndreas Gohr $out .= '<label for="plugin__captcha">' . $text . '</label> '; 6823d379a8SAndreas Gohr 6977e00bf9SAndreas Gohr switch ($this->getConf('mode')) { 709e312724SAndreas Gohr case 'math': 719d6f09afSAndreas Gohr case 'text': 729d6f09afSAndreas Gohr $out .= $this->_obfuscateText($code); 7377e00bf9SAndreas Gohr break; 7477e00bf9SAndreas Gohr case 'js': 759d6f09afSAndreas Gohr $out .= '<span id="plugin__captcha_code">' . $this->_obfuscateText($code) . '</span>'; 7677e00bf9SAndreas Gohr break; 7708f248e4SAndreas Gohr case 'svg': 7808f248e4SAndreas Gohr $out .= '<span class="svg" style="width:' . $this->getConf('width') . 'px; height:' . $this->getConf('height') . 'px">'; 7908f248e4SAndreas Gohr $out .= $this->_svgCAPTCHA($code); 8008f248e4SAndreas Gohr $out .= '</span>'; 8108f248e4SAndreas Gohr break; 8208f248e4SAndreas Gohr case 'svgaudio': 8308f248e4SAndreas Gohr $out .= '<span class="svg" style="width:' . $this->getConf('width') . 'px; height:' . $this->getConf('height') . 'px">'; 8408f248e4SAndreas Gohr $out .= $this->_svgCAPTCHA($code); 8508f248e4SAndreas Gohr $out .= '</span>'; 8608f248e4SAndreas Gohr $out .= '<a href="' . DOKU_BASE . 'lib/plugins/captcha/wav.php?secret=' . rawurlencode($secret) . '&id=' . $ID . '"' . 879efb703bSStefan Bethke ' class="JSnocheck audiolink" title="' . $this->getLang('soundlink') . '">'; 8808f248e4SAndreas Gohr $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/sound.png" width="16" height="16"' . 8908f248e4SAndreas Gohr ' alt="' . $this->getLang('soundlink') . '" /></a>'; 9008f248e4SAndreas Gohr break; 9177e00bf9SAndreas Gohr case 'image': 9277e00bf9SAndreas Gohr $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/img.php?secret=' . rawurlencode($secret) . '&id=' . $ID . '" ' . 9377e00bf9SAndreas Gohr ' width="' . $this->getConf('width') . '" height="' . $this->getConf('height') . '" alt="" /> '; 9477e00bf9SAndreas Gohr break; 9577e00bf9SAndreas Gohr case 'audio': 9677e00bf9SAndreas Gohr $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/img.php?secret=' . rawurlencode($secret) . '&id=' . $ID . '" ' . 9777e00bf9SAndreas Gohr ' width="' . $this->getConf('width') . '" height="' . $this->getConf('height') . '" alt="" /> '; 9877e00bf9SAndreas Gohr $out .= '<a href="' . DOKU_BASE . 'lib/plugins/captcha/wav.php?secret=' . rawurlencode($secret) . '&id=' . $ID . '"' . 999efb703bSStefan Bethke ' class="JSnocheck audiolink" title="' . $this->getLang('soundlink') . '">'; 10077e00bf9SAndreas Gohr $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/sound.png" width="16" height="16"' . 10177e00bf9SAndreas Gohr ' alt="' . $this->getLang('soundlink') . '" /></a>'; 10277e00bf9SAndreas Gohr break; 10352e95008SAndreas Gohr case 'figlet': 10452e95008SAndreas Gohr require_once(dirname(__FILE__) . '/figlet.php'); 10552e95008SAndreas Gohr $figlet = new phpFiglet(); 10652e95008SAndreas Gohr if ($figlet->loadfont(dirname(__FILE__) . '/figlet.flf')) { 10752e95008SAndreas Gohr $out .= '<pre>'; 10852e95008SAndreas Gohr $out .= rtrim($figlet->fetch($code)); 10952e95008SAndreas Gohr $out .= '</pre>'; 11052e95008SAndreas Gohr } else { 11152e95008SAndreas Gohr msg('Failed to load figlet.flf font file. CAPTCHA broken', -1); 11252e95008SAndreas Gohr } 11352e95008SAndreas Gohr break; 11477e00bf9SAndreas Gohr } 115df8afac4SAndreas Gohr $out .= ' <input type="text" size="' . $txtlen . '" name="' . $this->field_in . '" class="edit" /> '; 11623d379a8SAndreas Gohr 11723d379a8SAndreas Gohr // add honeypot field 1189d63c05fSlainme $out .= '<label class="no">' . $this->getLang('honeypot') . '<input type="text" name="' . $this->field_hp . '" /></label>'; 11977e00bf9SAndreas Gohr $out .= '</div>'; 12077e00bf9SAndreas Gohr return $out; 12177e00bf9SAndreas Gohr } 12277e00bf9SAndreas Gohr 12377e00bf9SAndreas Gohr /** 12418622736SAndreas Gohr * Checks if the CAPTCHA was solved correctly 12577e00bf9SAndreas Gohr * 12677e00bf9SAndreas Gohr * @param bool $msg when true, an error will be signalled through the msg() method 12777e00bf9SAndreas Gohr * @return bool true when the answer was correct, otherwise false 12877e00bf9SAndreas Gohr */ 12918622736SAndreas Gohr public function check($msg = true) 13018622736SAndreas Gohr { 131478e363cSAndreas Gohr global $INPUT; 132478e363cSAndreas Gohr 133478e363cSAndreas Gohr $field_sec = $INPUT->str($this->field_sec); 134478e363cSAndreas Gohr $field_in = $INPUT->str($this->field_in); 135478e363cSAndreas Gohr $field_hp = $INPUT->str($this->field_hp); 136478e363cSAndreas Gohr 137478e363cSAndreas Gohr // reconstruct captcha from provided $field_sec 138478e363cSAndreas Gohr $rand = $this->decrypt($field_sec); 1399e312724SAndreas Gohr 1409e312724SAndreas Gohr if ($this->getConf('mode') == 'math') { 1419e312724SAndreas Gohr $code = $this->_generateMATH($this->_fixedIdent(), $rand); 1429e312724SAndreas Gohr $code = $code[1]; 143df8afac4SAndreas Gohr } elseif ($this->getConf('mode') == 'question') { 144df8afac4SAndreas Gohr $code = $this->getConf('answer'); 1459e312724SAndreas Gohr } else { 14677e00bf9SAndreas Gohr $code = $this->_generateCAPTCHA($this->_fixedIdent(), $rand); 1479e312724SAndreas Gohr } 14877e00bf9SAndreas Gohr 149478e363cSAndreas Gohr // compare values 150478e363cSAndreas Gohr if (!$field_sec || 151478e363cSAndreas Gohr !$field_in || 15214e271ebSPatrick Brown $rand === false || 153478e363cSAndreas Gohr utf8_strtolower($field_in) != utf8_strtolower($code) || 154a285df67SAndreas Gohr trim($field_hp) !== '' || 155a285df67SAndreas Gohr !$this->retrieveCaptchaCookie($this->_fixedIdent(), $rand) 15623d379a8SAndreas Gohr ) { 15777e00bf9SAndreas Gohr if ($msg) msg($this->getLang('testfailed'), -1); 15877e00bf9SAndreas Gohr return false; 15977e00bf9SAndreas Gohr } 16077e00bf9SAndreas Gohr return true; 16177e00bf9SAndreas Gohr } 16277e00bf9SAndreas Gohr 16377e00bf9SAndreas Gohr /** 164cde3ece1SAndreas Gohr * Get the path where a captcha cookie would be stored 165a285df67SAndreas Gohr * 166a285df67SAndreas Gohr * We use a daily temp directory which is easy to clean up 167a285df67SAndreas Gohr * 168a285df67SAndreas Gohr * @param $fixed string the fixed part, any string 169a285df67SAndreas Gohr * @param $rand float some random number between 0 and 1 170a285df67SAndreas Gohr * @return string the path to the cookie file 171a285df67SAndreas Gohr */ 17218622736SAndreas Gohr protected function getCaptchaCookiePath($fixed, $rand) 17318622736SAndreas Gohr { 174a285df67SAndreas Gohr global $conf; 175a285df67SAndreas Gohr $path = $conf['tmpdir'] . '/captcha/' . date('Y-m-d') . '/' . md5($fixed . $rand) . '.cookie'; 176a285df67SAndreas Gohr io_makeFileDir($path); 177a285df67SAndreas Gohr return $path; 178a285df67SAndreas Gohr } 179a285df67SAndreas Gohr 180a285df67SAndreas Gohr /** 181cde3ece1SAndreas Gohr * remove all outdated captcha cookies 182cde3ece1SAndreas Gohr */ 18318622736SAndreas Gohr public function _cleanCaptchaCookies() 18418622736SAndreas Gohr { 185cde3ece1SAndreas Gohr global $conf; 186cde3ece1SAndreas Gohr $path = $conf['tmpdir'] . '/captcha/'; 187cde3ece1SAndreas Gohr $dirs = glob("$path/*", GLOB_ONLYDIR); 188cde3ece1SAndreas Gohr $today = date('Y-m-d'); 189cde3ece1SAndreas Gohr foreach ($dirs as $dir) { 190cde3ece1SAndreas Gohr if (basename($dir) === $today) continue; 191cde3ece1SAndreas Gohr if (!preg_match('/\/captcha\//', $dir)) continue; // safety net 192cde3ece1SAndreas Gohr io_rmdir($dir, true); 193cde3ece1SAndreas Gohr } 194cde3ece1SAndreas Gohr } 195cde3ece1SAndreas Gohr 196cde3ece1SAndreas Gohr /** 197a285df67SAndreas Gohr * Creates a one time captcha cookie 198a285df67SAndreas Gohr * 199a285df67SAndreas Gohr * This is used to prevent replay attacks. It is generated when the captcha form 200a285df67SAndreas Gohr * is shown and checked with the captcha check. Since we can not be sure about the 201a285df67SAndreas Gohr * session state (might be closed or open) we're not using it. 202a285df67SAndreas Gohr * 203a285df67SAndreas Gohr * We're not using the stored values for displaying the captcha image (or audio) 204a285df67SAndreas Gohr * but continue to use our encryption scheme. This way it's still possible to have 205a285df67SAndreas Gohr * multiple captcha checks going on in parallel (eg. with multiple browser tabs) 206a285df67SAndreas Gohr * 207a285df67SAndreas Gohr * @param $fixed string the fixed part, any string 208a285df67SAndreas Gohr * @param $rand float some random number between 0 and 1 209a285df67SAndreas Gohr */ 21018622736SAndreas Gohr protected function storeCaptchaCookie($fixed, $rand) 21118622736SAndreas Gohr { 212a285df67SAndreas Gohr $cache = $this->getCaptchaCookiePath($fixed, $rand); 213a285df67SAndreas Gohr touch($cache); 214a285df67SAndreas Gohr } 215a285df67SAndreas Gohr 216a285df67SAndreas Gohr /** 217a285df67SAndreas Gohr * Checks if the captcha cookie exists and deletes it 218a285df67SAndreas Gohr * 219a285df67SAndreas Gohr * @param $fixed string the fixed part, any string 220a285df67SAndreas Gohr * @param $rand float some random number between 0 and 1 221a285df67SAndreas Gohr * @return bool true if the cookie existed 222a285df67SAndreas Gohr */ 22318622736SAndreas Gohr protected function retrieveCaptchaCookie($fixed, $rand) 22418622736SAndreas Gohr { 225a285df67SAndreas Gohr $cache = $this->getCaptchaCookiePath($fixed, $rand); 226a285df67SAndreas Gohr if (file_exists($cache)) { 227a285df67SAndreas Gohr unlink($cache); 228a285df67SAndreas Gohr return true; 229a285df67SAndreas Gohr } 230a285df67SAndreas Gohr return false; 231a285df67SAndreas Gohr } 232a285df67SAndreas Gohr 233a285df67SAndreas Gohr /** 23477e00bf9SAndreas Gohr * Build a semi-secret fixed string identifying the current page and user 23577e00bf9SAndreas Gohr * 23677e00bf9SAndreas Gohr * This string is always the same for the current user when editing the same 23727d84d8dSAndreas Gohr * page revision, but only for one day. Editing a page before midnight and saving 23827d84d8dSAndreas Gohr * after midnight will result in a failed CAPTCHA once, but makes sure it can 23927d84d8dSAndreas Gohr * not be reused which is especially important for the registration form where the 24027d84d8dSAndreas Gohr * $ID usually won't change. 241104ec268SAndreas Gohr * 242104ec268SAndreas Gohr * @return string 24377e00bf9SAndreas Gohr */ 24418622736SAndreas Gohr public function _fixedIdent() 24518622736SAndreas Gohr { 24677e00bf9SAndreas Gohr global $ID; 24777e00bf9SAndreas Gohr $lm = @filemtime(wikiFN($ID)); 24827d84d8dSAndreas Gohr $td = date('Y-m-d'); 24963609b6eSAndreas Gohr $ip = clientIP(); 25063609b6eSAndreas Gohr $salt = auth_cookiesalt(); 25163609b6eSAndreas Gohr 25263609b6eSAndreas Gohr return sha1(join("\n", [$ID, $lm, $td, $ip, $salt])); 25377e00bf9SAndreas Gohr } 25477e00bf9SAndreas Gohr 25577e00bf9SAndreas Gohr /** 2569d6f09afSAndreas Gohr * Adds random space characters within the given text 2579d6f09afSAndreas Gohr * 2589d6f09afSAndreas Gohr * Keeps subsequent numbers without spaces (for math problem) 2599d6f09afSAndreas Gohr * 2609d6f09afSAndreas Gohr * @param $text 2619d6f09afSAndreas Gohr * @return string 2629d6f09afSAndreas Gohr */ 26318622736SAndreas Gohr protected function _obfuscateText($text) 26418622736SAndreas Gohr { 2659d6f09afSAndreas Gohr $new = ''; 2669d6f09afSAndreas Gohr 2679d6f09afSAndreas Gohr $spaces = array( 2689d6f09afSAndreas Gohr "\r", 2699d6f09afSAndreas Gohr "\n", 2709d6f09afSAndreas Gohr "\r\n", 2719d6f09afSAndreas Gohr ' ', 2729d6f09afSAndreas Gohr "\xC2\xA0", // \u00A0 NO-BREAK SPACE 2739d6f09afSAndreas Gohr "\xE2\x80\x80", // \u2000 EN QUAD 2749d6f09afSAndreas Gohr "\xE2\x80\x81", // \u2001 EM QUAD 2759d6f09afSAndreas Gohr "\xE2\x80\x82", // \u2002 EN SPACE 2769d6f09afSAndreas Gohr // "\xE2\x80\x83", // \u2003 EM SPACE 2779d6f09afSAndreas Gohr "\xE2\x80\x84", // \u2004 THREE-PER-EM SPACE 2789d6f09afSAndreas Gohr "\xE2\x80\x85", // \u2005 FOUR-PER-EM SPACE 2799d6f09afSAndreas Gohr "\xE2\x80\x86", // \u2006 SIX-PER-EM SPACE 2809d6f09afSAndreas Gohr "\xE2\x80\x87", // \u2007 FIGURE SPACE 2819d6f09afSAndreas Gohr "\xE2\x80\x88", // \u2008 PUNCTUATION SPACE 2829d6f09afSAndreas Gohr "\xE2\x80\x89", // \u2009 THIN SPACE 2839d6f09afSAndreas Gohr "\xE2\x80\x8A", // \u200A HAIR SPACE 2849d6f09afSAndreas Gohr "\xE2\x80\xAF", // \u202F NARROW NO-BREAK SPACE 2859d6f09afSAndreas Gohr "\xE2\x81\x9F", // \u205F MEDIUM MATHEMATICAL SPACE 2869d6f09afSAndreas Gohr 2879d6f09afSAndreas Gohr "\xE1\xA0\x8E\r\n", // \u180E MONGOLIAN VOWEL SEPARATOR 2889d6f09afSAndreas Gohr "\xE2\x80\x8B\r\n", // \u200B ZERO WIDTH SPACE 2899d6f09afSAndreas Gohr "\xEF\xBB\xBF\r\n", // \uFEFF ZERO WIDTH NO-BREAK SPACE 2909d6f09afSAndreas Gohr ); 2919d6f09afSAndreas Gohr 2929d6f09afSAndreas Gohr $len = strlen($text); 2939d6f09afSAndreas Gohr for ($i = 0; $i < $len - 1; $i++) { 29439bbdaefSAndreas Gohr $new .= $text[$i]; 2959d6f09afSAndreas Gohr 29639bbdaefSAndreas Gohr if (!is_numeric($text[$i + 1])) { 2979d6f09afSAndreas Gohr $new .= $spaces[array_rand($spaces)]; 2989d6f09afSAndreas Gohr } 2999d6f09afSAndreas Gohr } 30039bbdaefSAndreas Gohr $new .= $text[$len - 1]; 3019d6f09afSAndreas Gohr return $new; 3029d6f09afSAndreas Gohr } 3039d6f09afSAndreas Gohr 3049d6f09afSAndreas Gohr /** 305a02b2219SPatrick Brown * Generate some numbers from a known string and random number 306a02b2219SPatrick Brown * 307a02b2219SPatrick Brown * @param $fixed string the fixed part, any string 308a02b2219SPatrick Brown * @param $rand float some random number between 0 and 1 309a02b2219SPatrick Brown * @return string 310a02b2219SPatrick Brown */ 31118622736SAndreas Gohr protected function _generateNumbers($fixed, $rand) 31218622736SAndreas Gohr { 313a02b2219SPatrick Brown $fixed = hexdec(substr(md5($fixed), 5, 5)); // use part of the md5 to generate an int 314a02b2219SPatrick Brown $rand = $rand * 0xFFFFF; // bitmask from the random number 315a02b2219SPatrick Brown return md5($rand ^ $fixed); // combine both values 316a02b2219SPatrick Brown } 317a02b2219SPatrick Brown 318a02b2219SPatrick Brown /** 31928c14643SAndreas Gohr * Generates a random char string 32077e00bf9SAndreas Gohr * 321104ec268SAndreas Gohr * @param $fixed string the fixed part, any string 322104ec268SAndreas Gohr * @param $rand float some random number between 0 and 1 32323d379a8SAndreas Gohr * @return string 32477e00bf9SAndreas Gohr */ 32518622736SAndreas Gohr public function _generateCAPTCHA($fixed, $rand) 32618622736SAndreas Gohr { 327a02b2219SPatrick Brown $numbers = $this->_generateNumbers($fixed, $rand); 32877e00bf9SAndreas Gohr 32977e00bf9SAndreas Gohr // now create the letters 33077e00bf9SAndreas Gohr $code = ''; 3319a516edaSPatrick Brown $lettercount = $this->getConf('lettercount') * 2; 3329a516edaSPatrick Brown if ($lettercount > strlen($numbers)) $lettercount = strlen($numbers); 3339a516edaSPatrick Brown for ($i = 0; $i < $lettercount; $i += 2) { 33477e00bf9SAndreas Gohr $code .= chr(floor(hexdec($numbers[$i] . $numbers[$i + 1]) / 10) + 65); 33577e00bf9SAndreas Gohr } 33677e00bf9SAndreas Gohr 33777e00bf9SAndreas Gohr return $code; 33877e00bf9SAndreas Gohr } 33977e00bf9SAndreas Gohr 3409e312724SAndreas Gohr /** 3419e312724SAndreas Gohr * Create a mathematical task and its result 3429e312724SAndreas Gohr * 343104ec268SAndreas Gohr * @param $fixed string the fixed part, any string 344104ec268SAndreas Gohr * @param $rand float some random number between 0 and 1 345104ec268SAndreas Gohr * @return array taks, result 3469e312724SAndreas Gohr */ 34718622736SAndreas Gohr protected function _generateMATH($fixed, $rand) 34818622736SAndreas Gohr { 349a02b2219SPatrick Brown $numbers = $this->_generateNumbers($fixed, $rand); 3509e312724SAndreas Gohr 3519e312724SAndreas Gohr // first letter is the operator (+/-) 3529e312724SAndreas Gohr $op = (hexdec($numbers[0]) > 8) ? -1 : 1; 3539e312724SAndreas Gohr $num = array(hexdec($numbers[1] . $numbers[2]), hexdec($numbers[3])); 3549e312724SAndreas Gohr 3559e312724SAndreas Gohr // we only want positive results 3569e312724SAndreas Gohr if (($op < 0) && ($num[0] < $num[1])) rsort($num); 3579e312724SAndreas Gohr 3589e312724SAndreas Gohr // prepare result and task text 3599e312724SAndreas Gohr $res = $num[0] + ($num[1] * $op); 3609bc1fab2SApostolos P. Tsompanopoulos $task = $num[0] . (($op < 0) ? '-' : '+') . $num[1] . '= '; 3619e312724SAndreas Gohr 3629e312724SAndreas Gohr return array($task, $res); 3639e312724SAndreas Gohr } 36477e00bf9SAndreas Gohr 36577e00bf9SAndreas Gohr /** 36677e00bf9SAndreas Gohr * Create a CAPTCHA image 367104ec268SAndreas Gohr * 368104ec268SAndreas Gohr * @param string $text the letters to display 36977e00bf9SAndreas Gohr */ 37018622736SAndreas Gohr public function _imageCAPTCHA($text) 37118622736SAndreas Gohr { 37277e00bf9SAndreas Gohr $w = $this->getConf('width'); 37377e00bf9SAndreas Gohr $h = $this->getConf('height'); 37477e00bf9SAndreas Gohr 375dc091fd0SAndreas Gohr $fonts = glob(dirname(__FILE__) . '/fonts/*.ttf'); 376dc091fd0SAndreas Gohr 37777e00bf9SAndreas Gohr // create a white image 37828c14643SAndreas Gohr $img = imagecreatetruecolor($w, $h); 37928c14643SAndreas Gohr $white = imagecolorallocate($img, 255, 255, 255); 38028c14643SAndreas Gohr imagefill($img, 0, 0, $white); 38177e00bf9SAndreas Gohr 38277e00bf9SAndreas Gohr // add some lines as background noise 38377e00bf9SAndreas Gohr for ($i = 0; $i < 30; $i++) { 38477e00bf9SAndreas Gohr $color = imagecolorallocate($img, rand(100, 250), rand(100, 250), rand(100, 250)); 38577e00bf9SAndreas Gohr imageline($img, rand(0, $w), rand(0, $h), rand(0, $w), rand(0, $h), $color); 38677e00bf9SAndreas Gohr } 38777e00bf9SAndreas Gohr 38877e00bf9SAndreas Gohr // draw the letters 38928c14643SAndreas Gohr $txtlen = strlen($text); 39028c14643SAndreas Gohr for ($i = 0; $i < $txtlen; $i++) { 391dc091fd0SAndreas Gohr $font = $fonts[array_rand($fonts)]; 39277e00bf9SAndreas Gohr $color = imagecolorallocate($img, rand(0, 100), rand(0, 100), rand(0, 100)); 39377e00bf9SAndreas Gohr $size = rand(floor($h / 1.8), floor($h * 0.7)); 39477e00bf9SAndreas Gohr $angle = rand(-35, 35); 39577e00bf9SAndreas Gohr 39628c14643SAndreas Gohr $x = ($w * 0.05) + $i * floor($w * 0.9 / $txtlen); 39777e00bf9SAndreas Gohr $cheight = $size + ($size * 0.5); 39877e00bf9SAndreas Gohr $y = floor($h / 2 + $cheight / 3.8); 39977e00bf9SAndreas Gohr 40077e00bf9SAndreas Gohr imagettftext($img, $size, $angle, $x, $y, $color, $font, $text[$i]); 40177e00bf9SAndreas Gohr } 40277e00bf9SAndreas Gohr 40377e00bf9SAndreas Gohr header("Content-type: image/png"); 40477e00bf9SAndreas Gohr imagepng($img); 40577e00bf9SAndreas Gohr imagedestroy($img); 40677e00bf9SAndreas Gohr } 40777e00bf9SAndreas Gohr 408f044313dSAndreas Gohr /** 40908f248e4SAndreas Gohr * Create an SVG of the given text 41008f248e4SAndreas Gohr * 41108f248e4SAndreas Gohr * @param string $text 41208f248e4SAndreas Gohr * @return string 41308f248e4SAndreas Gohr */ 41418622736SAndreas Gohr public function _svgCAPTCHA($text) 41518622736SAndreas Gohr { 41608f248e4SAndreas Gohr require_once(__DIR__ . '/EasySVG.php'); 41708f248e4SAndreas Gohr 41808f248e4SAndreas Gohr $fonts = glob(__DIR__ . '/fonts/*.svg'); 41908f248e4SAndreas Gohr 42008f248e4SAndreas Gohr $x = 0; // where we start to draw 42108f248e4SAndreas Gohr $y = 100; // our max height 42208f248e4SAndreas Gohr 42308f248e4SAndreas Gohr $svg = new EasySVG(); 42408f248e4SAndreas Gohr 42508f248e4SAndreas Gohr // draw the letters 42608f248e4SAndreas Gohr $txtlen = strlen($text); 42708f248e4SAndreas Gohr for ($i = 0; $i < $txtlen; $i++) { 42808f248e4SAndreas Gohr $char = $text[$i]; 42908f248e4SAndreas Gohr $size = rand($y / 2, $y - $y * 0.1); // 50-90% 43008f248e4SAndreas Gohr $svg->setFontSVG($fonts[array_rand($fonts)]); 43108f248e4SAndreas Gohr 43208f248e4SAndreas Gohr $svg->setFontSize($size); 43308f248e4SAndreas Gohr $svg->setLetterSpacing(round(rand(1, 4) / 10, 2)); // 0.1 - 0.4 43408f248e4SAndreas Gohr $svg->addText($char, $x, rand(0, round($y - $size))); // random up and down 43508f248e4SAndreas Gohr 43608f248e4SAndreas Gohr list($w) = $svg->textDimensions($char); 43708f248e4SAndreas Gohr $x += $w; 43808f248e4SAndreas Gohr } 43908f248e4SAndreas Gohr 44008f248e4SAndreas Gohr $svg->addAttribute('width', $x . 'px'); 44108f248e4SAndreas Gohr $svg->addAttribute('height', $y . 'px'); 44208f248e4SAndreas Gohr $svg->addAttribute('viewbox', "0 0 $x $y"); 44308f248e4SAndreas Gohr return $svg->asXML(); 44408f248e4SAndreas Gohr } 44508f248e4SAndreas Gohr 44608f248e4SAndreas Gohr /** 44763609b6eSAndreas Gohr * Generate an audio captcha 44863609b6eSAndreas Gohr * 44963609b6eSAndreas Gohr * @param string $text 45063609b6eSAndreas Gohr */ 45163609b6eSAndreas Gohr public function _audioCAPTCHA($text){ 45263609b6eSAndreas Gohr global $conf; 45363609b6eSAndreas Gohr 45463609b6eSAndreas Gohr $lc = __DIR__ . '/lang/' . $conf['lang'] . '/audio/'; 45563609b6eSAndreas Gohr $en = __DIR__ . '/lang/en/audio/'; 45663609b6eSAndreas Gohr 45763609b6eSAndreas Gohr $wavs = []; 45863609b6eSAndreas Gohr 45963609b6eSAndreas Gohr $text = strtolower($text); 46063609b6eSAndreas Gohr $txtlen = strlen($text); 46163609b6eSAndreas Gohr for ($i = 0; $i < $txtlen; $i++) { 46263609b6eSAndreas Gohr $char = $text[$i]; 46363609b6eSAndreas Gohr $file = $lc . $char . '.wav'; 46463609b6eSAndreas Gohr if (!@file_exists($file)) $file = $en . $char . '.wav'; 46563609b6eSAndreas Gohr $wavs[] = $file; 46663609b6eSAndreas Gohr } 46763609b6eSAndreas Gohr 46863609b6eSAndreas Gohr header('Content-type: audio/x-wav'); 46963609b6eSAndreas Gohr header('Content-Disposition: attachment;filename=captcha.wav'); 47063609b6eSAndreas Gohr 47163609b6eSAndreas Gohr echo $this->joinwavs($wavs); 47263609b6eSAndreas Gohr } 47363609b6eSAndreas Gohr 47463609b6eSAndreas Gohr /** 475f044313dSAndreas Gohr * Encrypt the given string with the cookie salt 476f044313dSAndreas Gohr * 477f044313dSAndreas Gohr * @param string $data 478f044313dSAndreas Gohr * @return string 479f044313dSAndreas Gohr */ 48018622736SAndreas Gohr public function encrypt($data) 48118622736SAndreas Gohr { 482f044313dSAndreas Gohr if (function_exists('auth_encrypt')) { 483f044313dSAndreas Gohr $data = auth_encrypt($data, auth_cookiesalt()); // since binky 484f044313dSAndreas Gohr } else { 485f044313dSAndreas Gohr $data = PMA_blowfish_encrypt($data, auth_cookiesalt()); // deprecated 486f044313dSAndreas Gohr } 487f044313dSAndreas Gohr 488f044313dSAndreas Gohr return base64_encode($data); 489f044313dSAndreas Gohr } 490f044313dSAndreas Gohr 491f044313dSAndreas Gohr /** 492f044313dSAndreas Gohr * Decrypt the given string with the cookie salt 493f044313dSAndreas Gohr * 494f044313dSAndreas Gohr * @param string $data 495f044313dSAndreas Gohr * @return string 496f044313dSAndreas Gohr */ 49718622736SAndreas Gohr public function decrypt($data) 49818622736SAndreas Gohr { 499f044313dSAndreas Gohr $data = base64_decode($data); 50009870f99SPatrick Brown if ($data === false || $data === '') return false; 501f044313dSAndreas Gohr 502f044313dSAndreas Gohr if (function_exists('auth_decrypt')) { 503f044313dSAndreas Gohr return auth_decrypt($data, auth_cookiesalt()); // since binky 504f044313dSAndreas Gohr } else { 505f044313dSAndreas Gohr return PMA_blowfish_decrypt($data, auth_cookiesalt()); // deprecated 506f044313dSAndreas Gohr } 507f044313dSAndreas Gohr } 508969b14c4SAndreas Gohr 50963609b6eSAndreas Gohr 51063609b6eSAndreas Gohr /** 51163609b6eSAndreas Gohr * Join multiple wav files 51263609b6eSAndreas Gohr * 51363609b6eSAndreas Gohr * All wave files need to have the same format and need to be uncompressed. 51463609b6eSAndreas Gohr * The headers of the last file will be used (with recalculated datasize 51563609b6eSAndreas Gohr * of course) 51663609b6eSAndreas Gohr * 51763609b6eSAndreas Gohr * @link http://ccrma.stanford.edu/CCRMA/Courses/422/projects/WaveFormat/ 51863609b6eSAndreas Gohr * @link http://www.thescripts.com/forum/thread3770.html 51963609b6eSAndreas Gohr */ 52063609b6eSAndreas Gohr protected function joinwavs($wavs) 52163609b6eSAndreas Gohr { 52263609b6eSAndreas Gohr $fields = join( 52363609b6eSAndreas Gohr '/', array( 52463609b6eSAndreas Gohr 'H8ChunkID', 52563609b6eSAndreas Gohr 'VChunkSize', 52663609b6eSAndreas Gohr 'H8Format', 52763609b6eSAndreas Gohr 'H8Subchunk1ID', 52863609b6eSAndreas Gohr 'VSubchunk1Size', 52963609b6eSAndreas Gohr 'vAudioFormat', 53063609b6eSAndreas Gohr 'vNumChannels', 53163609b6eSAndreas Gohr 'VSampleRate', 53263609b6eSAndreas Gohr 'VByteRate', 53363609b6eSAndreas Gohr 'vBlockAlign', 53463609b6eSAndreas Gohr 'vBitsPerSample', 53563609b6eSAndreas Gohr ) 53663609b6eSAndreas Gohr ); 53763609b6eSAndreas Gohr 53863609b6eSAndreas Gohr $data = ''; 53963609b6eSAndreas Gohr foreach ($wavs as $wav) { 54063609b6eSAndreas Gohr $fp = fopen($wav, 'rb'); 54163609b6eSAndreas Gohr $header = fread($fp, 36); 54263609b6eSAndreas Gohr $info = unpack($fields, $header); 54363609b6eSAndreas Gohr 54463609b6eSAndreas Gohr // read optional extra stuff 54563609b6eSAndreas Gohr if ($info['Subchunk1Size'] > 16) { 54663609b6eSAndreas Gohr $header .= fread($fp, ($info['Subchunk1Size'] - 16)); 54763609b6eSAndreas Gohr } 54863609b6eSAndreas Gohr 54963609b6eSAndreas Gohr // read SubChunk2ID 55063609b6eSAndreas Gohr $header .= fread($fp, 4); 55163609b6eSAndreas Gohr 55263609b6eSAndreas Gohr // read Subchunk2Size 55363609b6eSAndreas Gohr $size = unpack('vsize', fread($fp, 4)); 55463609b6eSAndreas Gohr $size = $size['size']; 55563609b6eSAndreas Gohr 55663609b6eSAndreas Gohr // read data 55763609b6eSAndreas Gohr $data .= fread($fp, $size); 55863609b6eSAndreas Gohr } 55963609b6eSAndreas Gohr 56063609b6eSAndreas Gohr return $header . pack('V', strlen($data)) . $data; 56163609b6eSAndreas Gohr } 56263609b6eSAndreas Gohr 56377e00bf9SAndreas Gohr} 564