From 4fe8b7c1e87aeab458769893b6ae925af36cf6cd Mon Sep 17 00:00:00 2001 From: Stuart Clark Date: Tue, 25 Aug 2026 23:33:07 +0000 Subject: [PATCH 1/2] fix(#2): exclude system.site mail_notification by default, with an update hook --- README.md | 3 ++- .../install/decoupled_settings.settings.yml | 1 + decoupled_settings.install | 18 +++++++++++++ tests/src/Kernel/SettingsResolverTest.php | 26 +++++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9257b33..8db045d 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,8 @@ site value when it changes. separate action, and restores inheritance. - Only listed config objects are exposed, and within them only the keys their schema declares. Internal keys are always dropped, and an exclusion - list removes the rest. The site email address is excluded by default. + list removes the rest. The site email address and the notification + address are excluded by default. - Logo and favicon come from core's theme settings resolution, as usable URLs with core's fallbacks. - Responses carry the config cache tags of every object read, and a cache diff --git a/config/install/decoupled_settings.settings.yml b/config/install/decoupled_settings.settings.yml index 22284f7..a45fc1b 100644 --- a/config/install/decoupled_settings.settings.yml +++ b/config/install/decoupled_settings.settings.yml @@ -8,3 +8,4 @@ expose_theme_settings: true # Schema bounds the shape of an object, not the sensitivity of its values. excluded_keys: - 'system.site:mail' + - 'system.site:mail_notification' diff --git a/decoupled_settings.install b/decoupled_settings.install index 0dd003b..a489291 100644 --- a/decoupled_settings.install +++ b/decoupled_settings.install @@ -28,3 +28,21 @@ function decoupled_settings_install(): void { } } } + +/** + * Exclude system.site:mail_notification from the exposed settings. + * + * The shipped exclude list held system.site:mail but not the notification + * address next to it. Append it for existing installs. A site that wants + * the key exposed can remove it again: this hook runs once. + */ +function decoupled_settings_update_10001(): string { + $config = \Drupal::configFactory()->getEditable('decoupled_settings.settings'); + $excluded = $config->get('excluded_keys') ?? []; + if (!in_array('system.site:mail_notification', $excluded, TRUE)) { + $excluded[] = 'system.site:mail_notification'; + $config->set('excluded_keys', $excluded)->save(); + return 'system.site:mail_notification is now excluded.'; + } + return 'system.site:mail_notification was already excluded.'; +} diff --git a/tests/src/Kernel/SettingsResolverTest.php b/tests/src/Kernel/SettingsResolverTest.php index 3c218d0..4045078 100644 --- a/tests/src/Kernel/SettingsResolverTest.php +++ b/tests/src/Kernel/SettingsResolverTest.php @@ -56,6 +56,7 @@ protected function setUp(): void { $this->config('system.site') ->set('name', 'Global Site') ->set('slogan', 'Global slogan') + ->set('mail_notification', 'webmaster@example.com') ->save(); // Theme settings are off by default here, so each test opts in. @@ -141,6 +142,31 @@ public function testExcludedKeyIsNotExposed(): void { $resolved = $this->resolver->resolve(NULL, new CacheableMetadata()); $this->assertArrayNotHasKey('mail', $resolved['system.site']); + $this->assertArrayNotHasKey('mail_notification', $resolved['system.site']); + } + + /** + * The update hook appends the notification address exclusion once. + */ + public function testUpdateHookExcludesMailNotification(): void { + \Drupal::moduleHandler()->loadInclude('decoupled_settings', 'install'); + + // An install from before the exclusion shipped. + $this->config('decoupled_settings.settings') + ->set('excluded_keys', ['system.site:mail']) + ->save(); + $resolved = $this->resolver->resolve(NULL, new CacheableMetadata()); + $this->assertArrayHasKey('mail_notification', $resolved['system.site']); + + decoupled_settings_update_10001(); + + $resolved = $this->resolver->resolve(NULL, new CacheableMetadata()); + $this->assertArrayNotHasKey('mail_notification', $resolved['system.site']); + + // Running again does not duplicate the entry. + decoupled_settings_update_10001(); + $excluded = $this->config('decoupled_settings.settings')->get('excluded_keys'); + $this->assertCount(1, array_keys($excluded, 'system.site:mail_notification', TRUE)); } /** From 4a406de369d44d3af045e5d1b0475047d53d4c72 Mon Sep 17 00:00:00 2001 From: Stuart Clark Date: Wed, 26 Aug 2026 01:10:36 +0000 Subject: [PATCH 2/2] test(#2): cover the empty inherited cell for a NULL setting on purpose --- tests/src/Kernel/FormLogicTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/src/Kernel/FormLogicTest.php b/tests/src/Kernel/FormLogicTest.php index ea5547c..6b9f3d7 100644 --- a/tests/src/Kernel/FormLogicTest.php +++ b/tests/src/Kernel/FormLogicTest.php @@ -147,6 +147,27 @@ public function testOverrideFormListsInheritedValues(): void { $this->assertFalse((bool) $form['settings']['system.site:name']['enabled']['#default_value']); } + /** + * A setting with no value is listed with an empty inherited cell. + * + * The notification address is NULL on a fresh site. It is excluded by + * default, so this exposes it on purpose to render the NULL path. + */ + public function testOverrideFormListsNullValueAsEmpty(): void { + $this->config('decoupled_settings.settings') + ->set('excluded_keys', []) + ->save(); + $consumer = $this->createConsumer(); + + $form_state = new FormState(); + $form_state->addBuildInfo('args', [$consumer]); + $form = $this->container->get('form_builder') + ->buildForm(ConsumerOverridesForm::class, $form_state); + + $this->assertArrayHasKey('system.site:mail_notification', $form['settings']); + $this->assertSame('', $form['settings']['system.site:mail_notification']['inherited']['#markup']); + } + /** * An already overridden setting is shown as ticked, with its own value. */