Skip to content

Commit

Permalink
Added support to support client-side validation of CAPTCHA.
Browse files Browse the repository at this point in the history
  • Loading branch information
qiang.xue committed Mar 25, 2011
1 parent 548bb5f commit 40346c3
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 24 deletions.
57 changes: 54 additions & 3 deletions framework/validators/CCaptchaValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,21 @@ protected function validateAttribute($object,$attribute)
$value=$object->$attribute;
if($this->allowEmpty && $this->isEmpty($value))
return;
$captcha=$this->getCaptchaAction();
if(!$captcha->validate($value,$this->caseSensitive))
{
$message=$this->message!==null?$this->message:Yii::t('yii','The verification code is incorrect.');
$this->addError($object,$attribute,$message);
}
}

/**
* Returns the CAPTCHA action object.
* @return CCaptchaAction the action object
* @since 1.1.7
*/
protected function getCaptchaAction()
{
if(($captcha=Yii::app()->getController()->createAction($this->captchaAction))===null)
{
if(strpos($this->captchaAction,'/')!==false) // contains controller or module
Expand All @@ -62,11 +76,48 @@ protected function validateAttribute($object,$attribute)
throw new CException(Yii::t('yii','CCaptchaValidator.action "{id}" is invalid. Unable to find such an action in the current controller.',
array('{id}'=>$this->captchaAction)));
}
if(!$captcha->validate($value,$this->caseSensitive))
return $captcha;
}

/**
* Returns the JavaScript needed for performing client-side validation.
* @param CModel $object the data object being validated
* @param string $attribute the name of the attribute to be validated.
* @return string the client-side validation script.
* @see CActiveForm::enableClientValidation
* @since 1.1.7
*/
public function clientValidateAttribute($object,$attribute)
{
$captcha=$this->getCaptchaAction();
$message=$this->message!==null ? $this->message : Yii::t('yii','The verification code is incorrect.');
$message=strtr($message, array(
'{attribute}'=>$object->getAttributeLabel($attribute),
));
$code=$captcha->getVerifyCode(false);
$hash=$captcha->generateValidationHash($this->caseSensitive ? $code : strtolower($code));
$js="
var hash = $('body').data('{$this->captchaAction}.hash');
if (hash == null)
hash = $hash;
else
hash = hash[".($this->caseSensitive ? 0 : 1)."];
for(var i=value.length-1, h=0; i >= 0; --i) h+=value.".($this->caseSensitive ? '' : 'toLowerCase().')."charCodeAt(i);
if(h != hash) {
messages.push(".CJSON::encode($message).");
}
";

if($this->allowEmpty)
{
$message=$this->message!==null?$this->message:Yii::t('yii','The verification code is incorrect.');
$this->addError($object,$attribute,$message);
$js="
if($.trim(value)!='') {
$js
}
";
}

return $js;
}
}

5 changes: 2 additions & 3 deletions framework/web/widgets/CActiveForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@
*
* The AJAX-based validation has a few limitations. First, it does not work
* with file upload fields. Second, it should not be used to perform validations that
* may cause server-side state changes. For example, it is not suitable to perform
* CAPTCHA (see {@link CCaptchaAction}) validation, because each validation request
* will increase the number of tests by one. Third, it is not designed
* may cause server-side state changes. Third, it is not designed
* to work with tabular data input for the moment.
*
* Support for client-side validation varies for different validators. A validator
Expand All @@ -67,6 +65,7 @@
* At this moment, the following core validators support client-side validation:
* <ul>
* <li>{@link CBooleanValidator}</li>
* <li>{@link CCaptchaValidator}</li>
* <li>{@link CCompareValidator}</li>
* <li>{@link CEmailValidator}</li>
* <li>{@link CNumberValidator}</li>
Expand Down
40 changes: 29 additions & 11 deletions framework/web/widgets/captcha/CCaptcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,25 +113,42 @@ public function registerClientScript()
$id=$this->imageOptions['id'];
$url=$this->getController()->createUrl($this->captchaAction,array(CCaptchaAction::REFRESH_GET_VAR=>true));

$js="";
if($this->showRefreshButton)
{
$cs->registerScript('Yii.CCaptcha#'.$id,'dummy');
$label=$this->buttonLabel===null?Yii::t('yii','Get a new code'):$this->buttonLabel;
$button=$this->buttonType==='button'?'ajaxButton':'ajaxLink';
$html=CHtml::$button($label,$url,array('success'=>'js:function(html){jQuery("#'.$id.'").attr("src",html)}'),$this->buttonOptions);
$js="jQuery('#$id').after(\"".CJavaScript::quote($html).'");';
$cs->registerScript('Yii.CCaptcha#'.$id,$js);
$button=$this->buttonType;
if(isset($options['id']))
$buttonID=$options['id'];
else
$buttonID=$options['id']=$id.'_button';
$html=CHtml::$button($label, $url, $options);
$js="jQuery('#$id').after(".CJSON::encode($html).");";
$selector="#$buttonID";
}

if($this->clickableImage)
{
$js="jQuery('#$id').click(function(){"
.CHtml::ajax(array(
'url'=>$url,
'success'=>"js:function(html){jQuery('#$id').attr('src',html)}",
)).'});';
$cs->registerScript('Yii.CCaptcha#2'.$id,$js);
$selector=isset($selector) ? "$selector, #$id" : "#$id";

if(!isset($selector))
return;

$js.="
jQuery('$selector').live('click',function(){
jQuery.ajax({
url: ".CJSON::encode($url).",
dataType: 'json',
cache: false,
success: function(data) {
jQuery('#$id').attr('src', data['url']);
jQuery('body').data('{$this->captchaAction}.hash', [data['hash1'], data['hash2']]);
}
});
return false;
});
";
$cs->registerScript('Yii.CCaptcha#'.$id,$js);
}

/**
Expand All @@ -150,3 +167,4 @@ public static function checkRequirements()
return false;
}
}

29 changes: 22 additions & 7 deletions framework/web/widgets/captcha/CCaptchaAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,31 @@ public function run()
{
if(isset($_GET[self::REFRESH_GET_VAR])) // AJAX request for regenerating code
{
$this->getVerifyCode(true);
// we add a random 'v' parameter so that FireFox can refresh the image
// when src attribute of image tag is changed
echo $this->getController()->createUrl($this->getId(),array('v' => uniqid()));
$code=$this->getVerifyCode(true);
echo CJSON::encode(array(
'hash1'=>$this->generateValidationHash($code),
'hash2'=>$this->generateValidationHash(strtolower($code)),
// we add a random 'v' parameter so that FireFox can refresh the image
// when src attribute of image tag is changed
'url'=>$this->getController()->createUrl($this->getId(),array('v' => uniqid())),
));
}
else
{
$this->renderImage($this->getVerifyCode());
Yii::app()->end();
}
Yii::app()->end();
}

/**
* Generates a hash code that can be used for client side validation.
* @param string $code the CAPTCHA code
* @return string a hash code generated from the CAPTCHA code
* @since 1.1.7
*/
public function generateValidationHash($code)
{
for($h=0,$i=strlen($code)-1;$i>=0;--$i)
$h+=ord($code[$i]);
return $h;
}

/**
Expand Down

0 comments on commit 40346c3

Please sign in to comment.