summaryrefslogtreecommitdiff
path: root/framework/Web/UI
diff options
context:
space:
mode:
Diffstat (limited to 'framework/Web/UI')
-rw-r--r--framework/Web/UI/WebControls/TCaptcha.php241
-rw-r--r--framework/Web/UI/WebControls/TCaptchaValidator.php117
-rw-r--r--framework/Web/UI/WebControls/assets/captcha.php35
3 files changed, 393 insertions, 0 deletions
diff --git a/framework/Web/UI/WebControls/TCaptcha.php b/framework/Web/UI/WebControls/TCaptcha.php
new file mode 100644
index 00000000..a9467333
--- /dev/null
+++ b/framework/Web/UI/WebControls/TCaptcha.php
@@ -0,0 +1,241 @@
+<?php
+/**
+ * TCaptcha class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 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 <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;
+
+ /**
+ * @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.='&amp;length='.$this->getTokenLength();
+ $url.='&amp;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="<?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,$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;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TCaptchaValidator.php b/framework/Web/UI/WebControls/TCaptchaValidator.php
new file mode 100644
index 00000000..4385bbfe
--- /dev/null
+++ b/framework/Web/UI/WebControls/TCaptchaValidator.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * TCaptchaValidator class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2007 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Id$
+ * @package System.Web.UI.WebControls
+ */
+
+Prado::using('System.Web.UI.WebControls.TBaseValidator');
+Prado::using('System.Web.UI.WebControls.TCaptcha');
+
+/**
+ * TCaptchaValidator class
+ *
+ * TCaptchaValidator validates user input against a CAPTCHA represented by
+ * a {@link TCaptcha} control. The input control fails validation if its value
+ * is not the same as the token displayed in CAPTCHA. Note, if the user does
+ * not enter any thing, it is still considered as failing the validation.
+ *
+ * To use TCaptchaValidator, specify the {@link setControlToValidate ControlToValidate}
+ * to be the ID path of the input control (usually a {@link TTextBox} control}.
+ * Also specify the {@link setCaptchaControl CaptchaControl} to be the ID path of
+ * the CAPTCHA control that the user input should be compared with.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id$
+ * @package System.Web.UI.WebControls
+ * @since 3.1.1
+ */
+class TCaptchaValidator extends TBaseValidator
+{
+ /**
+ * Gets the name of the javascript class responsible for performing validation for this control.
+ * This method overrides the parent implementation.
+ * @return string the javascript class name
+ */
+ protected function getClientClassName()
+ {
+ return 'Prado.WebUI.TCaptchaValidator';
+ }
+
+ /**
+ * @return string the ID path of the CAPTCHA control to validate
+ */
+ public function getCaptchaControl()
+ {
+ return $this->getViewState('CaptchaControl','');
+ }
+
+ /**
+ * Sets the ID path of the CAPTCHA control to validate.
+ * The ID path is the dot-connected IDs of the controls reaching from
+ * the validator's naming container to the target control.
+ * @param string the ID path
+ */
+ public function setCaptchaControl($value)
+ {
+ $this->setViewState('CaptchaControl',TPropertyValue::ensureString($value),'');
+ }
+
+ /**
+ * This method overrides the parent's implementation.
+ * The validation succeeds if the input control has the same value
+ * as the one displayed in the corresponding CAPTCHA control.
+ *
+ * @return boolean whether the validation succeeds
+ */
+ protected function evaluateIsValid()
+ {
+ $value=$this->getValidationValue($this->getValidationTarget());
+ $control=$this->findCaptchaControl();
+ return $control->validate(trim($value));
+ }
+
+ /**
+ * @return TCaptchaControl the CAPTCHA control to be validated against
+ * @throws TConfigurationException if the CAPTCHA cannot be found according to {@link setCaptchaControl CaptchaControl}
+ */
+ protected function findCaptchaControl()
+ {
+ if(($id=$this->getCaptchaControl())==='')
+ throw new TConfigurationException('captchavalidator_captchacontrol_required');
+ else if(($control=$this->findControl($id))===null)
+ throw new TConfigurationException('captchavalidator_captchacontrol_inexistent',$id);
+ else if(!($control instanceof TCaptcha))
+ throw new TConfigurationException('captchavalidator_captchacontrol_invalid',$id);
+ else
+ return $control;
+ }
+
+ /**
+ * Returns an array of javascript validator options.
+ * @return array javascript validator options.
+ */
+ protected function getClientScriptOptions()
+ {
+ $options=parent::getClientScriptOptions();
+ $control=$this->findCaptchaControl();
+ if($control->getCaseSensitive())
+ {
+ $options['TokenHash']=crc32($control->getToken());
+ $options['CaseSensitive']=true;
+ }
+ else
+ {
+ $options['TokenHash']=crc32(strtoupper($control->getToken()));
+ $options['CaseSensitive']=false;
+ }
+ return $options;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/assets/captcha.php b/framework/Web/UI/WebControls/assets/captcha.php
new file mode 100644
index 00000000..aa3fdcc0
--- /dev/null
+++ b/framework/Web/UI/WebControls/assets/captcha.php
@@ -0,0 +1,35 @@
+<?php
+
+require_once(dirname(__FILE__).'/captcha_key.php');
+
+if(isset($_GET['pk']))
+ echo $_GET['pk'].$privateKey;
+else
+ echo $privateKey;
+
+function generateToken($publicKey,$privateKey,$tokenLength,$caseSensitive)
+{
+ $token=substr(hash2string(md5($publicKey.$privateKey)).hash2string(md5($privateKey.$publicKey)),0,$tokenLength);
+ return $caseSensitive?$token:strtoupper($token);
+}
+
+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;
+}
+
+?> \ No newline at end of file