From 6590222fc6284dbaf8d11cae99b1826eb6592273 Mon Sep 17 00:00:00 2001 From: Audrius Date: Wed, 9 Sep 2026 18:58:53 +0200 Subject: [PATCH] Send the merchant notification by default on a fresh install install() registered its hooks but seeded no configuration, so CONTACTFORM_SEND_NOTIFICATION_EMAIL and CONTACTFORM_SEND_CONFIRMATION_EMAIL did not exist on a new shop. Both are read with Configuration::get() before anything is sent, and the send block is guarded by their disjunction, so a freshly installed contact form accepted messages and mailed nobody - the merchant was never told a message had arrived. Seed the two settings, with different defaults. The notification goes to the shop's own contact address and is the reason the form exists, so it is on. The confirmation goes to whatever address the visitor typed, which is what makes the form usable to mail a third party, so it stays an explicit opt-in and is off. Uninstalling a module does not remove its configuration, so install() runs again over existing values on a reset or a reinstall. seedDefault() therefore writes only when no value exists yet, and tests that through Configuration::get(), which cascades shop -> shop group -> global the way the module's own reads do. Configuration::hasKey() inspects a single level and would report "absent" under multistore for a value set globally or on the shop group, overriding a choice the merchant had already made. --- contactform.php | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/contactform.php b/contactform.php index 20d70e9..1c3f2ac 100644 --- a/contactform.php +++ b/contactform.php @@ -76,7 +76,34 @@ public function __construct() */ public function install() { - return parent::install() && $this->registerHook(['registerGDPRConsent', 'displayContactContent']); + // Both settings are read with Configuration::get() before anything is sent, so + // leaving them unset ships a contact form that silently mails nobody. They are + // seeded differently on purpose: the notification goes to the shop's own contact + // address and is the point of the form, while the confirmation goes to whatever + // address the visitor typed, so it stays an explicit opt-in. + return parent::install() + && $this->seedDefault(self::SEND_NOTIFICATION_EMAIL, 1) + && $this->seedDefault(self::SEND_CONFIRMATION_EMAIL, 0) + && $this->registerHook(['registerGDPRConsent', 'displayContactContent']); + } + + /** + * Writes a default only when this shop has no answer yet. + * + * Uninstalling a module does not remove its configuration, so install() runs again + * over existing values on a reset or a reinstall; without this guard that would + * silently overwrite a choice the merchant had already made. + * + * The check goes through Configuration::get() rather than Configuration::hasKey() + * because get() cascades shop -> shop group -> global, the way every read in this + * module does, and returns false only when no level holds the key at all. hasKey() + * inspects a single level, so under multistore it reports "absent" for a value the + * merchant set globally or on the shop group, and the default would override it. + */ + private function seedDefault(string $key, int $value): bool + { + return false !== Configuration::get($key) + || (bool) Configuration::updateValue($key, $value); } /**