xref: /plugin/captcha/helper.php (revision 186227361d0efe9f0e20bd52c87dfbf939efb53a)
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) . '&amp;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) . '&amp;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) . '&amp;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) . '&amp;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