plugin = $plugin; $this->altchaService = $altchaService; } /** * @param array $args Enthält 'smarty' (JTLSmarty) und 'document' (phpQueryObject). */ public function generalTemplateIntegration(array $args): void { // WICHTIG: Dieser Hook läuft bei JEDEM Seitenaufruf im gesamten Shop. Ein Fehler hier // darf niemals die Anzeige irgendeiner Shop-Seite verhindern -- deshalb komplett // defensiv mit try/catch umschlossen. Im Zweifel wird einfach kein Widget angezeigt, // statt die Seite kaputt zu machen. try { $this->doTemplateIntegration($args); } catch (\Throwable $e) { $this->logDebug($args, 'Fehler bei Template-Integration: ' . $e->getMessage()); } } /** * @param array $args */ private function doTemplateIntegration(array $args): void { /** @var phpQueryObject $document */ $document = $args['document']; $registerEnabled = $this->altchaService->isEnabledForRegistration(); $newsletterEnabled = $this->altchaService->isEnabledForNewsletter(); $injected = false; $registerFormCount = 0; if ($registerEnabled) { $registerForm = $document->find('form.register-form'); $registerFormCount = \count($registerForm); if ($registerFormCount > 0) { $this->injectWidget($document, $registerForm); $injected = true; } } $newsletterFormCount = 0; if ($newsletterEnabled) { // JTLs phpQuery-Implementierung kennt kein closest(), daher über parents() aufwärts // suchen und das nächstgelegene Formular (erster Treffer) nehmen. $newsletterForm = $document->find('input[name="abonnieren"]')->parents('form')->eq(0); $newsletterFormCount = \count($newsletterForm); if ($newsletterFormCount > 0) { $this->injectWidget($document, $newsletterForm); $injected = true; } } $contactEnabled = $this->altchaService->isEnabledForContact(); $contactFormCount = 0; if ($contactEnabled) { $contactForm = $document->find('form.contact-form'); $contactFormCount = \count($contactForm); if ($contactFormCount > 0) { $this->injectWidget($document, $contactForm); $injected = true; } } $this->logDebug($args, \sprintf( 'Hook ausgeführt: register_enabled=%d register_forms_gefunden=%d ' . 'newsletter_enabled=%d newsletter_forms_gefunden=%d ' . 'contact_enabled=%d contact_forms_gefunden=%d widget_eingefuegt=%d', $registerEnabled ? 1 : 0, $registerFormCount, $newsletterEnabled ? 1 : 0, $newsletterFormCount, $contactEnabled ? 1 : 0, $contactFormCount, $injected ? 1 : 0 )); if ($injected) { $this->includeScript($document); } } /** * Schreibt eine Diagnosemeldung, sofern in den Plugin-Einstellungen "Debug-Logging aktivieren" * eingeschaltet ist. Die Meldung geht sowohl in das PHP-Errorlog des Servers als auch -- damit * sie auch ohne Serverzugriff einsehbar ist -- als HTML-Kommentar in die ausgelieferte Seite * (für Besucher unsichtbar, im Seitenquelltext aber direkt sichtbar). * * @param array $args */ private function logDebug(array $args, string $message): void { if (!$this->altchaService->isDebug()) { return; } error_log('[fp_altcha_spamschutz] ' . $message); $document = $args['document'] ?? null; if ($document instanceof phpQueryObject) { $safeMessage = htmlspecialchars($message, \ENT_QUOTES, 'UTF-8'); $document->find('body')->append(''); } } private function injectWidget(phpQueryObject $document, phpQueryObject $form): void { // Nicht doppelt einfügen (z. B. wenn die Seite mehrfach gefiltert wird). if (\count($form->find('.fp-altcha')) > 0) { return; } $challenge = $this->altchaService->createChallengeArray(); $challengeJson = htmlspecialchars( (string) json_encode($challenge, \JSON_UNESCAPED_SLASHES), \ENT_QUOTES, 'UTF-8' ); $isGerman = empty($_SESSION['cISOSprache']) || $_SESSION['cISOSprache'] === 'ger'; $labelPreparing = $isGerman ? 'Sicherheitsprüfung wird vorbereitet …' : 'Preparing security check …'; $labelPreparing = htmlspecialchars($labelPreparing, \ENT_QUOTES, 'UTF-8'); $labelVerified = $isGerman ? 'Ja, ich bin ein Mensch' : 'Security check
passed'; $labelVerified = htmlspecialchars($labelVerified, \ENT_QUOTES, 'UTF-8'); $labelFailed = $isGerman ? 'Sicherheitsprüfung fehlgeschlagen. Bitte Seite neu laden.' : 'Security check failed. Please reload the page.'; $labelFailed = htmlspecialchars($labelFailed, \ENT_QUOTES, 'UTF-8'); $labelWait = $isGerman ? 'Bitte einen Moment warten, die Sicherheitsprüfung läuft noch.' : 'Please wait a moment, the security check is still running.'; $labelWait = htmlspecialchars($labelWait, \ENT_QUOTES, 'UTF-8'); $html = '
' . ' ' . $labelPreparing . '' . '' . '
'; $submitButton = $form->find('button[type="submit"], input[type="submit"]'); // Bei schmalen Bootstrap-"input-group"-Formularen (z. B. das Newsletter-Feld im Footer: // ein einzeiliges Eingabefeld mit direkt angehängtem Absende-Button) wuerde das Widget, // direkt vor den Button gesetzt, mit in die schmale, einzeilige Button-Leiste gequetscht. // In diesem Fall wird das Widget stattdessen als eigene Zeile UNTER der gesamten // input-group platziert. $inputGroup = null; if (\count($submitButton) > 0) { $candidateGroup = $submitButton->eq(0)->parents('.input-group')->eq(0); if (\count($candidateGroup) > 0) { $inputGroup = $candidateGroup; } } if ($inputGroup !== null) { $inputGroup->after($html); } elseif (\count($submitButton) > 0) { $submitButton->eq(0)->before($html); } else { $form->append($html); } } private function includeScript(phpQueryObject $document): void { if (\count($document->find('script[data-fp-altcha-script]')) > 0) { return; } // Bewusst analog zum bewährten Muster anderer, quelloffener JTL5-Plugins direkt über // die globale URL_SHOP-Konstante aufgebaut, statt eine Plugin-Pfad-API zu erraten. $assetUrl = \URL_SHOP . '/plugins/fp_altcha_spamschutz/assets/fp-altcha.js'; $assetUrl = htmlspecialchars($assetUrl, \ENT_QUOTES, 'UTF-8'); $document->find('body')->append( '' ); } }