diff --git a/local/modules/Front/Controller/ContactController.php b/local/modules/Front/Controller/ContactController.php index e476a015..8bab6d1f 100644 --- a/local/modules/Front/Controller/ContactController.php +++ b/local/modules/Front/Controller/ContactController.php @@ -23,12 +23,16 @@ namespace Front\Controller; +use ReCaptcha\Event\ReCaptchaCheckEvent; +use ReCaptcha\Event\ReCaptchaEvents; +use Thelia\Model\ModuleQuery; use Thelia\Controller\Front\BaseFrontController; use Thelia\Form\Definition\FrontForm; use Thelia\Form\Exception\FormValidationException; use Thelia\Log\Tlog; use Thelia\Model\ConfigQuery; + /** * Class ContactController * @package Thelia\Controller\Front @@ -45,7 +49,16 @@ class ContactController extends BaseFrontController try { $form = $this->validateForm($contactForm); - + + $checkModule = ModuleQuery::create() + ->findOneByCode('ReCaptcha'); + + if($checkModule && $checkModule->getActivate()){ + $checkCaptchaEvent = new ReCaptchaCheckEvent(); + $this->dispatch(ReCaptchaEvents::CHECK_CAPTCHA_EVENT, $checkCaptchaEvent); + if ($checkCaptchaEvent->isHuman() == false) { throw new FormValidationException('Invalid reCAPTCHA'); } + } + $this->getMailer()->sendSimpleEmailMessage( [ ConfigQuery::getStoreEmail() => $form->get('name')->getData() ], [ ConfigQuery::getStoreEmail() => ConfigQuery::getStoreName() ], diff --git a/local/modules/Front/Controller/CustomerController.php b/local/modules/Front/Controller/CustomerController.php index 9c36aa1a..df3f065a 100644 --- a/local/modules/Front/Controller/CustomerController.php +++ b/local/modules/Front/Controller/CustomerController.php @@ -22,6 +22,9 @@ /*************************************************************************************/ namespace Front\Controller; +use ReCaptcha\Event\ReCaptchaCheckEvent; +use ReCaptcha\Event\ReCaptchaEvents; +use Thelia\Model\ModuleQuery; use Front\Front; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Thelia\Controller\Front\BaseFrontController; @@ -47,6 +50,7 @@ use Thelia\Model\NewsletterQuery; use Thelia\Tools\RememberMeTrait; use Thelia\Tools\URL; + /** * Class CustomerController * @package Thelia\Controller\Front @@ -161,6 +165,15 @@ class CustomerController extends BaseFrontController try { $form = $this->validateForm($customerCreation, "post"); + $checkModule = ModuleQuery::create() + ->findOneByCode('ReCaptcha'); + + if($checkModule && $checkModule->getActivate()){ + $checkCaptchaEvent = new ReCaptchaCheckEvent(); + $this->dispatch(ReCaptchaEvents::CHECK_CAPTCHA_EVENT, $checkCaptchaEvent); + if ($checkCaptchaEvent->isHuman() == false) { throw new \Exception('Invalid reCAPTCHA'); } + } + $customerCreateEvent = $this->createEventInstance($form->getData()); $this->dispatch(TheliaEvents::CUSTOMER_CREATEACCOUNT, $customerCreateEvent); diff --git a/local/modules/ReCaptcha/Action/ReCaptchaAction.php b/local/modules/ReCaptcha/Action/ReCaptchaAction.php new file mode 100644 index 00000000..4f8373cf --- /dev/null +++ b/local/modules/ReCaptcha/Action/ReCaptchaAction.php @@ -0,0 +1,55 @@ +request = $request; + } + + public function checkCaptcha(ReCaptchaCheckEvent $event) + { + $requestUrl = "https://www.google.com/recaptcha/api/siteverify"; + + $secretKey = ReCaptcha::getConfigValue('secret_key'); + $requestUrl .= "?secret=$secretKey"; + + $captchaResponse = $event->getCaptchaResponse(); + if (null == $captchaResponse) { + $captchaResponse = $this->request->request->get('g-recaptcha-response'); + } + + $requestUrl .= "&response=$captchaResponse"; + + $remoteIp = $event->getRemoteIp(); + if (null == $remoteIp) { + $remoteIp = $this->request->server->get('REMOTE_ADDR'); + } + + $requestUrl .= "&remoteip=$remoteIp"; + + $result = json_decode(file_get_contents($requestUrl), true); + + if ($result['success'] == true) { + $event->setHuman(true); + } + } + + public static function getSubscribedEvents() + { + return [ + ReCaptchaEvents::CHECK_CAPTCHA_EVENT => ['checkCaptcha', 128], + ]; + } +} diff --git a/local/modules/ReCaptcha/Config/config.xml b/local/modules/ReCaptcha/Config/config.xml new file mode 100644 index 00000000..2bcf4598 --- /dev/null +++ b/local/modules/ReCaptcha/Config/config.xml @@ -0,0 +1,49 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local/modules/ReCaptcha/Config/module.xml b/local/modules/ReCaptcha/Config/module.xml new file mode 100644 index 00000000..b056ed6c --- /dev/null +++ b/local/modules/ReCaptcha/Config/module.xml @@ -0,0 +1,41 @@ + + + ReCaptcha\ReCaptcha + + ReCaptcha + + + + ReCaptcha + + + + + en_US + fr_FR + + 2.0.2 + + + Vincent Lopes-Vicente + vlopes@openstudio.fr + + + classic + + 2.3.0 + other + diff --git a/local/modules/ReCaptcha/Config/routing.xml b/local/modules/ReCaptcha/Config/routing.xml new file mode 100644 index 00000000..adcae397 --- /dev/null +++ b/local/modules/ReCaptcha/Config/routing.xml @@ -0,0 +1,15 @@ + + + + + + ReCaptcha\Controller\ConfigurationController::viewAction + + + + ReCaptcha\Controller\ConfigurationController::saveAction + + + diff --git a/local/modules/ReCaptcha/Config/schema.xml b/local/modules/ReCaptcha/Config/schema.xml new file mode 100644 index 00000000..e42d1ef7 --- /dev/null +++ b/local/modules/ReCaptcha/Config/schema.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/local/modules/ReCaptcha/Controller/ConfigurationController.php b/local/modules/ReCaptcha/Controller/ConfigurationController.php new file mode 100644 index 00000000..285d7385 --- /dev/null +++ b/local/modules/ReCaptcha/Controller/ConfigurationController.php @@ -0,0 +1,50 @@ +render( + "recaptcha/configuration" + ); + } + + public function saveAction() + { + if (null !== $response = $this->checkAuth(array(AdminResources::MODULE), 'ReCaptcha', AccessManager::VIEW)) { + return $response; + } + + $form = $this->createForm("recaptcha_configuration.form"); + + try { + $data = $this->validateForm($form)->getData(); + + ReCaptcha::setConfigValue('site_key', $data['site_key']); + ReCaptcha::setConfigValue('secret_key', $data['secret_key']); + ReCaptcha::setConfigValue('captcha_style', $data['captcha_style']); + + } catch (\Exception $e) { + $this->setupFormErrorContext( + Translator::getInstance()->trans( + "Error", + [], + ReCaptcha::DOMAIN_NAME + ), + $e->getMessage(), + $form + ); + return $this->viewAction(); + } + + return $this->generateSuccessRedirect($form); + } +} diff --git a/local/modules/ReCaptcha/Event/ReCaptchaCheckEvent.php b/local/modules/ReCaptcha/Event/ReCaptchaCheckEvent.php new file mode 100644 index 00000000..42b30ddb --- /dev/null +++ b/local/modules/ReCaptcha/Event/ReCaptchaCheckEvent.php @@ -0,0 +1,80 @@ +captchaResponse = $captchaResponse; + } + + if (null !== $remoteIp) { + $this->remoteIp = $remoteIp; + } + } + + /** + * @return null + */ + public function getCaptchaResponse() + { + return $this->captchaResponse; + } + + /** + * @param null $captchaResponse + * @return ReCaptchaCheckEvent + */ + public function setCaptchaResponse($captchaResponse) + { + $this->captchaResponse = $captchaResponse; + return $this; + } + + /** + * @return null + */ + public function getRemoteIp() + { + return $this->remoteIp; + } + + /** + * @param null $remoteIp + * @return ReCaptchaCheckEvent + */ + public function setRemoteIp($remoteIp) + { + $this->remoteIp = $remoteIp; + return $this; + } + + /** + * @return bool + */ + public function isHuman() + { + return $this->human; + } + + /** + * @param bool $human + * @return ReCaptchaCheckEvent + */ + public function setHuman($human) + { + $this->human = $human; + return $this; + } +} diff --git a/local/modules/ReCaptcha/Event/ReCaptchaEvents.php b/local/modules/ReCaptcha/Event/ReCaptchaEvents.php new file mode 100644 index 00000000..2e8b054f --- /dev/null +++ b/local/modules/ReCaptcha/Event/ReCaptchaEvents.php @@ -0,0 +1,8 @@ +formBuilder + ->add( + "site_key", + "text", + [ + "data" => ReCaptcha::getConfigValue("site_key"), + "label"=>Translator::getInstance()->trans("Site key", array(), ReCaptcha::DOMAIN_NAME), + "label_attr" => ["for" => "site_key"], + "required" => true + ] + ) + ->add( + "secret_key", + "text", + [ + "data" => ReCaptcha::getConfigValue("secret_key"), + "label"=>Translator::getInstance()->trans("Secret key", array(), ReCaptcha::DOMAIN_NAME), + "label_attr" => ["for" => "secret_key"], + "required" => true + ] + ) + ->add( + "captcha_style", + "choice", + [ + "data" => ReCaptcha::getConfigValue("captcha_style"), + "label"=>Translator::getInstance()->trans("ReCaptcha style", array(), ReCaptcha::DOMAIN_NAME), + "label_attr" => ["for" => "captcha_style"], + "required" => true, + 'choices' => [ + 'normal'=>'Normal', + 'compact'=>'Compact', + 'invisible'=>'Invisible' + ] + ] + ); + } + + public function getName() + { + return "recaptcha_configuration_form"; + } +} diff --git a/local/modules/ReCaptcha/Hook/FrontHook.php b/local/modules/ReCaptcha/Hook/FrontHook.php new file mode 100644 index 00000000..cbcd3cef --- /dev/null +++ b/local/modules/ReCaptcha/Hook/FrontHook.php @@ -0,0 +1,27 @@ +add("
"); + } +} diff --git a/local/modules/ReCaptcha/I18n/backOffice/default/fr_FR.php b/local/modules/ReCaptcha/I18n/backOffice/default/fr_FR.php new file mode 100644 index 00000000..ca4e7806 --- /dev/null +++ b/local/modules/ReCaptcha/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,8 @@ + 'ReCaptcha configuration', + 'ReCaptcha module configuration' => 'ReCaptcha module configuration', + 'These infos are available here : ' => 'Ces infos sont disponibles ici : ', + 'reCAPTCHA access :' => 'reCAPTCHA accés :', +); diff --git a/local/modules/ReCaptcha/I18n/en_US.php b/local/modules/ReCaptcha/I18n/en_US.php new file mode 100644 index 00000000..0b4fa142 --- /dev/null +++ b/local/modules/ReCaptcha/I18n/en_US.php @@ -0,0 +1,4 @@ + 'The displayed english string', +); diff --git a/local/modules/ReCaptcha/I18n/fr_FR.php b/local/modules/ReCaptcha/I18n/fr_FR.php new file mode 100644 index 00000000..d0fba1e1 --- /dev/null +++ b/local/modules/ReCaptcha/I18n/fr_FR.php @@ -0,0 +1,7 @@ + 'Erreur', + 'Secret key' => 'Clé secrète', + 'Site key' => 'Clé du site', +); diff --git a/local/modules/ReCaptcha/LICENSE b/local/modules/ReCaptcha/LICENSE new file mode 100644 index 00000000..2152256c --- /dev/null +++ b/local/modules/ReCaptcha/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 OpenStudio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/local/modules/ReCaptcha/ReCaptcha.php b/local/modules/ReCaptcha/ReCaptcha.php new file mode 100644 index 00000000..dafde8f5 --- /dev/null +++ b/local/modules/ReCaptcha/ReCaptcha.php @@ -0,0 +1,55 @@ + TemplateDefinition::FRONT_OFFICE, + "code" => "recaptcha.js", + "title" => [ + "en_US" => "reCaptcha js", + "fr_FR" => "Js pour recaptcha", + ], + "block" => false, + "active" => true, + ], + [ + "type" => TemplateDefinition::FRONT_OFFICE, + "code" => "recaptcha.check", + "title" => [ + "en_US" => "reCaptcha check hook", + "fr_FR" => "reCaptcha check hook", + ], + "block" => false, + "active" => true, + ], + ]; + } +} diff --git a/local/modules/ReCaptcha/Readme.md b/local/modules/ReCaptcha/Readme.md new file mode 100644 index 00000000..cc85cd1e --- /dev/null +++ b/local/modules/ReCaptcha/Readme.md @@ -0,0 +1,62 @@ +# Re Captcha + +This module allow you to add easily a reCAPTCHA to your form +## Installation + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/re-captcha-module:~2.0.0 +``` + +## Usage + +Before using this module you have to create google api key here http://www.google.com/recaptcha/admin +next configure your reCAPTCHA access here http://your_site.com`/admin/module/ReCaptcha` with keys you obtained in Google's page +and choose which style of captcha you want : + +- A standard captcha (or a compact version of this one) + + ![Checkbox captcha](https://developers.google.com/recaptcha/images/newCaptchaAnchor.gif) + +- An invisible captcha + + ![Invisible captcha](https://developers.google.com/recaptcha/images/invisible_badge.png) + + +Then you'll need help from a developer to add some hooks in template and dispatch the check events, see details below. + +### Hook + +First if you don't have `{hook name="main.head-top"}` hook in your template you have to put this hook `{hook name="recaptcha.js"}` in the top of your head +Then add this hook `{hook name="recaptcha.check"}` in every form where you want to check if the user is human, +be careful if you want to use the invisible captcha this hook must be placed directly in the form tag like this : +``` + + {hook name="recaptcha.check"} + // End of the form +
+``` + +### Event + +To check in server-side if the captcha is valid you have to dispatch the "CHECK_CAPTCHA_EVENT" like this : +``` +$checkCaptchaEvent = new ReCaptchaCheckEvent(); +$this->dispatch(ReCaptchaEvents::CHECK_CAPTCHA_EVENT, $checkCaptchaEvent); +``` + +Then the result of check is available in `$checkCaptchaEvent->isHuman()`as boolean so you can do a test like this : +``` +if ($checkCaptchaEvent->isHuman() == false) { + throw new \Exception('Invalid captcha'); +} +``` + +Don't forget to add this use at the top of your class : +``` +use ReCaptcha\Event\ReCaptchaCheckEvent; +use ReCaptcha\Event\ReCaptchaEvents; +``` diff --git a/local/modules/ReCaptcha/composer.json b/local/modules/ReCaptcha/composer.json new file mode 100644 index 00000000..84b27144 --- /dev/null +++ b/local/modules/ReCaptcha/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/re-captcha-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "ReCaptcha" + } +} \ No newline at end of file diff --git a/local/modules/ReCaptcha/templates/backOffice/default/recaptcha/configuration.html b/local/modules/ReCaptcha/templates/backOffice/default/recaptcha/configuration.html new file mode 100644 index 00000000..1821c6d4 --- /dev/null +++ b/local/modules/ReCaptcha/templates/backOffice/default/recaptcha/configuration.html @@ -0,0 +1,69 @@ +{extends file="admin-layout.tpl"} + +{block name="after-bootstrap-css"} + +{/block} + +{block name="no-return-functions"} + {$admin_current_location = 'module'} +{/block} + +{block name="page-title"}{intl l='ReCaptcha module configuration' d='recaptcha.bo.default'}{/block} + +{block name="check-resource"}admin.module{/block} +{block name="check-access"}view{/block} +{block name="check-module"}ReCaptcha{/block} + +{block name="main-content"} +
+
+
+

+ {intl l="ReCaptcha configuration" d='recaptcha.bo.default'} +

+
+ {form name="recaptcha_configuration.form"} +
+ {form_hidden_fields form=$form} + + {if $form_error} +
{$form_error_message}
+ {/if} + + {form_field form=$form field='success_url'} + + {/form_field} + +
+
+

{intl l="reCAPTCHA access :" d='recaptcha.bo.default'}

+
+ {render_form_field form=$form field="site_key" value={$data}} +
+
+ {render_form_field form=$form field="secret_key" value={$data}} +
+
+ {render_form_field form=$form field="captcha_style" value={$data}} +
+
+

{intl l="These infos are available here : " d='recaptcha.bo.default'}http://www.google.com/recaptcha/admin

+
+
+ +
+
+
+
+ {/form} +
+
+{/block} + +{block name="javascript-initialization"} + +{/block} \ No newline at end of file diff --git a/local/modules/ReCaptcha/templates/frontOffice/default/recaptcha-js.html b/local/modules/ReCaptcha/templates/frontOffice/default/recaptcha-js.html new file mode 100644 index 00000000..40e662c5 --- /dev/null +++ b/local/modules/ReCaptcha/templates/frontOffice/default/recaptcha-js.html @@ -0,0 +1,22 @@ + + diff --git a/templates/frontOffice/boutique/contact.html b/templates/frontOffice/boutique/contact.html index dd716681..abd8ce30 100644 --- a/templates/frontOffice/boutique/contact.html +++ b/templates/frontOffice/boutique/contact.html @@ -34,6 +34,7 @@ {intl l="Send us a message"}
+ {if $form_error}
{$form_error_message}
{/if}
{form_field field="name"}
@@ -89,6 +90,11 @@
+
+ {hook name="recaptcha.check"} +
+
+ {hook name="contact.form-bottom"}
diff --git a/templates/frontOffice/boutique/register.html b/templates/frontOffice/boutique/register.html index 05e39602..14044fe3 100644 --- a/templates/frontOffice/boutique/register.html +++ b/templates/frontOffice/boutique/register.html @@ -306,6 +306,10 @@
{/form_field} +
+ {hook name="recaptcha.check"} +
+
{hook name="register.form-bottom"}