xref: /plugin/captcha/helper.php (revision 63609b6ea4366bd9c8e1bff48a61b5e5cd514a21)
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) . '&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    /**
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