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).'&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).'&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).'&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