diff --git a/composer.json b/composer.json index f645254..7fccfa1 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "melvinachterhuis/turnstile-shopware6-plugin", "description": "CloudFlare Turnstile Captcha", - "version": "0.1.05-alpha", + "version": "0.1.06-alpha", "type": "shopware-platform-plugin", "license": "MIT", "authors": [ @@ -13,8 +13,8 @@ } ], "require": { - "shopware/core": "6.6.*", - "ext-curl": "*" + "shopware/core": "~6.6.0", + "shopware/storefront:": "~6.6.0" }, "autoload": { "psr-4": { @@ -22,10 +22,14 @@ } }, "extra": { - "shopware-plugin-class": "TurnstileReMaster\\MelvTurnstile", + "shopware-plugin-class": "TurnstileReMaster\\TurnstileReMaster", "label": { "de-DE": "Turnstile-Remaster (Canary)", "en-GB": "Turnstile-Remaster (Canary)" + }, + "description": { + "de-DE": "CloudFlare Turnstile für Shopware 6, neu aufgebaut von SLINIcraftet204", + "en-GB": "CloudFlare Turnstile for Shopware 6, rebuilt by SLINIcraftet204" } } } diff --git a/src/Migration/Migration1664374217addTurnStileCaptcha.php b/src/Migration/Migration1664374217addTurnStileCaptcha.php deleted file mode 100644 index ea3f6c7..0000000 --- a/src/Migration/Migration1664374217addTurnStileCaptcha.php +++ /dev/null @@ -1,158 +0,0 @@ - - */ - private function getCloudflareDefaultConfig(): array - { - return [ - 'name' => self::CLOUDFLARE_CAPTCHA_KEY, // Oder 'Cloudflare Turnstile' für den Anzeigenamen - 'isActive' => false, // Standardmäßig deaktiviert - 'config' => [ - 'siteKey' => '', - 'secretKey' => '', - ], - ]; - } - - public function update(Connection $connection): void - { - // Hole die aktuelle globale Konfiguration (sales_channel_id IS NULL) - $currentConfig = $connection->fetchAssociative( - 'SELECT id, configuration_value FROM system_config WHERE configuration_key = :key AND sales_channel_id IS NULL LIMIT 1', - ['key' => self::CONFIG_KEY] - ); - - // Fall 1: Der Basiseintrag für Captchas existiert noch gar nicht (sehr unwahrscheinlich im Core, aber sicher ist sicher) - if ($currentConfig === false) { - // Erstelle einen neuen Eintrag mit allen Captchas (dies sollte selten passieren) - // Holen der Standard-Captchas von Shopware wäre hier besser, aber für den Moment erstellen wir nur mit unserem - $allCaptchas = $this->getDefaultShopwareCaptchas(); // Hole Standard-Captchas (siehe Helper unten) - $allCaptchas[self::CLOUDFLARE_CAPTCHA_KEY] = $this->getCloudflareDefaultConfig(); - - $connection->insert('system_config', [ - 'id' => Uuid::randomBytes(), - 'configuration_key' => self::CONFIG_KEY, - 'configuration_value' => json_encode(['_value' => $allCaptchas]), - 'sales_channel_id' => null, - 'created_at' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT), - ]); - return; // Fertig - } - - $configId = $currentConfig['id']; - $currentValueJson = $currentConfig['configuration_value']; - - // Fall 2: Eintrag existiert, aber Wert ist leer oder ungültig - if (empty($currentValueJson)) { - $configValue = ['_value' => []]; // Start mit leerem Array - } else { - $configValue = json_decode($currentValueJson, true); - // Prüfen, ob JSON-Dekodierung fehlschlug oder die Struktur nicht passt - if (json_last_error() !== JSON_ERROR_NONE || !isset($configValue['_value']) || !is_array($configValue['_value'])) { - // Logge einen Fehler oder überschreibe mit sinnvollem Standard? Fürs Erste überspringen wir. - // Alternativ: $configValue = ['_value' => []]; // Reset bei ungültiger Struktur - // Oder wir loggen es und tun nichts: error_log('Invalid JSON structure for ' . self::CONFIG_KEY); - return; // Änderung nicht sicher möglich - } - } - - // Fall 3: Eintrag existiert und ist gültig - Prüfen, ob unser Captcha schon da ist - if (isset($configValue['_value'][self::CLOUDFLARE_CAPTCHA_KEY])) { - // Unser Captcha ist bereits vorhanden, nichts zu tun. - return; - } - - // Fall 4: Unser Captcha hinzufügen - $configValue['_value'][self::CLOUDFLARE_CAPTCHA_KEY] = $this->getCloudflareDefaultConfig(); - - // Die aktualisierte Konfiguration zurück in die Datenbank schreiben - $connection->update( - 'system_config', - [ - // JSON mit korrektem Wrapping (_value) speichern - 'configuration_value' => json_encode($configValue), - // Wichtig: updated_at setzen, nicht created_at! - 'updated_at' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT), - ], - [ - // Sicherstellen, dass wir den korrekten Datensatz aktualisieren - 'id' => $configId, - ] - ); - } - - /** - * Helper, um die Standard-Captcha-Liste zu bekommen (vereinfacht). - * In einer echten Migration könnte man versuchen, diese dynamischer zu laden, - * aber für den Zweck der Migration ist eine feste Liste oft ausreichend. - * WICHTIG: Diese Liste muss ggf. an die Ziel-Shopware-Version angepasst werden! - * Prüfe die `shopware/core/System/SystemConfig/Service/ConfigurationService.php` - * oder ähnliche Core-Dateien der Zielversion für die korrekten Standard-Captchas. - * - * @return array - */ - private function getDefaultShopwareCaptchas(): array - { - // Diese Liste basiert auf deiner alten Migration und muss ggf. für SW 6.6 geprüft/angepasst werden! - // Shopware 6.6 enthält möglicherweise andere/mehr Standard-Captchas. - return [ - 'honeypot' => [ - 'name' => 'Honeypot', - 'isActive' => true, // Honeypot ist oft standardmäßig aktiv - ], - 'basicCaptcha' => [ - 'name' => 'basicCaptcha', - 'isActive' => false, - ], - 'googleReCaptchaV2' => [ - 'name' => 'googleReCaptchaV2', - 'isActive' => false, - 'config' => [ - 'siteKey' => '', - 'secretKey' => '', - 'invisible' => false, - ], - ], - 'googleReCaptchaV3' => [ - 'name' => 'googleReCaptchaV3', - 'isActive' => false, - 'config' => [ - 'siteKey' => '', - 'secretKey' => '', - 'thresholdScore' => 0.5, - ], - ], - // Füge hier ggf. weitere Standard-Captchas von SW 6.6 hinzu - ]; - } - - - public function updateDestructive(Connection $connection): void - { - // Implementiere, falls nötig (z.B. Entfernen alter Konfigurationen) - // Normalerweise hier nichts zu tun für das Hinzufügen eines Captchas. - } -} \ No newline at end of file diff --git a/src/Migration/Migration1700000000AddCloudflareTurnstileCaptcha.php b/src/Migration/Migration1700000000AddCloudflareTurnstileCaptcha.php new file mode 100644 index 0000000..238eb1c --- /dev/null +++ b/src/Migration/Migration1700000000AddCloudflareTurnstileCaptcha.php @@ -0,0 +1,97 @@ + self::CLOUDFLARE_CAPTCHA_KEY, // Technischer Name für die Identifikation + 'isActive' => false, // Standardmäßig deaktiviert + 'config' => [ + 'siteKey' => '', + 'secretKey' => '', + ], + ]; + } + + public function update(Connection $connection): void + { + $configId = $connection->fetchOne( + 'SELECT id FROM system_config WHERE configuration_key = :key AND sales_channel_id IS NULL LIMIT 1', + ['key' => self::CONFIG_KEY] + ); + + if ($configId === false) { + // Fallback: Eintrag existiert nicht -> Erstelle ihn mit CloudFlare Captcha + // Normalerweise existiert der Core-Eintrag bereits schon. + $connection->insert('system_config', [ + 'id' => Uuid::randomBytes(), + 'configuration_key' => self::CONFIG_KEY, + 'configuration_value' => json_encode(['_value' => [self::CLOUDFLARE_CAPTCHA_KEY => $this->getCloudflareDefaultConfig()]]), + 'sales_channel_id' => null, + 'created_at' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT), + ]); + return; + } + + // Eintrag existiert, hole aktuellen Wert + $currentValueJson = $connection->fetchOne( + 'SELECT configuration_value FROM system_config WHERE id = :id', + ['id' => $configId] + ); + + $configValue = ['_value' => []]; // Standard, falls JSON leer oder ungültig + if (!empty($currentValueJson)) { + $decoded = json_decode($currentValueJson, true); + if (json_last_error() === JSON_ERROR_NONE && isset($decoded['_value']) && is_array($decoded['_value'])) { + $configValue = $decoded; + } else { + // Log oder Fehlerbehandlung, wenn JSON-Struktur kaputt ist. + // Für die Migration wird mit einem leeren Array fortgesetzt oder abgebrochen. + error_log('TurnstileReMaster Migration: Invalid JSON structure found for ' . self::CONFIG_KEY); + // return; // Oder Abbruch, wenn es zu riskant ist + } + } + + // Prüfen, ob CloudFlare Captcha schon da ist + if (isset($configValue['_value'][self::CLOUDFLARE_CAPTCHA_KEY])) { + return; // Nichts zu tun + } + + // CloudFlare Captcha hinzufügen + $configValue['_value'][self::CLOUDFLARE_CAPTCHA_KEY] = $this->getCloudflareDefaultConfig(); + + // Aktualisierte Konfiguration speichern + $connection->update( + 'system_config', + [ + 'configuration_value' => json_encode($configValue), + 'updated_at' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT), + ], + ['id' => $configId] + ); + } + + public function updateDestructive(Connection $connection): void + { + // Nichts zu tun hier + } +} \ No newline at end of file diff --git a/src/Resources/app/administration/src/main.js b/src/Resources/app/administration/src/main.js index 66d3cd8..4e699f6 100644 --- a/src/Resources/app/administration/src/main.js +++ b/src/Resources/app/administration/src/main.js @@ -1,5 +1,5 @@ -// src/Resources/app/administration/src/main.js +// Import the component override import './module/sw-settings-basic-information/component/sw-settings-captcha-select-v2'; -// Füge zur Sicherheit hier einen Log hinzu, um zu sehen, ob DIESE Datei geladen wird -console.log('DEBUG Turnstile Plugin: main.js EXECUTED.'); \ No newline at end of file +// Debug log to confirm this file is loaded +console.log('[TurnstileReMaster] Admin main.js loaded.'); \ No newline at end of file diff --git a/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/index.js b/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/index.js index 8989686..ad82853 100644 --- a/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/index.js +++ b/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/index.js @@ -1,27 +1,86 @@ -// src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/index.js +import template from './sw-settings-captcha-select-v2.html.twig'; +import deDE from './snippet/de-DE.json'; +import enGB from './snippet/en-GB.json'; -const { Component } = Shopware; +const { Component, Locale } = Shopware; -// Einfachstes mögliches Override, um zu sehen, ob es überhaupt greift -console.error('!!!!!! DEBUG Turnstile Plugin: index.js override EXECUTED !!!!!!'); // Auffällige Meldung +// Register snippets +Locale.extend('de-DE', deDE); +Locale.extend('en-GB', enGB); + +console.log('[TurnstileReMaster] Overriding sw-settings-captcha-select-v2 component...'); Component.override('sw-settings-captcha-select-v2', { - template: ` -
-

TEMPLATE OVERRIDE ACTIVE (Turnstile Debug)

-

Wenn Sie das sehen, funktioniert das Template-Override.

-

Original-Template-Inhalt wird ignoriert.

- -
- `, + template, // Use the extended template - created() { - console.error('!!!!!! DEBUG Turnstile Plugin: Component CREATED Hook EXECUTED !!!!!!'); - // $super hier weglassen für den Minimaltest - }, + computed: { + // Override the computed property that generates the dropdown options + captchaOptions() { + console.log('[TurnstileReMaster] Computing captchaOptions...'); + console.log('[TurnstileReMaster] Current allCaptchaTypes:', this.allCaptchaTypes); - computed: { - // Alle computed properties für den Test entfernen oder leer lassen - // captchaOptions() { return []; } - } -}); \ No newline at end of file + // Build the base options using helper + const options = this.buildBaseCaptchaOptions(); + console.log('[TurnstileReMaster] Base options:', JSON.parse(JSON.stringify(options))); + + // Check if CloudFlare captcha exists in the config data + if (this.allCaptchaTypes && this.allCaptchaTypes.cloudFlareTurnstile) { + console.log('[TurnstileReMaster] cloudFlareTurnstile found in allCaptchaTypes, adding.'); + // Add our captcha option + options.push({ + value: 'cloudFlareTurnstile', // Must match the key used in PHP/Migration + label: this.$tc('sw-settings-basic-information.captcha.label.cloudFlareTurnstile'), + }); + } else { + console.warn('[TurnstileReMaster] cloudFlareTurnstile NOT found in allCaptchaTypes!'); + if (this.allCaptchaTypes) { + console.warn('[TurnstileReMaster] Available keys:', Object.keys(this.allCaptchaTypes)); + } + } + + // Sort options alphabetically by label (optional feature) + options.sort((a, b) => a.label.localeCompare(b.label)); + + console.log('[TurnstileReMaster] Final captchaOptions:', JSON.parse(JSON.stringify(options))); + return options; + }, + + // Helper to build base options based on available data + // Adapt this if Shopware adds/removes core captchas in future versions + buildBaseCaptchaOptions() { + const options = []; + if (!this.allCaptchaTypes) { + console.warn('[TurnstileReMaster] allCaptchaTypes is missing in buildBaseCaptchaOptions!'); + return []; + } + if (this.allCaptchaTypes.honeypot) options.push({ value: 'honeypot', label: this.$tc('sw-settings-basic-information.captcha.label.honeypot') }); + if (this.allCaptchaTypes.basicCaptcha) options.push({ value: 'basicCaptcha', label: this.$tc('sw-settings-basic-information.captcha.label.basicCaptcha') }); + if (this.allCaptchaTypes.googleReCaptchaV2) options.push({ value: 'googleReCaptchaV2', label: this.$tc('sw-settings-basic-information.captcha.label.googleReCaptchaV2') }); + if (this.allCaptchaTypes.googleReCaptchaV3) options.push({ value: 'googleReCaptchaV3', label: this.$tc('sw-settings-basic-information.captcha.label.googleReCaptchaV3') }); + // Add other core captchas here if necessary + return options; + } + }, + + // Debug methods for the template + methods: { + stringifyForDebug(value) { + try { + if (value && typeof value === 'object') { + return JSON.stringify(Object.keys(value)); + } + return JSON.stringify(value); + } catch (e) { return '[Error stringifying]'; } + }, + getCloudflareTurnstileDataForDebug() { + if (this.allCaptchaTypes && this.allCaptchaTypes.cloudFlareTurnstile) { + try { + return JSON.stringify(this.allCaptchaTypes.cloudFlareTurnstile, null, 2); + } catch (e) { return '[Error stringifying]'; } + } + return 'Not found in allCaptchaTypes'; + } + } +}); + +console.log('[TurnstileReMaster] Override applied.'); \ No newline at end of file diff --git a/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/snippet/de-DE.json b/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/snippet/de-DE.json index 40bd863..ecdb51f 100644 --- a/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/snippet/de-DE.json +++ b/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/snippet/de-DE.json @@ -1,12 +1,13 @@ { - "sw-settings-basic-information": { - "captcha": { - "label": { - "cloudFlareTurnstile": "CloudFlare Turnstile", - "cloudFlareTurnstileSiteKey": "CloudFlare Turnstile site key", - "cloudFlareTurnstileSecretKey": "CloudFlare Turnstile secret key", - "cloudFlareTurnstileDescription": "Turnstile is CloudFlare's CAPTCHA alternative. It automatically chooses from a rotating suite of non-intrusive browser challenges based on telemetry and client behavior exhibited during a session." - } - } + "sw-settings-basic-information": { + "captcha": { + "label": { + "cloudFlareTurnstile": "Cloudflare Turnstile", + "cloudFlareTurnstileSiteKey": "Turnstile Site Key", + "cloudFlareTurnstileSecretKey": "Turnstile Secret Key", + "cloudFlareTurnstileDescription": "Schützt Ihre Website vor Spam und Missbrauch durch Cloudflare Turnstile. Erhalten Sie Ihre Zugangsdaten bei Cloudflare.", + "getCredentials": "Zugangsdaten erhalten" + } } + } } \ No newline at end of file diff --git a/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/snippet/en-GB.json b/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/snippet/en-GB.json index 40bd863..f694b0d 100644 --- a/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/snippet/en-GB.json +++ b/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/snippet/en-GB.json @@ -1,12 +1,13 @@ { - "sw-settings-basic-information": { - "captcha": { - "label": { - "cloudFlareTurnstile": "CloudFlare Turnstile", - "cloudFlareTurnstileSiteKey": "CloudFlare Turnstile site key", - "cloudFlareTurnstileSecretKey": "CloudFlare Turnstile secret key", - "cloudFlareTurnstileDescription": "Turnstile is CloudFlare's CAPTCHA alternative. It automatically chooses from a rotating suite of non-intrusive browser challenges based on telemetry and client behavior exhibited during a session." - } - } + "sw-settings-basic-information": { + "captcha": { + "label": { + "cloudFlareTurnstile": "Cloudflare Turnstile", + "cloudFlareTurnstileSiteKey": "Turnstile Site Key", + "cloudFlareTurnstileSecretKey": "Turnstile Secret Key", + "cloudFlareTurnstileDescription": "Protects your website from spam and abuse using Cloudflare Turnstile. Get your credentials from Cloudflare.", + "getCredentials": "Get credentials" + } } + } } \ No newline at end of file diff --git a/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/sw-settings-captcha-select-v2.html.twig b/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/sw-settings-captcha-select-v2.html.twig index b8fc22c..f113693 100644 --- a/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/sw-settings-captcha-select-v2.html.twig +++ b/src/Resources/app/administration/src/module/sw-settings-basic-information/component/sw-settings-captcha-select-v2/sw-settings-captcha-select-v2.html.twig @@ -1,51 +1,50 @@ - -{% block sw_settings_captcha_select_v2_google_recaptcha_v2 %} +{# Extend the last block of the original component or a suitable container #} +{% block sw_settings_captcha_select_v2_google_recaptcha_v3 %} {% parent() %} + + {# Debug Output Block - Placed outside the v-if #} + {% block sw_settings_captcha_select_v2_debug_output_turnstile_remaster %} + + +
+

1. Template Override active: YES

+

2. Component Data (allCaptchaTypes): {{ allCaptchaTypes ? 'Exists' : 'MISSING!' }}

+

3. Keys in allCaptchaTypes: {{ stringifyForDebug(allCaptchaTypes) }}

+

4. cloudFlareTurnstile in allCaptchaTypes: {{ (allCaptchaTypes && allCaptchaTypes.cloudFlareTurnstile) ? 'YES' : 'NO!' }}

+

5. Raw cloudFlareTurnstile Data:

+
{{ getCloudflareTurnstileDataForDebug() }}
+

6. Generated Options Count: {{ captchaOptions ? captchaOptions.length : 'N/A' }}

+

7. Generated Options Values: {{ captchaOptions ? stringifyForDebug(captchaOptions.map(opt => opt.value)) : 'N/A' }}

+
+
+ {% endblock %} + + {# Cloudflare Turnstile Configuration Fields - Block is sibling to others #} {% block sw_settings_captcha_select_v2_cloudflare_turnstile %} - {% block sw_settings_captcha_select_v2_debug_output_turnstile %} - - -
-

1. Template Override active: YES

-

2. Component Data (allCaptchaTypes): {{ allCaptchaTypes ? 'Exists' : 'MISSING!' }}

-

3. Keys in allCaptchaTypes: {{ stringifyForDebug(allCaptchaTypes) }}

-

4. cloudFlareTurnstile in allCaptchaTypes: {{ (allCaptchaTypes && allCaptchaTypes.cloudFlareTurnstile) ? 'YES' : 'NO!' }}

-

5. Raw cloudFlareTurnstile Data:

-
{{ getCloudflareTurnstileDataForDebug() }}
-

6. Generated Options Count: {{ captchaOptions ? captchaOptions.length : 'N/A' }}

-

7. Generated Options Values: {{ captchaOptions ? stringifyForDebug(captchaOptions.map(opt => opt.value)) : 'N/A' }}

-
-
- {% endblock %} +

+ {{ $tc('sw-settings-basic-information.captcha.label.cloudFlareTurnstileDescription') }} + + {{ $tc('sw-settings-basic-information.captcha.label.getCredentials') }} + +

- - {% block sw_settings_captcha_select_v2_cloudflare_turnstile_description %} -

- {{ $tc('sw-settings-basic-information.captcha.label.cloudFlareTurnstileDescription') }} -

- {% endblock %} + - - {% block sw_settings_captcha_select_v2_cloudflare_turnstile_site_key %} - - {% endblock %} - - - {% block sw_settings_captcha_select_v2_cloudflare_turnstile_secret_key %} - - {% endblock %} +
{% endblock %} {% endblock %} \ No newline at end of file diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 0902b64..4422148 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -1,13 +1,16 @@ - - + - - - + + + + + + + \ No newline at end of file diff --git a/src/Resources/public/administration/js/melv-turnstile.js b/src/Resources/public/administration/js/melv-turnstile.js deleted file mode 100644 index 8989788..0000000 --- a/src/Resources/public/administration/js/melv-turnstile.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){var t={};function n(l){if(t[l])return t[l].exports;var r=t[l]={i:l,l:!1,exports:{}};return e[l].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,l){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:l})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var l=Object.create(null);if(n.r(l),Object.defineProperty(l,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(l,r,function(t){return e[t]}.bind(null,r));return l},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/bundles/melvturnstile/",n(n.s="wNoc")}({"+t2p":function(e){e.exports=JSON.parse('{"sw-settings-basic-information":{"captcha":{"label":{"cloudFlareTurnstile":"CloudFlare Turnstile","cloudFlareTurnstileSiteKey":"CloudFlare Turnstile site key","cloudFlareTurnstileSecretKey":"CloudFlare Turnstile secret key","cloudFlareTurnstileDescription":"Turnstile is CloudFlare\'s CAPTCHA alternative. It automatically chooses from a rotating suite of non-intrusive browser challenges based on telemetry and client behavior exhibited during a session."}}}}')},"R+yN":function(e){e.exports=JSON.parse('{"sw-settings-basic-information":{"captcha":{"label":{"cloudFlareTurnstile":"CloudFlare Turnstile","cloudFlareTurnstileSiteKey":"CloudFlare Turnstile site key","cloudFlareTurnstileSecretKey":"CloudFlare Turnstile secret key","cloudFlareTurnstileDescription":"Turnstile is CloudFlare\'s CAPTCHA alternative. It automatically chooses from a rotating suite of non-intrusive browser challenges based on telemetry and client behavior exhibited during a session."}}}}')},wNoc:function(e,t,n){"use strict";n.r(t);var l=n("+t2p"),r=n("R+yN"),s=Shopware,i=s.Component,c=s.Locale;c.extend("en-GB",l),c.extend("de-DE",r),i.override("sw-settings-captcha-select-v2",{template:'\n{% block sw_settings_captcha_select_v2_google_recaptcha_v2 %}\n {% parent() %}\n {% block sw_settings_captcha_select_v2_cloudflare_turnstile %}\n \n\n \n {% block sw_settings_captcha_select_v2_cloudflare_turnstile_description %}\n

\n {{ $tc(\'sw-settings-basic-information.captcha.label.cloudFlareTurnstileDescription\') }}\n

\n {% endblock %}\n\n \n {% block sw_settings_captcha_select_v2_cloudflare_turnstile_site_key %}\n \n {% endblock %}\n\n \n {% block sw_settings_captcha_select_v2_cloudflare_turnstile_secret_key %}\n \n {% endblock %}\n \n {% endblock %}\n{% endblock %}'})}}); \ No newline at end of file diff --git a/src/Resources/snippet/storefront.de-DE.json b/src/Resources/snippet/storefront.de-DE.json deleted file mode 100644 index a585d78..0000000 --- a/src/Resources/snippet/storefront.de-DE.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "captcha": { - "cloudFlareTurnstile": { - "dataProtectionInformation": "This site is protected by Turnstile and the CloudFlare Privacy Policy and Terms of Service apply." - } - } -} \ No newline at end of file diff --git a/src/Resources/snippet/storefront.en-GB.json b/src/Resources/snippet/storefront.en-GB.json deleted file mode 100644 index a585d78..0000000 --- a/src/Resources/snippet/storefront.en-GB.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "captcha": { - "cloudFlareTurnstile": { - "dataProtectionInformation": "This site is protected by Turnstile and the CloudFlare Privacy Policy and Terms of Service apply." - } - } -} \ No newline at end of file diff --git a/src/Resources/views/storefront/component/captcha/cloudFlareTurnstile.html.twig b/src/Resources/views/storefront/component/captcha/cloudFlareTurnstile.html.twig index faf59a0..8283026 100644 --- a/src/Resources/views/storefront/component/captcha/cloudFlareTurnstile.html.twig +++ b/src/Resources/views/storefront/component/captcha/cloudFlareTurnstile.html.twig @@ -1,9 +1,24 @@ -{% block component_captcha_cloudflare_turnstile %} -
+{% set turnstileSiteKey = config('core.basicInformation.activeCaptchasV2.cloudFlareTurnstile.config.siteKey') %} -
- {{ "captcha.cloudFlareTurnstile.dataProtectionInformation"|trans|sw_sanitize }} +{% block component_captcha_cloudflare_turnstile_content %} + {% if turnstileSiteKey %} +
+
{# You can make theme configurable later #} +
+ {# Add a hidden input to store the token. Cloudflare might inject this automatically, + but being explicit can help in some JS scenarios. Check CF docs if needed. #} + {# #} + + {# Optional: Data protection info - Add Snippet for this! #} +
-
-{% endblock %} + {% else %} + {# Log or display error if site key is missing? #} + + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/src/Resources/views/storefront/component/recaptcha.html.twig b/src/Resources/views/storefront/component/recaptcha.html.twig deleted file mode 100644 index 19e187f..0000000 --- a/src/Resources/views/storefront/component/recaptcha.html.twig +++ /dev/null @@ -1,11 +0,0 @@ -{% sw_extends '@Storefront/storefront/component/recaptcha.html.twig' %} - -{% block component_head_javascript_recaptcha %} - {{ parent() }} - {% block component_head_javascript_turnstile %} - {% set turnstileActive = config('core.basicInformation.activeCaptchasV2.cloudFlareTurnstile.isActive') %} - {% if turnstileActive %} - - {% endif %} - {% endblock %} -{% endblock %} \ No newline at end of file diff --git a/src/Resources/views/storefront/element/cms-element-form/form-components/cms-element-form-captcha.html.twig b/src/Resources/views/storefront/element/cms-element-form/form-components/cms-element-form-captcha.html.twig new file mode 100644 index 0000000..32c651a --- /dev/null +++ b/src/Resources/views/storefront/element/cms-element-form/form-components/cms-element-form-captcha.html.twig @@ -0,0 +1,26 @@ +{% sw_extends '@Storefront/storefront/element/cms-element-form/form-components/cms-element-form-captcha.html.twig' %} + +{# Override the captcha content block #} +{% block element_form_captcha_content %} + {% set turnstileConfig = config('core.basicInformation.activeCaptchasV2.cloudFlareTurnstile') %} + {% set activeFormCaptchaName = form.getCaptcha() ? form.getCaptcha().getName() : null %} + + {# Check if Cloudflare Turnstile is globally active AND selected for this form #} + {% if turnstileConfig and turnstileConfig.isActive and activeFormCaptchaName == 'cloudFlareTurnstile' %} + {# Include our Turnstile widget #} + {% sw_include '@TurnstileReMaster/storefront/component/captcha/cloudFlareTurnstile.html.twig' %} + {% else %} + {# Fallback to the original Shopware captcha rendering #} + {{ parent() }} + {% endif %} +{% endblock %} + +{# Optionally hide honeypot if Turnstile is active for this form #} +{% block element_form_captcha_honeypot %} + {% set turnstileConfig = config('core.basicInformation.activeCaptchasV2.cloudFlareTurnstile') %} + {% set activeFormCaptchaName = form.getCaptcha() ? form.getCaptcha().getName() : null %} + + {% if not (turnstileConfig and turnstileConfig.isActive and activeFormCaptchaName == 'cloudFlareTurnstile') %} + {{ parent() }} + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/src/Resources/views/storefront/layout/meta.html.twig b/src/Resources/views/storefront/layout/meta.html.twig new file mode 100644 index 0000000..c6a8e88 --- /dev/null +++ b/src/Resources/views/storefront/layout/meta.html.twig @@ -0,0 +1,11 @@ +{% sw_extends '@Storefront/storefront/layout/meta.html.twig' %} + +{% block layout_head_javascript_tracking %} + {{ parent() }} + + {# Load Cloudflare Turnstile API script if the captcha is active and configured #} + {% set turnstileConfig = config('core.basicInformation.activeCaptchasV2.cloudFlareTurnstile') %} + {% if turnstileConfig and turnstileConfig.isActive and turnstileConfig.config.siteKey %} + + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/src/Storefront/Framework/Captcha/CloudFlareTurnstile.php b/src/Storefront/Framework/Captcha/cloudFlareTurnstile.php similarity index 50% rename from src/Storefront/Framework/Captcha/CloudFlareTurnstile.php rename to src/Storefront/Framework/Captcha/cloudFlareTurnstile.php index 71d9686..62ccfa8 100644 --- a/src/Storefront/Framework/Captcha/CloudFlareTurnstile.php +++ b/src/Storefront/Framework/Captcha/cloudFlareTurnstile.php @@ -1,71 +1,69 @@ client = $client; $this->logger = $logger; } - /** - * {@inheritdoc} - * Validiert das Cloudflare Turnstile Captcha. - * Erwartet die Konfiguration im $captchaConfig Array. - */ - public function isValid(Request $request, array $captchaConfig): bool // Klare Signatur für SW >= 6.5 + public function isValid(Request $request, array $captchaConfig): bool { - $turnstileResponse = $request->request->get(self::CAPTCHA_REQUEST_PARAMETER); // Sicherer Zugriff auf POST-Parameter + $turnstileResponse = $request->request->get(self::CAPTCHA_REQUEST_PARAMETER); if (!\is_string($turnstileResponse) || $turnstileResponse === '') { - $this->logger->debug('Cloudflare Turnstile token missing in request.', ['request_params' => $request->request->all()]); + $this->logger->debug('Cloudflare Turnstile token missing in request.'); return false; } - // Hole Secret Key aus der Konfiguration $secretKey = $captchaConfig['config']['secretKey'] ?? null; if (!\is_string($secretKey) || $secretKey === '') { - $this->logger->error('Cloudflare Turnstile secret key is missing or not configured in Shopware settings.'); - // Gib false zurück, da ohne Secret Key keine Validierung möglich ist. - // Alternativ könnte man hier eine Exception werfen, aber false ist im Sinne von isValid() oft besser. + $this->logger->error('Cloudflare Turnstile secret key is missing or not configured.'); return false; } try { - $response = $this->client->request('POST', self::CLOUDFLARE_CAPTCHA_VERIFY_ENDPOINT, [ - 'form_params' => [ + // Prepare request data for PSR-18 client + $options = [ + 'body' => http_build_query([ // Send as form data 'secret' => $secretKey, 'response' => $turnstileResponse, - 'remoteip' => $request->getClientIp(), // Client IP mitsenden ist optional aber empfohlen + 'remoteip' => $request->getClientIp(), + ]), + 'headers' => [ + 'Content-Type' => 'application/x-www-form-urlencoded', ], - 'timeout' => 5, // Timeout hinzufügen, um lange Wartezeiten zu vermeiden - ]); + //TODO Timeout handling might need custom setup depending on the client implementation + ]; + + $apiRequest = $this->client->createRequest('POST', self::CLOUDFLARE_CAPTCHA_VERIFY_ENDPOINT); + foreach ($options['headers'] as $key => $value) { + $apiRequest = $apiRequest->withHeader($key, $value); + } + $apiRequest->getBody()->write($options['body']); + + + $response = $this->client->sendRequest($apiRequest); $responseBody = $response->getBody()->getContents(); $responseData = json_decode($responseBody, true); @@ -81,38 +79,27 @@ final class CloudFlareTurnstile extends AbstractCaptcha // 'final', wenn keine A } if ($responseData['success'] === true) { - return true; // Captcha ist gültig + $this->logger->debug('Cloudflare Turnstile validation successful.'); + return true; } - // Logge Fehlercodes von Cloudflare, falls vorhanden $errorCodes = $responseData['error-codes'] ?? []; $this->logger->info('Cloudflare Turnstile validation failed.', [ 'success' => false, 'error-codes' => $errorCodes, - 'clientIp' => $request->getClientIp() // Logge relevante Infos + 'clientIp' => $request->getClientIp() ]); - return false; // Captcha ist ungültig + return false; } catch (ClientExceptionInterface $e) { - // Fehler bei der HTTP-Anfrage an Cloudflare (Netzwerkproblem, Timeout etc.) - $this->logger->error('HTTP error during Cloudflare Turnstile verification.', [ - 'exception' => $e->getMessage(), - 'trace' => $e->getTraceAsString() // Optional für Debugging - ]); - return false; // Bei Fehlern als ungültig betrachten + $this->logger->error('HTTP error during Cloudflare Turnstile verification.', ['exception' => $e->getMessage()]); + return false; } catch (Throwable $e) { - // Fange andere mögliche Fehler ab (z.B. Guzzle-Fehler, JSON-Fehler etc.) - $this->logger->error('Unexpected error during Cloudflare Turnstile verification.', [ - 'exception' => $e->getMessage(), - 'trace' => $e->getTraceAsString() - ]); + $this->logger->error('Unexpected error during Cloudflare Turnstile verification.', ['exception' => $e->getMessage()]); return false; } } - /** - * {@inheritdoc} - */ public function getName(): string { return self::CAPTCHA_NAME; diff --git a/src/MelvTurnstile.php b/src/TurnstileReMaster.php similarity index 71% rename from src/MelvTurnstile.php rename to src/TurnstileReMaster.php index eb56f6c..32ce2d5 100644 --- a/src/MelvTurnstile.php +++ b/src/TurnstileReMaster.php @@ -4,6 +4,6 @@ namespace TurnstileReMaster; use Shopware\Core\Framework\Plugin; -class MelvTurnstile extends Plugin +class TurnstileReMaster extends Plugin { -} +} \ No newline at end of file