[solved] CCaptcha doesn't seems to work on CActiveForm.

Hi,

I implemented CCaptcha on my CActiveForm, I realised that everytime I validate the captcha fail, the captcha doesn’t changes its image. Do I have to manually add in jquery code in CActiveForm.clientOptions.afterValidate or there is a setting I didn’t set?

I also found out that when CCaptchaAction.testLimit is set to 1, validation always fails even if text that is entered is correct.

As it says in the CActiveForm documentation - http://www.yiiframew…api/CActiveForm




There are some limitations of CActiveForm regarding to its AJAX validation support.First, 

it does not validate with file upload fields.Second, it should not be used to perform validations 

that may cause server-side state change.For example, it is not suitable to perform CAPTCHA 

validation done by CCaptchActionbecause each validation request will increase the number 

of tests by one. Third, it is not designedto work with tabular data input for the moment.



Thanks and I would like apologize for missing out the important of the documentation. I will revert back to normal validation.

Once again thanks for your help.

You can validate captcha with Active Form by doing smth like this:




// rules :

array('verifyCode', 'captcha', 'on'=>'insert'),

array('verifyCode', 'activeCaptcha', 'on'=>'active'), // set "active" scenario when ajax validation is being performed.


public function activeCaptcha()

{

    $code = Yii::app()->controller->createAction('captcha')->verifyCode;

    if ($code != $this->verifyCode)

        $this->addError('verifyCode', 'Wrong code.');

}



CCaptchaAction.getVerifyCode() doesn’t regenerate a code by default.

Andy, I am trying to do ajax validation for Captcha validation code, but I am not successful (while otherwise Ajax validation is working). Please tell be what did I do wrong.

I have actionRegister in UserController that is used to register a new user.


