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 { 3477e00bf9SAndreas Gohr if (!$this->getConf('forusers') && $_SERVER['REMOTE_USER']) return false; 3577e00bf9SAndreas Gohr return true; 3677e00bf9SAndreas Gohr } 3777e00bf9SAndreas Gohr 3877e00bf9SAndreas Gohr /** 3977e00bf9SAndreas Gohr * Returns the HTML to display the CAPTCHA with the chosen method 4077e00bf9SAndreas Gohr */ 4118622736SAndreas Gohr public function getHTML() 4218622736SAndreas Gohr { 4377e00bf9SAndreas Gohr global $ID; 4477e00bf9SAndreas Gohr 4577e00bf9SAndreas Gohr $rand = (float)(rand(0, 10000)) / 10000; 46a285df67SAndreas Gohr $this->storeCaptchaCookie($this->_fixedIdent(), $rand); 47a285df67SAndreas Gohr 489e312724SAndreas Gohr if ($this->getConf('mode') == 'math') { 499e312724SAndreas Gohr $code = $this->_generateMATH($this->_fixedIdent(), $rand); 509e312724SAndreas Gohr $code = $code[0]; 519e312724SAndreas Gohr $text = $this->getLang('fillmath'); 52df8afac4SAndreas Gohr } elseif ($this->getConf('mode') == 'question') { 53a285df67SAndreas Gohr $code = ''; // not used 54df8afac4SAndreas Gohr $text = $this->getConf('question'); 559e312724SAndreas Gohr } else { 5677e00bf9SAndreas Gohr $code = $this->_generateCAPTCHA($this->_fixedIdent(), $rand); 579e312724SAndreas Gohr $text = $this->getLang('fillcaptcha'); 589e312724SAndreas Gohr } 59f044313dSAndreas Gohr $secret = $this->encrypt($rand); 6077e00bf9SAndreas Gohr 6128c14643SAndreas Gohr $txtlen = $this->getConf('lettercount'); 6228c14643SAndreas Gohr 6377e00bf9SAndreas Gohr $out = ''; 6477e00bf9SAndreas Gohr $out .= '<div id="plugin__captcha_wrapper">'; 6523d379a8SAndreas Gohr $out .= '<input type="hidden" name="' . $this->field_sec . '" value="' . hsc($secret) . '" />'; 669e312724SAndreas Gohr $out .= '<label for="plugin__captcha">' . $text . '</label> '; 6723d379a8SAndreas Gohr 6877e00bf9SAndreas Gohr switch ($this->getConf('mode')) { 699e312724SAndreas Gohr case 'math': 709d6f09afSAndreas Gohr case 'text': 719d6f09afSAndreas Gohr $out .= $this->_obfuscateText($code); 7277e00bf9SAndreas Gohr break; 7377e00bf9SAndreas Gohr case 'js': 749d6f09afSAndreas Gohr $out .= '<span id="plugin__captcha_code">' . $this->_obfuscateText($code) . '</span>'; 7577e00bf9SAndreas Gohr break; 7608f248e4SAndreas Gohr case 'svg': 7708f248e4SAndreas Gohr $out .= '<span class="svg" style="width:' . $this->getConf('width') . 'px; height:' . $this->getConf('height') . 'px">'; 7808f248e4SAndreas Gohr $out .= $this->_svgCAPTCHA($code); 7908f248e4SAndreas Gohr $out .= '</span>'; 8008f248e4SAndreas Gohr break; 8108f248e4SAndreas Gohr case 'svgaudio': 8208f248e4SAndreas Gohr $out .= '<span class="svg" style="width:' . $this->getConf('width') . 'px; height:' . $this->getConf('height') . 'px">'; 8308f248e4SAndreas Gohr $out .= $this->_svgCAPTCHA($code); 8408f248e4SAndreas Gohr $out .= '</span>'; 8508f248e4SAndreas Gohr $out .= '<a href="' . DOKU_BASE . 'lib/plugins/captcha/wav.php?secret=' . rawurlencode($secret) . '&id=' . $ID . '"' . 869efb703bSStefan Bethke ' class="JSnocheck audiolink" title="' . $this->getLang('soundlink') . '">'; 8708f248e4SAndreas Gohr $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/sound.png" width="16" height="16"' . 8808f248e4SAndreas Gohr ' alt="' . $this->getLang('soundlink') . '" /></a>'; 8908f248e4SAndreas Gohr break; 9077e00bf9SAndreas Gohr case 'image': 9177e00bf9SAndreas Gohr $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/img.php?secret=' . rawurlencode($secret) . '&id=' . $ID . '" ' . 9277e00bf9SAndreas Gohr ' width="' . $this->getConf('width') . '" height="' . $this->getConf('height') . '" alt="" /> '; 9377e00bf9SAndreas Gohr break; 9477e00bf9SAndreas Gohr case 'audio': 9577e00bf9SAndreas Gohr $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/img.php?secret=' . rawurlencode($secret) . '&id=' . $ID . '" ' . 9677e00bf9SAndreas Gohr ' width="' . $this->getConf('width') . '" height="' . $this->getConf('height') . '" alt="" /> '; 9777e00bf9SAndreas Gohr $out .= '<a href="' . DOKU_BASE . 'lib/plugins/captcha/wav.php?secret=' . rawurlencode($secret) . '&id=' . $ID . '"' . 989efb703bSStefan Bethke ' class="JSnocheck audiolink" title="' . $this->getLang('soundlink') . '">'; 9977e00bf9SAndreas Gohr $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/sound.png" width="16" height="16"' . 10077e00bf9SAndreas Gohr ' alt="' . $this->getLang('soundlink') . '" /></a>'; 10177e00bf9SAndreas Gohr break; 10252e95008SAndreas Gohr case 'figlet': 10352e95008SAndreas Gohr require_once(dirname(__FILE__) . '/figlet.php'); 10452e95008SAndreas Gohr $figlet = new phpFiglet(); 10552e95008SAndreas Gohr if ($figlet->loadfont(dirname(__FILE__) . '/figlet.flf')) { 10652e95008SAndreas Gohr $out .= '<pre>'; 10752e95008SAndreas Gohr $out .= rtrim($figlet->fetch($code)); 10852e95008SAndreas Gohr $out .= '</pre>'; 10952e95008SAndreas Gohr } else { 11052e95008SAndreas Gohr msg('Failed to load figlet.flf font file. CAPTCHA broken', -1); 11152e95008SAndreas Gohr } 11252e95008SAndreas Gohr break; 11377e00bf9SAndreas Gohr } 114df8afac4SAndreas Gohr $out .= ' <input type="text" size="' . $txtlen . '" name="' . $this->field_in . '" class="edit" /> '; 11523d379a8SAndreas Gohr 11623d379a8SAndreas Gohr // add honeypot field 1179d63c05fSlainme $out .= '<label class="no">' . $this->getLang('honeypot') . '<input type="text" name="' . $this->field_hp . '" /></label>'; 11877e00bf9SAndreas Gohr $out .= '</div>'; 11977e00bf9SAndreas Gohr return $out; 12077e00bf9SAndreas Gohr } 12177e00bf9SAndreas Gohr 12277e00bf9SAndreas Gohr /** 12318622736SAndreas Gohr * Checks if the CAPTCHA was solved correctly 12477e00bf9SAndreas Gohr * 12577e00bf9SAndreas Gohr * @param bool $msg when true, an error will be signalled through the msg() method 12677e00bf9SAndreas Gohr * @return bool true when the answer was correct, otherwise false 12777e00bf9SAndreas Gohr */ 12818622736SAndreas Gohr public function check($msg = true) 12918622736SAndreas Gohr { 130478e363cSAndreas Gohr global $INPUT; 131478e363cSAndreas Gohr 132478e363cSAndreas Gohr $field_sec = $INPUT->str($this->field_sec); 133478e363cSAndreas Gohr $field_in = $INPUT->str($this->field_in); 134478e363cSAndreas Gohr $field_hp = $INPUT->str($this->field_hp); 135478e363cSAndreas Gohr 136478e363cSAndreas Gohr // reconstruct captcha from provided $field_sec 137478e363cSAndreas Gohr $rand = $this->decrypt($field_sec); 1389e312724SAndreas Gohr 1399e312724SAndreas Gohr if ($this->getConf('mode') == 'math') { 1409e312724SAndreas Gohr $code = $this->_generateMATH($this->_fixedIdent(), $rand); 1419e312724SAndreas Gohr $code = $code[1]; 142df8afac4SAndreas Gohr } elseif ($this->getConf('mode') == 'question') { 143df8afac4SAndreas Gohr $code = $this->getConf('answer'); 1449e312724SAndreas Gohr } else { 14577e00bf9SAndreas Gohr $code = $this->_generateCAPTCHA($this->_fixedIdent(), $rand); 1469e312724SAndreas Gohr } 14777e00bf9SAndreas Gohr 148478e363cSAndreas Gohr // compare values 149478e363cSAndreas Gohr if (!$field_sec || 150478e363cSAndreas Gohr !$field_in || 15114e271ebSPatrick Brown $rand === false || 152478e363cSAndreas Gohr utf8_strtolower($field_in) != utf8_strtolower($code) || 153a285df67SAndreas Gohr trim($field_hp) !== '' || 154a285df67SAndreas Gohr !$this->retrieveCaptchaCookie($this->_fixedIdent(), $rand) 15523d379a8SAndreas Gohr ) { 15677e00bf9SAndreas Gohr if ($msg) msg($this->getLang('testfailed'), -1); 15777e00bf9SAndreas Gohr return false; 15877e00bf9SAndreas Gohr } 15977e00bf9SAndreas Gohr return true; 16077e00bf9SAndreas Gohr } 16177e00bf9SAndreas Gohr 16277e00bf9SAndreas Gohr /** 163cde3ece1SAndreas Gohr * Get the path where a captcha cookie would be stored 164a285df67SAndreas Gohr * 165a285df67SAndreas Gohr * We use a daily temp directory which is easy to clean up 166a285df67SAndreas Gohr * 167a285df67SAndreas Gohr * @param $fixed string the fixed part, any string 168a285df67SAndreas Gohr * @param $rand float some random number between 0 and 1 169a285df67SAndreas Gohr * @return string the path to the cookie file 170a285df67SAndreas Gohr */ 17118622736SAndreas Gohr protected function getCaptchaCookiePath($fixed, $rand) 17218622736SAndreas Gohr { 173a285df67SAndreas Gohr global $conf; 174a285df67SAndreas Gohr $path = $conf['tmpdir'] . '/captcha/' . date('Y-m-d') . '/' . md5($fixed . $rand) . '.cookie'; 175a285df67SAndreas Gohr io_makeFileDir($path); 176a285df67SAndreas Gohr return $path; 177a285df67SAndreas Gohr } 178a285df67SAndreas Gohr 179a285df67SAndreas Gohr /** 180cde3ece1SAndreas Gohr * remove all outdated captcha cookies 181cde3ece1SAndreas Gohr */ 18218622736SAndreas Gohr public function _cleanCaptchaCookies() 18318622736SAndreas Gohr { 184cde3ece1SAndreas Gohr global $conf; 185cde3ece1SAndreas Gohr $path = $conf['tmpdir'] . '/captcha/'; 186cde3ece1SAndreas Gohr $dirs = glob("$path/*", GLOB_ONLYDIR); 187cde3ece1SAndreas Gohr $today = date('Y-m-d'); 188cde3ece1SAndreas Gohr foreach ($dirs as $dir) { 189cde3ece1SAndreas Gohr if (basename($dir) === $today) continue; 190cde3ece1SAndreas Gohr if (!preg_match('/\/captcha\//', $dir)) continue; // safety net 191cde3ece1SAndreas Gohr io_rmdir($dir, true); 192cde3ece1SAndreas Gohr } 193cde3ece1SAndreas Gohr } 194cde3ece1SAndreas Gohr 195cde3ece1SAndreas Gohr /** 196a285df67SAndreas Gohr * Creates a one time captcha cookie 197a285df67SAndreas Gohr * 198a285df67SAndreas Gohr * This is used to prevent replay attacks. It is generated when the captcha form 199a285df67SAndreas Gohr * is shown and checked with the captcha check. Since we can not be sure about the 200a285df67SAndreas Gohr * session state (might be closed or open) we're not using it. 201a285df67SAndreas Gohr * 202a285df67SAndreas Gohr * We're not using the stored values for displaying the captcha image (or audio) 203a285df67SAndreas Gohr * but continue to use our encryption scheme. This way it's still possible to have 204a285df67SAndreas Gohr * multiple captcha checks going on in parallel (eg. with multiple browser tabs) 205a285df67SAndreas Gohr * 206a285df67SAndreas Gohr * @param $fixed string the fixed part, any string 207a285df67SAndreas Gohr * @param $rand float some random number between 0 and 1 208a285df67SAndreas Gohr */ 20918622736SAndreas Gohr protected function storeCaptchaCookie($fixed, $rand) 21018622736SAndreas Gohr { 211a285df67SAndreas Gohr $cache = $this->getCaptchaCookiePath($fixed, $rand); 212a285df67SAndreas Gohr touch($cache); 213a285df67SAndreas Gohr } 214a285df67SAndreas Gohr 215a285df67SAndreas Gohr /** 216a285df67SAndreas Gohr * Checks if the captcha cookie exists and deletes it 217a285df67SAndreas Gohr * 218a285df67SAndreas Gohr * @param $fixed string the fixed part, any string 219a285df67SAndreas Gohr * @param $rand float some random number between 0 and 1 220a285df67SAndreas Gohr * @return bool true if the cookie existed 221a285df67SAndreas Gohr */ 22218622736SAndreas Gohr protected function retrieveCaptchaCookie($fixed, $rand) 22318622736SAndreas Gohr { 224a285df67SAndreas Gohr $cache = $this->getCaptchaCookiePath($fixed, $rand); 225a285df67SAndreas Gohr if (file_exists($cache)) { 226a285df67SAndreas Gohr unlink($cache); 227a285df67SAndreas Gohr return true; 228a285df67SAndreas Gohr } 229a285df67SAndreas Gohr return false; 230a285df67SAndreas Gohr } 231a285df67SAndreas Gohr 232a285df67SAndreas Gohr /** 23377e00bf9SAndreas Gohr * Build a semi-secret fixed string identifying the current page and user 23477e00bf9SAndreas Gohr * 23577e00bf9SAndreas Gohr * This string is always the same for the current user when editing the same 23627d84d8dSAndreas Gohr * page revision, but only for one day. Editing a page before midnight and saving 23727d84d8dSAndreas Gohr * after midnight will result in a failed CAPTCHA once, but makes sure it can 23827d84d8dSAndreas Gohr * not be reused which is especially important for the registration form where the 23927d84d8dSAndreas Gohr * $ID usually won't change. 240104ec268SAndreas Gohr * 241104ec268SAndreas Gohr * @return string 24277e00bf9SAndreas Gohr */ 24318622736SAndreas Gohr public function _fixedIdent() 24418622736SAndreas Gohr { 24577e00bf9SAndreas Gohr global $ID; 24677e00bf9SAndreas Gohr $lm = @filemtime(wikiFN($ID)); 24727d84d8dSAndreas Gohr $td = date('Y-m-d'); 248*63609b6eSAndreas Gohr $ip = clientIP(); 249*63609b6eSAndreas Gohr $salt = auth_cookiesalt(); 250*63609b6eSAndreas Gohr 251*63609b6eSAndreas Gohr return sha1(join("\n", [$ID, $lm, $td, $ip, $salt])); 25277e00bf9SAndreas Gohr } 25377e00bf9SAndreas Gohr 25477e00bf9SAndreas Gohr /** 2559d6f09afSAndreas Gohr * Adds random space characters within the given text 2569d6f09afSAndreas Gohr * 2579d6f09afSAndreas Gohr * Keeps subsequent numbers without spaces (for math problem) 2589d6f09afSAndreas Gohr * 2599d6f09afSAndreas Gohr * @param $text 2609d6f09afSAndreas Gohr * @return string 2619d6f09afSAndreas Gohr */ 26218622736SAndreas Gohr protected function _obfuscateText($text) 26318622736SAndreas Gohr { 2649d6f09afSAndreas Gohr $new = ''; 2659d6f09afSAndreas Gohr 2669d6f09afSAndreas Gohr $spaces = array( 2679d6f09afSAndreas Gohr "\r", 2689d6f09afSAndreas Gohr "\n", 2699d6f09afSAndreas Gohr "\r\n", 2709d6f09afSAndreas Gohr ' ', 2719d6f09afSAndreas Gohr "\xC2\xA0", // \u00A0 NO-BREAK SPACE 2729d6f09afSAndreas Gohr "\xE2\x80\x80", // \u2000 EN QUAD 2739d6f09afSAndreas Gohr "\xE2\x80\x81", // \u2001 EM QUAD 2749d6f09afSAndreas Gohr "\xE2\x80\x82", // \u2002 EN SPACE 2759d6f09afSAndreas Gohr // "\xE2\x80\x83", // \u2003 EM SPACE 2769d6f09afSAndreas Gohr "\xE2\x80\x84", // \u2004 THREE-PER-EM SPACE 2779d6f09afSAndreas Gohr "\xE2\x80\x85", // \u2005 FOUR-PER-EM SPACE 2789d6f09afSAndreas Gohr "\xE2\x80\x86", // \u2006 SIX-PER-EM SPACE 2799d6f09afSAndreas Gohr "\xE2\x80\x87", // \u2007 FIGURE SPACE 2809d6f09afSAndreas Gohr "\xE2\x80\x88", // \u2008 PUNCTUATION SPACE 2819d6f09afSAndreas Gohr "\xE2\x80\x89", // \u2009 THIN SPACE 2829d6f09afSAndreas Gohr "\xE2\x80\x8A", // \u200A HAIR SPACE 2839d6f09afSAndreas Gohr "\xE2\x80\xAF", // \u202F NARROW NO-BREAK SPACE 2849d6f09afSAndreas Gohr "\xE2\x81\x9F", // \u205F MEDIUM MATHEMATICAL SPACE 2859d6f09afSAndreas Gohr 2869d6f09afSAndreas Gohr "\xE1\xA0\x8E\r\n", // \u180E MONGOLIAN VOWEL SEPARATOR 2879d6f09afSAndreas Gohr "\xE2\x80\x8B\r\n", // \u200B ZERO WIDTH SPACE 2889d6f09afSAndreas Gohr "\xEF\xBB\xBF\r\n", // \uFEFF ZERO WIDTH NO-BREAK SPACE 2899d6f09afSAndreas Gohr ); 2909d6f09afSAndreas Gohr 2919d6f09afSAndreas Gohr $len = strlen($text); 2929d6f09afSAndreas Gohr for ($i = 0; $i < $len - 1; $i++) { 29339bbdaefSAndreas Gohr $new .= $text[$i]; 2949d6f09afSAndreas Gohr 29539bbdaefSAndreas Gohr if (!is_numeric($text[$i + 1])) { 2969d6f09afSAndreas Gohr $new .= $spaces[array_rand($spaces)]; 2979d6f09afSAndreas Gohr } 2989d6f09afSAndreas Gohr } 29939bbdaefSAndreas Gohr $new .= $text[$len - 1]; 3009d6f09afSAndreas Gohr return $new; 3019d6f09afSAndreas Gohr } 3029d6f09afSAndreas Gohr 3039d6f09afSAndreas Gohr /** 304a02b2219SPatrick Brown * Generate some numbers from a known string and random number 305a02b2219SPatrick Brown * 306a02b2219SPatrick Brown * @param $fixed string the fixed part, any string 307a02b2219SPatrick Brown * @param $rand float some random number between 0 and 1 308a02b2219SPatrick Brown * @return string 309a02b2219SPatrick Brown */ 31018622736SAndreas Gohr protected function _generateNumbers($fixed, $rand) 31118622736SAndreas Gohr { 312a02b2219SPatrick Brown $fixed = hexdec(substr(md5($fixed), 5, 5)); // use part of the md5 to generate an int 313a02b2219SPatrick Brown $rand = $rand * 0xFFFFF; // bitmask from the random number 314a02b2219SPatrick Brown return md5($rand ^ $fixed); // combine both values 315a02b2219SPatrick Brown } 316a02b2219SPatrick Brown 317a02b2219SPatrick Brown /** 31828c14643SAndreas Gohr * Generates a random char string 31977e00bf9SAndreas Gohr * 320104ec268SAndreas Gohr * @param $fixed string the fixed part, any string 321104ec268SAndreas Gohr * @param $rand float some random number between 0 and 1 32223d379a8SAndreas Gohr * @return string 32377e00bf9SAndreas Gohr */ 32418622736SAndreas Gohr public function _generateCAPTCHA($fixed, $rand) 32518622736SAndreas Gohr { 326a02b2219SPatrick Brown $numbers = $this->_generateNumbers($fixed, $rand); 32777e00bf9SAndreas Gohr 32877e00bf9SAndreas Gohr // now create the letters 32977e00bf9SAndreas Gohr $code = ''; 3309a516edaSPatrick Brown $lettercount = $this->getConf('lettercount') * 2; 3319a516edaSPatrick Brown if ($lettercount > strlen($numbers)) $lettercount = strlen($numbers); 3329a516edaSPatrick Brown for ($i = 0; $i < $lettercount; $i += 2) { 33377e00bf9SAndreas Gohr $code .= chr(floor(hexdec($numbers[$i] . $numbers[$i + 1]) / 10) + 65); 33477e00bf9SAndreas Gohr } 33577e00bf9SAndreas Gohr 33677e00bf9SAndreas Gohr return $code; 33777e00bf9SAndreas Gohr } 33877e00bf9SAndreas Gohr 3399e312724SAndreas Gohr /** 3409e312724SAndreas Gohr * Create a mathematical task and its result 3419e312724SAndreas Gohr * 342104ec268SAndreas Gohr * @param $fixed string the fixed part, any string 343104ec268SAndreas Gohr * @param $rand float some random number between 0 and 1 344104ec268SAndreas Gohr * @return array taks, result 3459e312724SAndreas Gohr */ 34618622736SAndreas Gohr protected function _generateMATH($fixed, $rand) 34718622736SAndreas Gohr { 348a02b2219SPatrick Brown $numbers = $this->_generateNumbers($fixed, $rand); 3499e312724SAndreas Gohr 3509e312724SAndreas Gohr // first letter is the operator (+/-) 3519e312724SAndreas Gohr $op = (hexdec($numbers[0]) > 8) ? -1 : 1; 3529e312724SAndreas Gohr $num = array(hexdec($numbers[1] . $numbers[2]), hexdec($numbers[3])); 3539e312724SAndreas Gohr 3549e312724SAndreas Gohr // we only want positive results 3559e312724SAndreas Gohr if (($op < 0) && ($num[0] < $num[1])) rsort($num); 3569e312724SAndreas Gohr 3579e312724SAndreas Gohr // prepare result and task text 3589e312724SAndreas Gohr $res = $num[0] + ($num[1] * $op); 3599bc1fab2SApostolos P. Tsompanopoulos $task = $num[0] . (($op < 0) ? '-' : '+') . $num[1] . '= '; 3609e312724SAndreas Gohr 3619e312724SAndreas Gohr return array($task, $res); 3629e312724SAndreas Gohr } 36377e00bf9SAndreas Gohr 36477e00bf9SAndreas Gohr /** 36577e00bf9SAndreas Gohr * Create a CAPTCHA image 366104ec268SAndreas Gohr * 367104ec268SAndreas Gohr * @param string $text the letters to display 36877e00bf9SAndreas Gohr */ 36918622736SAndreas Gohr public function _imageCAPTCHA($text) 37018622736SAndreas Gohr { 37177e00bf9SAndreas Gohr $w = $this->getConf('width'); 37277e00bf9SAndreas Gohr $h = $this->getConf('height'); 37377e00bf9SAndreas Gohr 374dc091fd0SAndreas Gohr $fonts = glob(dirname(__FILE__) . '/fonts/*.ttf'); 375dc091fd0SAndreas Gohr 37677e00bf9SAndreas Gohr // create a white image 37728c14643SAndreas Gohr $img = imagecreatetruecolor($w, $h); 37828c14643SAndreas Gohr $white = imagecolorallocate($img, 255, 255, 255); 37928c14643SAndreas Gohr imagefill($img, 0, 0, $white); 38077e00bf9SAndreas Gohr 38177e00bf9SAndreas Gohr // add some lines as background noise 38277e00bf9SAndreas Gohr for ($i = 0; $i < 30; $i++) { 38377e00bf9SAndreas Gohr $color = imagecolorallocate($img, rand(100, 250), rand(100, 250), rand(100, 250)); 38477e00bf9SAndreas Gohr imageline($img, rand(0, $w), rand(0, $h), rand(0, $w), rand(0, $h), $color); 38577e00bf9SAndreas Gohr } 38677e00bf9SAndreas Gohr 38777e00bf9SAndreas Gohr // draw the letters 38828c14643SAndreas Gohr $txtlen = strlen($text); 38928c14643SAndreas Gohr for ($i = 0; $i < $txtlen; $i++) { 390dc091fd0SAndreas Gohr $font = $fonts[array_rand($fonts)]; 39177e00bf9SAndreas Gohr $color = imagecolorallocate($img, rand(0, 100), rand(0, 100), rand(0, 100)); 39277e00bf9SAndreas Gohr $size = rand(floor($h / 1.8), floor($h * 0.7)); 39377e00bf9SAndreas Gohr $angle = rand(-35, 35); 39477e00bf9SAndreas Gohr 39528c14643SAndreas Gohr $x = ($w * 0.05) + $i * floor($w * 0.9 / $txtlen); 39677e00bf9SAndreas Gohr $cheight = $size + ($size * 0.5); 39777e00bf9SAndreas Gohr $y = floor($h / 2 + $cheight / 3.8); 39877e00bf9SAndreas Gohr 39977e00bf9SAndreas Gohr imagettftext($img, $size, $angle, $x, $y, $color, $font, $text[$i]); 40077e00bf9SAndreas Gohr } 40177e00bf9SAndreas Gohr 40277e00bf9SAndreas Gohr header("Content-type: image/png"); 40377e00bf9SAndreas Gohr imagepng($img); 40477e00bf9SAndreas Gohr imagedestroy($img); 40577e00bf9SAndreas Gohr } 40677e00bf9SAndreas Gohr 407f044313dSAndreas Gohr /** 40808f248e4SAndreas Gohr * Create an SVG of the given text 40908f248e4SAndreas Gohr * 41008f248e4SAndreas Gohr * @param string $text 41108f248e4SAndreas Gohr * @return string 41208f248e4SAndreas Gohr */ 41318622736SAndreas Gohr public function _svgCAPTCHA($text) 41418622736SAndreas Gohr { 41508f248e4SAndreas Gohr require_once(__DIR__ . '/EasySVG.php'); 41608f248e4SAndreas Gohr 41708f248e4SAndreas Gohr $fonts = glob(__DIR__ . '/fonts/*.svg'); 41808f248e4SAndreas Gohr 41908f248e4SAndreas Gohr $x = 0; // where we start to draw 42008f248e4SAndreas Gohr $y = 100; // our max height 42108f248e4SAndreas Gohr 42208f248e4SAndreas Gohr $svg = new EasySVG(); 42308f248e4SAndreas Gohr 42408f248e4SAndreas Gohr // draw the letters 42508f248e4SAndreas Gohr $txtlen = strlen($text); 42608f248e4SAndreas Gohr for ($i = 0; $i < $txtlen; $i++) { 42708f248e4SAndreas Gohr $char = $text[$i]; 42808f248e4SAndreas Gohr $size = rand($y / 2, $y - $y * 0.1); // 50-90% 42908f248e4SAndreas Gohr $svg->setFontSVG($fonts[array_rand($fonts)]); 43008f248e4SAndreas Gohr 43108f248e4SAndreas Gohr $svg->setFontSize($size); 43208f248e4SAndreas Gohr $svg->setLetterSpacing(round(rand(1, 4) / 10, 2)); // 0.1 - 0.4 43308f248e4SAndreas Gohr $svg->addText($char, $x, rand(0, round($y - $size))); // random up and down 43408f248e4SAndreas Gohr 43508f248e4SAndreas Gohr list($w) = $svg->textDimensions($char); 43608f248e4SAndreas Gohr $x += $w; 43708f248e4SAndreas Gohr } 43808f248e4SAndreas Gohr 43908f248e4SAndreas Gohr $svg->addAttribute('width', $x . 'px'); 44008f248e4SAndreas Gohr $svg->addAttribute('height', $y . 'px'); 44108f248e4SAndreas Gohr $svg->addAttribute('viewbox', "0 0 $x $y"); 44208f248e4SAndreas Gohr return $svg->asXML(); 44308f248e4SAndreas Gohr } 44408f248e4SAndreas Gohr 44508f248e4SAndreas Gohr /** 446*63609b6eSAndreas Gohr * Generate an audio captcha 447*63609b6eSAndreas Gohr * 448*63609b6eSAndreas Gohr * @param string $text 449*63609b6eSAndreas Gohr */ 450*63609b6eSAndreas Gohr public function _audioCAPTCHA($text){ 451*63609b6eSAndreas Gohr global $conf; 452*63609b6eSAndreas Gohr 453*63609b6eSAndreas Gohr $lc = __DIR__ . '/lang/' . $conf['lang'] . '/audio/'; 454*63609b6eSAndreas Gohr $en = __DIR__ . '/lang/en/audio/'; 455*63609b6eSAndreas Gohr 456*63609b6eSAndreas Gohr $wavs = []; 457*63609b6eSAndreas Gohr 458*63609b6eSAndreas Gohr $text = strtolower($text); 459*63609b6eSAndreas Gohr $txtlen = strlen($text); 460*63609b6eSAndreas Gohr for ($i = 0; $i < $txtlen; $i++) { 461*63609b6eSAndreas Gohr $char = $text[$i]; 462*63609b6eSAndreas Gohr $file = $lc . $char . '.wav'; 463*63609b6eSAndreas Gohr if (!@file_exists($file)) $file = $en . $char . '.wav'; 464*63609b6eSAndreas Gohr $wavs[] = $file; 465*63609b6eSAndreas Gohr } 466*63609b6eSAndreas Gohr 467*63609b6eSAndreas Gohr header('Content-type: audio/x-wav'); 468*63609b6eSAndreas Gohr header('Content-Disposition: attachment;filename=captcha.wav'); 469*63609b6eSAndreas Gohr 470*63609b6eSAndreas Gohr echo $this->joinwavs($wavs); 471*63609b6eSAndreas Gohr } 472*63609b6eSAndreas Gohr 473*63609b6eSAndreas Gohr /** 474f044313dSAndreas Gohr * Encrypt the given string with the cookie salt 475f044313dSAndreas Gohr * 476f044313dSAndreas Gohr * @param string $data 477f044313dSAndreas Gohr * @return string 478f044313dSAndreas Gohr */ 47918622736SAndreas Gohr public function encrypt($data) 48018622736SAndreas Gohr { 481f044313dSAndreas Gohr if (function_exists('auth_encrypt')) { 482f044313dSAndreas Gohr $data = auth_encrypt($data, auth_cookiesalt()); // since binky 483f044313dSAndreas Gohr } else { 484f044313dSAndreas Gohr $data = PMA_blowfish_encrypt($data, auth_cookiesalt()); // deprecated 485f044313dSAndreas Gohr } 486f044313dSAndreas Gohr 487f044313dSAndreas Gohr return base64_encode($data); 488f044313dSAndreas Gohr } 489f044313dSAndreas Gohr 490f044313dSAndreas Gohr /** 491f044313dSAndreas Gohr * Decrypt the given string with the cookie salt 492f044313dSAndreas Gohr * 493f044313dSAndreas Gohr * @param string $data 494f044313dSAndreas Gohr * @return string 495f044313dSAndreas Gohr */ 49618622736SAndreas Gohr public function decrypt($data) 49718622736SAndreas Gohr { 498f044313dSAndreas Gohr $data = base64_decode($data); 49909870f99SPatrick Brown if ($data === false || $data === '') return false; 500f044313dSAndreas Gohr 501f044313dSAndreas Gohr if (function_exists('auth_decrypt')) { 502f044313dSAndreas Gohr return auth_decrypt($data, auth_cookiesalt()); // since binky 503f044313dSAndreas Gohr } else { 504f044313dSAndreas Gohr return PMA_blowfish_decrypt($data, auth_cookiesalt()); // deprecated 505f044313dSAndreas Gohr } 506f044313dSAndreas Gohr } 507969b14c4SAndreas Gohr 508*63609b6eSAndreas Gohr 509*63609b6eSAndreas Gohr /** 510*63609b6eSAndreas Gohr * Join multiple wav files 511*63609b6eSAndreas Gohr * 512*63609b6eSAndreas Gohr * All wave files need to have the same format and need to be uncompressed. 513*63609b6eSAndreas Gohr * The headers of the last file will be used (with recalculated datasize 514*63609b6eSAndreas Gohr * of course) 515*63609b6eSAndreas Gohr * 516*63609b6eSAndreas Gohr * @link http://ccrma.stanford.edu/CCRMA/Courses/422/projects/WaveFormat/ 517*63609b6eSAndreas Gohr * @link http://www.thescripts.com/forum/thread3770.html 518*63609b6eSAndreas Gohr */ 519*63609b6eSAndreas Gohr protected function joinwavs($wavs) 520*63609b6eSAndreas Gohr { 521*63609b6eSAndreas Gohr $fields = join( 522*63609b6eSAndreas Gohr '/', array( 523*63609b6eSAndreas Gohr 'H8ChunkID', 524*63609b6eSAndreas Gohr 'VChunkSize', 525*63609b6eSAndreas Gohr 'H8Format', 526*63609b6eSAndreas Gohr 'H8Subchunk1ID', 527*63609b6eSAndreas Gohr 'VSubchunk1Size', 528*63609b6eSAndreas Gohr 'vAudioFormat', 529*63609b6eSAndreas Gohr 'vNumChannels', 530*63609b6eSAndreas Gohr 'VSampleRate', 531*63609b6eSAndreas Gohr 'VByteRate', 532*63609b6eSAndreas Gohr 'vBlockAlign', 533*63609b6eSAndreas Gohr 'vBitsPerSample', 534*63609b6eSAndreas Gohr ) 535*63609b6eSAndreas Gohr ); 536*63609b6eSAndreas Gohr 537*63609b6eSAndreas Gohr $data = ''; 538*63609b6eSAndreas Gohr foreach ($wavs as $wav) { 539*63609b6eSAndreas Gohr $fp = fopen($wav, 'rb'); 540*63609b6eSAndreas Gohr $header = fread($fp, 36); 541*63609b6eSAndreas Gohr $info = unpack($fields, $header); 542*63609b6eSAndreas Gohr 543*63609b6eSAndreas Gohr // read optional extra stuff 544*63609b6eSAndreas Gohr if ($info['Subchunk1Size'] > 16) { 545*63609b6eSAndreas Gohr $header .= fread($fp, ($info['Subchunk1Size'] - 16)); 546*63609b6eSAndreas Gohr } 547*63609b6eSAndreas Gohr 548*63609b6eSAndreas Gohr // read SubChunk2ID 549*63609b6eSAndreas Gohr $header .= fread($fp, 4); 550*63609b6eSAndreas Gohr 551*63609b6eSAndreas Gohr // read Subchunk2Size 552*63609b6eSAndreas Gohr $size = unpack('vsize', fread($fp, 4)); 553*63609b6eSAndreas Gohr $size = $size['size']; 554*63609b6eSAndreas Gohr 555*63609b6eSAndreas Gohr // read data 556*63609b6eSAndreas Gohr $data .= fread($fp, $size); 557*63609b6eSAndreas Gohr } 558*63609b6eSAndreas Gohr 559*63609b6eSAndreas Gohr return $header . pack('V', strlen($data)) . $data; 560*63609b6eSAndreas Gohr } 561*63609b6eSAndreas Gohr 56277e00bf9SAndreas Gohr} 563