diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b35d9a --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +#Google ReCaptcha v2.0 Yii 2.x.x extension +[![Latest Stable Version](https://poser.pugx.org/brussens/yii2-recaptcha/v/stable)](https://packagist.org/packages/brussens/yii2-recaptcha) +[![Total Downloads](https://poser.pugx.org/brussens/yii2-recaptcha/downloads)](https://packagist.org/packages/brussens/yii2-recaptcha) +[![License](https://poser.pugx.org/brussens/yii2-recaptcha/license)](https://packagist.org/packages/brussens/yii2-recaptcha) +##Install +Either run +``` +php composer.phar require --prefer-dist brussens/yii2-recaptcha "*" +``` + +or add + +``` +"brussens/yii2-recaptcha": "*" +``` + +to the require section of your `composer.json` file. + +Add to your config file: +```php +'components' => [ + ... + 'recaptcha' => [ + 'class' => 'brussens\\yii2\\extensions\\recaptcha\\Component', + 'siteKey' => '!!!Insert your public key here!!!', + 'secretKey' => '!!!Insert your secret key here!!!' + ], + ... +], +``` + +Add in your model validation rules +```php +public function rules() +{ + return [ + ... + ['verifyCode', \brussens\yii2\extensions\recaptcha\Validator::className()], + ... + ]; +} +``` + +Add in your view +```php +echo $form->field($model, 'verifyCode')->widget(\brussens\yii2\extensions\recaptcha\Widget::className()); +``` + +If you use Pjax or multiple widgets on page +```php +echo $form->field($model, 'verifyCode')->widget( + \brussens\yii2\extensions\recaptcha\Widget::className(), [ + 'options' => [ + 'id' => 'insert-unique-widget-id' + ] +]); +``` \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f350aae --- /dev/null +++ b/composer.json @@ -0,0 +1,26 @@ +{ + "name": "brussens/yii2-recaptcha", + "description": "Google ReCaptcha v2.0 Yii 2.x.x extension", + "license": "MIT", + "type": "yii2-extension", + "version":"1.0.0", + "keywords": ["yii2", "yii", "recaptcha", "google"], + "authors": [ + { + "name": "Brusensky Dmitry", + "email": "brussens@nativeweb.ru", + "url": "http://nativeweb.ru" + } + ], + "minimum-stability": "dev", + "require": { + "php": ">=5.4.0", + "yiisoft/yii2": "*", + "google/recaptcha": "~1.1" + }, + "autoload": { + "psr-4": { + "brussens\\yii2\\extensions\\recaptcha\\": "src" + } + } +} \ No newline at end of file diff --git a/src/Component.php b/src/Component.php new file mode 100644 index 0000000..17c188f --- /dev/null +++ b/src/Component.php @@ -0,0 +1,40 @@ + + * @since 1.0.0 + * @version 1.0.0 + * @link https://github.com/brussens/yii2-recaptcha + * @copyright 2017 Brusenskiy Dmitry + * @license http://opensource.org/licenses/MIT MIT + * @package brussens\yii2\extensions\recaptcha + */ +namespace brussens\yii2\extensions\recaptcha; +use yii\base\InvalidConfigException; + +class Component extends \yii\base\Component +{ + /** + * Recaptcha site secret key. + * @var + */ + public $secretKey; + /** + * Recaptcha site public key. + * @var + */ + public $siteKey; + /** + * @throws InvalidConfigException + */ + public function init() + { + if(!$this->secretKey) { + throw new InvalidConfigException('Required `secretKey` param isn\'t set .'); + } + if(!$this->siteKey) { + throw new InvalidConfigException('Required `siteKey` param isn\'t set .'); + } + } +} \ No newline at end of file diff --git a/src/Validator.php b/src/Validator.php new file mode 100644 index 0000000..611b9c2 --- /dev/null +++ b/src/Validator.php @@ -0,0 +1,60 @@ + + * @since 1.0.0 + * @version 1.0.0 + * @link https://github.com/brussens/yii2-recaptcha + * @copyright 2017 Brusenskiy Dmitry + * @license http://opensource.org/licenses/MIT MIT + * @package brussens\yii2\extensions\recaptcha + */ +namespace brussens\yii2\extensions\recaptcha; +use Yii; +use ReCaptcha\ReCaptcha; +class Validator extends \yii\validators\Validator +{ + /** + * @var bool + */ + public $skipOnEmpty = false; + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if (!$this->message) { + $this->message = Yii::t('yii', 'The verification code is incorrect.'); + } + } + /** + * @param mixed $value + * @return array|null + */ + protected function validateValue($value) + { + $request = Yii::$app->getRequest(); + $value = $request->post('g-recaptcha-response'); + if(!$value) { + return [Yii::t('yii', '{attribute} cannot be blank.'), []]; + } + $recaptcha = new ReCaptcha(Yii::$app->recaptcha->secretKey); + $response = $recaptcha->verify($value, Yii::$app->getRequest()->getUserIP()); + return $response->isSuccess() ? null : [$this->message, []]; + } + /** + * @param \yii\base\Model $model + * @param string $attribute + * @param \yii\web\View $view + * @return string + */ + public function clientValidateAttribute($model, $attribute, $view) + { + $message = Yii::t('yii', '{attribute} cannot be blank.', [ + 'attribute' => $model->getAttributeLabel($attribute) + ]); + return "(function(messages){if(!grecaptcha.getResponse()){messages.push('{$message}');}})(messages);"; + } +} \ No newline at end of file diff --git a/src/Widget.php b/src/Widget.php new file mode 100644 index 0000000..0609c96 --- /dev/null +++ b/src/Widget.php @@ -0,0 +1,104 @@ + + * @since 1.0.0 + * @version 1.0.0 + * @link https://github.com/brussens/yii2-recaptcha + * @copyright 2017 Brusenskiy Dmitry + * @license http://opensource.org/licenses/MIT MIT + * @package brussens\yii2\extensions\recaptcha + */ +namespace brussens\yii2\extensions\recaptcha; +use Yii; +use yii\helpers\ArrayHelper; +use yii\helpers\Html; +use yii\widgets\InputWidget; +use yii\helpers\Json; +class Widget extends InputWidget +{ + /** + * Except languages. + */ + const EXCEPT = ['zh-HK','zh-CN','zh-TW','en-GB','fr-CA','de-AT','de-CH','pt-BR','pt-PT','es-419']; + /** + * Options of JS script. + * @see https://developers.google.com/recaptcha/docs/display#js_api + * @var array + */ + public $clientOptions = []; + /** + * Flag of rendering noscript section. + * @var bool + */ + public $renderNoScript = true; + /** + * @inheritdoc + */ + public function run() + { + parent::run(); + $this->registerScripts(); + + echo $this->hasModel() ? + Html::activeHiddenInput($this->model, $this->attribute, $this->options) : + Html::hiddenInput($this->name, $this->value, $this->options); + + echo Html::tag('div', null, [ + 'id' => $this->options['id'] . '-recaptcha-container' + ]); + if($this->renderNoScript) { + $this->renderNoScript(); + } + } + /** + * Registration client scripts. + */ + protected function registerScripts() + { + $view = $this->getView(); + $view->registerJsFile( + "//www.google.com/recaptcha/api.js?hl=" . $this->getLanguagePrefix(), + ['position' => $view::POS_HEAD, 'async' => true, 'defer' => true] + ); + + $options = ArrayHelper::merge(['sitekey' => Yii::$app->recaptcha->siteKey], $this->clientOptions); + $view->registerJs( + 'grecaptcha.render("' . $this->options['id'] . '-recaptcha-container", ' . Json::encode($options) . ');', + $view::POS_LOAD + ); + } + /** + * Rendering noscript section. + */ + protected function renderNoScript() + { + echo Html::beginTag('noscript'); + echo Html::tag('iframe', null, [ + 'src' => 'https://www.google.com/recaptcha/api/fallback?k=' . Yii::$app->recaptcha->siteKey, + 'frameborder' => 0, + 'width' => '302px', + 'height' => '423px', + 'scrolling' => 'no', + 'border-style' => 'none' + ]); + echo Html::textarea('g-recaptcha-response', null, [ + 'class' => 'form-control', + 'style' => 'margin-top: 15px' + ]); + echo Html::endTag('noscript'); + } + /** + * Normalize language code. + * @return string + */ + protected function getLanguagePrefix() + { + $language = Yii::$app->language; + if(!in_array($language, self::EXCEPT) && preg_match('/[a-z]+-[A-Z0-9]+/', $language)) { + $language = explode('-', $language)[0]; + } + return $language; + } +} \ No newline at end of file