Shopware 6 Formulare mit Google reCAPTCHA schützen

Seit der Version 1.1.0 des Basis-Plugins "Formulare mit Google reCAPTCHA" ist es möglich, dass eigene Formulare mit Google reCAPTCHA geschützt werden können. Diese Anleitung soll Ihnen dabei helfen eigene Erweiterungen für das Basis-Plugin zu erstellen um Ihre Formulare mit Google reCAPTCHA zu schützen. Als Beispiel dient das Plugin "Newsletter-Formular mit Google reCAPTCHA".

Newsletter-Formular mit Google reCAPTCHA

Einführung in das Pluginsystem von Shopware 6

Bevor Sie loslegen können, sollten Sie wissen wie Plugins in Shopware 6 erstellt werden. Bitte lesen Sie mehr zum Pluginsystem in der Shopware-Dokumentation.

Auf die Schnelle erklärt

  1. Es muss eine Route hinzugefügt werden (service.xml und JavaScript)
  2. Es muss geprüft werden ob die Route zutreffend ist und verarbeitet werden kann
  3. Es muss die Route verarbeitet werden und bei einem Fehlerfall eine Exception ausgeben werden
  4. Es muss bei einem Fehlerfall eine Response zurückgegeben werden

Route aufnehmen

Um das Formular beim versenden zu prüfen, muss die Adresse (Route-Name) bekannt sein und dem Basis-Plugin mitgeteilt werden, dass eine bestimmte Formular-Route verarbeitet werden soll.

Wir fügen dem Basis-Plugin per service.xml im Resources Ordner einen neuen Service hinzu, der die Formular-Route verarbeiten soll.

<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="Gbmed\FormNewsletter\Framework\Captcha\FormRoutes\Newsletter" autowire="true">
            <tag name="gbmed.form.routes"/>
        </service>
    </services>
</container>

Wichtig dabei ist <tag name="gbmed.form.routes"/>, damit dem Basis-Plugin der Service hinzugefügt wird.

Damit nun das Frontend auch was zu tun hat und das Formular mit den nötigen Google-Token bestückt wird, muss per JavaScript das Formular-Element an das Basis-Plugin hinzugefügt werden. Dazu dient das Template Resources/views/storefront/base.html.twig.

{% sw_extends '@Storefront/storefront/base.html.twig' %}

{% block base_body_script %}
    {{ parent() }}

    <script>
        {# add form elements #}
        window.gbmedFormsOptions.forms.push(
            document.querySelector("form[action='{{ path('frontend.form.newsletter.register.handle') }}']")
        );
    </script>
{% endblock %}

Es müssen Formular-Elemente an das Basis-Plugin hinzugefügt werden. In diesem Beispiel wird das Element per Form-Action ermittelt. Sie können aber auch per document.getElementById() oder document.getElementsByClassName() die Formular-Elemente hinzufügen. Im Frontend sollte nun unten rechts das Google reCAPTCHA Logo zu sehen und das Hidden-Field g-recaptcha-response vorhanden sein!

<form action="/form/newsletter" method="post" data-form-csrf-handler="true" data-form-validation="true" novalidate="">
    ...
    <input type="hidden" name="g-recaptcha-response" value="6Up8uHYU2j50qgUyMONbtGxeIcTJy7t3acngDgYZYGHURtPsVUgy5B1tpl2wBoE7WNR1dvv4b...">
    ...
</form>

Route verarbeiten

Der an das Basis-Plugin übergebene Service ist eine Erweiterung von FormRoutesAbstract

<?php
declare(strict_types=1);

namespace Gbmed\FormNewsletter\Framework\Captcha\FormRoutes;

use Gbmed\Form\Framework\Exception\CaptchaInvalidException;
use Gbmed\Form\Framework\Captcha\FormRoutes\FormRoutesAbstract;
use Gbmed\Form\Framework\Captcha\FormRoutes\FormRoutesInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;

class Newsletter extends FormRoutesAbstract
{
    public const ROUTE_NAME = 'frontend.form.newsletter.register.handle';

    /**
     * @param string $route
     * @return FormRoutesInterface|null
     */
    public function support(string $route): ?FormRoutesInterface
    {
        return $route === static::ROUTE_NAME ? $this : null;
    }

    /**
     * handle your functionality here and return true to start recaptcha request validation.
     * throw CaptchaInvalidException to display error message which is handled by self::response
     *
     * @return bool
     * @throws CaptchaInvalidException
     */
    public function handle(): bool
    {
        /*
        // example
        $value = 2;
        if($value !== 1){
            throw new CaptchaInvalidException('value is not 1');
        }
        */

        return true;
    }

    /**
     * return response on invalid captcha exception
     *
     * @param CaptchaInvalidException $exception
     * @return JsonResponse
     */
    public function response(CaptchaInvalidException $exception): ?Response
    {
        return new JsonResponse([
            [
                'type' => 'danger',
                'alert' => $this->renderView('@Storefront/storefront/utilities/alert.html.twig', [
                    'type' => 'danger',
                    'list' => [
                        $exception->getMessage()
                    ],
                ]),
            ]
        ]);
    }
}
  • support(string $route)
    Überprüft ob die aufgerufene Route von Ihrem Service verarbeitet werden soll.
  • handle()
    Hier haben Sie die Möglichkeit Daten zu verarbeiten. Wenn es Fehler bei der Datenverarbeitung gibt, dann geben Sie eine CaptchaInvalidException aus, sonst wird immer ein true zurückgegeben, damit die reCAPTCHA Validierung ausgeführt wird.
  • response(CaptchaInvalidException $exception)
    Wenn Sie eine Fehlermeldung ausgeben müssen, dann können Sie hier die CaptchaInvalidException verarbeiten und ein Response für die Darstellung der Meldung zurückgegeben.

Wissenswertes

FlashBag statt JsonResponse

Manche Formulare verarbeiten die Daten per Ajax, daher wird der JsonResponse für die Fehlermeldung benötigt. Shopware hat aber auch ein Block um Fehlermeldung anzuzeigen: src/Storefront/Resources/views/storefront/base.html.twig

{% block base_flashbags %}
    <div class="flashbags container">
        {% for type, messages in app.flashes %}
            {% sw_include '@Storefront/storefront/utilities/alert.html.twig' with { type: type, list: messages } %}
        {% endfor %}
    </div>
{% endblock %}

Wenn Sie kein JsonResponse zurückgeben und Ihre Fehlermeldungen in das FlashBag übergeben wollen, dann können Sie folgenden Codeschnippsel nutzen:

public function response(CaptchaInvalidException $exception): ?Response
{
    $this->container->get('session')->getFlashBag()->add('danger', $exception->getMessage());

    return $this->redirectToRoute(
        'frontend.account.register.page'
    );
}

Die Methode redirectToRoute() benötigt einen Route-Namen um eine URL zu generieren (empfohlen), sonst können Sie $this->redirect('/absolute-url/die-sie-kennen/und-gültig-ist') zurück geben.