unchanged
Title
Show captcha after <N> unsuccessfull attempts
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>**N** - in the view, you have to show captcha field if number of unsuccessfull attempts are greater than<N>**N** In the LoginForm model, you can use 'scenario' to set different required fields, so: ~~~ [php] 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: ~~~ [php] public $verifyCode; ~~~ In the view, add this code (show captcha field if scenario is set to 'captchaRequired', will see later): ~~~ [php] <?php 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: ~~~ [php] 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. ~~~ [php] 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: ~~~ [php] 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 inprotected/models/LoginFormprotected/models/LoginForm.php we have set two different required fields depending on scenario: ~~~ [php] 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: ~~~ [php] 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: ~~~ [php] <?php 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>