In this mini howto I would like to show how to add a required captcha field in the login form, after a defined number of unsuccessfull attempts. To do this, I will use the blog demo that you have in default Yii download package (path/to/yii/demos/blog).
Basically, you need three things:
in the model, you have to add captcha field as a required field in the rules() method
in the controller, you have to create a different LoginForm model if number of unsuccessfull attempts are greater than N
in the view, you have to show captcha field if number of unsuccessfull attempts are greater than N
In the LoginForm model, you can use 'scenario' to set different required fields, so:
public function rules() { return array( // username and password are required array('username, password', 'required'), // rememberMe needs to be a boolean array('rememberMe', 'boolean'), // password needs to be authenticated array('password', 'authenticate'), // add these lines below array('username,password,verifyCode','required','on'=>'captchaRequired'), array('verifyCode', 'captcha', 'allowEmpty'=>!CCaptcha::checkRequirements()), ); }
Moreover, add verifyCode as public property:
public $verifyCode;
In the view, add this code (show captcha field if scenario is set to 'captchaRequired', will see later):
if($model->scenario == 'captchaRequired'): <div class="row"> <?php echo CHtml::activeLabelEx($model,'verifyCode'); <div> <?php $this->widget('CCaptcha'); <?php echo CHtml::activeTextField($model,'verifyCode'); </div> <div class="hint">Please enter the letters as they are shown in the image above. <br/>Letters are not case-sensitive.</div> </div> <?php endif;
Now, the controller. First, add a property to set maximum allowed attempts and a counter that trace failed attempts time to time:
public $attempts = 5; // allowed 5 attempts public $counter;
then, add a private function that returns true if 'captchaRequired' session value is greater than number of failed attempts.
private function captchaRequired() { return Yii::app()->session->itemAt('captchaRequired') >= $this->attempts; }
We will use this function to know if captcha is required or not. Now, remain to modify actionLogin() method:
public function actionLogin() { $model = $this->captchaRequired()? new LoginForm('captchaRequired') : new LoginForm; // if it is ajax validation request if(isset($_POST['ajax']) && $_POST['ajax']==='login-form') { echo CActiveForm::validate($model); Yii::app()->end(); } // collect user input data if(isset($_POST['LoginForm'])) { $model->attributes=$_POST['LoginForm']; // validate user input and redirect to the previous page if valid if($model->validate() && $model->login()) $this->redirect(Yii::app()->user->returnUrl); else { $this->counter = Yii::app()->session->itemAt('captchaRequired') + 1; Yii::app()->session->add('captchaRequired',$this->counter); } } // display the login form $this->render('login',array('model'=>$model)); }
Note that:
- if function captchaRequired() returns true create LoginForm with scenario 'captchaRequired', else create LoginForm with default scenario. This is useful because in protected/models/LoginForm.php we have set two different required fields depending on scenario:
public function rules() { return array( array('username, password', 'required'), array('username,password,verifyCode','required','on'=>'captchaRequired'), [... missing code...] }
- if validation passes redirect to a specific page, but what if validation doesn't pass? In this case we increment the counter, then set a session named 'captchaRequired' with counter value, in this way:
if($model->validate() && $model->login()) $this->redirect(Yii::app()->user->returnUrl); else { $this->counter = Yii::app()->session->itemAt('captchaRequired') + 1; Yii::app()->session->add('captchaRequired',$this->counter); }
When 'captchaRequired' session will be equal to maximum allowed attempts (property $attempts) private function captchaRequired() will return true and then LoginForm('captchaRequired') will be created. With scenario set to 'captchaRequired' captcha will be show in the view:
if($model->scenario == 'captchaRequired'): // code to show captcha <?php endif;
Easy, uh? ;)
References
http://www.yiiframework.com/forum/index.php/topic/21561-captcha-custom-validation http://drupal.org/node/536274