public function actionRegister()

	{

		$model=new User('register');


		// if it is ajax validation request, validate model

		if(isset($_POST['ajax']) && $_POST['ajax']==='register-form')

		{

			echo CActiveForm::validate($model);

			Yii::app()->end();

		}

And this is the user model code:


	public function rules()

	{


		return array( 

                         ...

			

			// Added for Verify Code (Captcha) 

			array('verifyCode', 'captcha', 'allowEmpty'=>!extension_loaded('gd') ,'on'=>'register'),

			array('verifyCode', 'activeCaptcha', 'on'=>'register'), 

			

		);

	}




    public function activeCaptcha()

    {

        $code = Yii::app()->controller->registerAction('captcha')->verifyCode;

        if ($code != $this->verifyCode)

            $this->addError('verifyCode', 'Wrong code.');

    }






Both rules you have are applied to the "register" scenario. For activeCaptcha validation you need a special scenario. In your case the first rule regenerates verifyCode on each request (it also depends on testLimit of course).

Sorry, I don’t get it.

Besides, I have noticed that if I include array(‘verifyCode’, ‘activeCaptcha’, ‘on’=>‘register’), in the user model, ajax validation stops working for all fields and I get error when the following error when the form is submitted:


CException


Description


Property "UserController.registerAction" is not defined.


Source File


C:\xampp\htdocs\Yii\framework\base\CComponent.php(264)


00252:      * @since 1.0.2

00253:      */

00254:     public function __call($name,$parameters)

00255:     {

00256:         if($this->_m!==null)

00257:         {

00258:             foreach($this->_m as $object)

00259:             {

00260:                 if($object->getEnabled() && method_exists($object,$name))

00261:                     return call_user_func_array(array($object,$name),$parameters);

00262:             }

00263:         }

00264:         if(class_exists('Closure', false) && $this->$name instanceof Closure)

00265:             return call_user_func_array($this->$name, $parameters);

00266:         throw new CException(Yii::t('yii','{class} does not have a method named "{name}".',

00267:             array('{class}'=>get_class($this), '{name}'=>$name)));

00268:     }

00269: 

00270:     /**

00271:      * Returns the named behavior object.

00272:      * The name 'asa' stands for 'as a'.

00273:      * @param string the behavior name

00274:      * @return IBehavior the behavior object, or null if the behavior does not exist

00275:      * @since 1.0.2

00276:      */

Stack Trace


#0 C:\xampp\htdocs\Yii\framework\base\CComponent.php(264): CComponent->__get('registerAction')

#1 [internal function]: CComponent->__call('registerAction', Array)

#2 C:\xampp\htdocs\Yii\blogtest\protected\models\User.php(79): UserController->registerAction('captcha')

#3 C:\xampp\htdocs\Yii\framework\validators\CInlineValidator.php(39): User->activeCaptcha('verifyCode', Array)

#4 C:\xampp\htdocs\Yii\framework\validators\CValidator.php(178): CInlineValidator->validateAttribute(Object(User), 'verifyCode')

#5 C:\xampp\htdocs\Yii\framework\base\CModel.php(150): CValidator->validate(Object(User), NULL)

#6 C:\xampp\htdocs\Yii\framework\db\ar\CActiveRecord.php(723): CModel->validate(NULL)

#7 C:\xampp\htdocs\Yii\blogtest\protected\controllers\UserController.php(155): CActiveRecord->save()

#8 C:\xampp\htdocs\Yii\framework\web\actions\CInlineAction.php(32): UserController->actionRegister()

#9 C:\xampp\htdocs\Yii\framework\web\CController.php(300): CInlineAction->run()

#10 C:\xampp\htdocs\Yii\framework\web\filters\CFilterChain.php(129): CController->runAction(Object(CInlineAction))

#11 C:\xampp\htdocs\Yii\framework\web\filters\CFilter.php(41): CFilterChain->run()

#12 C:\xampp\htdocs\Yii\framework\web\CController.php(1049): CFilter->filter(Object(CFilterChain))

#13 C:\xampp\htdocs\Yii\framework\web\filters\CInlineFilter.php(59): CController->filterAccessControl(Object(CFilterChain))

#14 C:\xampp\htdocs\Yii\framework\web\filters\CFilterChain.php(126): CInlineFilter->filter(Object(CFilterChain))

#15 C:\xampp\htdocs\Yii\framework\web\CController.php(283): CFilterChain->run()

#16 C:\xampp\htdocs\Yii\framework\web\CController.php(257): CController->runActionWithFilters(Object(CInlineAction), Array)

#17 C:\xampp\htdocs\Yii\framework\web\CWebApplication.php(324): CController->run('register')

#18 C:\xampp\htdocs\Yii\framework\web\CWebApplication.php(121): CWebApplication->runController('user/register')

#19 C:\xampp\htdocs\Yii\framework\base\CApplication.php(135): CWebApplication->processRequest()

#20 C:\xampp\htdocs\Yii\blogtest\index.php(18): CApplication->run()

I use the Captcha code only in the register scenario. I added the second captcha rule (array(‘verifyCode’, ‘activeCaptcha’, ‘on’=>‘register’), ) and the function activeCaptcha() in the User model after reading this post. Could you tell me the modification required to make the ajax validation for other fields as well as captcha verifyCode? Thank you so much for your help.

What’s registerAction? There is no such code in my example :)

Ok, let’s make it more clear.

When you call CActiveForm::validate($model) both rules are applied. First rule works as usual. It changes code if user types it wrong several times (depends on testLimit param). But if you use ajax validation, it will always happen, and user won’t see changed image. That’s why we need another rule in the case of ajax request… That’s why I used “active” scenario, which I set when it is ajax request.

Your situation is a little different, because you have "register" scenario. I can suggest to delete the first rule and leave only the second one. activeCaptcha() will look now like:




public function activeCaptcha() {

    $code = Yii::app()->controller->createAction('captcha')->getVerifyCode();

    if ($code != $this->verifyCode)

        $this->addError('verifyCode', 'Wrong code.');

    if (!(isset($_POST['ajax']) && $_POST['ajax']==='user-form'))

        Yii::app()->controller->createAction('captcha')->getVerifyCode(true);

}



I took it from russian forum, didn’t test, but hope you’ll figure it out :)

@andy_s

Your solution looked intriguing to me at first - but testLimit will not work anymore, so it’s easy for an evil person to fake ajax requests and brute-force the captcha code.

I’ve therefore used another approach - which i admit is very “hackish” :) :

Rules:





            array('captcha','required','on'=>'register'),

            // MUST BE AFTER ABOVE LINE:

            array('captcha','captcha','captchaAction'=>'user/captcha','skipOnError'=>true,'on'=>'register'),



