summaryrefslogtreecommitdiff
path: root/framework/Web/UI/WebControls/TCaptcha.php
diff options
context:
space:
mode:
authorctrlaltca <>2012-07-12 11:21:01 +0000
committerctrlaltca <>2012-07-12 11:21:01 +0000
commit903ae8a581fac1e6917fc3e31d2ad8fb91df80c3 (patch)
treee08bf04f0823650a231227ac3499121270172a23 /framework/Web/UI/WebControls/TCaptcha.php
parent3e4e6e66aeb3f8fea4e1eb4237498ef9d2358f63 (diff)
standardize the use of unix eol; use svn properties to enforce native eol
Diffstat (limited to 'framework/Web/UI/WebControls/TCaptcha.php')
-rw-r--r--framework/Web/UI/WebControls/TCaptcha.php990
1 files changed, 495 insertions, 495 deletions
diff --git a/framework/Web/UI/WebControls/TCaptcha.php b/framework/Web/UI/WebControls/TCaptcha.php
index 7bcf5643..5ec870ce 100644
--- a/framework/Web/UI/WebControls/TCaptcha.php
+++ b/framework/Web/UI/WebControls/TCaptcha.php
@@ -1,495 +1,495 @@
-<?php
-/**
- * TCaptcha class file
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @link http://www.pradosoft.com/
- * @copyright Copyright &copy; 2005-2012 PradoSoft
- * @license http://www.pradosoft.com/license/
- * @version $Id$
- * @package System.Web.UI.WebControls
- */
-
-Prado::using('System.Web.UI.WebControls.TImage');
-
-/**
- * TCaptcha class.
- *
- * Notice: while this class is easy to use and implement, it does not provide full security.
- * In fact, it's easy to bypass the checks reusing old, already-validated tokens (reply attack).
- * A better alternative is provided by {@link TReCaptcha}.
- *
- * 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$
- * @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);
- else
- throw 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);
- else
- throw 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);
- else
- throw 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);
- else
- throw 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();
- else
- return 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];
- else
- throw 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');
- }
-}
-
+<?php
+/**
+ * TCaptcha class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2012 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Id$
+ * @package System.Web.UI.WebControls
+ */
+
+Prado::using('System.Web.UI.WebControls.TImage');
+
+/**
+ * TCaptcha class.
+ *
+ * Notice: while this class is easy to use and implement, it does not provide full security.
+ * In fact, it's easy to bypass the checks reusing old, already-validated tokens (reply attack).
+ * A better alternative is provided by {@link TReCaptcha}.
+ *
+ * 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$
+ * @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);
+ else
+ throw 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);
+ else
+ throw 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);
+ else
+ throw 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);
+ else
+ throw 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();
+ else
+ return 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];
+ else
+ throw 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');
+ }
+}
+