* @link http://www.pradosoft.com/ * @copyright Copyright © 2005-2007 PradoSoft * @license http://www.pradosoft.com/license/ * @version $Id$ * @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. * * 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 setTokenLength TokenLength}. To use case-insensitive * comparison and generate upper-case-only token, set {@link setCaseSensitive CaseSensitive} * to false. * * 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. * * @author Qiang Xue * @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; /** * @return integer the length of the token. Defaults to 6. */ public function getTokenLength() { return $this->getViewState('TokenLength',6); } /** * @param integer the length of the token. It must be between 2 and 40. */ public function setTokenLength($value) { $length=TPropertyValue::ensureInteger($value); if($length>=self::MIN_TOKEN_LENGTH && $length<=self::MAX_TOKEN_LENGTH) $this->setViewState('TokenLength',$length,6); else throw new TConfigurationException('captcha_TokenLength_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 public key used for generating the token. A random one will be generated 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->getTokenLength(),$this->getCaseSensitive()); return $this->generateToken(); } /** * @return string the private key used for generating the token. This is randomly generated and kept in a file for persistency. */ public function getPrivateKey() { $fileName=$this->generatePrivateKeyFile(); $content=file_get_contents($fileName); $matches=array(); if(preg_match("/privateKey='(.*?)'/ms",$content,$matches)>0) return $matches[1]; else throw new TConfigurationException('captcha_private_unknown'); } /** * 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) { 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('RegenerateToken'); $this->setPublicKey(''); } /** * Configures the image URL that shows the token. * @param mixed event parameter */ public function onPreRender($param) { parent::onPreRender($param); if($this->getViewState('RegenerateToken',true)) { $this->getToken(); // call to ensure the token is regenerated $url=$this->getApplication()->getAssetManager()->publishFilePath($this->getCaptchaScriptFile()); $url.='?pk='.urlencode($this->getPublicKey()); $url.='&length='.$this->getTokenLength(); $url.='&case='.($this->getCaseSensitive()?'1':'0'); $this->setImageUrl($url); $this->generatePrivateKeyFile(); $this->setViewState('RegenerateToken',false); } } /** * @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'; } /** * 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=""; 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,$tokenLength,$caseSensitive) { $token=substr($this->hash2string(md5($publicKey.$privateKey)).$this->hash2string(md5($privateKey.$publicKey)),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 0-9, a-z and A-Z. * @return string the converted string */ protected function hash2string($hex,$alphabet='') { if(strlen($alphabet)<2) $alphabet='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $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; } } ?>