xref: /plugin/captcha/helper.php (revision 64382f292cb5c1c7154e8fd095ef7180282ebf84)
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    {
34*64382f29SAndreas Gohr        global $INPUT;
35*64382f29SAndreas Gohr        if (!$this->getConf('forusers') && $INPUT->server->str('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     */
4218622736SAndreas Gohr    public function getHTML()
4318622736SAndreas Gohr    {
4477e00bf9SAndreas Gohr        global $ID;
4577e00bf9SAndreas Gohr
4677e00bf9SAndreas Gohr        $rand = (float)(rand(0, 10000)) / 10000;
47a285df67SAndreas Gohr        $this->storeCaptchaCookie($this->_fixedIdent(), $rand);
48a285df67SAndreas Gohr
499e312724SAndreas Gohr        if ($this->getConf('mode') == 'math') {
509e312724SAndreas Gohr            $code = $this->_generateMATH($this->_fixedIdent(), $rand);
519e312724SAndreas Gohr            $code = $code[0];
529e312724SAndreas Gohr            $text = $this->getLang('fillmath');
53df8afac4SAndreas Gohr        } elseif ($this->getConf('mode') == 'question') {
54a285df67SAndreas Gohr            $code = ''; // not used
55df8afac4SAndreas Gohr            $text = $this->getConf('question');
569e312724SAndreas Gohr        } else {
5777e00bf9SAndreas Gohr            $code = $this->_generateCAPTCHA($this->_fixedIdent(), $rand);
589e312724SAndreas Gohr            $text = $this->getLang('fillcaptcha');
599e312724SAndreas Gohr        }
60f044313dSAndreas Gohr        $secret = $this->encrypt($rand);
6177e00bf9SAndreas Gohr
6228c14643SAndreas Gohr        $txtlen = $this->getConf('lettercount');
6328c14643SAndreas Gohr
6477e00bf9SAndreas Gohr        $out = '';
6577e00bf9SAndreas Gohr        $out .= '<div id="plugin__captcha_wrapper">';
6623d379a8SAndreas Gohr        $out .= '<input type="hidden" name="' . $this->field_sec . '" value="' . hsc($secret) . '" />';
679e312724SAndreas Gohr        $out .= '<label for="plugin__captcha">' . $text . '</label> ';
6823d379a8SAndreas Gohr
6977e00bf9SAndreas Gohr        switch ($this->getConf('mode')) {
709e312724SAndreas Gohr            case 'math':
719d6f09afSAndreas Gohr            case 'text':
729d6f09afSAndreas Gohr                $out .= $this->_obfuscateText($code);
7377e00bf9SAndreas Gohr                break;
7477e00bf9SAndreas Gohr            case 'js':
759d6f09afSAndreas Gohr                $out .= '<span id="plugin__captcha_code">' . $this->_obfuscateText($code) . '</span>';
7677e00bf9SAndreas Gohr                break;
7708f248e4SAndreas Gohr            case 'svg':
7808f248e4SAndreas Gohr                $out .= '<span class="svg" style="width:' . $this->getConf('width') . 'px; height:' . $this->getConf('height') . 'px">';
7908f248e4SAndreas Gohr                $out .= $this->_svgCAPTCHA($code);
8008f248e4SAndreas Gohr                $out .= '</span>';
8108f248e4SAndreas Gohr                break;
8208f248e4SAndreas Gohr            case 'svgaudio':
8308f248e4SAndreas Gohr                $out .= '<span class="svg" style="width:' . $this->getConf('width') . 'px; height:' . $this->getConf('height') . 'px">';
8408f248e4SAndreas Gohr                $out .= $this->_svgCAPTCHA($code);
8508f248e4SAndreas Gohr                $out .= '</span>';
8608f248e4SAndreas Gohr                $out .= '<a href="' . DOKU_BASE . 'lib/plugins/captcha/wav.php?secret=' . rawurlencode($secret) . '&amp;id=' . $ID . '"' .
879efb703bSStefan Bethke                    ' class="JSnocheck audiolink" title="' . $this->getLang('soundlink') . '">';
8808f248e4SAndreas Gohr                $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/sound.png" width="16" height="16"' .
8908f248e4SAndreas Gohr                    ' alt="' . $this->getLang('soundlink') . '" /></a>';
9008f248e4SAndreas Gohr                break;
9177e00bf9SAndreas Gohr            case 'image':
9277e00bf9SAndreas Gohr                $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/img.php?secret=' . rawurlencode($secret) . '&amp;id=' . $ID . '" ' .
9377e00bf9SAndreas Gohr                    ' width="' . $this->getConf('width') . '" height="' . $this->getConf('height') . '" alt="" /> ';
9477e00bf9SAndreas Gohr                break;
9577e00bf9SAndreas Gohr            case 'audio':
9677e00bf9SAndreas Gohr                $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/img.php?secret=' . rawurlencode($secret) . '&amp;id=' . $ID . '" ' .
9777e00bf9SAndreas Gohr                    ' width="' . $this->getConf('width') . '" height="' . $this->getConf('height') . '" alt="" /> ';
9877e00bf9SAndreas Gohr                $out .= '<a href="' . DOKU_BASE . 'lib/plugins/captcha/wav.php?secret=' . rawurlencode($secret) . '&amp;id=' . $ID . '"' .
999efb703bSStefan Bethke                    ' class="JSnocheck audiolink" title="' . $this->getLang('soundlink') . '">';
10077e00bf9SAndreas Gohr                $out .= '<img src="' . DOKU_BASE . 'lib/plugins/captcha/sound.png" width="16" height="16"' .
10177e00bf9SAndreas Gohr                    ' alt="' . $this->getLang('soundlink') . '" /></a>';
10277e00bf9SAndreas Gohr                break;
10352e95008SAndreas Gohr            case 'figlet':
10452e95008SAndreas Gohr                require_once(dirname(__FILE__) . '/figlet.php');
10552e95008SAndreas Gohr                $figlet = new phpFiglet();
10652e95008SAndreas Gohr                if ($figlet->loadfont(dirname(__FILE__) . '/figlet.flf')) {
10752e95008SAndreas Gohr                    $out .= '<pre>';
10852e95008SAndreas Gohr                    $out .= rtrim($figlet->fetch($code));
10952e95008SAndreas Gohr                    $out .= '</pre>';
11052e95008SAndreas Gohr                } else {
11152e95008SAndreas Gohr                    msg('Failed to load figlet.flf font file. CAPTCHA broken', -1);
11252e95008SAndreas Gohr                }
11352e95008SAndreas Gohr                break;
11477e00bf9SAndreas Gohr        }
115df8afac4SAndreas Gohr        $out .= ' <input type="text" size="' . $txtlen . '" name="' . $this->field_in . '" class="edit" /> ';
11623d379a8SAndreas Gohr
11723d379a8SAndreas Gohr        // add honeypot field
1189d63c05fSlainme        $out .= '<label class="no">' . $this->getLang('honeypot') . '<input type="text" name="' . $this->field_hp . '" /></label>';
11977e00bf9SAndreas Gohr        $out .= '</div>';
12077e00bf9SAndreas Gohr        return $out;
12177e00bf9SAndreas Gohr    }
12277e00bf9SAndreas Gohr
12377e00bf9SAndreas Gohr    /**
12418622736SAndreas Gohr     * Checks if the CAPTCHA was solved correctly
12577e00bf9SAndreas Gohr     *
12677e00bf9SAndreas Gohr     * @param bool $msg when true, an error will be signalled through the msg() method
12777e00bf9SAndreas Gohr     * @return bool true when the answer was correct, otherwise false
12877e00bf9SAndreas Gohr     */
12918622736SAndreas Gohr    public function check($msg = true)
13018622736SAndreas Gohr    {
131478e363cSAndreas Gohr        global $INPUT;
132478e363cSAndreas Gohr
133478e363cSAndreas Gohr        $field_sec = $INPUT->str($this->field_sec);
134478e363cSAndreas Gohr        $field_in = $INPUT->str($this->field_in);
135478e363cSAndreas Gohr        $field_hp = $INPUT->str($this->field_hp);
136478e363cSAndreas Gohr
137478e363cSAndreas Gohr        // reconstruct captcha from provided $field_sec
138478e363cSAndreas Gohr        $rand = $this->decrypt($field_sec);
1399e312724SAndreas Gohr
1409e312724SAndreas Gohr        if ($this->getConf('mode') == 'math') {
1419e312724SAndreas Gohr            $code = $this->_generateMATH($this->_fixedIdent(), $rand);
1429e312724SAndreas Gohr            $code = $code[1];
143df8afac4SAndreas Gohr        } elseif ($this->getConf('mode') == 'question') {
144df8afac4SAndreas Gohr            $code = $this->getConf('answer');
1459e312724SAndreas Gohr        } else {
14677e00bf9SAndreas Gohr            $code = $this->_generateCAPTCHA($this->_fixedIdent(), $rand);
1479e312724SAndreas Gohr        }
14877e00bf9SAndreas Gohr
149478e363cSAndreas Gohr        // compare values
150478e363cSAndreas Gohr        if (!$field_sec ||
151478e363cSAndreas Gohr            !$field_in ||
15214e271ebSPatrick Brown            $rand === false ||
153478e363cSAndreas Gohr            utf8_strtolower($field_in) != utf8_strtolower($code) ||
154a285df67SAndreas Gohr            trim($field_hp) !== '' ||
155a285df67SAndreas Gohr            !$this->retrieveCaptchaCookie($this->_fixedIdent(), $rand)
15623d379a8SAndreas Gohr        ) {
15777e00bf9SAndreas Gohr            if ($msg) msg($this->getLang('testfailed'), -1);
15877e00bf9SAndreas Gohr            return false;
15977e00bf9SAndreas Gohr        }
16077e00bf9SAndreas Gohr        return true;
16177e00bf9SAndreas Gohr    }
16277e00bf9SAndreas Gohr
16377e00bf9SAndreas Gohr    /**
164cde3ece1SAndreas Gohr     * Get the path where a captcha cookie would be stored
165a285df67SAndreas Gohr     *
166a285df67SAndreas Gohr     * We use a daily temp directory which is easy to clean up
167a285df67SAndreas Gohr     *
168a285df67SAndreas Gohr     * @param $fixed string the fixed part, any string
169a285df67SAndreas Gohr     * @param $rand  float  some random number between 0 and 1
170a285df67SAndreas Gohr     * @return string the path to the cookie file
171a285df67SAndreas Gohr     */
17218622736SAndreas Gohr    protected function getCaptchaCookiePath($fixed, $rand)
17318622736SAndreas Gohr    {
174a285df67SAndreas Gohr        global $conf;
175a285df67SAndreas Gohr        $path = $conf['tmpdir'] . '/captcha/' . date('Y-m-d') . '/' . md5($fixed . $rand) . '.cookie';
176a285df67SAndreas Gohr        io_makeFileDir($path);
177a285df67SAndreas Gohr        return $path;
178a285df67SAndreas Gohr    }
179a285df67SAndreas Gohr
180a285df67SAndreas Gohr    /**
181cde3ece1SAndreas Gohr     * remove all outdated captcha cookies
182cde3ece1SAndreas Gohr     */
18318622736SAndreas Gohr    public function _cleanCaptchaCookies()
18418622736SAndreas Gohr    {
185cde3ece1SAndreas Gohr        global $conf;
186cde3ece1SAndreas Gohr        $path = $conf['tmpdir'] . '/captcha/';
187cde3ece1SAndreas Gohr        $dirs = glob("$path/*", GLOB_ONLYDIR);
188cde3ece1SAndreas Gohr        $today = date('Y-m-d');
189cde3ece1SAndreas Gohr        foreach ($dirs as $dir) {
190cde3ece1SAndreas Gohr            if (basename($dir) === $today) continue;
191cde3ece1SAndreas Gohr            if (!preg_match('/\/captcha\//', $dir)) continue; // safety net
192cde3ece1SAndreas Gohr            io_rmdir($dir, true);
193cde3ece1SAndreas Gohr        }
194cde3ece1SAndreas Gohr    }
195cde3ece1SAndreas Gohr
196cde3ece1SAndreas Gohr    /**
197a285df67SAndreas Gohr     * Creates a one time captcha cookie
198a285df67SAndreas Gohr     *
199a285df67SAndreas Gohr     * This is used to prevent replay attacks. It is generated when the captcha form
200a285df67SAndreas Gohr     * is shown and checked with the captcha check. Since we can not be sure about the
201a285df67SAndreas Gohr     * session state (might be closed or open) we're not using it.
202a285df67SAndreas Gohr     *
203a285df67SAndreas Gohr     * We're not using the stored values for displaying the captcha image (or audio)
204a285df67SAndreas Gohr     * but continue to use our encryption scheme. This way it's still possible to have
205a285df67SAndreas Gohr     * multiple captcha checks going on in parallel (eg. with multiple browser tabs)
206a285df67SAndreas Gohr     *
207a285df67SAndreas Gohr     * @param $fixed string the fixed part, any string
208a285df67SAndreas Gohr     * @param $rand  float  some random number between 0 and 1
209a285df67SAndreas Gohr     */
21018622736SAndreas Gohr    protected function storeCaptchaCookie($fixed, $rand)
21118622736SAndreas Gohr    {
212a285df67SAndreas Gohr        $cache = $this->getCaptchaCookiePath($fixed, $rand);
213a285df67SAndreas Gohr        touch($cache);
214a285df67SAndreas Gohr    }
215a285df67SAndreas Gohr
216a285df67SAndreas Gohr    /**
217a285df67SAndreas Gohr     * Checks if the captcha cookie exists and deletes it
218a285df67SAndreas Gohr     *
219a285df67SAndreas Gohr     * @param $fixed string the fixed part, any string
220a285df67SAndreas Gohr     * @param $rand  float  some random number between 0 and 1
221a285df67SAndreas Gohr     * @return bool true if the cookie existed
222a285df67SAndreas Gohr     */
22318622736SAndreas Gohr    protected function retrieveCaptchaCookie($fixed, $rand)
22418622736SAndreas Gohr    {
225a285df67SAndreas Gohr        $cache = $this->getCaptchaCookiePath($fixed, $rand);
226a285df67SAndreas Gohr        if (file_exists($cache)) {
227a285df67SAndreas Gohr            unlink($cache);
228a285df67SAndreas Gohr            return true;
229a285df67SAndreas Gohr        }
230a285df67SAndreas Gohr        return false;
231a285df67SAndreas Gohr    }
232a285df67SAndreas Gohr
233a285df67SAndreas Gohr    /**
23477e00bf9SAndreas Gohr     * Build a semi-secret fixed string identifying the current page and user
23577e00bf9SAndreas Gohr     *
23677e00bf9SAndreas Gohr     * This string is always the same for the current user when editing the same
23727d84d8dSAndreas Gohr     * page revision, but only for one day. Editing a page before midnight and saving
23827d84d8dSAndreas Gohr     * after midnight will result in a failed CAPTCHA once, but makes sure it can
23927d84d8dSAndreas Gohr     * not be reused which is especially important for the registration form where the
24027d84d8dSAndreas Gohr     * $ID usually won't change.
241104ec268SAndreas Gohr     *
242104ec268SAndreas Gohr     * @return string
24377e00bf9SAndreas Gohr     */
24418622736SAndreas Gohr    public function _fixedIdent()
24518622736SAndreas Gohr    {
24677e00bf9SAndreas Gohr        global $ID;
24777e00bf9SAndreas Gohr        $lm = @filemtime(wikiFN($ID));
24827d84d8dSAndreas Gohr        $td = date('Y-m-d');
24963609b6eSAndreas Gohr        $ip = clientIP();
25063609b6eSAndreas Gohr        $salt = auth_cookiesalt();
25163609b6eSAndreas Gohr
25263609b6eSAndreas Gohr        return sha1(join("\n", [$ID, $lm, $td, $ip, $salt]));
25377e00bf9SAndreas Gohr    }
25477e00bf9SAndreas Gohr
25577e00bf9SAndreas Gohr    /**
2569d6f09afSAndreas Gohr     * Adds random space characters within the given text
2579d6f09afSAndreas Gohr     *
2589d6f09afSAndreas Gohr     * Keeps subsequent numbers without spaces (for math problem)
2599d6f09afSAndreas Gohr     *
2609d6f09afSAndreas Gohr     * @param $text
2619d6f09afSAndreas Gohr     * @return string
2629d6f09afSAndreas Gohr     */
26318622736SAndreas Gohr    protected function _obfuscateText($text)
26418622736SAndreas Gohr    {
2659d6f09afSAndreas Gohr        $new = '';
2669d6f09afSAndreas Gohr
2679d6f09afSAndreas Gohr        $spaces = array(
2689d6f09afSAndreas Gohr            "\r",
2699d6f09afSAndreas Gohr            "\n",
2709d6f09afSAndreas Gohr            "\r\n",
2719d6f09afSAndreas Gohr            ' ',
2729d6f09afSAndreas Gohr            "\xC2\xA0", // \u00A0    NO-BREAK SPACE
2739d6f09afSAndreas Gohr            "\xE2\x80\x80", // \u2000    EN QUAD
2749d6f09afSAndreas Gohr            "\xE2\x80\x81", // \u2001    EM QUAD
2759d6f09afSAndreas Gohr            "\xE2\x80\x82", // \u2002    EN SPACE
2769d6f09afSAndreas Gohr            //         "\xE2\x80\x83", // \u2003    EM SPACE
2779d6f09afSAndreas Gohr            "\xE2\x80\x84", // \u2004    THREE-PER-EM SPACE
2789d6f09afSAndreas Gohr            "\xE2\x80\x85", // \u2005    FOUR-PER-EM SPACE
2799d6f09afSAndreas Gohr            "\xE2\x80\x86", // \u2006    SIX-PER-EM SPACE
2809d6f09afSAndreas Gohr            "\xE2\x80\x87", // \u2007    FIGURE SPACE
2819d6f09afSAndreas Gohr            "\xE2\x80\x88", // \u2008    PUNCTUATION SPACE
2829d6f09afSAndreas Gohr            "\xE2\x80\x89", // \u2009    THIN SPACE
2839d6f09afSAndreas Gohr            "\xE2\x80\x8A", // \u200A    HAIR SPACE
2849d6f09afSAndreas Gohr            "\xE2\x80\xAF", // \u202F    NARROW NO-BREAK SPACE
2859d6f09afSAndreas Gohr            "\xE2\x81\x9F", // \u205F    MEDIUM MATHEMATICAL SPACE
2869d6f09afSAndreas Gohr
2879d6f09afSAndreas Gohr            "\xE1\xA0\x8E\r\n", // \u180E    MONGOLIAN VOWEL SEPARATOR
2889d6f09afSAndreas Gohr            "\xE2\x80\x8B\r\n", // \u200B    ZERO WIDTH SPACE
2899d6f09afSAndreas Gohr            "\xEF\xBB\xBF\r\n", // \uFEFF    ZERO WIDTH NO-BREAK SPACE
2909d6f09afSAndreas Gohr        );
2919d6f09afSAndreas Gohr
2929d6f09afSAndreas Gohr        $len = strlen($text);
2939d6f09afSAndreas Gohr        for ($i = 0; $i < $len - 1; $i++) {
29439bbdaefSAndreas Gohr            $new .= $text[$i];
2959d6f09afSAndreas Gohr
29639bbdaefSAndreas Gohr            if (!is_numeric($text[$i + 1])) {
2979d6f09afSAndreas Gohr                $new .= $spaces[array_rand($spaces)];
2989d6f09afSAndreas Gohr            }
2999d6f09afSAndreas Gohr        }
30039bbdaefSAndreas Gohr        $new .= $text[$len - 1];
3019d6f09afSAndreas Gohr        return $new;
3029d6f09afSAndreas Gohr    }
3039d6f09afSAndreas Gohr
3049d6f09afSAndreas Gohr    /**
305a02b2219SPatrick Brown     * Generate some numbers from a known string and random number
306a02b2219SPatrick Brown     *
307a02b2219SPatrick Brown     * @param $fixed string the fixed part, any string
308a02b2219SPatrick Brown     * @param $rand  float  some random number between 0 and 1
309a02b2219SPatrick Brown     * @return string
310a02b2219SPatrick Brown     */
31118622736SAndreas Gohr    protected function _generateNumbers($fixed, $rand)
31218622736SAndreas Gohr    {
313a02b2219SPatrick Brown        $fixed = hexdec(substr(md5($fixed), 5, 5)); // use part of the md5 to generate an int
314a02b2219SPatrick Brown        $rand = $rand * 0xFFFFF; // bitmask from the random number
315a02b2219SPatrick Brown        return md5($rand ^ $fixed); // combine both values
316a02b2219SPatrick Brown    }
317a02b2219SPatrick Brown
318a02b2219SPatrick Brown    /**
31928c14643SAndreas Gohr     * Generates a random char string
32077e00bf9SAndreas Gohr     *
321104ec268SAndreas Gohr     * @param $fixed string the fixed part, any string
322104ec268SAndreas Gohr     * @param $rand  float  some random number between 0 and 1
32323d379a8SAndreas Gohr     * @return string
32477e00bf9SAndreas Gohr     */
32518622736SAndreas Gohr    public function _generateCAPTCHA($fixed, $rand)
32618622736SAndreas Gohr    {
327a02b2219SPatrick Brown        $numbers = $this->_generateNumbers($fixed, $rand);
32877e00bf9SAndreas Gohr
32977e00bf9SAndreas Gohr        // now create the letters
33077e00bf9SAndreas Gohr        $code = '';
3319a516edaSPatrick Brown        $lettercount = $this->getConf('lettercount') * 2;
3329a516edaSPatrick Brown        if ($lettercount > strlen($numbers)) $lettercount = strlen($numbers);
3339a516edaSPatrick Brown        for ($i = 0; $i < $lettercount; $i += 2) {
33477e00bf9SAndreas Gohr            $code .= chr(floor(hexdec($numbers[$i] . $numbers[$i + 1]) / 10) + 65);
33577e00bf9SAndreas Gohr        }
33677e00bf9SAndreas Gohr
33777e00bf9SAndreas Gohr        return $code;
33877e00bf9SAndreas Gohr    }
33977e00bf9SAndreas Gohr
3409e312724SAndreas Gohr    /**
3419e312724SAndreas Gohr     * Create a mathematical task and its result
3429e312724SAndreas Gohr     *
343104ec268SAndreas Gohr     * @param $fixed string the fixed part, any string
344104ec268SAndreas Gohr     * @param $rand  float  some random number between 0 and 1
345104ec268SAndreas Gohr     * @return array taks, result
3469e312724SAndreas Gohr     */
34718622736SAndreas Gohr    protected function _generateMATH($fixed, $rand)
34818622736SAndreas Gohr    {
349a02b2219SPatrick Brown        $numbers = $this->_generateNumbers($fixed, $rand);
3509e312724SAndreas Gohr
3519e312724SAndreas Gohr        // first letter is the operator (+/-)
3529e312724SAndreas Gohr        $op = (hexdec($numbers[0]) > 8) ? -1 : 1;
3539e312724SAndreas Gohr        $num = array(hexdec($numbers[1] . $numbers[2]), hexdec($numbers[3]));
3549e312724SAndreas Gohr
3559e312724SAndreas Gohr        // we only want positive results
3569e312724SAndreas Gohr        if (($op < 0) && ($num[0] < $num[1])) rsort($num);
3579e312724SAndreas Gohr
3589e312724SAndreas Gohr        // prepare result and task text
3599e312724SAndreas Gohr        $res = $num[0] + ($num[1] * $op);
3609bc1fab2SApostolos P. Tsompanopoulos        $task = $num[0] . (($op < 0) ? '-' : '+') . $num[1] . '= ';
3619e312724SAndreas Gohr
3629e312724SAndreas Gohr        return array($task, $res);
3639e312724SAndreas Gohr    }
36477e00bf9SAndreas Gohr
36577e00bf9SAndreas Gohr    /**
36677e00bf9SAndreas Gohr     * Create a CAPTCHA image
367104ec268SAndreas Gohr     *
368104ec268SAndreas Gohr     * @param string $text the letters to display
36977e00bf9SAndreas Gohr     */
37018622736SAndreas Gohr    public function _imageCAPTCHA($text)
37118622736SAndreas Gohr    {
37277e00bf9SAndreas Gohr        $w = $this->getConf('width');
37377e00bf9SAndreas Gohr        $h = $this->getConf('height');
37477e00bf9SAndreas Gohr
375dc091fd0SAndreas Gohr        $fonts = glob(dirname(__FILE__) . '/fonts/*.ttf');
376dc091fd0SAndreas Gohr
37777e00bf9SAndreas Gohr        // create a white image
37828c14643SAndreas Gohr        $img = imagecreatetruecolor($w, $h);
37928c14643SAndreas Gohr        $white = imagecolorallocate($img, 255, 255, 255);
38028c14643SAndreas Gohr        imagefill($img, 0, 0, $white);
38177e00bf9SAndreas Gohr
38277e00bf9SAndreas Gohr        // add some lines as background noise
38377e00bf9SAndreas Gohr        for ($i = 0; $i < 30; $i++) {
38477e00bf9SAndreas Gohr            $color = imagecolorallocate($img, rand(100, 250), rand(100, 250), rand(100, 250));
38577e00bf9SAndreas Gohr            imageline($img, rand(0, $w), rand(0, $h), rand(0, $w), rand(0, $h), $color);
38677e00bf9SAndreas Gohr        }
38777e00bf9SAndreas Gohr
38877e00bf9SAndreas Gohr        // draw the letters
38928c14643SAndreas Gohr        $txtlen = strlen($text);
39028c14643SAndreas Gohr        for ($i = 0; $i < $txtlen; $i++) {
391dc091fd0SAndreas Gohr            $font = $fonts[array_rand($fonts)];
39277e00bf9SAndreas Gohr            $color = imagecolorallocate($img, rand(0, 100), rand(0, 100), rand(0, 100));
39377e00bf9SAndreas Gohr            $size = rand(floor($h / 1.8), floor($h * 0.7));
39477e00bf9SAndreas Gohr            $angle = rand(-35, 35);
39577e00bf9SAndreas Gohr
39628c14643SAndreas Gohr            $x = ($w * 0.05) + $i * floor($w * 0.9 / $txtlen);
39777e00bf9SAndreas Gohr            $cheight = $size + ($size * 0.5);
39877e00bf9SAndreas Gohr            $y = floor($h / 2 + $cheight / 3.8);
39977e00bf9SAndreas Gohr
40077e00bf9SAndreas Gohr            imagettftext($img, $size, $angle, $x, $y, $color, $font, $text[$i]);
40177e00bf9SAndreas Gohr        }
40277e00bf9SAndreas Gohr
40377e00bf9SAndreas Gohr        header("Content-type: image/png");
40477e00bf9SAndreas Gohr        imagepng($img);
40577e00bf9SAndreas Gohr        imagedestroy($img);
40677e00bf9SAndreas Gohr    }
40777e00bf9SAndreas Gohr
408f044313dSAndreas Gohr    /**
40908f248e4SAndreas Gohr     * Create an SVG of the given text
41008f248e4SAndreas Gohr     *
41108f248e4SAndreas Gohr     * @param string $text
41208f248e4SAndreas Gohr     * @return string
41308f248e4SAndreas Gohr     */
41418622736SAndreas Gohr    public function _svgCAPTCHA($text)
41518622736SAndreas Gohr    {
41608f248e4SAndreas Gohr        require_once(__DIR__ . '/EasySVG.php');
41708f248e4SAndreas Gohr
41808f248e4SAndreas Gohr        $fonts = glob(__DIR__ . '/fonts/*.svg');
41908f248e4SAndreas Gohr
42008f248e4SAndreas Gohr        $x = 0; // where we start to draw
42108f248e4SAndreas Gohr        $y = 100; // our max height
42208f248e4SAndreas Gohr
42308f248e4SAndreas Gohr        $svg = new EasySVG();
42408f248e4SAndreas Gohr
42508f248e4SAndreas Gohr        // draw the letters
42608f248e4SAndreas Gohr        $txtlen = strlen($text);
42708f248e4SAndreas Gohr        for ($i = 0; $i < $txtlen; $i++) {
42808f248e4SAndreas Gohr            $char = $text[$i];
42908f248e4SAndreas Gohr            $size = rand($y / 2, $y - $y * 0.1); // 50-90%
43008f248e4SAndreas Gohr            $svg->setFontSVG($fonts[array_rand($fonts)]);
43108f248e4SAndreas Gohr
43208f248e4SAndreas Gohr            $svg->setFontSize($size);
43308f248e4SAndreas Gohr            $svg->setLetterSpacing(round(rand(1, 4) / 10, 2)); // 0.1 - 0.4
43408f248e4SAndreas Gohr            $svg->addText($char, $x, rand(0, round($y - $size))); // random up and down
43508f248e4SAndreas Gohr
43608f248e4SAndreas Gohr            list($w) = $svg->textDimensions($char);
43708f248e4SAndreas Gohr            $x += $w;
43808f248e4SAndreas Gohr        }
43908f248e4SAndreas Gohr
44008f248e4SAndreas Gohr        $svg->addAttribute('width', $x . 'px');
44108f248e4SAndreas Gohr        $svg->addAttribute('height', $y . 'px');
44208f248e4SAndreas Gohr        $svg->addAttribute('viewbox', "0 0 $x $y");
44308f248e4SAndreas Gohr        return $svg->asXML();
44408f248e4SAndreas Gohr    }
44508f248e4SAndreas Gohr
44608f248e4SAndreas Gohr    /**
44763609b6eSAndreas Gohr     * Generate an audio captcha
44863609b6eSAndreas Gohr     *
44963609b6eSAndreas Gohr     * @param string $text
45063609b6eSAndreas Gohr     */
45163609b6eSAndreas Gohr    public function _audioCAPTCHA($text){
45263609b6eSAndreas Gohr        global $conf;
45363609b6eSAndreas Gohr
45463609b6eSAndreas Gohr        $lc = __DIR__ . '/lang/' . $conf['lang'] . '/audio/';
45563609b6eSAndreas Gohr        $en = __DIR__ . '/lang/en/audio/';
45663609b6eSAndreas Gohr
45763609b6eSAndreas Gohr        $wavs = [];
45863609b6eSAndreas Gohr
45963609b6eSAndreas Gohr        $text = strtolower($text);
46063609b6eSAndreas Gohr        $txtlen = strlen($text);
46163609b6eSAndreas Gohr        for ($i = 0; $i < $txtlen; $i++) {
46263609b6eSAndreas Gohr            $char = $text[$i];
46363609b6eSAndreas Gohr            $file = $lc . $char . '.wav';
46463609b6eSAndreas Gohr            if (!@file_exists($file)) $file = $en . $char . '.wav';
46563609b6eSAndreas Gohr            $wavs[] = $file;
46663609b6eSAndreas Gohr        }
46763609b6eSAndreas Gohr
46863609b6eSAndreas Gohr        header('Content-type: audio/x-wav');
46963609b6eSAndreas Gohr        header('Content-Disposition: attachment;filename=captcha.wav');
47063609b6eSAndreas Gohr
47163609b6eSAndreas Gohr        echo $this->joinwavs($wavs);
47263609b6eSAndreas Gohr    }
47363609b6eSAndreas Gohr
47463609b6eSAndreas Gohr    /**
475f044313dSAndreas Gohr     * Encrypt the given string with the cookie salt
476f044313dSAndreas Gohr     *
477f044313dSAndreas Gohr     * @param string $data
478f044313dSAndreas Gohr     * @return string
479f044313dSAndreas Gohr     */
48018622736SAndreas Gohr    public function encrypt($data)
48118622736SAndreas Gohr    {
482f044313dSAndreas Gohr        if (function_exists('auth_encrypt')) {
483f044313dSAndreas Gohr            $data = auth_encrypt($data, auth_cookiesalt()); // since binky
484f044313dSAndreas Gohr        } else {
485f044313dSAndreas Gohr            $data = PMA_blowfish_encrypt($data, auth_cookiesalt()); // deprecated
486f044313dSAndreas Gohr        }
487f044313dSAndreas Gohr
488f044313dSAndreas Gohr        return base64_encode($data);
489f044313dSAndreas Gohr    }
490f044313dSAndreas Gohr
491f044313dSAndreas Gohr    /**
492f044313dSAndreas Gohr     * Decrypt the given string with the cookie salt
493f044313dSAndreas Gohr     *
494f044313dSAndreas Gohr     * @param string $data
495f044313dSAndreas Gohr     * @return string
496f044313dSAndreas Gohr     */
49718622736SAndreas Gohr    public function decrypt($data)
49818622736SAndreas Gohr    {
499f044313dSAndreas Gohr        $data = base64_decode($data);
50009870f99SPatrick Brown        if ($data === false || $data === '') return false;
501f044313dSAndreas Gohr
502f044313dSAndreas Gohr        if (function_exists('auth_decrypt')) {
503f044313dSAndreas Gohr            return auth_decrypt($data, auth_cookiesalt()); // since binky
504f044313dSAndreas Gohr        } else {
505f044313dSAndreas Gohr            return PMA_blowfish_decrypt($data, auth_cookiesalt()); // deprecated
506f044313dSAndreas Gohr        }
507f044313dSAndreas Gohr    }
508969b14c4SAndreas Gohr
50963609b6eSAndreas Gohr
51063609b6eSAndreas Gohr    /**
51163609b6eSAndreas Gohr     * Join multiple wav files
51263609b6eSAndreas Gohr     *
51363609b6eSAndreas Gohr     * All wave files need to have the same format and need to be uncompressed.
51463609b6eSAndreas Gohr     * The headers of the last file will be used (with recalculated datasize
51563609b6eSAndreas Gohr     * of course)
51663609b6eSAndreas Gohr     *
51763609b6eSAndreas Gohr     * @link http://ccrma.stanford.edu/CCRMA/Courses/422/projects/WaveFormat/
51863609b6eSAndreas Gohr     * @link http://www.thescripts.com/forum/thread3770.html
51963609b6eSAndreas Gohr     */
52063609b6eSAndreas Gohr    protected function joinwavs($wavs)
52163609b6eSAndreas Gohr    {
52263609b6eSAndreas Gohr        $fields = join(
52363609b6eSAndreas Gohr            '/', array(
52463609b6eSAndreas Gohr                'H8ChunkID',
52563609b6eSAndreas Gohr                'VChunkSize',
52663609b6eSAndreas Gohr                'H8Format',
52763609b6eSAndreas Gohr                'H8Subchunk1ID',
52863609b6eSAndreas Gohr                'VSubchunk1Size',
52963609b6eSAndreas Gohr                'vAudioFormat',
53063609b6eSAndreas Gohr                'vNumChannels',
53163609b6eSAndreas Gohr                'VSampleRate',
53263609b6eSAndreas Gohr                'VByteRate',
53363609b6eSAndreas Gohr                'vBlockAlign',
53463609b6eSAndreas Gohr                'vBitsPerSample',
53563609b6eSAndreas Gohr            )
53663609b6eSAndreas Gohr        );
53763609b6eSAndreas Gohr
53863609b6eSAndreas Gohr        $data = '';
53963609b6eSAndreas Gohr        foreach ($wavs as $wav) {
54063609b6eSAndreas Gohr            $fp = fopen($wav, 'rb');
54163609b6eSAndreas Gohr            $header = fread($fp, 36);
54263609b6eSAndreas Gohr            $info = unpack($fields, $header);
54363609b6eSAndreas Gohr
54463609b6eSAndreas Gohr            // read optional extra stuff
54563609b6eSAndreas Gohr            if ($info['Subchunk1Size'] > 16) {
54663609b6eSAndreas Gohr                $header .= fread($fp, ($info['Subchunk1Size'] - 16));
54763609b6eSAndreas Gohr            }
54863609b6eSAndreas Gohr
54963609b6eSAndreas Gohr            // read SubChunk2ID
55063609b6eSAndreas Gohr            $header .= fread($fp, 4);
55163609b6eSAndreas Gohr
55263609b6eSAndreas Gohr            // read Subchunk2Size
55363609b6eSAndreas Gohr            $size = unpack('vsize', fread($fp, 4));
55463609b6eSAndreas Gohr            $size = $size['size'];
55563609b6eSAndreas Gohr
55663609b6eSAndreas Gohr            // read data
55763609b6eSAndreas Gohr            $data .= fread($fp, $size);
55863609b6eSAndreas Gohr        }
55963609b6eSAndreas Gohr
56063609b6eSAndreas Gohr        return $header . pack('V', strlen($data)) . $data;
56163609b6eSAndreas Gohr    }
56263609b6eSAndreas Gohr
56377e00bf9SAndreas Gohr}
564