This makes sure, that captcha is only validated if something is filled into the captcha textbox. So the limit counter will not increase before someone starts to enter the captcha code.

Now i make sure, that the captcha image is refreshed on every ajax validation call. This dirty js hack does the trick:

In the form view:





<?php

// Every time validation is performed, we reload the CAPTCHA

$updateCaptcha=<<<EOD

function(form,attribute,data,hasError) {

    var i=form.find('.captcha img').first(),h=/^.*\/v\//.exec(i.attr('src'));

    i.attr('src',h+Math.floor(10000*Math.random()));

    return true;

}

EOD

;?>



This takes the source of the captcha image, which looks like “/user/captcha/v/4cac8da986faf” in my case. That strange suffix is an arbitrary unique string, that makes sure, the browser doesn’t cache this image. So i used a regex to cut this off and replace it with a random number between 0 and 10000.

Finally i registered this snippet with the afterValidateAttribute event:





    <?php $form=$this->beginWidget('CActiveForm',array(

        'id'=>'user-registration',

        'enableAjaxValidation'=>true,

        'focus'=>'login',

        'clientOptions'=>array(

            'validateOnSubmit'=>true,

            'afterValidateAttribute'=>'js:'.$updateCaptcha,

            'afterValidate'=>'js:'.$updateCaptcha,

        ),

    )); ?>



So far it seems to work well: Image is always up to date and testLimit still works.

Coming back to this issue i found that i had to fix my example above. Here’s the setup that really works:

Model rules:


// Special setup for captcha handling + AJAX CActiveForm

array('captcha','required','on'=>'register'),

array('captcha',  // Must be _after_ required rule

    'captcha',

    'on'=>'register',

    'captchaAction'=>'user/captcha',

    'skipOnError'=>true,    // Important: Only validate captcha if 'required' had no error (a.k.a. "if not empty")

),



Controller:


 public function actions()

{

    return array(

        // captcha action renders the CAPTCHA image displayed on the contact page

        'captcha'=>array(

            'class'=>'CCaptchaAction',

            'backColor'=>0xFFFFFF,

            'testLimit'=>3,

        ),

    );

}



View:


// Snippet that reloads the captcha image after validation

 $updateCaptcha=<<<EOD

function(form,attribute,data,hasError) {

    var i=form.find('.captcha img').first(),

 		h=/^.*\/v\//.exec(i.attr('src'));  // will cut off the number part at the end of image src URL (".../v/123456")

    i.attr('src',h+Math.floor(100000*Math.random()));  // creates a new random number to prevent image caching

    return true;

}

EOD;


$form=$this->beginWidget('CActiveForm',array(

    'id'=>'user-registration',

    'enableAjaxValidation'=>true,

    'focus'=>'login',

    'clientOptions'=>array(

        'validateOnSubmit'=>true,

        'afterValidateAttribute'=>'js:'.$updateCaptcha,

        'afterValidate'=>'js:'.$updateCaptcha,

    ),

));



The key to this concept is:

1. Only validate captcha if not empty

This will also increment its validation counter and create a new image if testLimit was reached.

2. Refresh the captcha image on every validation

This makes sure that the user always sees the up-to-date image in case testLimit was reached and a new image was generated

I found that specifying captchaAction and setting skipOnError to true in the Model validator fixed my issue.

Edit: Arg its still flakey. It is possible to get the captcha out of sync still with ajax validation and bad input… not sure if it is purely client side of if an errant submit button is doing it.

After checking issue 602 and setting testLimit to 0 in the captcha config, I seem to be doing ok.




'captcha'=>array(

				'class'=>'CCaptchaAction',

				'backColor'=>0xFFFFFF,

				'testLimit'=>0, 

			),




Ok, it’s works for me.

Did someone get Mike’s solution work on current Yii version? My captcha image is not displayed after ajax validation.

the JS worked for me… the model skipOnError option instead make the captcha never change :s

Ah, I forgot an important thing:

I’m using urlFormat = ‘path’ in my main config but the captcha url is still “user/captcha?v=14645” instead of “user/captcha/v/14645”

… is this a bug?

Have you maybe set appendParams to false?

Hello Mike, i have my curlmanager’s appendParams set to false and then my captcha location is /site/captcha/v/517a23b252453.png. But still not working, it redirect to site/login.php.

How can i solve this? Thanks in advance :)