Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/*** TCaptcha class file** @author Qiang Xue <qiang.xue@gmail.com>* @link http://www.pradosoft.com/* @copyright Copyright © 2005-2008 PradoSoft* @license http://www.pradosoft.com/license/* @version $Id: TCaptcha.php 2541 2008-10-21 15:05:13Z qiang.xue $* @package System.Web.UI.WebControls*/Prado::using('System.Web.UI.WebControls.TImage');/*** TCaptcha class.** TCaptcha displays a CAPTCHA (a token displayed as an image) that can be used* to determine if the input is entered by a real user instead of some program.** Unlike other CAPTCHA scripts, TCaptcha does not need session or cookie.** The token (a string consisting of alphanumeric characters) displayed is automatically* generated and can be configured in several ways. To specify the length of characters* in the token, set {@link setMinTokenLength MinTokenLength} and {@link setMaxTokenLength MaxTokenLength}.* To use case-insensitive comparison and generate upper-case-only token, set {@link setCaseSensitive CaseSensitive}* to false. Advanced users can try to set {@link setTokenAlphabet TokenAlphabet}, which* specifies what characters can appear in tokens.** The validation of the token is related with two properties: {@link setTestLimit TestLimit}* and {@link setTokenExpiry TokenExpiry}. The former specifies how many times a token can* be tested with on the server side, and the latter says when a generated token will expire.** To specify the appearance of the generated token image, set {@link setTokenImageTheme TokenImageTheme}* to be an integer between 0 and 63. And to adjust the generated image size, set {@link setTokenFontSize TokenFontSize}* (you may also set {@link TWebControl::setWidth Width}, but the scaled image may not look good.)* By setting {@link setChangingTokenBackground ChangingTokenBackground} to true, the image background* of the token will be variating even though the token is the same during postbacks.** Upon postback, user input can be validated by calling {@link validate()}.* The {@link TCaptchaValidator} control can also be used to do validation, which provides* client-side validation besides the server-side validation. By default, the token will* remain the same during multiple postbacks. A new one can be generated by calling* {@link regenerateToken()} manually.** The following template shows a typical use of TCaptcha control:* <code>* <com:TCaptcha ID="Captcha" />* <com:TTextBox ID="Input" />* <com:TCaptchaValidator CaptchaControl="Captcha"* ControlToValidate="Input"* ErrorMessage="You are challenged!" />* </code>** @author Qiang Xue <qiang.xue@gmail.com>* @version $Id: TCaptcha.php 2541 2008-10-21 15:05:13Z qiang.xue $* @package System.Web.UI.WebControls* @since 3.1.1*/class TCaptcha extends TImage{const MIN_TOKEN_LENGTH=2;const MAX_TOKEN_LENGTH=40;private $_privateKey;private $_validated=false;/*** @return integer the theme of the token image. Defaults to 0.*/public function getTokenImageTheme(){return $this->getViewState('TokenImageTheme',0);}/*** Sets the theme of the token image.* You may test each theme to find out the one you like the most.* Below is the explanation of the theme value:* It is treated as a 5-bit integer. Each bit toggles a specific feature of the image.* Bit 0 (the least significant): whether the image is opaque (1) or transparent (0).* Bit 1: whether we should add white noise to the image (1) or not (0).* Bit 2: whether we should add a grid to the image (1) or not (0).* Bit 3: whether we should add some scribbles to the image (1) or not (0).* Bit 4: whether the image background should be morphed (1) or not (0).* Bit 5: whether the token text should cast a shadow (1) or not (0).* @param integer the theme of the token image. It must be an integer between 0 and 63.*/public function setTokenImageTheme($value){$value=TPropertyValue::ensureInteger($value);if($value>=0 && $value<=63)$this->setViewState('TokenImageTheme',$value,0);elsethrow new TConfigurationException('captcha_tokenimagetheme_invalid',0,63);}/*** @return integer the font size used for displaying the token in an image. Defaults to 30.*/public function getTokenFontSize(){return $this->getViewState('TokenFontSize',30);}/*** Sets the font size used for displaying the token in an image.* This property affects the generated token image size.* The image width is proportional to this font size.* @param integer the font size used for displaying the token in an image. It must be an integer between 20 and 100.*/public function setTokenFontSize($value){$value=TPropertyValue::ensureInteger($value);if($value>=20 && $value<=100)$this->setViewState('TokenFontSize',$value,30);elsethrow new TConfigurationException('captcha_tokenfontsize_invalid',20,100);}/*** @return integer the minimum length of the token. Defaults to 4.*/public function getMinTokenLength(){return $this->getViewState('MinTokenLength',4);}/*** @param integer the minimum length of the token. It must be between 2 and 40.*/public function setMinTokenLength($value){$length=TPropertyValue::ensureInteger($value);if($length>=self::MIN_TOKEN_LENGTH && $length<=self::MAX_TOKEN_LENGTH)$this->setViewState('MinTokenLength',$length,4);elsethrow new TConfigurationException('captcha_mintokenlength_invalid',self::MIN_TOKEN_LENGTH,self::MAX_TOKEN_LENGTH);}/*** @return integer the maximum length of the token. Defaults to 6.*/public function getMaxTokenLength(){return $this->getViewState('MaxTokenLength',6);}/*** @param integer the maximum length of the token. It must be between 2 and 40.*/public function setMaxTokenLength($value){$length=TPropertyValue::ensureInteger($value);if($length>=self::MIN_TOKEN_LENGTH && $length<=self::MAX_TOKEN_LENGTH)$this->setViewState('MaxTokenLength',$length,6);elsethrow new TConfigurationException('captcha_maxtokenlength_invalid',self::MIN_TOKEN_LENGTH,self::MAX_TOKEN_LENGTH);}/*** @return boolean whether the token should be treated as case-sensitive. Defaults to true.*/public function getCaseSensitive(){return $this->getViewState('CaseSensitive',true);}/*** @param boolean whether the token should be treated as case-sensitive. If false, only upper-case letters will appear in the token.*/public function setCaseSensitive($value){$this->setViewState('CaseSensitive',TPropertyValue::ensureBoolean($value),true);}/*** @return string the characters that may appear in the token. Defaults to '234578adefhijmnrtABDEFGHJLMNRT'.*/public function getTokenAlphabet(){return $this->getViewState('TokenAlphabet','234578adefhijmnrtABDEFGHJLMNRT');}/*** @param string the characters that may appear in the token. At least 2 characters must be specified.*/public function setTokenAlphabet($value){if(strlen($value)<2)throw new TConfigurationException('captcha_tokenalphabet_invalid');$this->setViewState('TokenAlphabet',$value,'234578adefhijmnrtABDEFGHJLMNRT');}/*** @return integer the number of seconds that a generated token will remain valid. Defaults to 600 seconds (10 minutes).*/public function getTokenExpiry(){return $this->getViewState('TokenExpiry',600);}/*** @param integer the number of seconds that a generated token will remain valid. A value smaller than 1 means the token will not expire.*/public function setTokenExpiry($value){$this->setViewState('TokenExpiry',TPropertyValue::ensureInteger($value),600);}/*** @return boolean whether the background of the token image should be variated during postbacks. Defaults to false.*/public function getChangingTokenBackground(){return $this->getViewState('ChangingTokenBackground',false);}/*** @param boolean whether the background of the token image should be variated during postbacks.*/public function setChangingTokenBackground($value){$this->setViewState('ChangingTokenBackground',TPropertyValue::ensureBoolean($value),false);}/*** @return integer how many times a generated token can be tested. Defaults to 5.*/public function getTestLimit(){return $this->getViewState('TestLimit',5);}/*** @param integer how many times a generated token can be tested. For unlimited tests, set it to 0.*/public function setTestLimit($value){$this->setViewState('TestLimit',TPropertyValue::ensureInteger($value),5);}/*** @return boolean whether the currently generated token has expired.*/public function getIsTokenExpired(){if(($expiry=$this->getTokenExpiry())>0 && ($start=$this->getViewState('TokenGenerated',0))>0)return $expiry+$start<time();elsereturn false;}/*** @return string the public key used for generating the token. A random one will be generated and returned if this is not set.*/public function getPublicKey(){if(($publicKey=$this->getViewState('PublicKey',''))===''){$publicKey=$this->generateRandomKey();$this->setPublicKey($publicKey);}return $publicKey;}/*** @param string the public key used for generating the token. A random one will be generated if this is not set.*/public function setPublicKey($value){$this->setViewState('PublicKey',$value,'');}/*** @return string the token that will be displayed*/public function getToken(){return $this->generateToken($this->getPublicKey(),$this->getPrivateKey(),$this->getTokenAlphabet(),$this->getTokenLength(),$this->getCaseSensitive());}/*** @return integer the length of the token to be generated.*/protected function getTokenLength(){if(($tokenLength=$this->getViewState('TokenLength'))===null){$minLength=$this->getMinTokenLength();$maxLength=$this->getMaxTokenLength();if($minLength>$maxLength)$tokenLength=rand($maxLength,$minLength);else if($minLength<$maxLength)$tokenLength=rand($minLength,$maxLength);else$tokenLength=$minLength;$this->setViewState('TokenLength',$tokenLength);}return $tokenLength;}/*** @return string the private key used for generating the token. This is randomly generated and kept in a file for persistency.*/public function getPrivateKey(){if($this->_privateKey===null){$fileName=$this->generatePrivateKeyFile();$content=file_get_contents($fileName);$matches=array();if(preg_match("/privateKey='(.*?)'/ms",$content,$matches)>0)$this->_privateKey=$matches[1];elsethrow new TConfigurationException('captcha_privatekey_unknown');}return $this->_privateKey;}/*** Validates a user input with the token.* @param string user input* @return boolean if the user input is not the same as the token.*/public function validate($input){$number=$this->getViewState('TestNumber',0);if(!$this->_validated){$this->setViewState('TestNumber',++$number);$this->_validated=true;}if($this->getIsTokenExpired() || (($limit=$this->getTestLimit())>0 && $number>$limit)){$this->regenerateToken();return false;}return ($this->getToken()===($this->getCaseSensitive()?$input:strtoupper($input)));}/*** Regenerates the token to be displayed.* By default, a token, once generated, will remain the same during the following page postbacks.* Calling this method will generate a new token.*/public function regenerateToken(){$this->clearViewState('TokenLength');$this->setPublicKey('');$this->clearViewState('TokenGenerated');$this->clearViewState('RandomSeed');$this->clearViewState('TestNumber',0);}/*** Configures the image URL that shows the token.* @param mixed event parameter*/public function onPreRender($param){parent::onPreRender($param);if(!self::checkRequirements())throw new TConfigurationException('captcha_imagettftext_required');if(!$this->getViewState('TokenGenerated',0)){$manager=$this->getApplication()->getAssetManager();$manager->publishFilePath($this->getFontFile());$url=$manager->publishFilePath($this->getCaptchaScriptFile());$url.='?options='.urlencode($this->getTokenImageOptions());$this->setImageUrl($url);$this->setViewState('TokenGenerated',time());}}/*** @return string the options to be passed to the token image generator*/protected function getTokenImageOptions(){$privateKey=$this->getPrivateKey(); // call this method to ensure private key is generated$token=$this->getToken();$options=array();$options['publicKey']=$this->getPublicKey();$options['tokenLength']=strlen($token);$options['caseSensitive']=$this->getCaseSensitive();$options['alphabet']=$this->getTokenAlphabet();$options['fontSize']=$this->getTokenFontSize();$options['theme']=$this->getTokenImageTheme();if(($randomSeed=$this->getViewState('RandomSeed',0))===0){$randomSeed=(int)(microtime()*1000000);$this->setViewState('RandomSeed',$randomSeed);}$options['randomSeed']=$this->getChangingTokenBackground()?0:$randomSeed;$str=serialize($options);return base64_encode(md5($privateKey.$str).$str);}/*** @return string the file path of the PHP script generating the token image*/protected function getCaptchaScriptFile(){return dirname(__FILE__).DIRECTORY_SEPARATOR.'assets'.DIRECTORY_SEPARATOR.'captcha.php';}protected function getFontFile(){return dirname(__FILE__).DIRECTORY_SEPARATOR.'assets'.DIRECTORY_SEPARATOR.'verase.ttf';}/*** Generates a file with a randomly generated private key.* @return string the path of the file keeping the private key*/protected function generatePrivateKeyFile(){$captchaScript=$this->getCaptchaScriptFile();$path=dirname($this->getApplication()->getAssetManager()->getPublishedPath($captchaScript));$fileName=$path.DIRECTORY_SEPARATOR.'captcha_key.php';if(!is_file($fileName)){@mkdir($path);$key=$this->generateRandomKey();$content="<?php\$privateKey='$key';?>";file_put_contents($fileName,$content);}return $fileName;}/*** @return string a randomly generated key*/protected function generateRandomKey(){return md5(rand().rand().rand().rand());}/*** Generates the token.* @param string public key* @param string private key* @param integer the length of the token* @param boolean whether the token is case sensitive* @return string the token generated.*/protected function generateToken($publicKey,$privateKey,$alphabet,$tokenLength,$caseSensitive){$token=substr($this->hash2string(md5($publicKey.$privateKey),$alphabet).$this->hash2string(md5($privateKey.$publicKey),$alphabet),0,$tokenLength);return $caseSensitive?$token:strtoupper($token);}/*** Converts a hash string into a string with characters consisting of alphanumeric characters.* @param string the hexadecimal representation of the hash string* @param string the alphabet used to represent the converted string. If empty, it means '234578adefhijmnrtwyABDEFGHIJLMNQRTWY', which excludes those confusing characters.* @return string the converted string*/protected function hash2string($hex,$alphabet=''){if(strlen($alphabet)<2)$alphabet='234578adefhijmnrtABDEFGHJLMNQRT';$hexLength=strlen($hex);$base=strlen($alphabet);$result='';for($i=0;$i<$hexLength;$i+=6){$number=hexdec(substr($hex,$i,6));while($number){$result.=$alphabet[$number%$base];$number=floor($number/$base);}}return $result;}/*** Checks the requirements needed for generating CAPTCHA images.* TCaptach requires GD2 with TrueType font support and PNG image support.* @return boolean whether the requirements are satisfied.*/public static function checkRequirements(){return extension_loaded('gd') && function_exists('imagettftext') && function_exists('imagepng');}}