xref: /plugin/captcha/helper.php (revision 39bbdaefefb2b581b10c9e3e697e969de944ba7f)
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 */
1477e00bf9SAndreas Gohrclass helper_plugin_captcha extends DokuWiki_Plugin {
1577e00bf9SAndreas Gohr
1623d379a8SAndreas Gohr    protected $field_in = 'plugin__captcha';
1723d379a8SAndreas Gohr    protected $field_sec = 'plugin__captcha_secret';
1823d379a8SAndreas Gohr    protected $field_hp = 'plugin__captcha_honeypot';
1923d379a8SAndreas Gohr
2023d379a8SAndreas Gohr    /**
2123d379a8SAndreas Gohr     * Constructor. Initializes field names
2223d379a8SAndreas Gohr     */
23104ec268SAndreas Gohr    public function __construct() {
2423d379a8SAndreas Gohr        $this->field_in  = md5($this->_fixedIdent().$this->field_in);
2523d379a8SAndreas Gohr        $this->field_sec = md5($this->_fixedIdent().$this->field_sec);
2623d379a8SAndreas Gohr        $this->field_hp  = md5($this->_fixedIdent().$this->field_hp);
2723d379a8SAndreas Gohr    }
2823d379a8SAndreas Gohr
2977e00bf9SAndreas Gohr    /**
3077e00bf9SAndreas Gohr     * Check if the CAPTCHA should be used. Always check this before using the methods below.
3177e00bf9SAndreas Gohr     *
3277e00bf9SAndreas Gohr     * @return bool true when the CAPTCHA should be used
3377e00bf9SAndreas Gohr     */
34104ec268SAndreas Gohr    public function isEnabled() {
3577e00bf9SAndreas Gohr        if(!$this->getConf('forusers') && $_SERVER['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     */
42104ec268SAndreas Gohr    public function getHTML() {
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).'&amp;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).'&amp;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).'&amp;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).'&amp;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    /**
12377e00bf9SAndreas Gohr     * Checks if the 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     */
128104ec268SAndreas Gohr    public function check($msg = true) {
129478e363cSAndreas Gohr        global $INPUT;
130478e363cSAndreas Gohr
131478e363cSAndreas Gohr        $field_sec = $INPUT->str($this->field_sec);
132478e363cSAndreas Gohr        $field_in  = $INPUT->str($this->field_in);
133478e363cSAndreas Gohr        $field_hp  = $INPUT->str($this->field_hp);
134478e363cSAndreas Gohr
135478e363cSAndreas Gohr        // reconstruct captcha from provided $field_sec
136478e363cSAndreas Gohr        $rand = $this->decrypt($field_sec);
1379e312724SAndreas Gohr
1389e312724SAndreas Gohr        if($this->getConf('mode') == 'math') {
1399e312724SAndreas Gohr            $code = $this->_generateMATH($this->_fixedIdent(), $rand);
1409e312724SAndreas Gohr            $code = $code[1];
141df8afac4SAndreas Gohr        } elseif($this->getConf('mode') == 'question') {
142df8afac4SAndreas Gohr            $code = $this->getConf('answer');
1439e312724SAndreas Gohr        } else {
14477e00bf9SAndreas Gohr            $code = $this->_generateCAPTCHA($this->_fixedIdent(), $rand);
1459e312724SAndreas Gohr        }
14677e00bf9SAndreas Gohr
147478e363cSAndreas Gohr        // compare values
148478e363cSAndreas Gohr        if(!$field_sec ||
149478e363cSAndreas Gohr            !$field_in ||
15014e271ebSPatrick Brown            $rand === false ||
151478e363cSAndreas Gohr            utf8_strtolower($field_in) != utf8_strtolower($code) ||
152a285df67SAndreas Gohr            trim($field_hp) !== '' ||
153a285df67SAndreas Gohr            !$this->retrieveCaptchaCookie($this->_fixedIdent(), $rand)
15423d379a8SAndreas Gohr        ) {
15577e00bf9SAndreas Gohr            if($msg) msg($this->getLang('testfailed'), -1);
15677e00bf9SAndreas Gohr            return false;
15777e00bf9SAndreas Gohr        }
15877e00bf9SAndreas Gohr        return true;
15977e00bf9SAndreas Gohr    }
16077e00bf9SAndreas Gohr
16177e00bf9SAndreas Gohr    /**
162cde3ece1SAndreas Gohr     * Get the path where a captcha cookie would be stored
163a285df67SAndreas Gohr     *
164a285df67SAndreas Gohr     * We use a daily temp directory which is easy to clean up
165a285df67SAndreas Gohr     *
166a285df67SAndreas Gohr     * @param $fixed string the fixed part, any string
167a285df67SAndreas Gohr     * @param $rand  float  some random number between 0 and 1
168a285df67SAndreas Gohr     * @return string the path to the cookie file
169a285df67SAndreas Gohr     */
170a285df67SAndreas Gohr    protected function getCaptchaCookiePath($fixed, $rand) {
171a285df67SAndreas Gohr        global $conf;
172a285df67SAndreas Gohr        $path = $conf['tmpdir'] . '/captcha/' . date('Y-m-d') . '/' . md5($fixed . $rand) . '.cookie';
173a285df67SAndreas Gohr        io_makeFileDir($path);
174a285df67SAndreas Gohr        return $path;
175a285df67SAndreas Gohr    }
176a285df67SAndreas Gohr
177a285df67SAndreas Gohr    /**
178cde3ece1SAndreas Gohr     * remove all outdated captcha cookies
179cde3ece1SAndreas Gohr     */
180cde3ece1SAndreas Gohr    public function _cleanCaptchaCookies() {
181cde3ece1SAndreas Gohr        global $conf;
182cde3ece1SAndreas Gohr        $path = $conf['tmpdir'] . '/captcha/';
183cde3ece1SAndreas Gohr        $dirs = glob("$path/*", GLOB_ONLYDIR);
184cde3ece1SAndreas Gohr        $today = date('Y-m-d');
185cde3ece1SAndreas Gohr        foreach($dirs as $dir) {
186cde3ece1SAndreas Gohr            if(basename($dir) === $today) continue;
187cde3ece1SAndreas Gohr            if(!preg_match('/\/captcha\//', $dir)) continue; // safety net
188cde3ece1SAndreas Gohr            io_rmdir($dir, true);
189cde3ece1SAndreas Gohr        }
190cde3ece1SAndreas Gohr    }
191cde3ece1SAndreas Gohr
192cde3ece1SAndreas Gohr    /**
193a285df67SAndreas Gohr     * Creates a one time captcha cookie
194a285df67SAndreas Gohr     *
195a285df67SAndreas Gohr     * This is used to prevent replay attacks. It is generated when the captcha form
196a285df67SAndreas Gohr     * is shown and checked with the captcha check. Since we can not be sure about the
197a285df67SAndreas Gohr     * session state (might be closed or open) we're not using it.
198a285df67SAndreas Gohr     *
199a285df67SAndreas Gohr     * We're not using the stored values for displaying the captcha image (or audio)
200a285df67SAndreas Gohr     * but continue to use our encryption scheme. This way it's still possible to have
201a285df67SAndreas Gohr     * multiple captcha checks going on in parallel (eg. with multiple browser tabs)
202a285df67SAndreas Gohr     *
203a285df67SAndreas Gohr     * @param $fixed string the fixed part, any string
204a285df67SAndreas Gohr     * @param $rand  float  some random number between 0 and 1
205a285df67SAndreas Gohr     */
206a285df67SAndreas Gohr    protected function storeCaptchaCookie($fixed, $rand) {
207a285df67SAndreas Gohr        $cache = $this->getCaptchaCookiePath($fixed, $rand);
208a285df67SAndreas Gohr        touch($cache);
209a285df67SAndreas Gohr    }
210a285df67SAndreas Gohr
211a285df67SAndreas Gohr    /**
212a285df67SAndreas Gohr     * Checks if the captcha cookie exists and deletes it
213a285df67SAndreas Gohr     *
214a285df67SAndreas Gohr     * @param $fixed string the fixed part, any string
215a285df67SAndreas Gohr     * @param $rand  float  some random number between 0 and 1
216a285df67SAndreas Gohr     * @return bool true if the cookie existed
217a285df67SAndreas Gohr     */
218a285df67SAndreas Gohr    protected function retrieveCaptchaCookie($fixed, $rand) {
219a285df67SAndreas Gohr        $cache = $this->getCaptchaCookiePath($fixed, $rand);
220a285df67SAndreas Gohr        if(file_exists($cache)) {
221a285df67SAndreas Gohr            unlink($cache);
222a285df67SAndreas Gohr            return true;
223a285df67SAndreas Gohr        }
224a285df67SAndreas Gohr        return false;
225a285df67SAndreas Gohr    }
226a285df67SAndreas Gohr
227a285df67SAndreas Gohr    /**
22877e00bf9SAndreas Gohr     * Build a semi-secret fixed string identifying the current page and user
22977e00bf9SAndreas Gohr     *
23077e00bf9SAndreas Gohr     * This string is always the same for the current user when editing the same
23127d84d8dSAndreas Gohr     * page revision, but only for one day. Editing a page before midnight and saving
23227d84d8dSAndreas Gohr     * after midnight will result in a failed CAPTCHA once, but makes sure it can
23327d84d8dSAndreas Gohr     * not be reused which is especially important for the registration form where the
23427d84d8dSAndreas Gohr     * $ID usually won't change.
235104ec268SAndreas Gohr     *
236104ec268SAndreas Gohr     * @return string
23777e00bf9SAndreas Gohr     */
238104ec268SAndreas Gohr    public function _fixedIdent() {
23977e00bf9SAndreas Gohr        global $ID;
24077e00bf9SAndreas Gohr        $lm = @filemtime(wikiFN($ID));
24127d84d8dSAndreas Gohr        $td = date('Y-m-d');
24277e00bf9SAndreas Gohr        return auth_browseruid() .
24377e00bf9SAndreas Gohr            auth_cookiesalt() .
24427d84d8dSAndreas Gohr            $ID . $lm . $td;
24577e00bf9SAndreas Gohr    }
24677e00bf9SAndreas Gohr
24777e00bf9SAndreas Gohr    /**
2489d6f09afSAndreas Gohr     * Adds random space characters within the given text
2499d6f09afSAndreas Gohr     *
2509d6f09afSAndreas Gohr     * Keeps subsequent numbers without spaces (for math problem)
2519d6f09afSAndreas Gohr     *
2529d6f09afSAndreas Gohr     * @param $text
2539d6f09afSAndreas Gohr     * @return string
2549d6f09afSAndreas Gohr     */
2559d6f09afSAndreas Gohr    protected function _obfuscateText($text) {
2569d6f09afSAndreas Gohr        $new = '';
2579d6f09afSAndreas Gohr
2589d6f09afSAndreas Gohr        $spaces = array(
2599d6f09afSAndreas Gohr            "\r",
2609d6f09afSAndreas Gohr            "\n",
2619d6f09afSAndreas Gohr            "\r\n",
2629d6f09afSAndreas Gohr            ' ',
2639d6f09afSAndreas Gohr            "\xC2\xA0", // \u00A0    NO-BREAK SPACE
2649d6f09afSAndreas Gohr            "\xE2\x80\x80", // \u2000    EN QUAD
2659d6f09afSAndreas Gohr            "\xE2\x80\x81", // \u2001    EM QUAD
2669d6f09afSAndreas Gohr            "\xE2\x80\x82", // \u2002    EN SPACE
2679d6f09afSAndreas Gohr            //         "\xE2\x80\x83", // \u2003    EM SPACE
2689d6f09afSAndreas Gohr            "\xE2\x80\x84", // \u2004    THREE-PER-EM SPACE
2699d6f09afSAndreas Gohr            "\xE2\x80\x85", // \u2005    FOUR-PER-EM SPACE
2709d6f09afSAndreas Gohr            "\xE2\x80\x86", // \u2006    SIX-PER-EM SPACE
2719d6f09afSAndreas Gohr            "\xE2\x80\x87", // \u2007    FIGURE SPACE
2729d6f09afSAndreas Gohr            "\xE2\x80\x88", // \u2008    PUNCTUATION SPACE
2739d6f09afSAndreas Gohr            "\xE2\x80\x89", // \u2009    THIN SPACE
2749d6f09afSAndreas Gohr            "\xE2\x80\x8A", // \u200A    HAIR SPACE
2759d6f09afSAndreas Gohr            "\xE2\x80\xAF", // \u202F    NARROW NO-BREAK SPACE
2769d6f09afSAndreas Gohr            "\xE2\x81\x9F", // \u205F    MEDIUM MATHEMATICAL SPACE
2779d6f09afSAndreas Gohr
2789d6f09afSAndreas Gohr            "\xE1\xA0\x8E\r\n", // \u180E    MONGOLIAN VOWEL SEPARATOR
2799d6f09afSAndreas Gohr            "\xE2\x80\x8B\r\n", // \u200B    ZERO WIDTH SPACE
2809d6f09afSAndreas Gohr            "\xEF\xBB\xBF\r\n", // \uFEFF    ZERO WIDTH NO-BREAK SPACE
2819d6f09afSAndreas Gohr        );
2829d6f09afSAndreas Gohr
2839d6f09afSAndreas Gohr        $len = strlen($text);
2849d6f09afSAndreas Gohr        for($i = 0; $i < $len - 1; $i++) {
285*39bbdaefSAndreas Gohr            $new .= $text[$i];
2869d6f09afSAndreas Gohr
287*39bbdaefSAndreas Gohr            if(!is_numeric($text[$i + 1])) {
2889d6f09afSAndreas Gohr                $new .= $spaces[array_rand($spaces)];
2899d6f09afSAndreas Gohr            }
2909d6f09afSAndreas Gohr        }
291*39bbdaefSAndreas Gohr        $new .= $text[$len - 1];
2929d6f09afSAndreas Gohr        return $new;
2939d6f09afSAndreas Gohr    }
2949d6f09afSAndreas Gohr
2959d6f09afSAndreas Gohr    /**
296a02b2219SPatrick Brown     * Generate some numbers from a known string and random number
297a02b2219SPatrick Brown     *
298a02b2219SPatrick Brown     * @param $fixed string the fixed part, any string
299a02b2219SPatrick Brown     * @param $rand  float  some random number between 0 and 1
300a02b2219SPatrick Brown     * @return string
301a02b2219SPatrick Brown     */
3023ee37481SAndreas Gohr    protected function _generateNumbers($fixed, $rand) {
303a02b2219SPatrick Brown        $fixed   = hexdec(substr(md5($fixed), 5, 5)); // use part of the md5 to generate an int
304a02b2219SPatrick Brown        $rand = $rand * 0xFFFFF; // bitmask from the random number
305a02b2219SPatrick Brown        return md5($rand ^ $fixed); // combine both values
306a02b2219SPatrick Brown    }
307a02b2219SPatrick Brown
308a02b2219SPatrick Brown    /**
30928c14643SAndreas Gohr     * Generates a random char string
31077e00bf9SAndreas Gohr     *
311104ec268SAndreas Gohr     * @param $fixed string the fixed part, any string
312104ec268SAndreas Gohr     * @param $rand  float  some random number between 0 and 1
31323d379a8SAndreas Gohr     * @return string
31477e00bf9SAndreas Gohr     */
315104ec268SAndreas Gohr    public function _generateCAPTCHA($fixed, $rand) {
316a02b2219SPatrick Brown        $numbers = $this->_generateNumbers($fixed, $rand);
31777e00bf9SAndreas Gohr
31877e00bf9SAndreas Gohr        // now create the letters
31977e00bf9SAndreas Gohr        $code = '';
3209a516edaSPatrick Brown        $lettercount = $this->getConf('lettercount') * 2;
3219a516edaSPatrick Brown        if($lettercount > strlen($numbers)) $lettercount = strlen($numbers);
3229a516edaSPatrick Brown        for($i = 0; $i < $lettercount; $i += 2) {
32377e00bf9SAndreas Gohr            $code .= chr(floor(hexdec($numbers[$i].$numbers[$i + 1]) / 10) + 65);
32477e00bf9SAndreas Gohr        }
32577e00bf9SAndreas Gohr
32677e00bf9SAndreas Gohr        return $code;
32777e00bf9SAndreas Gohr    }
32877e00bf9SAndreas Gohr
3299e312724SAndreas Gohr    /**
3309e312724SAndreas Gohr     * Create a mathematical task and its result
3319e312724SAndreas Gohr     *
332104ec268SAndreas Gohr     * @param $fixed string the fixed part, any string
333104ec268SAndreas Gohr     * @param $rand  float  some random number between 0 and 1
334104ec268SAndreas Gohr     * @return array taks, result
3359e312724SAndreas Gohr     */
336104ec268SAndreas Gohr    protected function _generateMATH($fixed, $rand) {
337a02b2219SPatrick Brown        $numbers = $this->_generateNumbers($fixed, $rand);
3389e312724SAndreas Gohr
3399e312724SAndreas Gohr        // first letter is the operator (+/-)
3409e312724SAndreas Gohr        $op  = (hexdec($numbers[0]) > 8) ? -1 : 1;
3419e312724SAndreas Gohr        $num = array(hexdec($numbers[1].$numbers[2]), hexdec($numbers[3]));
3429e312724SAndreas Gohr
3439e312724SAndreas Gohr        // we only want positive results
3449e312724SAndreas Gohr        if(($op < 0) && ($num[0] < $num[1])) rsort($num);
3459e312724SAndreas Gohr
3469e312724SAndreas Gohr        // prepare result and task text
3479e312724SAndreas Gohr        $res  = $num[0] + ($num[1] * $op);
3489bc1fab2SApostolos P. Tsompanopoulos        $task = $num[0].(($op < 0) ? '-' : '+').$num[1].'= ';
3499e312724SAndreas Gohr
3509e312724SAndreas Gohr        return array($task, $res);
3519e312724SAndreas Gohr    }
35277e00bf9SAndreas Gohr
35377e00bf9SAndreas Gohr    /**
35477e00bf9SAndreas Gohr     * Create a CAPTCHA image
355104ec268SAndreas Gohr     *
356104ec268SAndreas Gohr     * @param string $text the letters to display
35777e00bf9SAndreas Gohr     */
358104ec268SAndreas Gohr    public function _imageCAPTCHA($text) {
35977e00bf9SAndreas Gohr        $w = $this->getConf('width');
36077e00bf9SAndreas Gohr        $h = $this->getConf('height');
36177e00bf9SAndreas Gohr
362dc091fd0SAndreas Gohr        $fonts = glob(dirname(__FILE__).'/fonts/*.ttf');
363dc091fd0SAndreas Gohr
36477e00bf9SAndreas Gohr        // create a white image
36528c14643SAndreas Gohr        $img   = imagecreatetruecolor($w, $h);
36628c14643SAndreas Gohr        $white = imagecolorallocate($img, 255, 255, 255);
36728c14643SAndreas Gohr        imagefill($img, 0, 0, $white);
36877e00bf9SAndreas Gohr
36977e00bf9SAndreas Gohr        // add some lines as background noise
37077e00bf9SAndreas Gohr        for($i = 0; $i < 30; $i++) {
37177e00bf9SAndreas Gohr            $color = imagecolorallocate($img, rand(100, 250), rand(100, 250), rand(100, 250));
37277e00bf9SAndreas Gohr            imageline($img, rand(0, $w), rand(0, $h), rand(0, $w), rand(0, $h), $color);
37377e00bf9SAndreas Gohr        }
37477e00bf9SAndreas Gohr
37577e00bf9SAndreas Gohr        // draw the letters
37628c14643SAndreas Gohr        $txtlen = strlen($text);
37728c14643SAndreas Gohr        for($i = 0; $i < $txtlen; $i++) {
378dc091fd0SAndreas Gohr            $font  = $fonts[array_rand($fonts)];
37977e00bf9SAndreas Gohr            $color = imagecolorallocate($img, rand(0, 100), rand(0, 100), rand(0, 100));
38077e00bf9SAndreas Gohr            $size  = rand(floor($h / 1.8), floor($h * 0.7));
38177e00bf9SAndreas Gohr            $angle = rand(-35, 35);
38277e00bf9SAndreas Gohr
38328c14643SAndreas Gohr            $x       = ($w * 0.05) + $i * floor($w * 0.9 / $txtlen);
38477e00bf9SAndreas Gohr            $cheight = $size + ($size * 0.5);
38577e00bf9SAndreas Gohr            $y       = floor($h / 2 + $cheight / 3.8);
38677e00bf9SAndreas Gohr
38777e00bf9SAndreas Gohr            imagettftext($img, $size, $angle, $x, $y, $color, $font, $text[$i]);
38877e00bf9SAndreas Gohr        }
38977e00bf9SAndreas Gohr
39077e00bf9SAndreas Gohr        header("Content-type: image/png");
39177e00bf9SAndreas Gohr        imagepng($img);
39277e00bf9SAndreas Gohr        imagedestroy($img);
39377e00bf9SAndreas Gohr    }
39477e00bf9SAndreas Gohr
395f044313dSAndreas Gohr    /**
39608f248e4SAndreas Gohr     * Create an SVG of the given text
39708f248e4SAndreas Gohr     *
39808f248e4SAndreas Gohr     * @param string $text
39908f248e4SAndreas Gohr     * @return string
40008f248e4SAndreas Gohr     */
40108f248e4SAndreas Gohr    public function _svgCAPTCHA($text) {
40208f248e4SAndreas Gohr        require_once(__DIR__ . '/EasySVG.php');
40308f248e4SAndreas Gohr
40408f248e4SAndreas Gohr        $fonts = glob(__DIR__ . '/fonts/*.svg');
40508f248e4SAndreas Gohr
40608f248e4SAndreas Gohr        $x = 0; // where we start to draw
40708f248e4SAndreas Gohr        $y = 100; // our max height
40808f248e4SAndreas Gohr
40908f248e4SAndreas Gohr        $svg = new EasySVG();
41008f248e4SAndreas Gohr
41108f248e4SAndreas Gohr        // draw the letters
41208f248e4SAndreas Gohr        $txtlen = strlen($text);
41308f248e4SAndreas Gohr        for($i = 0; $i < $txtlen; $i++) {
41408f248e4SAndreas Gohr            $char = $text[$i];
41508f248e4SAndreas Gohr            $size = rand($y / 2, $y - $y * 0.1); // 50-90%
41608f248e4SAndreas Gohr            $svg->setFontSVG($fonts[array_rand($fonts)]);
41708f248e4SAndreas Gohr
41808f248e4SAndreas Gohr            $svg->setFontSize($size);
41908f248e4SAndreas Gohr            $svg->setLetterSpacing(round(rand(1, 4) / 10, 2)); // 0.1 - 0.4
42008f248e4SAndreas Gohr            $svg->addText($char, $x, rand(0, round($y - $size))); // random up and down
42108f248e4SAndreas Gohr
42208f248e4SAndreas Gohr            list($w) = $svg->textDimensions($char);
42308f248e4SAndreas Gohr            $x += $w;
42408f248e4SAndreas Gohr        }
42508f248e4SAndreas Gohr
42608f248e4SAndreas Gohr        $svg->addAttribute('width', $x . 'px');
42708f248e4SAndreas Gohr        $svg->addAttribute('height', $y . 'px');
42808f248e4SAndreas Gohr        $svg->addAttribute('viewbox', "0 0 $x $y");
42908f248e4SAndreas Gohr        return $svg->asXML();
43008f248e4SAndreas Gohr    }
43108f248e4SAndreas Gohr
43208f248e4SAndreas Gohr    /**
433f044313dSAndreas Gohr     * Encrypt the given string with the cookie salt
434f044313dSAndreas Gohr     *
435f044313dSAndreas Gohr     * @param string $data
436f044313dSAndreas Gohr     * @return string
437f044313dSAndreas Gohr     */
438f044313dSAndreas Gohr    public function encrypt($data) {
439f044313dSAndreas Gohr        if(function_exists('auth_encrypt')) {
440f044313dSAndreas Gohr            $data = auth_encrypt($data, auth_cookiesalt()); // since binky
441f044313dSAndreas Gohr        } else {
442f044313dSAndreas Gohr            $data = PMA_blowfish_encrypt($data, auth_cookiesalt()); // deprecated
443f044313dSAndreas Gohr        }
444f044313dSAndreas Gohr
445f044313dSAndreas Gohr        return base64_encode($data);
446f044313dSAndreas Gohr    }
447f044313dSAndreas Gohr
448f044313dSAndreas Gohr    /**
449f044313dSAndreas Gohr     * Decrypt the given string with the cookie salt
450f044313dSAndreas Gohr     *
451f044313dSAndreas Gohr     * @param string $data
452f044313dSAndreas Gohr     * @return string
453f044313dSAndreas Gohr     */
454f044313dSAndreas Gohr    public function decrypt($data) {
455f044313dSAndreas Gohr        $data = base64_decode($data);
45609870f99SPatrick Brown        if($data === false || $data === '') return false;
457f044313dSAndreas Gohr
458f044313dSAndreas Gohr        if(function_exists('auth_decrypt')) {
459f044313dSAndreas Gohr            return auth_decrypt($data, auth_cookiesalt()); // since binky
460f044313dSAndreas Gohr        } else {
461f044313dSAndreas Gohr            return PMA_blowfish_decrypt($data, auth_cookiesalt()); // deprecated
462f044313dSAndreas Gohr        }
463f044313dSAndreas Gohr    }
46477e00bf9SAndreas Gohr}
465