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 777e00bf9SAndreas Gohr// must be run within Dokuwiki 877e00bf9SAndreas Gohrif (!defined('DOKU_INC')) die(); 977e00bf9SAndreas Gohrif (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 107218f96cSAndreas Gohr 11a285df67SAndreas Gohr/** 12a285df67SAndreas Gohr * Class helper_plugin_captcha 13a285df67SAndreas Gohr */ 14*18622736SAndreas Gohrclass helper_plugin_captcha extends DokuWiki_Plugin 15*18622736SAndreas Gohr{ 1677e00bf9SAndreas Gohr 1723d379a8SAndreas Gohr protected $field_in = 'plugin__captcha'; 1823d379a8SAndreas Gohr protected $field_sec = 'plugin__captcha_secret'; 1923d379a8SAndreas Gohr protected $field_hp = 'plugin__captcha_honeypot'; 2023d379a8SAndreas Gohr 2123d379a8SAndreas Gohr /** 2223d379a8SAndreas Gohr * Constructor. Initializes field names 2323d379a8SAndreas Gohr */ 24*18622736SAndreas Gohr public function __construct() 25*18622736SAndreas Gohr { 2623d379a8SAndreas Gohr $this->field_in = md5($this->_fixedIdent() . $this->field_in); 2723d379a8SAndreas Gohr $this->field_sec = md5($this->_fixedIdent() . $this->field_sec); 2823d379a8SAndreas Gohr $this->field_hp = md5($this->_fixedIdent() . $this->field_hp); 2923d379a8SAndreas Gohr } 3023d379a8SAndreas Gohr 3177e00bf9SAndreas Gohr /** 3277e00bf9SAndreas Gohr * Check if the CAPTCHA should be used. Always check this before using the methods below. 3377e00bf9SAndreas Gohr * 3477e00bf9SAndreas Gohr * @return bool true when the CAPTCHA should be used 3577e00bf9SAndreas Gohr */ 36*18622736SAndreas Gohr public function isEnabled() 37*18622736SAndreas Gohr { 3877e00bf9SAndreas Gohr if (!$this->getConf('forusers') && $_SERVER['REMOTE_USER']) return false; 3977e00bf9SAndreas Gohr return true; 4077e00bf9SAndreas Gohr } 4177e00bf9SAndreas Gohr 4277e00bf9SAndreas Gohr /** 4377e00bf9SAndreas Gohr * Returns the HTML to display the CAPTCHA with the chosen method 4477e00bf9SAndreas Gohr */ 45*18622736SAndreas Gohr public function getHTML() 46*18622736SAndreas Gohr { 4777e00bf9SAndreas Gohr global $ID; 4877e00bf9SAndreas Gohr 4977e00bf9SAndreas Gohr $rand = (float)(rand(0, 10000)) / 10000; 50a285df67SAndreas Gohr $this->storeCaptchaCookie($this->_fixedIdent(), $rand); 51a285df67SAndreas Gohr 529e312724SAndreas Gohr if ($this->getConf('mode') == 'math') { 539e312724SAndreas Gohr $code = $this->_generateMATH($this->_fixedIdent(), $rand); 549e312724SAndreas Gohr $code = $code[0]; 559e312724SAndreas Gohr $text = $this->getLang('fillmath'); 56df8afac4SAndreas Gohr } elseif ($this->getConf('mode') == 'question') { 57a285df67SAndreas Gohr $code = ''; // not used 58df8afac4SAndreas Gohr $text = $this->getConf('question'); 599e312724SAndreas Gohr } else { 6077e00bf9SAndreas Gohr $code = $this->_generateCAPTCHA($this->_fixedIdent(), $rand); 619e312724SAndreas Gohr $text = $this->getLang('fillcaptcha'); 629e312724SAndreas Gohr } 63f044313dSAndreas Gohr $secret = $this->encrypt($rand); 6477e00bf9SAndreas Gohr 6528c14643SAndreas Gohr $txtlen = $this->getConf('lettercount'); 6628c14643SAndreas Gohr 6777e00bf9SAndreas Gohr $out = ''; 6877e00bf9SAndreas Gohr $out .= '<div id="plugin__captcha_wrapper">'; 6923d379a8SAndreas Gohr $out .= '<input type="hidden" name="' . $this->field_sec . '" value="' . hsc($secret) . '" />'; 709e312724SAndreas Gohr $out .= '<label for="plugin__captcha">' . $text . '</label> '; 7123d379a8SAndreas Gohr 7277e00bf9SAndreas Gohr switch ($this->getConf('mode')) { 739e312724SAndreas Gohr case 'math': 749d6f09afSAndreas Gohr case 'text': 759d6f09afSAndreas Gohr $out .= $this->_obfuscateText($code); 7677e00bf9SAndreas Gohr break; 7777e00bf9SAndreas Gohr case 'js': 789d6f09afSAndreas Gohr $out .= '<span id="plugin__captcha_code">' . $this->_obfuscateText($code) . '</span>'; 7977e00bf9SAndreas Gohr break; 8008f248e4SAndreas Gohr case 'svg': 8108f248e4SAndreas Gohr $out .= '<span class="svg" style="width:' . $this->getConf('width') . 'px; height:' . $this->getConf('height') . 'px">'; 8208f248e4SAndreas Gohr $out .= $this->_svgCAPTCHA($code); 8308f248e4SAndreas Gohr $out .= '</span>'; 8408f248e4SAndreas Gohr break; 8508f248e4SAndreas Gohr case 'svgaudio': 8608f248e4SAndreas Gohr $out .= '<span class="svg" style="width:' . $this->getConf('width') . 'px; height:' . $this->getConf('height') . 'px">'; 8708f248e4SAndreas Gohr $out .= $this->_svgCAPTCHA($code); 8808f248e4SAndreas Gohr $out .= '</span>'; 8908f248e4SAndreas Gohr $out .= '<a href="' . DOKU_BASE . 'lib/plugins/captcha/wav.php?secret=' . rawurlencode($secret) . '&id=' . $ID . '"' . 909efb703bSStefan Bethke ' class="JSnocheck audiolink" title="' . $this->getLang('soundlink') . '">'; 9108f248e4SAndreas Gohr $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/sound.png" width="16" height="16"' . 9208f248e4SAndreas Gohr ' alt="' . $this->getLang('soundlink') . '" /></a>'; 9308f248e4SAndreas Gohr break; 9477e00bf9SAndreas Gohr case 'image': 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 break; 9877e00bf9SAndreas Gohr case 'audio': 9977e00bf9SAndreas Gohr $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/img.php?secret=' . rawurlencode($secret) . '&id=' . $ID . '" ' . 10077e00bf9SAndreas Gohr ' width="' . $this->getConf('width') . '" height="' . $this->getConf('height') . '" alt="" /> '; 10177e00bf9SAndreas Gohr $out .= '<a href="' . DOKU_BASE . 'lib/plugins/captcha/wav.php?secret=' . rawurlencode($secret) . '&id=' . $ID . '"' . 1029efb703bSStefan Bethke ' class="JSnocheck audiolink" title="' . $this->getLang('soundlink') . '">'; 10377e00bf9SAndreas Gohr $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/sound.png" width="16" height="16"' . 10477e00bf9SAndreas Gohr ' alt="' . $this->getLang('soundlink') . '" /></a>'; 10577e00bf9SAndreas Gohr break; 10652e95008SAndreas Gohr case 'figlet': 10752e95008SAndreas Gohr require_once(dirname(__FILE__) . '/figlet.php'); 10852e95008SAndreas Gohr $figlet = new phpFiglet(); 10952e95008SAndreas Gohr if ($figlet->loadfont(dirname(__FILE__) . '/figlet.flf')) { 11052e95008SAndreas Gohr $out .= '<pre>'; 11152e95008SAndreas Gohr $out .= rtrim($figlet->fetch($code)); 11252e95008SAndreas Gohr $out .= '</pre>'; 11352e95008SAndreas Gohr } else { 11452e95008SAndreas Gohr msg('Failed to load figlet.flf font file. CAPTCHA broken', -1); 11552e95008SAndreas Gohr } 11652e95008SAndreas Gohr break; 11777e00bf9SAndreas Gohr } 118df8afac4SAndreas Gohr $out .= ' <input type="text" size="' . $txtlen . '" name="' . $this->field_in . '" class="edit" /> '; 11923d379a8SAndreas Gohr 12023d379a8SAndreas Gohr // add honeypot field 1219d63c05fSlainme $out .= '<label class="no">' . $this->getLang('honeypot') . '<input type="text" name="' . $this->field_hp . '" /></label>'; 12277e00bf9SAndreas Gohr $out .= '</div>'; 12377e00bf9SAndreas Gohr return $out; 12477e00bf9SAndreas Gohr } 12577e00bf9SAndreas Gohr 12677e00bf9SAndreas Gohr /** 127*18622736SAndreas Gohr * Checks if the CAPTCHA was solved correctly 12877e00bf9SAndreas Gohr * 12977e00bf9SAndreas Gohr * @param bool $msg when true, an error will be signalled through the msg() method 13077e00bf9SAndreas Gohr * @return bool true when the answer was correct, otherwise false 13177e00bf9SAndreas Gohr */ 132*18622736SAndreas Gohr public function check($msg = true) 133*18622736SAndreas Gohr { 134478e363cSAndreas Gohr global $INPUT; 135478e363cSAndreas Gohr 136478e363cSAndreas Gohr $field_sec = $INPUT->str($this->field_sec); 137478e363cSAndreas Gohr $field_in = $INPUT->str($this->field_in); 138478e363cSAndreas Gohr $field_hp = $INPUT->str($this->field_hp); 139478e363cSAndreas Gohr 140478e363cSAndreas Gohr // reconstruct captcha from provided $field_sec 141478e363cSAndreas Gohr $rand = $this->decrypt($field_sec); 1429e312724SAndreas Gohr 1439e312724SAndreas Gohr if ($this->getConf('mode') == 'math') { 1449e312724SAndreas Gohr $code = $this->_generateMATH($this->_fixedIdent(), $rand); 1459e312724SAndreas Gohr $code = $code[1]; 146df8afac4SAndreas Gohr } elseif ($this->getConf('mode') == 'question') { 147df8afac4SAndreas Gohr $code = $this->getConf('answer'); 1489e312724SAndreas Gohr } else { 14977e00bf9SAndreas Gohr $code = $this->_generateCAPTCHA($this->_fixedIdent(), $rand); 1509e312724SAndreas Gohr } 15177e00bf9SAndreas Gohr 152478e363cSAndreas Gohr // compare values 153478e363cSAndreas Gohr if (!$field_sec || 154478e363cSAndreas Gohr !$field_in || 15514e271ebSPatrick Brown $rand === false || 156478e363cSAndreas Gohr utf8_strtolower($field_in) != utf8_strtolower($code) || 157a285df67SAndreas Gohr trim($field_hp) !== '' || 158a285df67SAndreas Gohr !$this->retrieveCaptchaCookie($this->_fixedIdent(), $rand) 15923d379a8SAndreas Gohr ) { 16077e00bf9SAndreas Gohr if ($msg) msg($this->getLang('testfailed'), -1); 16177e00bf9SAndreas Gohr return false; 16277e00bf9SAndreas Gohr } 16377e00bf9SAndreas Gohr return true; 16477e00bf9SAndreas Gohr } 16577e00bf9SAndreas Gohr 16677e00bf9SAndreas Gohr /** 167cde3ece1SAndreas Gohr * Get the path where a captcha cookie would be stored 168a285df67SAndreas Gohr * 169a285df67SAndreas Gohr * We use a daily temp directory which is easy to clean up 170a285df67SAndreas Gohr * 171a285df67SAndreas Gohr * @param $fixed string the fixed part, any string 172a285df67SAndreas Gohr * @param $rand float some random number between 0 and 1 173a285df67SAndreas Gohr * @return string the path to the cookie file 174a285df67SAndreas Gohr */ 175*18622736SAndreas Gohr protected function getCaptchaCookiePath($fixed, $rand) 176*18622736SAndreas Gohr { 177a285df67SAndreas Gohr global $conf; 178a285df67SAndreas Gohr $path = $conf['tmpdir'] . '/captcha/' . date('Y-m-d') . '/' . md5($fixed . $rand) . '.cookie'; 179a285df67SAndreas Gohr io_makeFileDir($path); 180a285df67SAndreas Gohr return $path; 181a285df67SAndreas Gohr } 182a285df67SAndreas Gohr 183a285df67SAndreas Gohr /** 184cde3ece1SAndreas Gohr * remove all outdated captcha cookies 185cde3ece1SAndreas Gohr */ 186*18622736SAndreas Gohr public function _cleanCaptchaCookies() 187*18622736SAndreas Gohr { 188cde3ece1SAndreas Gohr global $conf; 189cde3ece1SAndreas Gohr $path = $conf['tmpdir'] . '/captcha/'; 190cde3ece1SAndreas Gohr $dirs = glob("$path/*", GLOB_ONLYDIR); 191cde3ece1SAndreas Gohr $today = date('Y-m-d'); 192cde3ece1SAndreas Gohr foreach ($dirs as $dir) { 193cde3ece1SAndreas Gohr if (basename($dir) === $today) continue; 194cde3ece1SAndreas Gohr if (!preg_match('/\/captcha\//', $dir)) continue; // safety net 195cde3ece1SAndreas Gohr io_rmdir($dir, true); 196cde3ece1SAndreas Gohr } 197cde3ece1SAndreas Gohr } 198cde3ece1SAndreas Gohr 199cde3ece1SAndreas Gohr /** 200a285df67SAndreas Gohr * Creates a one time captcha cookie 201a285df67SAndreas Gohr * 202a285df67SAndreas Gohr * This is used to prevent replay attacks. It is generated when the captcha form 203a285df67SAndreas Gohr * is shown and checked with the captcha check. Since we can not be sure about the 204a285df67SAndreas Gohr * session state (might be closed or open) we're not using it. 205a285df67SAndreas Gohr * 206a285df67SAndreas Gohr * We're not using the stored values for displaying the captcha image (or audio) 207a285df67SAndreas Gohr * but continue to use our encryption scheme. This way it's still possible to have 208a285df67SAndreas Gohr * multiple captcha checks going on in parallel (eg. with multiple browser tabs) 209a285df67SAndreas Gohr * 210a285df67SAndreas Gohr * @param $fixed string the fixed part, any string 211a285df67SAndreas Gohr * @param $rand float some random number between 0 and 1 212a285df67SAndreas Gohr */ 213*18622736SAndreas Gohr protected function storeCaptchaCookie($fixed, $rand) 214*18622736SAndreas Gohr { 215a285df67SAndreas Gohr $cache = $this->getCaptchaCookiePath($fixed, $rand); 216a285df67SAndreas Gohr touch($cache); 217a285df67SAndreas Gohr } 218a285df67SAndreas Gohr 219a285df67SAndreas Gohr /** 220a285df67SAndreas Gohr * Checks if the captcha cookie exists and deletes it 221a285df67SAndreas Gohr * 222a285df67SAndreas Gohr * @param $fixed string the fixed part, any string 223a285df67SAndreas Gohr * @param $rand float some random number between 0 and 1 224a285df67SAndreas Gohr * @return bool true if the cookie existed 225a285df67SAndreas Gohr */ 226*18622736SAndreas Gohr protected function retrieveCaptchaCookie($fixed, $rand) 227*18622736SAndreas Gohr { 228a285df67SAndreas Gohr $cache = $this->getCaptchaCookiePath($fixed, $rand); 229a285df67SAndreas Gohr if (file_exists($cache)) { 230a285df67SAndreas Gohr unlink($cache); 231a285df67SAndreas Gohr return true; 232a285df67SAndreas Gohr } 233a285df67SAndreas Gohr return false; 234a285df67SAndreas Gohr } 235a285df67SAndreas Gohr 236a285df67SAndreas Gohr /** 23777e00bf9SAndreas Gohr * Build a semi-secret fixed string identifying the current page and user 23877e00bf9SAndreas Gohr * 23977e00bf9SAndreas Gohr * This string is always the same for the current user when editing the same 24027d84d8dSAndreas Gohr * page revision, but only for one day. Editing a page before midnight and saving 24127d84d8dSAndreas Gohr * after midnight will result in a failed CAPTCHA once, but makes sure it can 24227d84d8dSAndreas Gohr * not be reused which is especially important for the registration form where the 24327d84d8dSAndreas Gohr * $ID usually won't change. 244104ec268SAndreas Gohr * 245104ec268SAndreas Gohr * @return string 24677e00bf9SAndreas Gohr */ 247*18622736SAndreas Gohr public function _fixedIdent() 248*18622736SAndreas Gohr { 24977e00bf9SAndreas Gohr global $ID; 25077e00bf9SAndreas Gohr $lm = @filemtime(wikiFN($ID)); 25127d84d8dSAndreas Gohr $td = date('Y-m-d'); 25277e00bf9SAndreas Gohr return auth_browseruid() . 25377e00bf9SAndreas Gohr auth_cookiesalt() . 25427d84d8dSAndreas Gohr $ID . $lm . $td; 25577e00bf9SAndreas Gohr } 25677e00bf9SAndreas Gohr 25777e00bf9SAndreas Gohr /** 2589d6f09afSAndreas Gohr * Adds random space characters within the given text 2599d6f09afSAndreas Gohr * 2609d6f09afSAndreas Gohr * Keeps subsequent numbers without spaces (for math problem) 2619d6f09afSAndreas Gohr * 2629d6f09afSAndreas Gohr * @param $text 2639d6f09afSAndreas Gohr * @return string 2649d6f09afSAndreas Gohr */ 265*18622736SAndreas Gohr protected function _obfuscateText($text) 266*18622736SAndreas Gohr { 2679d6f09afSAndreas Gohr $new = ''; 2689d6f09afSAndreas Gohr 2699d6f09afSAndreas Gohr $spaces = array( 2709d6f09afSAndreas Gohr "\r", 2719d6f09afSAndreas Gohr "\n", 2729d6f09afSAndreas Gohr "\r\n", 2739d6f09afSAndreas Gohr ' ', 2749d6f09afSAndreas Gohr "\xC2\xA0", // \u00A0 NO-BREAK SPACE 2759d6f09afSAndreas Gohr "\xE2\x80\x80", // \u2000 EN QUAD 2769d6f09afSAndreas Gohr "\xE2\x80\x81", // \u2001 EM QUAD 2779d6f09afSAndreas Gohr "\xE2\x80\x82", // \u2002 EN SPACE 2789d6f09afSAndreas Gohr // "\xE2\x80\x83", // \u2003 EM SPACE 2799d6f09afSAndreas Gohr "\xE2\x80\x84", // \u2004 THREE-PER-EM SPACE 2809d6f09afSAndreas Gohr "\xE2\x80\x85", // \u2005 FOUR-PER-EM SPACE 2819d6f09afSAndreas Gohr "\xE2\x80\x86", // \u2006 SIX-PER-EM SPACE 2829d6f09afSAndreas Gohr "\xE2\x80\x87", // \u2007 FIGURE SPACE 2839d6f09afSAndreas Gohr "\xE2\x80\x88", // \u2008 PUNCTUATION SPACE 2849d6f09afSAndreas Gohr "\xE2\x80\x89", // \u2009 THIN SPACE 2859d6f09afSAndreas Gohr "\xE2\x80\x8A", // \u200A HAIR SPACE 2869d6f09afSAndreas Gohr "\xE2\x80\xAF", // \u202F NARROW NO-BREAK SPACE 2879d6f09afSAndreas Gohr "\xE2\x81\x9F", // \u205F MEDIUM MATHEMATICAL SPACE 2889d6f09afSAndreas Gohr 2899d6f09afSAndreas Gohr "\xE1\xA0\x8E\r\n", // \u180E MONGOLIAN VOWEL SEPARATOR 2909d6f09afSAndreas Gohr "\xE2\x80\x8B\r\n", // \u200B ZERO WIDTH SPACE 2919d6f09afSAndreas Gohr "\xEF\xBB\xBF\r\n", // \uFEFF ZERO WIDTH NO-BREAK SPACE 2929d6f09afSAndreas Gohr ); 2939d6f09afSAndreas Gohr 2949d6f09afSAndreas Gohr $len = strlen($text); 2959d6f09afSAndreas Gohr for ($i = 0; $i < $len - 1; $i++) { 29639bbdaefSAndreas Gohr $new .= $text[$i]; 2979d6f09afSAndreas Gohr 29839bbdaefSAndreas Gohr if (!is_numeric($text[$i + 1])) { 2999d6f09afSAndreas Gohr $new .= $spaces[array_rand($spaces)]; 3009d6f09afSAndreas Gohr } 3019d6f09afSAndreas Gohr } 30239bbdaefSAndreas Gohr $new .= $text[$len - 1]; 3039d6f09afSAndreas Gohr return $new; 3049d6f09afSAndreas Gohr } 3059d6f09afSAndreas Gohr 3069d6f09afSAndreas Gohr /** 307a02b2219SPatrick Brown * Generate some numbers from a known string and random number 308a02b2219SPatrick Brown * 309a02b2219SPatrick Brown * @param $fixed string the fixed part, any string 310a02b2219SPatrick Brown * @param $rand float some random number between 0 and 1 311a02b2219SPatrick Brown * @return string 312a02b2219SPatrick Brown */ 313*18622736SAndreas Gohr protected function _generateNumbers($fixed, $rand) 314*18622736SAndreas Gohr { 315a02b2219SPatrick Brown $fixed = hexdec(substr(md5($fixed), 5, 5)); // use part of the md5 to generate an int 316a02b2219SPatrick Brown $rand = $rand * 0xFFFFF; // bitmask from the random number 317a02b2219SPatrick Brown return md5($rand ^ $fixed); // combine both values 318a02b2219SPatrick Brown } 319a02b2219SPatrick Brown 320a02b2219SPatrick Brown /** 32128c14643SAndreas Gohr * Generates a random char string 32277e00bf9SAndreas Gohr * 323104ec268SAndreas Gohr * @param $fixed string the fixed part, any string 324104ec268SAndreas Gohr * @param $rand float some random number between 0 and 1 32523d379a8SAndreas Gohr * @return string 32677e00bf9SAndreas Gohr */ 327*18622736SAndreas Gohr public function _generateCAPTCHA($fixed, $rand) 328*18622736SAndreas Gohr { 329a02b2219SPatrick Brown $numbers = $this->_generateNumbers($fixed, $rand); 33077e00bf9SAndreas Gohr 33177e00bf9SAndreas Gohr // now create the letters 33277e00bf9SAndreas Gohr $code = ''; 3339a516edaSPatrick Brown $lettercount = $this->getConf('lettercount') * 2; 3349a516edaSPatrick Brown if ($lettercount > strlen($numbers)) $lettercount = strlen($numbers); 3359a516edaSPatrick Brown for ($i = 0; $i < $lettercount; $i += 2) { 33677e00bf9SAndreas Gohr $code .= chr(floor(hexdec($numbers[$i] . $numbers[$i + 1]) / 10) + 65); 33777e00bf9SAndreas Gohr } 33877e00bf9SAndreas Gohr 33977e00bf9SAndreas Gohr return $code; 34077e00bf9SAndreas Gohr } 34177e00bf9SAndreas Gohr 3429e312724SAndreas Gohr /** 3439e312724SAndreas Gohr * Create a mathematical task and its result 3449e312724SAndreas Gohr * 345104ec268SAndreas Gohr * @param $fixed string the fixed part, any string 346104ec268SAndreas Gohr * @param $rand float some random number between 0 and 1 347104ec268SAndreas Gohr * @return array taks, result 3489e312724SAndreas Gohr */ 349*18622736SAndreas Gohr protected function _generateMATH($fixed, $rand) 350*18622736SAndreas Gohr { 351a02b2219SPatrick Brown $numbers = $this->_generateNumbers($fixed, $rand); 3529e312724SAndreas Gohr 3539e312724SAndreas Gohr // first letter is the operator (+/-) 3549e312724SAndreas Gohr $op = (hexdec($numbers[0]) > 8) ? -1 : 1; 3559e312724SAndreas Gohr $num = array(hexdec($numbers[1] . $numbers[2]), hexdec($numbers[3])); 3569e312724SAndreas Gohr 3579e312724SAndreas Gohr // we only want positive results 3589e312724SAndreas Gohr if (($op < 0) && ($num[0] < $num[1])) rsort($num); 3599e312724SAndreas Gohr 3609e312724SAndreas Gohr // prepare result and task text 3619e312724SAndreas Gohr $res = $num[0] + ($num[1] * $op); 3629bc1fab2SApostolos P. Tsompanopoulos $task = $num[0] . (($op < 0) ? '-' : '+') . $num[1] . '= '; 3639e312724SAndreas Gohr 3649e312724SAndreas Gohr return array($task, $res); 3659e312724SAndreas Gohr } 36677e00bf9SAndreas Gohr 36777e00bf9SAndreas Gohr /** 36877e00bf9SAndreas Gohr * Create a CAPTCHA image 369104ec268SAndreas Gohr * 370104ec268SAndreas Gohr * @param string $text the letters to display 37177e00bf9SAndreas Gohr */ 372*18622736SAndreas Gohr public function _imageCAPTCHA($text) 373*18622736SAndreas Gohr { 37477e00bf9SAndreas Gohr $w = $this->getConf('width'); 37577e00bf9SAndreas Gohr $h = $this->getConf('height'); 37677e00bf9SAndreas Gohr 377dc091fd0SAndreas Gohr $fonts = glob(dirname(__FILE__) . '/fonts/*.ttf'); 378dc091fd0SAndreas Gohr 37977e00bf9SAndreas Gohr // create a white image 38028c14643SAndreas Gohr $img = imagecreatetruecolor($w, $h); 38128c14643SAndreas Gohr $white = imagecolorallocate($img, 255, 255, 255); 38228c14643SAndreas Gohr imagefill($img, 0, 0, $white); 38377e00bf9SAndreas Gohr 38477e00bf9SAndreas Gohr // add some lines as background noise 38577e00bf9SAndreas Gohr for ($i = 0; $i < 30; $i++) { 38677e00bf9SAndreas Gohr $color = imagecolorallocate($img, rand(100, 250), rand(100, 250), rand(100, 250)); 38777e00bf9SAndreas Gohr imageline($img, rand(0, $w), rand(0, $h), rand(0, $w), rand(0, $h), $color); 38877e00bf9SAndreas Gohr } 38977e00bf9SAndreas Gohr 39077e00bf9SAndreas Gohr // draw the letters 39128c14643SAndreas Gohr $txtlen = strlen($text); 39228c14643SAndreas Gohr for ($i = 0; $i < $txtlen; $i++) { 393dc091fd0SAndreas Gohr $font = $fonts[array_rand($fonts)]; 39477e00bf9SAndreas Gohr $color = imagecolorallocate($img, rand(0, 100), rand(0, 100), rand(0, 100)); 39577e00bf9SAndreas Gohr $size = rand(floor($h / 1.8), floor($h * 0.7)); 39677e00bf9SAndreas Gohr $angle = rand(-35, 35); 39777e00bf9SAndreas Gohr 39828c14643SAndreas Gohr $x = ($w * 0.05) + $i * floor($w * 0.9 / $txtlen); 39977e00bf9SAndreas Gohr $cheight = $size + ($size * 0.5); 40077e00bf9SAndreas Gohr $y = floor($h / 2 + $cheight / 3.8); 40177e00bf9SAndreas Gohr 40277e00bf9SAndreas Gohr imagettftext($img, $size, $angle, $x, $y, $color, $font, $text[$i]); 40377e00bf9SAndreas Gohr } 40477e00bf9SAndreas Gohr 40577e00bf9SAndreas Gohr header("Content-type: image/png"); 40677e00bf9SAndreas Gohr imagepng($img); 40777e00bf9SAndreas Gohr imagedestroy($img); 40877e00bf9SAndreas Gohr } 40977e00bf9SAndreas Gohr 410f044313dSAndreas Gohr /** 41108f248e4SAndreas Gohr * Create an SVG of the given text 41208f248e4SAndreas Gohr * 41308f248e4SAndreas Gohr * @param string $text 41408f248e4SAndreas Gohr * @return string 41508f248e4SAndreas Gohr */ 416*18622736SAndreas Gohr public function _svgCAPTCHA($text) 417*18622736SAndreas Gohr { 41808f248e4SAndreas Gohr require_once(__DIR__ . '/EasySVG.php'); 41908f248e4SAndreas Gohr 42008f248e4SAndreas Gohr $fonts = glob(__DIR__ . '/fonts/*.svg'); 42108f248e4SAndreas Gohr 42208f248e4SAndreas Gohr $x = 0; // where we start to draw 42308f248e4SAndreas Gohr $y = 100; // our max height 42408f248e4SAndreas Gohr 42508f248e4SAndreas Gohr $svg = new EasySVG(); 42608f248e4SAndreas Gohr 42708f248e4SAndreas Gohr // draw the letters 42808f248e4SAndreas Gohr $txtlen = strlen($text); 42908f248e4SAndreas Gohr for ($i = 0; $i < $txtlen; $i++) { 43008f248e4SAndreas Gohr $char = $text[$i]; 43108f248e4SAndreas Gohr $size = rand($y / 2, $y - $y * 0.1); // 50-90% 43208f248e4SAndreas Gohr $svg->setFontSVG($fonts[array_rand($fonts)]); 43308f248e4SAndreas Gohr 43408f248e4SAndreas Gohr $svg->setFontSize($size); 43508f248e4SAndreas Gohr $svg->setLetterSpacing(round(rand(1, 4) / 10, 2)); // 0.1 - 0.4 43608f248e4SAndreas Gohr $svg->addText($char, $x, rand(0, round($y - $size))); // random up and down 43708f248e4SAndreas Gohr 43808f248e4SAndreas Gohr list($w) = $svg->textDimensions($char); 43908f248e4SAndreas Gohr $x += $w; 44008f248e4SAndreas Gohr } 44108f248e4SAndreas Gohr 44208f248e4SAndreas Gohr $svg->addAttribute('width', $x . 'px'); 44308f248e4SAndreas Gohr $svg->addAttribute('height', $y . 'px'); 44408f248e4SAndreas Gohr $svg->addAttribute('viewbox', "0 0 $x $y"); 44508f248e4SAndreas Gohr return $svg->asXML(); 44608f248e4SAndreas Gohr } 44708f248e4SAndreas Gohr 44808f248e4SAndreas Gohr /** 449f044313dSAndreas Gohr * Encrypt the given string with the cookie salt 450f044313dSAndreas Gohr * 451f044313dSAndreas Gohr * @param string $data 452f044313dSAndreas Gohr * @return string 453f044313dSAndreas Gohr */ 454*18622736SAndreas Gohr public function encrypt($data) 455*18622736SAndreas Gohr { 456f044313dSAndreas Gohr if (function_exists('auth_encrypt')) { 457f044313dSAndreas Gohr $data = auth_encrypt($data, auth_cookiesalt()); // since binky 458f044313dSAndreas Gohr } else { 459f044313dSAndreas Gohr $data = PMA_blowfish_encrypt($data, auth_cookiesalt()); // deprecated 460f044313dSAndreas Gohr } 461f044313dSAndreas Gohr 462f044313dSAndreas Gohr return base64_encode($data); 463f044313dSAndreas Gohr } 464f044313dSAndreas Gohr 465f044313dSAndreas Gohr /** 466f044313dSAndreas Gohr * Decrypt the given string with the cookie salt 467f044313dSAndreas Gohr * 468f044313dSAndreas Gohr * @param string $data 469f044313dSAndreas Gohr * @return string 470f044313dSAndreas Gohr */ 471*18622736SAndreas Gohr public function decrypt($data) 472*18622736SAndreas Gohr { 473f044313dSAndreas Gohr $data = base64_decode($data); 47409870f99SPatrick Brown if ($data === false || $data === '') return false; 475f044313dSAndreas Gohr 476f044313dSAndreas Gohr if (function_exists('auth_decrypt')) { 477f044313dSAndreas Gohr return auth_decrypt($data, auth_cookiesalt()); // since binky 478f044313dSAndreas Gohr } else { 479f044313dSAndreas Gohr return PMA_blowfish_decrypt($data, auth_cookiesalt()); // deprecated 480f044313dSAndreas Gohr } 481f044313dSAndreas Gohr } 48277e00bf9SAndreas Gohr} 483