completely reworked the project, changed paths, renamed files, removed files, added files and changes structure
This commit is contained in:
parent
23201641c9
commit
792df48fbb
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,158 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace TurnstileReMaster\Migration; // Stelle sicher, dass der Namespace korrekt ist
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Shopware\Core\Defaults;
|
||||
use Shopware\Core\Framework\Migration\MigrationStep;
|
||||
use Shopware\Core\Framework\Uuid\Uuid; // Importieren für `created_at` falls nicht vorhanden
|
||||
use Shopware\Core\System\SystemConfig\SystemConfigService; // Kann nützlich sein, aber hier reicht Connection
|
||||
|
||||
class Migration1664374217addTurnStileCaptcha extends MigrationStep
|
||||
{
|
||||
// Der System-Config Key für die Captchas
|
||||
private const CONFIG_KEY = 'core.basicInformation.activeCaptchasV2';
|
||||
// Der eindeutige Key für unser Captcha innerhalb der Konfiguration
|
||||
private const CLOUDFLARE_CAPTCHA_KEY = 'cloudFlareTurnstile';
|
||||
|
||||
public function getCreationTimestamp(): int
|
||||
{
|
||||
return 1664374217; // Behalte den ursprünglichen Timestamp bei
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Standardkonfiguration für Cloudflare Turnstile zurück.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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<string, mixed>
|
||||
*/
|
||||
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.
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace TurnstileReMaster\Migration;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Shopware\Core\Defaults;
|
||||
use Shopware\Core\Framework\Migration\MigrationStep;
|
||||
use Shopware\Core\Framework\Uuid\Uuid;
|
||||
|
||||
class Migration1700000000AddCloudflareTurnstileCaptcha extends MigrationStep
|
||||
{
|
||||
// Der System-Config Key für die Captchas
|
||||
private const CONFIG_KEY = 'core.basicInformation.activeCaptchasV2';
|
||||
// Der eindeutige Key für CloudFlare Captcha innerhalb der Konfiguration
|
||||
private const CLOUDFLARE_CAPTCHA_KEY = 'cloudFlareTurnstile';
|
||||
|
||||
// generischer Timestamp um nicht mit anderen Migrationen zu kollidieren
|
||||
public function getCreationTimestamp(): int
|
||||
{
|
||||
return 1700000000;
|
||||
}
|
||||
|
||||
private function getCloudflareDefaultConfig(): array
|
||||
{
|
||||
return [
|
||||
'name' => 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
|
||||
}
|
||||
}
|
@ -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.');
|
||||
// Debug log to confirm this file is loaded
|
||||
console.log('[TurnstileReMaster] Admin main.js loaded.');
|
@ -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: `
|
||||
<div style="border: 5px solid red; padding: 20px; margin: 20px;">
|
||||
<h1>TEMPLATE OVERRIDE ACTIVE (Turnstile Debug)</h1>
|
||||
<p>Wenn Sie das sehen, funktioniert das Template-Override.</p>
|
||||
<p>Original-Template-Inhalt wird ignoriert.</p>
|
||||
<!-- Ursprüngliches Template wird bewusst NICHT geladen -->
|
||||
</div>
|
||||
`,
|
||||
|
||||
created() {
|
||||
console.error('!!!!!! DEBUG Turnstile Plugin: Component CREATED Hook EXECUTED !!!!!!');
|
||||
// $super hier weglassen für den Minimaltest
|
||||
},
|
||||
template, // Use the extended template
|
||||
|
||||
computed: {
|
||||
// Alle computed properties für den Test entfernen oder leer lassen
|
||||
// captchaOptions() { return []; }
|
||||
// Override the computed property that generates the dropdown options
|
||||
captchaOptions() {
|
||||
console.log('[TurnstileReMaster] Computing captchaOptions...');
|
||||
console.log('[TurnstileReMaster] Current allCaptchaTypes:', this.allCaptchaTypes);
|
||||
|
||||
// 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.');
|
@ -2,10 +2,11 @@
|
||||
"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."
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,11 @@
|
||||
"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."
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
<!-- eslint-disable-next-line sw-deprecation-rules/no-twigjs-blocks -->
|
||||
{% 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() %}
|
||||
{% block sw_settings_captcha_select_v2_cloudflare_turnstile %}
|
||||
{% block sw_settings_captcha_select_v2_debug_output_turnstile %}
|
||||
<sw-alert variant="info" :showIcon="true" style="margin-bottom: 20px;">
|
||||
<template #title>Debug Info (Turnstile Plugin)</template>
|
||||
|
||||
{# Debug Output Block - Placed outside the v-if #}
|
||||
{% block sw_settings_captcha_select_v2_debug_output_turnstile_remaster %}
|
||||
<sw-alert variant="info" :showIcon="true" style="margin-bottom: 20px; margin-top: 20px;">
|
||||
<template #title>Debug Info (TurnstileReMaster)</template>
|
||||
<div style="font-family: monospace; white-space: pre-wrap; word-break: break-all; max-height: 300px; overflow: auto; font-size: 12px;">
|
||||
<p><strong>1. Template Override active:</strong> YES</p>
|
||||
<p><strong>2. Component Data (allCaptchaTypes):</strong> {{ allCaptchaTypes ? 'Exists' : 'MISSING!' }}</p>
|
||||
@ -17,35 +18,33 @@
|
||||
</div>
|
||||
</sw-alert>
|
||||
{% endblock %}
|
||||
|
||||
{# Cloudflare Turnstile Configuration Fields - Block is sibling to others #}
|
||||
{% block sw_settings_captcha_select_v2_cloudflare_turnstile %}
|
||||
<sw-container
|
||||
v-if="currentValue.cloudFlareTurnstile.isActive"
|
||||
v-if="currentValue && currentValue.cloudFlareTurnstile && currentValue.cloudFlareTurnstile.isActive"
|
||||
class="sw-settings-captcha-select-v2__cloudflare-turnstile"
|
||||
>
|
||||
|
||||
<!-- eslint-disable-next-line sw-deprecation-rules/no-twigjs-blocks -->
|
||||
{% block sw_settings_captcha_select_v2_cloudflare_turnstile_description %}
|
||||
<p class="sw-settings-captcha-select-v2__description sw-settings-captcha-select-v2__cloudflare-turnstile-description">
|
||||
{{ $tc('sw-settings-basic-information.captcha.label.cloudFlareTurnstileDescription') }}
|
||||
<sw-external-link href="https://developers.cloudflare.com/turnstile/" target="_blank">
|
||||
{{ $tc('sw-settings-basic-information.captcha.label.getCredentials') }}
|
||||
</sw-external-link>
|
||||
</p>
|
||||
{% endblock %}
|
||||
|
||||
<!-- eslint-disable-next-line sw-deprecation-rules/no-twigjs-blocks -->
|
||||
{% block sw_settings_captcha_select_v2_cloudflare_turnstile_site_key %}
|
||||
<sw-text-field
|
||||
v-model="currentValue.cloudFlareTurnstile.config.siteKey"
|
||||
name="cloudFlareTurnstileSiteKey"
|
||||
required
|
||||
:label="$tc('sw-settings-basic-information.captcha.label.cloudFlareTurnstileSiteKey')"
|
||||
/>
|
||||
{% endblock %}
|
||||
|
||||
<!-- eslint-disable-next-line sw-deprecation-rules/no-twigjs-blocks -->
|
||||
{% block sw_settings_captcha_select_v2_cloudflare_turnstile_secret_key %}
|
||||
<sw-text-field
|
||||
v-model="currentValue.cloudFlareTurnstile.config.secretKey"
|
||||
name="cloudFlareTurnstileSecretKey"
|
||||
required
|
||||
:label="$tc('sw-settings-basic-information.captcha.label.cloudFlareTurnstileSecretKey')"
|
||||
/>
|
||||
{% endblock %}
|
||||
</sw-container>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
@ -1,13 +1,16 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<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="TurnstileReMaster\Storefront\Framework\Captcha\CloudFlareTurnstile">
|
||||
<argument type="service" id="shopware.captcha.client"/>
|
||||
<tag name="shopware.captcha" priority="50"/>
|
||||
<service id="TurnstileReMaster\Storefront\Framework\Captcha\CloudFlareTurnstile" public="true">
|
||||
<!-- Inject the standard Shopware PSR-18 HTTP client -->
|
||||
<argument type="service" id="shopware.http_client"/>
|
||||
<!-- Inject the standard logger -->
|
||||
<argument type="service" id="logger"/>
|
||||
<!-- Tag the service as a captcha provider -->
|
||||
<tag name="shopware.captcha"/>
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
@ -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 <sw-container\n v-if="currentValue.cloudFlareTurnstile.isActive"\n class="sw-settings-captcha-select-v2__cloudflare-turnstile"\n >\n\n \n {% block sw_settings_captcha_select_v2_cloudflare_turnstile_description %}\n <p class="sw-settings-captcha-select-v2__description sw-settings-captcha-select-v2__cloudflare-turnstile-description">\n {{ $tc(\'sw-settings-basic-information.captcha.label.cloudFlareTurnstileDescription\') }}\n </p>\n {% endblock %}\n\n \n {% block sw_settings_captcha_select_v2_cloudflare_turnstile_site_key %}\n <sw-text-field\n v-model="currentValue.cloudFlareTurnstile.config.siteKey"\n name="cloudFlareTurnstileSiteKey"\n :label="$tc(\'sw-settings-basic-information.captcha.label.cloudFlareTurnstileSiteKey\')"\n />\n {% endblock %}\n\n \n {% block sw_settings_captcha_select_v2_cloudflare_turnstile_secret_key %}\n <sw-text-field\n v-model="currentValue.cloudFlareTurnstile.config.secretKey"\n name="cloudFlareTurnstileSecretKey"\n :label="$tc(\'sw-settings-basic-information.captcha.label.cloudFlareTurnstileSecretKey\')"\n />\n {% endblock %}\n </sw-container>\n {% endblock %}\n{% endblock %}'})}});
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"captcha": {
|
||||
"cloudFlareTurnstile": {
|
||||
"dataProtectionInformation": "This site is protected by Turnstile and the CloudFlare <a href=\"https://www.cloudflare.com/en-gb/privacypolicy/\">Privacy Policy</a> and <a href=\"https://www.cloudflare.com/en-gb/website-terms/\">Terms of Service</a> apply."
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"captcha": {
|
||||
"cloudFlareTurnstile": {
|
||||
"dataProtectionInformation": "This site is protected by Turnstile and the CloudFlare <a href=\"https://www.cloudflare.com/en-gb/privacypolicy/\">Privacy Policy</a> and <a href=\"https://www.cloudflare.com/en-gb/website-terms/\">Terms of Service</a> apply."
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,24 @@
|
||||
{% block component_captcha_cloudflare_turnstile %}
|
||||
<div class="cf-turnstile mb-2"
|
||||
data-sitekey="{{ config('core.basicInformation.activeCaptchasV2.cloudFlareTurnstile.config.siteKey') }}">
|
||||
{% set turnstileSiteKey = config('core.basicInformation.activeCaptchasV2.cloudFlareTurnstile.config.siteKey') %}
|
||||
|
||||
<div class="data-protection-information cf_turnstile-protection-information mb-2">
|
||||
{{ "captcha.cloudFlareTurnstile.dataProtectionInformation"|trans|sw_sanitize }}
|
||||
{% block component_captcha_cloudflare_turnstile_content %}
|
||||
{% if turnstileSiteKey %}
|
||||
<div class="turnstile-captcha-container mb-3">
|
||||
<div class="cf-turnstile"
|
||||
data-sitekey="{{ turnstileSiteKey }}"
|
||||
data-theme="light"> {# You can make theme configurable later #}
|
||||
</div>
|
||||
{# 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. #}
|
||||
{# <input type="hidden" name="cf-turnstile-response"> #}
|
||||
|
||||
{# Optional: Data protection info - Add Snippet for this! #}
|
||||
<div class="form-text captcha-consent-info mt-2">
|
||||
{# {{ "captcha.cloudflareTurnstileDataProtection"|trans|sw_sanitize }} #}
|
||||
{{ "Bitte beachten Sie die Datenschutzbestimmungen von Cloudflare."|trans|sw_sanitize }}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{# Log or display error if site key is missing? #}
|
||||
<!-- Turnstile Site Key is not configured. -->
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -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 %}
|
||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
@ -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 %}
|
11
src/Resources/views/storefront/layout/meta.html.twig
Normal file
11
src/Resources/views/storefront/layout/meta.html.twig
Normal file
@ -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 %}
|
||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -1,71 +1,69 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace TurnstileReMaster\Storefront\Framework\Captcha; // Stelle sicher, dass der Namespace korrekt ist
|
||||
namespace TurnstileReMaster\Storefront\Framework\Captcha;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use Psr\Log\LoggerInterface; // Logger hinzufügen
|
||||
use Psr\Http\Client\ClientInterface; // Use PSR-18 ClientInterface
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shopware\Storefront\Framework\Captcha\AbstractCaptcha;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Throwable; // Für generischere Exception-Behandlung
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @internal // Beibehalten, wenn es eine interne Klasse des Plugins ist
|
||||
*/
|
||||
final class CloudFlareTurnstile extends AbstractCaptcha // 'final', wenn keine Ableitung geplant ist
|
||||
final class CloudFlareTurnstile extends AbstractCaptcha
|
||||
{
|
||||
public const CAPTCHA_NAME = 'cloudFlareTurnstile';
|
||||
// Injected by CF in form
|
||||
public const CAPTCHA_REQUEST_PARAMETER = 'cf-turnstile-response';
|
||||
private const CLOUDFLARE_CAPTCHA_VERIFY_ENDPOINT = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
|
||||
|
||||
private ClientInterface $client;
|
||||
private LoggerInterface $logger; // Logger-Instanz
|
||||
private LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(
|
||||
ClientInterface $client,
|
||||
LoggerInterface $logger // Logger injizieren
|
||||
ClientInterface $client, // Inject PSR-18 client
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->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;
|
@ -4,6 +4,6 @@ namespace TurnstileReMaster;
|
||||
|
||||
use Shopware\Core\Framework\Plugin;
|
||||
|
||||
class MelvTurnstile extends Plugin
|
||||
class TurnstileReMaster extends Plugin
|
||||
{
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user