@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.