A Refreshable Captcha In Yii2

By default, Yii2’s captcha doesn’t come with a ‘refresh’ link/button which lets you refresh the image. This can be frustrating as often the image is quite unreadable (even for humans!). Below is an implementation of CaptchaRefreshable which adds a refresh link to the standard Yii2 captcha. This widget is included in the Foundationize Yii2 with Foundation 6 configuration. A Refreshable Captcha in Yii2

CaptchaRefreshable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
 * CaptchaRefreshable.php - Captcha which includes a refresh link underneath the image
 * 
 * Note: Works with Foundation 6, modify the $template to suit your needs
 */
 
namespace frontend\models;
 
 
class CaptchaRefreshable extends \yii\captcha\Captcha
{
    /**
     * Overrides the $template HTML
     */
    public function init() 
    {
        $refresh_a = \yii\helpers\Html::a('refresh', '#', [
            'id' => 'refresh-captcha', 
            'class' => 'text-small' // .text-small { font-size: 0.85em; } - include in your CSS
                ]);
        
        $this->template = '
<div id="verify-code" class="row">
<div class="large-3 columns">{image} ' . $refresh_a . '</div>
<div class="large-6 columns">{input}</div>
</div>
'; parent::init(); } /** * Register the refresh JS */ public function registerClientScript() { $view = $this->getView(); $view->registerJs(" $('#refresh-captcha').on('click', function(e){ e.preventDefault(); $('#verify-code img').yiiCaptcha('refresh'); }) "); parent::registerClientScript(); } }
/*
 * CaptchaRefreshable.php - Captcha which includes a refresh link underneath the image
 * 
 * Note: Works with Foundation 6, modify the $template to suit your needs
 */

namespace frontend\models;


class CaptchaRefreshable extends \yii\captcha\Captcha
{
    /**
     * Overrides the $template HTML
     */
    public function init() 
    {
        $refresh_a = \yii\helpers\Html::a('refresh', '#', [
            'id' => 'refresh-captcha', 
            'class' => 'text-small' // .text-small { font-size: 0.85em; } - include in your CSS
                ]);
        
        $this->template = '
<div id="verify-code" class="row">
<div class="large-3 columns">{image} ' . $refresh_a . '</div>
<div class="large-6 columns">{input}</div>
</div>
'; parent::init(); } /** * Register the refresh JS */ public function registerClientScript() { $view = $this->getView(); $view->registerJs(" $('#refresh-captcha').on('click', function(e){ e.preventDefault(); $('#verify-code img').yiiCaptcha('refresh'); }) "); parent::registerClientScript(); } }

Using the refreshable captcha

To use this refreshable captcha in your view (no longer do you need to include the template as an option when creating the verifyCode field):

1
<?= $form->field($model, 'verifyCode')->widget(frontend\models\CaptchaRefreshable::className()) ?>
<?= $form->field($model, 'verifyCode')->widget(frontend\models\CaptchaRefreshable::className()) ?>

What I found happens when refreshing the captcha is, on form submit, the model validation will fail because the ajax validation (seems you cannot disable it for captcha widget) will cause a new captcha to be generated and subsequent validation fails.

Avoiding captcha refresh failure with CaptchaRefreshableAction

Add the following class to your frontend/models folder:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace frontend\models;
 
use Yii;
 
 
class CaptchaRefreshableAction extends \yii\captcha\CaptchaAction
{
    public function validate($input, $caseSensitive)
    {
        // Skip validation on AJAX requests, as it expires the captcha.
        if (Yii::$app->request->isAjax) {
            return true;
        }
        return parent::validate($input, $caseSensitive);
    }
}
namespace frontend\models;

use Yii;


class CaptchaRefreshableAction extends \yii\captcha\CaptchaAction
{
    public function validate($input, $caseSensitive)
    {
        // Skip validation on AJAX requests, as it expires the captcha.
        if (Yii::$app->request->isAjax) {
            return true;
        }
        return parent::validate($input, $caseSensitive);
    }
}

In your SiteController, update your actions() method captcha entry to point to new class:

1
2
3
4
5
6
7
8
9
10
11
12
public function actions()
    {
        return [
            'error' => [
                'class' => 'yii\web\ErrorAction',
            ],
            'captcha' => [
                'class' => 'frontend\models\CaptchaRefreshableAction',
                'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
            ],
        ];
    }
public function actions()
    {
        return [
            'error' => [
                'class' => 'yii\web\ErrorAction',
            ],
            'captcha' => [
                'class' => 'frontend\models\CaptchaRefreshableAction',
                'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
            ],
        ];
    }

Your refreshed captcha should now work correctly.

Thoughts? Please don’t hesitate to let us know in the comments below, thanks!

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*