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 if($this->getConf('mode') == 'math'){ 45 $code = $this->_generateMATH($this->_fixedIdent(),$rand); 46 $code = $code[0]; 47 $text = $this->getLang('fillmath'); 48 } else { 49 $code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand); 50 $text = $this->getLang('fillcaptcha'); 51 } 52 $secret = PMA_blowfish_encrypt($rand,auth_cookiesalt()); 53 54 $out = ''; 55 $out .= '<div id="plugin__captcha_wrapper">'; 56 $out .= '<input type="hidden" name="'.$this->field_sec.'" value="'.hsc($secret).'" />'; 57 $out .= '<label for="plugin__captcha">'.$text.'</label> '; 58 59 switch($this->getConf('mode')){ 60 case 'text': 61 case 'math': 62 $out .= $code; 63 break; 64 case 'js': 65 $out .= '<span id="plugin__captcha_code">'.$code.'</span>'; 66 break; 67 case 'image': 68 $out .= '<img src="'.DOKU_BASE.'lib/plugins/captcha/img.php?secret='.rawurlencode($secret).'&id='.$ID.'" '. 69 ' width="'.$this->getConf('width').'" height="'.$this->getConf('height').'" alt="" /> '; 70 break; 71 case 'audio': 72 $out .= '<img src="'.DOKU_BASE.'lib/plugins/captcha/img.php?secret='.rawurlencode($secret).'&id='.$ID.'" '. 73 ' width="'.$this->getConf('width').'" height="'.$this->getConf('height').'" alt="" /> '; 74 $out .= '<a href="'.DOKU_BASE.'lib/plugins/captcha/wav.php?secret='.rawurlencode($secret).'&id='.$ID.'"'. 75 ' class="JSnocheck" title="'.$this->getLang('soundlink').'">'; 76 $out .= '<img src="'.DOKU_BASE.'lib/plugins/captcha/sound.png" width="16" height="16"'. 77 ' alt="'.$this->getLang('soundlink').'" /></a>'; 78 break; 79 case 'figlet': 80 require_once(dirname(__FILE__).'/figlet.php'); 81 $figlet = new phpFiglet(); 82 if($figlet->loadfont(dirname(__FILE__).'/figlet.flf')){ 83 $out .= '<pre>'; 84 $out .= rtrim($figlet->fetch($code)); 85 $out .= '</pre>'; 86 }else{ 87 msg('Failed to load figlet.flf font file. CAPTCHA broken',-1); 88 } 89 break; 90 } 91 $out .= ' <input type="text" size="5" maxlength="5" name="'.$this->field_in.'" class="edit" /> '; 92 93 // add honeypot field 94 $out .= '<label class="no">Please keep this field empty: <input type="text" name="'.$this->field_hp.'" /></label>'; 95 $out .= '</div>'; 96 return $out; 97 } 98 99 /** 100 * Checks if the the CAPTCHA was solved correctly 101 * 102 * @param bool $msg when true, an error will be signalled through the msg() method 103 * @return bool true when the answer was correct, otherwise false 104 */ 105 function check($msg=true){ 106 // compare provided string with decrypted captcha 107 $rand = PMA_blowfish_decrypt($_REQUEST[$this->field_sec],auth_cookiesalt()); 108 109 if($this->getConf('mode') == 'math'){ 110 $code = $this->_generateMATH($this->_fixedIdent(),$rand); 111 $code = $code[1]; 112 }else{ 113 $code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand); 114 } 115 116 if(!$_REQUEST[$this->field_sec] || 117 !$_REQUEST[$this->field_in] || 118 strtoupper($_REQUEST[$this->field_in]) != $code || 119 trim($_REQUEST[$this->field_hp]) !== '' 120 ){ 121 if($msg) msg($this->getLang('testfailed'),-1); 122 return false; 123 } 124 return true; 125 } 126 127 /** 128 * Build a semi-secret fixed string identifying the current page and user 129 * 130 * This string is always the same for the current user when editing the same 131 * page revision. 132 */ 133 function _fixedIdent(){ 134 global $ID; 135 $lm = @filemtime(wikiFN($ID)); 136 return auth_browseruid(). 137 auth_cookiesalt(). 138 $ID.$lm; 139 } 140 141 /** 142 * Generates a random 5 char string 143 * 144 * @param $fixed string - the fixed part, any string 145 * @param $rand float - some random number between 0 and 1 146 * @return string 147 */ 148 function _generateCAPTCHA($fixed,$rand){ 149 $fixed = hexdec(substr(md5($fixed),5,5)); // use part of the md5 to generate an int 150 $numbers = md5($rand * $fixed); // combine both values 151 152 // now create the letters 153 $code = ''; 154 for($i=0;$i<10;$i+=2){ 155 $code .= chr(floor(hexdec($numbers[$i].$numbers[$i+1])/10) + 65); 156 } 157 158 return $code; 159 } 160 161 /** 162 * Create a mathematical task and its result 163 * 164 * @param $fixed string - the fixed part, any string 165 * @param $rand float - some random number between 0 and 1 166 * @return string 167 */ 168 function _generateMATH($fixed, $rand){ 169 $fixed = hexdec(substr(md5($fixed),5,5)); // use part of the md5 to generate an int 170 $numbers = md5($rand * $fixed); // combine both values 171 172 // first letter is the operator (+/-) 173 $op = (hexdec($numbers[0]) > 8 ) ? -1 : 1; 174 $num = array(hexdec($numbers[1].$numbers[2]), hexdec($numbers[3])); 175 176 // we only want positive results 177 if(($op < 0) && ($num[0] < $num[1])) rsort($num); 178 179 // prepare result and task text 180 $res = $num[0] + ($num[1] * $op); 181 $task = $num[0] . (($op < 0) ? ' - ' : ' + ') . $num[1] . ' = ?'; 182 183 return array($task, $res); 184 } 185 186 /** 187 * Create a CAPTCHA image 188 */ 189 function _imageCAPTCHA($text){ 190 $w = $this->getConf('width'); 191 $h = $this->getConf('height'); 192 193 $fonts = glob(dirname(__FILE__).'/fonts/*.ttf'); 194 195 // create a white image 196 $img = imagecreate($w, $h); 197 imagecolorallocate($img, 255, 255, 255); 198 199 // add some lines as background noise 200 for ($i = 0; $i < 30; $i++) { 201 $color = imagecolorallocate($img,rand(100, 250),rand(100, 250),rand(100, 250)); 202 imageline($img,rand(0,$w),rand(0,$h),rand(0,$w),rand(0,$h),$color); 203 } 204 205 // draw the letters 206 for ($i = 0; $i < strlen($text); $i++){ 207 $font = $fonts[array_rand($fonts)]; 208 $color = imagecolorallocate($img, rand(0, 100), rand(0, 100), rand(0, 100)); 209 $size = rand(floor($h/1.8),floor($h*0.7)); 210 $angle = rand(-35, 35); 211 212 $x = ($w*0.05) + $i * floor($w*0.9/5); 213 $cheight = $size + ($size*0.5); 214 $y = floor($h / 2 + $cheight / 3.8); 215 216 imagettftext($img, $size, $angle, $x, $y, $color, $font, $text[$i]); 217 } 218 219 header("Content-type: image/png"); 220 imagepng($img); 221 imagedestroy($img); 222 } 223 224 225} 226