yiisoft / yii

Yii PHP Framework 1.1.x
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
4.84k stars 2.29k forks source link

Captcha and ajax validation #2815

Closed pavlepredic closed 10 years ago

pavlepredic commented 11 years ago

CCaptchaAction::validate() method currently refreshes captcha code after certain amount of times (as set in CCaptchaAction::testLimit). However, when you use ajax validation this leads to unexpected results. Namely, each time ajax validation is performed, captchacount is incremented, and when it reaches testLimit, captcha code is refreshed in user's session, but the page will still display the old code. A workaround is to exclude captcha property from ajax validation, but perhaps there is a better solution. Maybe something like this:

if(!Yii::app()->request->getIsAjaxRequest() && $session[$name] > $this->testLimit && $this->testLimit > 0)
            $this->getVerifyCode(true);
cebe commented 11 years ago

Why do you need ajax validation for a captcha? Captcha is to be used to prevent bots from sending the form. You only need it on the final submit request.

pavlepredic commented 11 years ago

I don't need ajax validation for captcha, that is exactly what I'm saying. But currently, if you validate your model using ajax validation (as described here: http://www.yiiframework.com/doc/api/1.1/CActiveForm#enableAjaxValidation-detail), you will end up validating your captcha property as well.

Abbas-Hashemian commented 10 years ago

i had same problem. The captcha is at the end of the form. When we validate the username ajaxically the captcha field is not filled but the validation is set on the form bunch. So the CCaptchaValidaor::validateAttribute(...) calls the $captcha->validate($value,$this->caseSensitive) and next BOOM because the captcha session on the server will be refreshed but the image in the client is old. However i guess yii friends are thinking about a better solution for yii infrastructure to validate onchange ajax validations for the form fields separately(only for the form field with validating class) but currently and alternatively we can add/remove the captcha validation in the model rules based on whether it is an ajax validation with the id of the CActiveForm widget or not.

cebe commented 10 years ago

You can disable ajaxValidation in the captcha's error method:

        <?php echo $form->error($model,'verifyCode', array(), false, false); ?>

http://www.yiiframework.com/doc/api/1.1/CActiveForm#error-detail

Abbas-Hashemian commented 10 years ago
public function error($model,$attribute,$htmlOptions=array(),$enableAjaxValidation=false,$enableClientValidation=true)
{

Even i had set my $enableAjaxValidation to false by default to have ajax validations only for the ajax-validation-enabled fields. enableAjaxValidation only prevents the onChange event of the field from starting an ajax validation post on the browser. When the onChange event of the username field(ajax validation enable) creates an ajax validation post request , on the server side we have something like this :

if(isset($_POST['ajax']) && $_POST['ajax']=='FormID'){ echo \CActiveForm::validate($Model); //::validate($Model) runs the captcha validation (has been set in the model rules) and the captcha validation causes the captcha session to get refreshed \Yii::app()->end(); }

i'm using yii 1.1.14 and my IDE is not connected to yii project git repository. So i don't know if this problem has been resolved recently. i have resolved this problem for myself by adding a conditional exception in the yii CCaptchaValidator. Hope to be useful

Abbas-Hashemian commented 10 years ago

The captcha validation refreshs the session because the captcha field value is absent(ajax validation is disabled) and the testTime=3 property will not affect

abennouna commented 10 years ago

A workaround is to refresh client-side the captcha after a failed ajax validation.

Example view:

<?php $form = $this->beginWidget('CActiveForm', array(
    'id' => 'user-form',
    'enableAjaxValidation' => true,
    'clientOptions' => array(
        ...
        'validateOnSubmit' => true,
        'afterValidate' => new CJavaScriptExpression('function(form, data, hasError) {$("#newCaptcha").click()}'),
    ),
)); ?>
...
    <?php $this->widget('CCaptcha', array(
        // Standard with button / link: set the "button" id to 'newCaptcha'
        'buttonOptions' => array(
            'id' => 'newCaptcha',
        ),
        // If you want to hide the button / link, set instead the "image" id to 'newCaptcha'
        //'clickableImage' => true,
        //'showRefreshButton' => false,
        //'imageOptions' => array(
        //  'id' => 'newCaptcha',
        //),
    )); ?>
...

tested with 1.1.14 only

Abbas-Hashemian commented 10 years ago

Another solution is to extend the CCaptchaValidator and use the extended version in your validation rules :

class Captcha extends \CCaptchaValidator {
    protected function validateAttribute($object, $attribute) {
        if (property_exists($object, "DontValidateCaptcha") && $object->DontValidateCaptcha)
            return;
        parent::validateAttribute($object, $attribute);
    }
}

in validation rules :

        array('txtCaptcha', '\Validators\Captcha'),

next prepare an ajax validator method as below for yourself to reuse it repetitively (it can be a static method in your FormModel base or your AR base :

        if (property_exists($Model, 'DontValidateCaptcha'))
            $Model->DontValidateCaptcha = true;
        echo \CActiveForm::validate($Model);
        \Yii::app()->end();
cebe commented 10 years ago

closing this as of #3336.