├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── composer.lock └── src ├── Captcha.php ├── CaptchaAction.php ├── CaptchaAsset.php ├── CaptchaValidator.php ├── ValidationAsset.php └── assets ├── juliardi.captcha.js └── juliardi.validation.js /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Muhammad Safri Juliardi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yii2 Captcha Extension 2 | 3 | [![Latest Stable Version](https://img.shields.io/packagist/v/juliardi/yii2-captcha?label=stable)](https://packagist.org/packages/juliardi/yii2-captcha) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/juliardi/yii2-captcha)](https://packagist.org/packages/juliardi/yii2-captcha) 5 | [![Latest Stable Release Date](https://img.shields.io/github/release-date/juliardi/yii2-captcha)](https://github.com/juliardi/yii2-captcha) 6 | [![License](https://img.shields.io/github/license/juliardi/yii2-captcha)](https://github.com/juliardi/yii2-captcha) 7 | 8 | > Yii2 Captcha uses [Gregwar's Captcha library](https://github.com/Gregwar/Captcha) wrapper for Yii2. 9 | 10 | ## Table of Contents 11 | 12 | - [Yii2 Captcha Extension](#yii2-captcha-extension) 13 | - [Table of Contents](#table-of-contents) 14 | - [Instalation](#instalation) 15 | - [Usage](#usage) 16 | - [Action](#action) 17 | - [View](#view) 18 | - [Validation](#validation) 19 | 20 | ## Instalation 21 | 22 | Package is available on [Packagist](https://packagist.org/packages/juliardi/yii2-captcha), you can install it using [Composer](https://getcomposer.org). 23 | 24 | ```shell 25 | composer require juliardi/yii2-captcha "*" 26 | ``` 27 | 28 | or add to the require section of your `composer.json` file. 29 | 30 | ```shell 31 | "juliardi/yii2-captcha": "*" 32 | ``` 33 | 34 | ## Usage 35 | 36 | This extension has 3 different steps. First is calling `juliardi\captcha\CaptchaAction` to provide [CAPTCHA](https://en.wikipedia.org/wiki/CAPTCHA) image - a way of preventing website spamming, then rendering CAPTCHA image in view with `juliardi\captcha\Captcha`, and validating user input against the generated CAPTCHA code with `juliardi\captcha\CaptchaValidator`. 37 | 38 | Here is how to setup this extension for each step : 39 | 40 | ### Action 41 | 42 | Add the following method into your Controller. 43 | 44 | ```php 45 | public function actions() 46 | { 47 | return [ 48 | 'captcha' => [ 49 | 'class' => \juliardi\captcha\CaptchaAction::class, 50 | 51 | /** 52 | * How many times should the same CAPTCHA be displayed. Defaults to 3. 53 | * A value less than or equal to 0 means the test is unlimited (available since version 1.1.2). 54 | */ 55 | 'testLimit' => 3, // int 56 | 57 | /** 58 | * The width of the generated CAPTCHA image. Defaults to 150. 59 | */ 60 | 'width' => 150, // int 61 | 62 | /** 63 | * The height of the generated CAPTCHA image. Defaults to 40. 64 | */ 65 | 'height' => 40, // int 66 | 67 | /** 68 | * The minimum & maximum length for randomly generated word. Defaults to [5, 7] | min 5 max 7. 69 | * 70 | * If an array is provided, the first value will be used as the minimum length and the second value will be used as the maximum length. 71 | * 72 | * **Note:** The minimum length must be at least 3 and the maximum length must be at most 20. 73 | * 74 | */ 75 | 'length' => [5, 7], // int|int[] | // Random word length will be between 5 and 7 characters 76 | 77 | /** 78 | * The quality of the generated JPEG image. Valid values are 1 - 100. Defaults to 80. 79 | */ 80 | 'quality' => 80, // int 81 | 82 | /** 83 | * The fixed verification code. When this property is set, 84 | * 85 | * This is mainly used in automated tests where we want to be able to reproduce 86 | * the same verification code each time we run the tests. 87 | * If not set, it means the verification code will be randomly generated. 88 | */ 89 | // 'fixedVerifyCode' => 'testme', // string|null 90 | ], 91 | ]; 92 | } 93 | ``` 94 | 95 | ### View 96 | 97 | Add the following code to your view to render CAPTCHA image and input. 98 | 99 | The following example shows how to use this widget with a model attribute: 100 | 101 | ```php 102 | use juliardi\captcha\Captcha; 103 | 104 | echo Captcha::widget([ 105 | 'model' => $model, 106 | 'attribute' => 'captcha', 107 | 108 | // configure additional widget properties here 109 | /** 110 | * The route of the action that generates the CAPTCHA images. 111 | * The action represented by this route must be an action of [[CaptchaAction]]. 112 | * Please refer to [[\yii\helpers\Url::toRoute()]] for acceptable formats. 113 | */ 114 | 'captchaAction' => 'site/captcha', // string|array 115 | 116 | /** 117 | * HTML attributes to be applied to the CAPTCHA image tag. 118 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 119 | */ 120 | 'imageOptions' => [], // array 121 | 122 | /** 123 | * The template for arranging the CAPTCHA image tag and the text input tag. 124 | * In this template, the token `{image}` will be replaced with the actual image tag, 125 | * while `{input}` will be replaced with the text input tag. 126 | */ 127 | 'template' => '{image} {input}', // string 128 | 129 | /** 130 | * HTML attributes for the input tag. 131 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 132 | */ 133 | 'options' => ['class' => 'form-control'], // array 134 | 135 | ]); 136 | ``` 137 | 138 | The following example will use the name property instead: 139 | 140 | ```php 141 | use juliardi\captcha\Captcha; 142 | 143 | echo Captcha::widget([ 144 | 'name' => 'captcha', 145 | ]); 146 | ``` 147 | 148 | You can also use this widget in an [ActiveForm](https://www.yiiframework.com/doc/api/2.0/yii-widgets-activeform) using the [widget()](https://www.yiiframework.com/doc/api/2.0/yii-widgets-activefield#widget()-detail) method, for example like this: 149 | 150 | ```php 151 | field($model, 'captcha')->widget(\juliardi\captcha\Captcha::class, [ 152 | // configure additional widget properties here 153 | ]) ?> 154 | ``` 155 | 156 | ### Validation 157 | 158 | Add the following rule to your model to validate the captcha input : 159 | 160 | ```php 161 | use juliardi\captcha\CaptchaValidator; 162 | 163 | public function rules() 164 | { 165 | return [ 166 | ... some other rules... 167 | ['captcha', CaptchaValidator::class], 168 | ]; 169 | } 170 | ``` 171 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "juliardi/yii2-captcha", 3 | "description": "Captcha library wrapper for Yii2", 4 | "type": "yii2-extension", 5 | "keywords": ["yii2","extension","captcha"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Muhammad Safri Juliardi", 10 | "email": "ardi93@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "yiisoft/yii2": "~2.0.0", 15 | "gregwar/captcha": "1.*" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "juliardi\\captcha\\": "src" 20 | } 21 | }, 22 | "config": { 23 | "allow-plugins": { 24 | "yiisoft/yii2-composer": true 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "c96f17c81fdf810c460a901dcd9999ad", 8 | "packages": [ 9 | { 10 | "name": "bower-asset/inputmask", 11 | "version": "3.3.10", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/RobinHerbots/Inputmask.git", 15 | "reference": "14873e5775964275d13621cbe2b52e4448af3707" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/14873e5775964275d13621cbe2b52e4448af3707", 20 | "reference": "14873e5775964275d13621cbe2b52e4448af3707", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "bower-asset/jquery": ">=1.7" 25 | }, 26 | "type": "bower-asset-library", 27 | "extra": { 28 | "bower-asset-main": [ 29 | "./dist/inputmask/inputmask.js", 30 | "./dist/inputmask/inputmask.extensions.js", 31 | "./dist/inputmask/inputmask.date.extensions.js", 32 | "./dist/inputmask/inputmask.numeric.extensions.js", 33 | "./dist/inputmask/inputmask.phone.extensions.js", 34 | "./dist/inputmask/jquery.inputmask.js", 35 | "./dist/inputmask/global/document.js", 36 | "./dist/inputmask/global/window.js", 37 | "./dist/inputmask/phone-codes/phone.js", 38 | "./dist/inputmask/phone-codes/phone-be.js", 39 | "./dist/inputmask/phone-codes/phone-nl.js", 40 | "./dist/inputmask/phone-codes/phone-ru.js", 41 | "./dist/inputmask/phone-codes/phone-uk.js", 42 | "./dist/inputmask/dependencyLibs/inputmask.dependencyLib.jqlite.js", 43 | "./dist/inputmask/dependencyLibs/inputmask.dependencyLib.jquery.js", 44 | "./dist/inputmask/dependencyLibs/inputmask.dependencyLib.js", 45 | "./dist/inputmask/bindings/inputmask.binding.js" 46 | ], 47 | "bower-asset-ignore": [ 48 | "**/*", 49 | "!dist/*", 50 | "!dist/inputmask/*", 51 | "!dist/min/*", 52 | "!dist/min/inputmask/*" 53 | ] 54 | }, 55 | "license": [ 56 | "http://opensource.org/licenses/mit-license.php" 57 | ], 58 | "description": "Inputmask is a javascript library which creates an input mask. Inputmask can run against vanilla javascript, jQuery and jqlite.", 59 | "keywords": [ 60 | "form", 61 | "input", 62 | "inputmask", 63 | "jquery", 64 | "mask", 65 | "plugins" 66 | ] 67 | }, 68 | { 69 | "name": "bower-asset/jquery", 70 | "version": "3.2.1", 71 | "source": { 72 | "type": "git", 73 | "url": "https://github.com/jquery/jquery-dist.git", 74 | "reference": "77d2a51d0520d2ee44173afdf4e40a9201f5964e" 75 | }, 76 | "dist": { 77 | "type": "zip", 78 | "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/77d2a51d0520d2ee44173afdf4e40a9201f5964e", 79 | "reference": "77d2a51d0520d2ee44173afdf4e40a9201f5964e", 80 | "shasum": "" 81 | }, 82 | "type": "bower-asset-library", 83 | "extra": { 84 | "bower-asset-main": "dist/jquery.js", 85 | "bower-asset-ignore": [ 86 | "package.json" 87 | ] 88 | }, 89 | "license": [ 90 | "MIT" 91 | ], 92 | "keywords": [ 93 | "browser", 94 | "javascript", 95 | "jquery", 96 | "library" 97 | ] 98 | }, 99 | { 100 | "name": "bower-asset/punycode", 101 | "version": "v1.3.2", 102 | "source": { 103 | "type": "git", 104 | "url": "https://github.com/bestiejs/punycode.js.git", 105 | "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" 106 | }, 107 | "dist": { 108 | "type": "zip", 109 | "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", 110 | "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", 111 | "shasum": "" 112 | }, 113 | "type": "bower-asset-library", 114 | "extra": { 115 | "bower-asset-main": "punycode.js", 116 | "bower-asset-ignore": [ 117 | "coverage", 118 | "tests", 119 | ".*", 120 | "component.json", 121 | "Gruntfile.js", 122 | "node_modules", 123 | "package.json" 124 | ] 125 | } 126 | }, 127 | { 128 | "name": "bower-asset/yii2-pjax", 129 | "version": "2.0.7.1", 130 | "source": { 131 | "type": "git", 132 | "url": "https://github.com/yiisoft/jquery-pjax.git", 133 | "reference": "aef7b953107264f00234902a3880eb50dafc48be" 134 | }, 135 | "dist": { 136 | "type": "zip", 137 | "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/aef7b953107264f00234902a3880eb50dafc48be", 138 | "reference": "aef7b953107264f00234902a3880eb50dafc48be", 139 | "shasum": "" 140 | }, 141 | "require": { 142 | "bower-asset/jquery": ">=1.8" 143 | }, 144 | "type": "bower-asset-library", 145 | "extra": { 146 | "bower-asset-main": "./jquery.pjax.js", 147 | "bower-asset-ignore": [ 148 | ".travis.yml", 149 | "Gemfile", 150 | "Gemfile.lock", 151 | "CONTRIBUTING.md", 152 | "vendor/", 153 | "script/", 154 | "test/" 155 | ] 156 | }, 157 | "license": [ 158 | "MIT" 159 | ] 160 | }, 161 | { 162 | "name": "cebe/markdown", 163 | "version": "1.2.1", 164 | "source": { 165 | "type": "git", 166 | "url": "https://github.com/cebe/markdown.git", 167 | "reference": "9bac5e971dd391e2802dca5400bbeacbaea9eb86" 168 | }, 169 | "dist": { 170 | "type": "zip", 171 | "url": "https://api.github.com/repos/cebe/markdown/zipball/9bac5e971dd391e2802dca5400bbeacbaea9eb86", 172 | "reference": "9bac5e971dd391e2802dca5400bbeacbaea9eb86", 173 | "shasum": "" 174 | }, 175 | "require": { 176 | "lib-pcre": "*", 177 | "php": ">=5.4.0" 178 | }, 179 | "require-dev": { 180 | "cebe/indent": "*", 181 | "facebook/xhprof": "*@dev", 182 | "phpunit/phpunit": "4.1.*" 183 | }, 184 | "bin": [ 185 | "bin/markdown" 186 | ], 187 | "type": "library", 188 | "extra": { 189 | "branch-alias": { 190 | "dev-master": "1.2.x-dev" 191 | } 192 | }, 193 | "autoload": { 194 | "psr-4": { 195 | "cebe\\markdown\\": "" 196 | } 197 | }, 198 | "notification-url": "https://packagist.org/downloads/", 199 | "license": [ 200 | "MIT" 201 | ], 202 | "authors": [ 203 | { 204 | "name": "Carsten Brandt", 205 | "email": "mail@cebe.cc", 206 | "homepage": "http://cebe.cc/", 207 | "role": "Creator" 208 | } 209 | ], 210 | "description": "A super fast, highly extensible markdown parser for PHP", 211 | "homepage": "https://github.com/cebe/markdown#readme", 212 | "keywords": [ 213 | "extensible", 214 | "fast", 215 | "gfm", 216 | "markdown", 217 | "markdown-extra" 218 | ], 219 | "time": "2018-03-26T11:24:36+00:00" 220 | }, 221 | { 222 | "name": "ezyang/htmlpurifier", 223 | "version": "v4.17.0", 224 | "source": { 225 | "type": "git", 226 | "url": "https://github.com/ezyang/htmlpurifier.git", 227 | "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c" 228 | }, 229 | "dist": { 230 | "type": "zip", 231 | "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c", 232 | "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c", 233 | "shasum": "" 234 | }, 235 | "require": { 236 | "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" 237 | }, 238 | "require-dev": { 239 | "cerdic/css-tidy": "^1.7 || ^2.0", 240 | "simpletest/simpletest": "dev-master" 241 | }, 242 | "suggest": { 243 | "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", 244 | "ext-bcmath": "Used for unit conversion and imagecrash protection", 245 | "ext-iconv": "Converts text to and from non-UTF-8 encodings", 246 | "ext-tidy": "Used for pretty-printing HTML" 247 | }, 248 | "type": "library", 249 | "autoload": { 250 | "files": [ 251 | "library/HTMLPurifier.composer.php" 252 | ], 253 | "psr-0": { 254 | "HTMLPurifier": "library/" 255 | }, 256 | "exclude-from-classmap": [ 257 | "/library/HTMLPurifier/Language/" 258 | ] 259 | }, 260 | "notification-url": "https://packagist.org/downloads/", 261 | "license": [ 262 | "LGPL-2.1-or-later" 263 | ], 264 | "authors": [ 265 | { 266 | "name": "Edward Z. Yang", 267 | "email": "admin@htmlpurifier.org", 268 | "homepage": "http://ezyang.com" 269 | } 270 | ], 271 | "description": "Standards compliant HTML filter written in PHP", 272 | "homepage": "http://htmlpurifier.org/", 273 | "keywords": [ 274 | "html" 275 | ], 276 | "time": "2023-11-17T15:01:25+00:00" 277 | }, 278 | { 279 | "name": "gregwar/captcha", 280 | "version": "v1.1.3", 281 | "source": { 282 | "type": "git", 283 | "url": "https://github.com/Gregwar/Captcha.git", 284 | "reference": "b4f22bc4794b652fccefae5a5be9565305e11572" 285 | }, 286 | "dist": { 287 | "type": "zip", 288 | "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/b4f22bc4794b652fccefae5a5be9565305e11572", 289 | "reference": "b4f22bc4794b652fccefae5a5be9565305e11572", 290 | "shasum": "" 291 | }, 292 | "require": { 293 | "ext-gd": "*", 294 | "php": ">=5.3.0" 295 | }, 296 | "type": "captcha", 297 | "autoload": { 298 | "psr-4": { 299 | "Gregwar\\Captcha\\": "/" 300 | } 301 | }, 302 | "notification-url": "https://packagist.org/downloads/", 303 | "license": [ 304 | "MIT" 305 | ], 306 | "authors": [ 307 | { 308 | "name": "Grégoire Passault", 309 | "email": "g.passault@gmail.com", 310 | "homepage": "http://www.gregwar.com/" 311 | }, 312 | { 313 | "name": "Jeremy Livingston", 314 | "email": "jeremy.j.livingston@gmail.com" 315 | } 316 | ], 317 | "description": "Captcha generator", 318 | "homepage": "https://github.com/Gregwar/Captcha", 319 | "keywords": [ 320 | "bot", 321 | "captcha", 322 | "spam" 323 | ], 324 | "time": "2017-09-27T06:58:14+00:00" 325 | }, 326 | { 327 | "name": "paragonie/random_compat", 328 | "version": "v9.99.100", 329 | "source": { 330 | "type": "git", 331 | "url": "https://github.com/paragonie/random_compat.git", 332 | "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" 333 | }, 334 | "dist": { 335 | "type": "zip", 336 | "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", 337 | "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", 338 | "shasum": "" 339 | }, 340 | "require": { 341 | "php": ">= 7" 342 | }, 343 | "require-dev": { 344 | "phpunit/phpunit": "4.*|5.*", 345 | "vimeo/psalm": "^1" 346 | }, 347 | "suggest": { 348 | "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." 349 | }, 350 | "type": "library", 351 | "notification-url": "https://packagist.org/downloads/", 352 | "license": [ 353 | "MIT" 354 | ], 355 | "authors": [ 356 | { 357 | "name": "Paragon Initiative Enterprises", 358 | "email": "security@paragonie.com", 359 | "homepage": "https://paragonie.com" 360 | } 361 | ], 362 | "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", 363 | "keywords": [ 364 | "csprng", 365 | "polyfill", 366 | "pseudorandom", 367 | "random" 368 | ], 369 | "time": "2020-10-15T08:29:30+00:00" 370 | }, 371 | { 372 | "name": "yiisoft/yii2", 373 | "version": "2.0.49.4", 374 | "source": { 375 | "type": "git", 376 | "url": "https://github.com/yiisoft/yii2-framework.git", 377 | "reference": "deec9b7330a09e06ab9e002c8718a8b478524dda" 378 | }, 379 | "dist": { 380 | "type": "zip", 381 | "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/deec9b7330a09e06ab9e002c8718a8b478524dda", 382 | "reference": "deec9b7330a09e06ab9e002c8718a8b478524dda", 383 | "shasum": "" 384 | }, 385 | "require": { 386 | "bower-asset/inputmask": "~3.2.2 | ~3.3.5", 387 | "bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", 388 | "bower-asset/punycode": "1.3.*", 389 | "bower-asset/yii2-pjax": "~2.0.1", 390 | "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0", 391 | "ext-ctype": "*", 392 | "ext-mbstring": "*", 393 | "ezyang/htmlpurifier": "^4.6", 394 | "lib-pcre": "*", 395 | "paragonie/random_compat": ">=1", 396 | "php": ">=5.4.0", 397 | "yiisoft/yii2-composer": "~2.0.4" 398 | }, 399 | "bin": [ 400 | "yii" 401 | ], 402 | "type": "library", 403 | "extra": { 404 | "branch-alias": { 405 | "dev-master": "2.0.x-dev" 406 | } 407 | }, 408 | "autoload": { 409 | "psr-4": { 410 | "yii\\": "" 411 | } 412 | }, 413 | "notification-url": "https://packagist.org/downloads/", 414 | "license": [ 415 | "BSD-3-Clause" 416 | ], 417 | "authors": [ 418 | { 419 | "name": "Qiang Xue", 420 | "email": "qiang.xue@gmail.com", 421 | "homepage": "https://www.yiiframework.com/", 422 | "role": "Founder and project lead" 423 | }, 424 | { 425 | "name": "Alexander Makarov", 426 | "email": "sam@rmcreative.ru", 427 | "homepage": "https://rmcreative.ru/", 428 | "role": "Core framework development" 429 | }, 430 | { 431 | "name": "Maurizio Domba", 432 | "homepage": "http://mdomba.info/", 433 | "role": "Core framework development" 434 | }, 435 | { 436 | "name": "Carsten Brandt", 437 | "email": "mail@cebe.cc", 438 | "homepage": "https://www.cebe.cc/", 439 | "role": "Core framework development" 440 | }, 441 | { 442 | "name": "Timur Ruziev", 443 | "email": "resurtm@gmail.com", 444 | "homepage": "http://resurtm.com/", 445 | "role": "Core framework development" 446 | }, 447 | { 448 | "name": "Paul Klimov", 449 | "email": "klimov.paul@gmail.com", 450 | "role": "Core framework development" 451 | }, 452 | { 453 | "name": "Dmitry Naumenko", 454 | "email": "d.naumenko.a@gmail.com", 455 | "role": "Core framework development" 456 | }, 457 | { 458 | "name": "Boudewijn Vahrmeijer", 459 | "email": "info@dynasource.eu", 460 | "homepage": "http://dynasource.eu", 461 | "role": "Core framework development" 462 | } 463 | ], 464 | "description": "Yii PHP Framework Version 2", 465 | "homepage": "https://www.yiiframework.com/", 466 | "keywords": [ 467 | "framework", 468 | "yii2" 469 | ], 470 | "funding": [ 471 | { 472 | "url": "https://github.com/yiisoft", 473 | "type": "github" 474 | }, 475 | { 476 | "url": "https://opencollective.com/yiisoft", 477 | "type": "open_collective" 478 | }, 479 | { 480 | "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2", 481 | "type": "tidelift" 482 | } 483 | ], 484 | "time": "2024-06-10T20:26:16+00:00" 485 | }, 486 | { 487 | "name": "yiisoft/yii2-composer", 488 | "version": "2.0.10", 489 | "source": { 490 | "type": "git", 491 | "url": "https://github.com/yiisoft/yii2-composer.git", 492 | "reference": "94bb3f66e779e2774f8776d6e1bdeab402940510" 493 | }, 494 | "dist": { 495 | "type": "zip", 496 | "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/94bb3f66e779e2774f8776d6e1bdeab402940510", 497 | "reference": "94bb3f66e779e2774f8776d6e1bdeab402940510", 498 | "shasum": "" 499 | }, 500 | "require": { 501 | "composer-plugin-api": "^1.0 | ^2.0" 502 | }, 503 | "require-dev": { 504 | "composer/composer": "^1.0 | ^2.0@dev", 505 | "phpunit/phpunit": "<7" 506 | }, 507 | "type": "composer-plugin", 508 | "extra": { 509 | "class": "yii\\composer\\Plugin", 510 | "branch-alias": { 511 | "dev-master": "2.0.x-dev" 512 | } 513 | }, 514 | "autoload": { 515 | "psr-4": { 516 | "yii\\composer\\": "" 517 | } 518 | }, 519 | "notification-url": "https://packagist.org/downloads/", 520 | "license": [ 521 | "BSD-3-Clause" 522 | ], 523 | "authors": [ 524 | { 525 | "name": "Qiang Xue", 526 | "email": "qiang.xue@gmail.com" 527 | }, 528 | { 529 | "name": "Carsten Brandt", 530 | "email": "mail@cebe.cc" 531 | } 532 | ], 533 | "description": "The composer plugin for Yii extension installer", 534 | "keywords": [ 535 | "composer", 536 | "extension installer", 537 | "yii2" 538 | ], 539 | "funding": [ 540 | { 541 | "url": "https://github.com/yiisoft", 542 | "type": "github" 543 | }, 544 | { 545 | "url": "https://opencollective.com/yiisoft", 546 | "type": "open_collective" 547 | }, 548 | { 549 | "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2-composer", 550 | "type": "tidelift" 551 | } 552 | ], 553 | "time": "2020-06-24T00:04:01+00:00" 554 | } 555 | ], 556 | "packages-dev": [], 557 | "aliases": [], 558 | "minimum-stability": "stable", 559 | "stability-flags": [], 560 | "prefer-stable": false, 561 | "prefer-lowest": false, 562 | "platform": [], 563 | "platform-dev": [], 564 | "plugin-api-version": "1.1.0" 565 | } 566 | -------------------------------------------------------------------------------- /src/Captcha.php: -------------------------------------------------------------------------------- 1 | $model, 30 | * 'attribute' => 'captcha', 31 | * ]); 32 | * ``` 33 | * 34 | * The following example will use the name property instead: 35 | * 36 | * ```php 37 | * echo Captcha::widget([ 38 | * 'name' => 'captcha', 39 | * ]); 40 | * ``` 41 | * 42 | * You can also use this widget in an [[\yii\widgets\ActiveForm|ActiveForm]] using the [[\yii\widgets\ActiveField::widget()|widget()]] 43 | * method, for example like this: 44 | * 45 | * ```php 46 | * field($model, 'captcha')->widget(\juliardi\captcha\Captcha::class, [ 47 | * // configure additional widget properties here 48 | * ]) ?> 49 | * ``` 50 | */ 51 | class Captcha extends InputWidget 52 | { 53 | /** 54 | * @var string|array the route of the action that generates the CAPTCHA images. 55 | * The action represented by this route must be an action of [[CaptchaAction]]. 56 | * Please refer to [[\yii\helpers\Url::toRoute()]] for acceptable formats. 57 | */ 58 | public $captchaAction = 'site/captcha'; 59 | 60 | /** 61 | * @var array HTML attributes to be applied to the CAPTCHA image tag. 62 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 63 | */ 64 | public $imageOptions = []; 65 | 66 | /** 67 | * @var string the template for arranging the CAPTCHA image tag and the text input tag. 68 | * In this template, the token `{image}` will be replaced with the actual image tag, 69 | * while `{input}` will be replaced with the text input tag. 70 | */ 71 | public $template = '{image} {input}'; 72 | 73 | /** 74 | * @var array the HTML attributes for the input tag. 75 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 76 | */ 77 | public $options = ['class' => 'form-control']; 78 | 79 | 80 | /** 81 | * Initializes the widget. 82 | */ 83 | public function init() 84 | { 85 | parent::init(); 86 | 87 | if (!isset($this->imageOptions['id'])) { 88 | $this->imageOptions['id'] = $this->options['id'] . '-image'; 89 | } 90 | } 91 | 92 | /** 93 | * Renders the widget. 94 | */ 95 | public function run() 96 | { 97 | $this->registerClientScript(); 98 | $input = $this->renderInputHtml('text'); 99 | $route = $this->captchaAction; 100 | if (is_array($route)) { 101 | $route['v'] = uniqid('', true); 102 | } else { 103 | $route = [$route, 'v' => uniqid('', true)]; 104 | } 105 | $image = Html::img($route, $this->imageOptions); 106 | echo strtr($this->template, [ 107 | '{input}' => $input, 108 | '{image}' => $image, 109 | ]); 110 | } 111 | 112 | /** 113 | * Registers the needed JavaScript. 114 | */ 115 | public function registerClientScript() 116 | { 117 | $options = $this->getClientOptions(); 118 | $options = empty($options) ? '' : Json::htmlEncode($options); 119 | $id = $this->imageOptions['id']; 120 | $view = $this->getView(); 121 | CaptchaAsset::register($view); 122 | $view->registerJs("jQuery('#$id').juliardiCaptcha($options);"); 123 | } 124 | 125 | /** 126 | * Returns the options for the captcha JS widget. 127 | * @return array the options 128 | */ 129 | protected function getClientOptions() 130 | { 131 | $route = $this->captchaAction; 132 | if (is_array($route)) { 133 | $route[CaptchaAction::REFRESH_GET_VAR] = 1; 134 | } else { 135 | $route = [$route, CaptchaAction::REFRESH_GET_VAR => 1]; 136 | } 137 | 138 | $options = [ 139 | 'refreshUrl' => Url::toRoute($route), 140 | 'hashKey' => 'juliardiCaptcha/' . trim($route[0], '/'), 141 | ]; 142 | 143 | return $options; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/CaptchaAction.php: -------------------------------------------------------------------------------- 1 | [5, 7], // Random word length will be between 5 and 7 characters 61 | * ``` 62 | * 63 | * **Note:** The minimum length must be at least 3 and the maximum length must be at most 20. 64 | * 65 | */ 66 | public $length = [5, 7]; 67 | 68 | /** 69 | * @var int the quality of the generated JPEG image. Valid values are 1 - 100. Defaults to 80. 70 | */ 71 | public $quality = 80; 72 | 73 | /** 74 | * @var string|null the fixed verification code. When this property is set, 75 | * [[getVerifyCode()]] will always return the value of this property. 76 | * This is mainly used in automated tests where we want to be able to reproduce 77 | * the same verification code each time we run the tests. 78 | * If not set, it means the verification code will be randomly generated. 79 | */ 80 | public $fixedVerifyCode; 81 | 82 | /** 83 | * CaptchaBuilder instance 84 | * 85 | * @var \Gregwar\Captcha\CaptchaBuilder 86 | */ 87 | protected $captchaBuilder; 88 | 89 | /** 90 | * PhraseBuilder instance 91 | * 92 | * @var \Gregwar\Captcha\PhraseBuilder 93 | */ 94 | protected $phraseBuilder; 95 | 96 | 97 | /** 98 | * Initializes the action. 99 | */ 100 | public function init() 101 | { 102 | parent::init(); 103 | 104 | $this->phraseBuilder = new PhraseBuilder($this->normalizeLength()); 105 | $this->captchaBuilder = new CaptchaBuilder($this->fixedVerifyCode, $this->phraseBuilder); 106 | } 107 | 108 | /** 109 | * Runs the action. 110 | */ 111 | public function run() 112 | { 113 | if (Yii::$app->request->getQueryParam(self::REFRESH_GET_VAR) !== null) { 114 | // AJAX request for regenerating code 115 | $code = $this->getVerifyCode(true); 116 | Yii::$app->response->format = Response::FORMAT_JSON; 117 | return [ 118 | 'hash1' => $this->generateValidationHash($code), 119 | 'hash2' => $this->generateValidationHash(strtolower($code)), 120 | // we add a random 'v' parameter so that FireFox can refresh the image 121 | // when src attribute of image tag is changed 122 | 'url' => Url::to([$this->id, 'v' => uniqid('', true)]), 123 | ]; 124 | } 125 | 126 | $this->setHttpHeaders(); 127 | Yii::$app->response->format = Response::FORMAT_RAW; 128 | 129 | $this->captchaBuilder->build($this->width, $this->height); 130 | 131 | return $this->captchaBuilder->get($this->quality); 132 | } 133 | 134 | /** 135 | * Generates a hash code that can be used for client-side validation. 136 | * @param string $code the CAPTCHA code 137 | * @return string a hash code generated from the CAPTCHA code 138 | */ 139 | public function generateValidationHash($code) 140 | { 141 | for ($h = 0, $i = strlen($code) - 1; $i >= 0; --$i) { 142 | $h += ord($code[$i]) << $i; 143 | } 144 | 145 | return $h; 146 | } 147 | 148 | /** 149 | * Gets the verification code. 150 | * @param bool $regenerate whether the verification code should be regenerated. 151 | * @return string the verification code. 152 | */ 153 | public function getVerifyCode($regenerate = false) 154 | { 155 | if ($this->fixedVerifyCode !== null) { 156 | return $this->fixedVerifyCode; 157 | } 158 | 159 | $session = Yii::$app->getSession(); 160 | $session->open(); 161 | $name = $this->getSessionKey(); 162 | if ($session[$name] === null || $regenerate) { 163 | $session[$name] = $this->generateVerifyCode(); 164 | $session[$name . 'count'] = 1; 165 | } 166 | 167 | return $session[$name]; 168 | } 169 | 170 | /** 171 | * Validates the input to see if it matches the generated code. 172 | * @param string $input user input 173 | * @param bool $caseSensitive whether the comparison should be case-sensitive 174 | * @return bool whether the input is valid 175 | */ 176 | public function validate($input, $caseSensitive) 177 | { 178 | $code = $this->getVerifyCode(); 179 | $valid = $caseSensitive ? ($input === $code) : strcasecmp($input, $code) === 0; 180 | $session = Yii::$app->getSession(); 181 | $session->open(); 182 | $name = $this->getSessionKey() . 'count'; 183 | $session[$name] += 1; 184 | if ($valid || $session[$name] > $this->testLimit && $this->testLimit > 0) { 185 | $this->getVerifyCode(true); 186 | } 187 | 188 | return $valid; 189 | } 190 | 191 | /** 192 | * Generates a new verification code. 193 | * @return string the generated verification code 194 | */ 195 | protected function generateVerifyCode() 196 | { 197 | return $this->captchaBuilder->getPhrase(); 198 | } 199 | 200 | /** 201 | * Returns the session variable name used to store verification code. 202 | * @return string the session variable name 203 | */ 204 | protected function getSessionKey() 205 | { 206 | return '__captcha/' . $this->getUniqueId(); 207 | } 208 | 209 | /** 210 | * Sets the HTTP headers needed by image response. 211 | */ 212 | protected function setHttpHeaders() 213 | { 214 | Yii::$app->getResponse()->getHeaders() 215 | ->set('Pragma', 'public') 216 | ->set('Expires', '0') 217 | ->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') 218 | ->set('Content-Transfer-Encoding', 'binary') 219 | ->set('Content-type', 'image/jpeg'); 220 | } 221 | 222 | /** 223 | * Normalize the length property 224 | * 225 | * @return int 226 | */ 227 | protected function normalizeLength() 228 | { 229 | if (is_array($this->length)) { 230 | $minLength = (int) $this->length[0]; 231 | $maxLength = (int) $this->length[1]; 232 | } else { 233 | $minLength = (int) $this->length; 234 | $maxLength = (int) $this->length; 235 | } 236 | 237 | if ($minLength > $maxLength) { 238 | $maxLength = $minLength; 239 | } 240 | if ($minLength < 3) { 241 | $minLength = 3; 242 | } 243 | if ($maxLength > 20) { 244 | $maxLength = 20; 245 | } 246 | 247 | return random_int($minLength, $maxLength); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/CaptchaAsset.php: -------------------------------------------------------------------------------- 1 | message === null) { 44 | $this->message = Yii::t('yii', 'The verification code is incorrect.'); 45 | } 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | protected function validateValue($value) 52 | { 53 | $captcha = $this->createCaptchaAction(); 54 | $valid = !is_array($value) && $captcha->validate($value, $this->caseSensitive); 55 | 56 | return $valid ? null : [$this->message, []]; 57 | } 58 | 59 | /** 60 | * Creates the CAPTCHA action object from the route specified by [[captchaAction]]. 61 | * @return \yii\captcha\CaptchaAction the action object 62 | * @throws InvalidConfigException 63 | */ 64 | public function createCaptchaAction() 65 | { 66 | $ca = Yii::$app->createController($this->captchaAction); 67 | if ($ca !== false) { 68 | /* @var $controller \yii\base\Controller */ 69 | list($controller, $actionID) = $ca; 70 | $action = $controller->createAction($actionID); 71 | if ($action !== null) { 72 | return $action; 73 | } 74 | } 75 | throw new InvalidConfigException('Invalid CAPTCHA action ID: ' . $this->captchaAction); 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function clientValidateAttribute($model, $attribute, $view) 82 | { 83 | ValidationAsset::register($view); 84 | $options = $this->getClientOptions($model, $attribute); 85 | 86 | return 'juliardi.validation.captcha(value, messages, ' . Json::htmlEncode($options) . ');'; 87 | } 88 | 89 | /** 90 | * {@inheritdoc} 91 | */ 92 | public function getClientOptions($model, $attribute) 93 | { 94 | $captcha = $this->createCaptchaAction(); 95 | $code = $captcha->getVerifyCode(false); 96 | $hash = $captcha->generateValidationHash($this->caseSensitive ? $code : strtolower($code)); 97 | $options = [ 98 | 'hash' => $hash, 99 | 'hashKey' => 'juliardiCaptcha/' . $captcha->getUniqueId(), 100 | 'caseSensitive' => $this->caseSensitive, 101 | 'message' => Yii::$app->getI18n()->format($this->message, [ 102 | 'attribute' => $model->getAttributeLabel($attribute), 103 | ], Yii::$app->language), 104 | ]; 105 | if ($this->skipOnEmpty) { 106 | $options['skipOnEmpty'] = 1; 107 | } 108 | 109 | return $options; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/ValidationAsset.php: -------------------------------------------------------------------------------- 1 | = 0; --i) { 27 | h += v.charCodeAt(i) << i; 28 | } 29 | if (h != hash) { 30 | pub.addMessage(messages, options.message, value); 31 | } 32 | }, 33 | }; 34 | 35 | return pub; 36 | })(jQuery); 37 | --------------------------------------------------------------------------------