xref: /plugin/captcha/helper.php (revision 23d379a8751f4cf2f32c650d9bd7ac3b349484e6)
1<?php
2/**
3 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
4 * @author     Andreas Gohr <andi@splitbrain.org>
5 */
6// must be run within Dokuwiki
7if(!defined('DOKU_INC')) die();
8if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
9require_once(DOKU_INC.'inc/blowfish.php');
10
11class helper_plugin_captcha extends DokuWiki_Plugin {
12
13    protected $field_in  = 'plugin__captcha';
14    protected $field_sec = 'plugin__captcha_secret';
15    protected $field_hp  = 'plugin__captcha_honeypot';
16
17    /**
18     * Constructor. Initializes field names
19     */
20    function __construct(){
21        $this->field_in  = md5($this->_fixedIdent() . $this->field_in);
22        $this->field_sec = md5($this->_fixedIdent() . $this->field_sec);
23        $this->field_hp  = md5($this->_fixedIdent() . $this->field_hp);
24    }
25
26
27    /**
28     * Check if the CAPTCHA should be used. Always check this before using the methods below.
29     *
30     * @return bool true when the CAPTCHA should be used
31     */
32    function isEnabled(){
33        if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']) return false;
34        return true;
35    }
36
37    /**
38     * Returns the HTML to display the CAPTCHA with the chosen method
39     */
40    function getHTML(){
41        global $ID;
42
43        $rand = (float) (rand(0,10000))/10000;
44        $code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand);
45        $secret = PMA_blowfish_encrypt($rand,auth_cookiesalt());
46
47        $out  = '';
48        $out .= '<div id="plugin__captcha_wrapper">';
49        $out .= '<input type="hidden" name="'.$this->field_sec.'" value="'.hsc($secret).'" />';
50        $out .= '<label for="plugin__captcha">'.$this->getLang('fillcaptcha').'</label> ';
51        $out .= '<input type="text" size="5" maxlength="5" name="'.$this->field_in.'" class="edit" /> ';
52
53        switch($this->getConf('mode')){
54            case 'text':
55                $out .= $code;
56                break;
57            case 'js':
58                $out .= '<span id="plugin__captcha_code">'.$code.'</span>';
59                break;
60            case 'image':
61                $out .= '<img src="'.DOKU_BASE.'lib/plugins/captcha/img.php?secret='.rawurlencode($secret).'&amp;id='.$ID.'" '.
62                        ' width="'.$this->getConf('width').'" height="'.$this->getConf('height').'" alt="" /> ';
63                break;
64            case 'audio':
65                $out .= '<img src="'.DOKU_BASE.'lib/plugins/captcha/img.php?secret='.rawurlencode($secret).'&amp;id='.$ID.'" '.
66                        ' width="'.$this->getConf('width').'" height="'.$this->getConf('height').'" alt="" /> ';
67                $out .= '<a href="'.DOKU_BASE.'lib/plugins/captcha/wav.php?secret='.rawurlencode($secret).'&amp;id='.$ID.'"'.
68                        ' class="JSnocheck" title="'.$this->getLang('soundlink').'">';
69                $out .= '<img src="'.DOKU_BASE.'lib/plugins/captcha/sound.png" width="16" height="16"'.
70                        ' alt="'.$this->getLang('soundlink').'" /></a>';
71                break;
72            case 'figlet':
73                require_once(dirname(__FILE__).'/figlet.php');
74                $figlet = new phpFiglet();
75                if($figlet->loadfont(dirname(__FILE__).'/figlet.flf')){
76                    $out .= '<pre>';
77                    $out .= rtrim($figlet->fetch($code));
78                    $out .= '</pre>';
79                }else{
80                    msg('Failed to load figlet.flf font file. CAPTCHA broken',-1);
81                }
82                break;
83        }
84
85        // add honeypot field
86        $out .= '<label class="no">Please keep this field empty: <input type="text" name="'.$this->field_hp.'" /></label>';
87        $out .= '</div>';
88        return $out;
89    }
90
91    /**
92     * Checks if the the CAPTCHA was solved correctly
93     *
94     * @param  bool $msg when true, an error will be signalled through the msg() method
95     * @return bool true when the answer was correct, otherwise false
96     */
97    function check($msg=true){
98        // compare provided string with decrypted captcha
99        $rand = PMA_blowfish_decrypt($_REQUEST[$this->field_sec],auth_cookiesalt());
100        $code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand);
101
102        if(!$_REQUEST[$this->field_sec] ||
103           !$_REQUEST[$this->field_in] ||
104           strtoupper($_REQUEST[$this->field_in]) != $code ||
105           trim($_REQUEST[$this->field_hp]) !== ''
106          ){
107            if($msg) msg($this->getLang('testfailed'),-1);
108            return false;
109        }
110        return true;
111    }
112
113    /**
114     * Build a semi-secret fixed string identifying the current page and user
115     *
116     * This string is always the same for the current user when editing the same
117     * page revision.
118     */
119    function _fixedIdent(){
120        global $ID;
121        $lm = @filemtime(wikiFN($ID));
122        return auth_browseruid().
123               auth_cookiesalt().
124               $ID.$lm;
125    }
126
127    /**
128     * Generates a random 5 char string
129     *
130     * @param $fixed string - the fixed part, any string
131     * @param $rand  float  - some random number between 0 and 1
132     * @return string
133     */
134    function _generateCAPTCHA($fixed,$rand){
135        $fixed = hexdec(substr(md5($fixed),5,5)); // use part of the md5 to generate an int
136        $numbers = md5($rand * $fixed); // combine both values
137
138        // now create the letters
139        $code = '';
140        for($i=0;$i<10;$i+=2){
141            $code .= chr(floor(hexdec($numbers[$i].$numbers[$i+1])/10) + 65);
142        }
143
144        return $code;
145    }
146
147
148    /**
149     * Create a CAPTCHA image
150     */
151    function _imageCAPTCHA($text){
152        $w = $this->getConf('width');
153        $h = $this->getConf('height');
154
155        $fonts = glob(dirname(__FILE__).'/fonts/*.ttf');
156
157        // create a white image
158        $img = imagecreate($w, $h);
159        imagecolorallocate($img, 255, 255, 255);
160
161        // add some lines as background noise
162        for ($i = 0; $i < 30; $i++) {
163            $color = imagecolorallocate($img,rand(100, 250),rand(100, 250),rand(100, 250));
164            imageline($img,rand(0,$w),rand(0,$h),rand(0,$w),rand(0,$h),$color);
165        }
166
167        // draw the letters
168        for ($i = 0; $i < strlen($text); $i++){
169            $font  = $fonts[array_rand($fonts)];
170            $color = imagecolorallocate($img, rand(0, 100), rand(0, 100), rand(0, 100));
171            $size  = rand(floor($h/1.8),floor($h*0.7));
172            $angle = rand(-35, 35);
173
174            $x = ($w*0.05) +  $i * floor($w*0.9/5);
175            $cheight = $size + ($size*0.5);
176            $y = floor($h / 2 + $cheight / 3.8);
177
178            imagettftext($img, $size, $angle, $x, $y, $color, $font, $text[$i]);
179        }
180
181        header("Content-type: image/png");
182        imagepng($img);
183        imagedestroy($img);
184    }
185
186
187}
188