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)
+
+ 
+
+- An invisible captcha
+
+ 
+
+
+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 :
+```
+
+```
+
+### 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}
+
+
+{/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"}