From 1c179cb78ce963978c8e24b3a3a09b1294a17483 Mon Sep 17 00:00:00 2001 From: Fabian Bircher Date: Wed, 4 Mar 2020 09:40:31 +0100 Subject: [PATCH] Issue #3117135 by claudiu.cristea, mpp: TypeError: Argument 1 passed to NestedArray::unsetValue() must be of the type array, null given --- .../ConfigIgnoreEventSubscriber.php | 13 ++- .../Functional/ConfigWithTranslationTest.php | 102 ++++++++++++++++-- .../Unit/ConfigIgnorePatternResolverTest.php | 6 +- 3 files changed, 105 insertions(+), 16 deletions(-) diff --git a/src/EventSubscriber/ConfigIgnoreEventSubscriber.php b/src/EventSubscriber/ConfigIgnoreEventSubscriber.php index ccccac4..4aa1fdb 100644 --- a/src/EventSubscriber/ConfigIgnoreEventSubscriber.php +++ b/src/EventSubscriber/ConfigIgnoreEventSubscriber.php @@ -106,7 +106,7 @@ class ConfigIgnoreEventSubscriber implements EventSubscriberInterface { * The active storage on import. The sync storage on export. */ protected function transformStorage(StorageInterface $transformation_storage, StorageInterface $destination_storage) { - $ignored_configs = $this->getIgnoredConfigs(); + $ignored_configs = $this->getIgnoredConfigs($transformation_storage); $collection_names = $transformation_storage->getAllCollectionNames(); array_unshift($collection_names, StorageInterface::DEFAULT_COLLECTION); @@ -147,9 +147,9 @@ class ConfigIgnoreEventSubscriber implements EventSubscriberInterface { else { $source_data = $transformation_storage->read($config_name); foreach ($keys as $key) { - NestedArray::unsetValue($import_data, $key); + NestedArray::unsetValue($source_data, $key); } - $transformation_storage->write($config_name, $import_data); + $transformation_storage->write($config_name, $source_data); } } } @@ -159,6 +159,9 @@ class ConfigIgnoreEventSubscriber implements EventSubscriberInterface { /** * Returns the list of all ignored configs by expanding the wildcards. * + * @param \Drupal\Core\Config\StorageInterface $transformation_storage + * The transformation config storage. + * * @return array * An associative array keyed by config name and having the values either * NULL, if the whole config is ignored, or an array of keys to be ignored. @@ -173,7 +176,7 @@ class ConfigIgnoreEventSubscriber implements EventSubscriberInterface { * ] * @endcode */ - protected function getIgnoredConfigs() { + protected function getIgnoredConfigs(StorageInterface $transformation_storage) { /** @var string[] $ignored_configs_patterns */ $ignored_configs_patterns = $this->configFactory->get('config_ignore.settings')->get('ignored_config_entities'); $this->moduleHandler->invokeAll('config_ignore_settings_alter', [&$ignored_configs_patterns]); @@ -191,7 +194,7 @@ class ConfigIgnoreEventSubscriber implements EventSubscriberInterface { } $ignored_configs = []; - foreach ($this->configFactory->listAll() as $config_name) { + foreach ($transformation_storage->listAll() as $config_name) { foreach ($ignored_configs_patterns as $ignored_config_pattern) { if (strpos($ignored_config_pattern, ':') !== FALSE) { // Some patterns are defining also a key. diff --git a/tests/src/Functional/ConfigWithTranslationTest.php b/tests/src/Functional/ConfigWithTranslationTest.php index 957349d..82ec5b3 100644 --- a/tests/src/Functional/ConfigWithTranslationTest.php +++ b/tests/src/Functional/ConfigWithTranslationTest.php @@ -41,8 +41,9 @@ class ConfigWithTranslationTest extends BrowserTestBase { ConfigurableLanguage::createFromLangcode('ro')->save(); - // Add a translation to user.role.anonymous. + // Add a translation to user.role.anonymous and user.role.authenticated. $this->translateConfig('user.role.anonymous', 'label', 'Utilizator anonim', 'ro'); + $this->translateConfig('user.role.authenticated', 'label', 'Utilizator autentificat', 'ro'); // Export the default configuration. $this->drush('config:export', [], ['yes' => NULL]); @@ -51,9 +52,17 @@ class ConfigWithTranslationTest extends BrowserTestBase { $this->assertExportedValue('user.settings', 'anonymous', 'Anonymous'); $this->assertExportedValue('user.role.anonymous', 'label', 'Anonymous user'); $this->assertExportedValue('user.role.anonymous', 'label', 'Utilizator anonim', 'ro'); + $this->assertExportedValue('user.role.authenticated', 'label', 'Authenticated user'); + $this->assertExportedValue('user.role.authenticated', 'weight', 1); + $this->assertExportedValue('user.role.authenticated', 'is_admin', FALSE); + $this->assertExportedValue('user.role.authenticated', 'label', 'Utilizator autentificat', 'ro'); - // Ignore user.role.anonymous. - $this->config('config_ignore.settings')->set('ignored_config_entities', ['user.role.anonymous'])->save(); + // Ignore user.role.anonymous and two keys from user.role.authenticated. + $this->config('config_ignore.settings')->set('ignored_config_entities', [ + 'user.role.anonymous', + 'user.role.authenticated:weight', + 'user.role.authenticated:is_admin', + ])->save(); } /** @@ -63,19 +72,27 @@ class ConfigWithTranslationTest extends BrowserTestBase { // Change configurations in the active store. $this->config('user.settings')->set('anonymous', 'Visitor')->save(); $this->config('user.role.anonymous')->set('label', 'Visitor')->save(); - // Change also the translation of user.role.anonymous. + $this->config('user.role.authenticated') + ->set('label', 'Authenticated') + ->set('weight', 2) + ->set('is_admin', TRUE) + ->save(); + // Change translations of user.role.anonymous and user.role.authenticated. $this->translateConfig('user.role.anonymous', 'label', 'Vizitator', 'ro'); + $this->translateConfig('user.role.authenticated', 'label', 'Logat', 'ro'); // Get config status. $this->drush('config:status', [], ['format' => 'json']); $diff = (array) $this->getOutputFromJSON(); // Check that only config_ignore.settings and user.settings are shown. - $this->assertCount(2, $diff); + $this->assertCount(3, $diff); $this->assertArrayHasKey('config_ignore.settings', $diff); $this->assertSame(['name' => 'config_ignore.settings', 'state' => 'Different'], $diff['config_ignore.settings']); $this->assertArrayHasKey('user.settings', $diff); $this->assertSame(['name' => 'user.settings', 'state' => 'Different'], $diff['user.settings']); + $this->assertArrayHasKey('user.role.authenticated', $diff); + $this->assertSame(['name' => 'user.role.authenticated', 'state' => 'Different'], $diff['user.role.authenticated']); } /** @@ -85,8 +102,14 @@ class ConfigWithTranslationTest extends BrowserTestBase { // Change configurations in the active store. $this->config('user.settings')->set('anonymous', 'Visitor')->save(); $this->config('user.role.anonymous')->set('label', 'Visitor')->save(); - // Change also the translation of user.role.anonymous. + $this->config('user.role.authenticated') + ->set('label', 'Authenticated') + ->set('weight', 2) + ->set('is_admin', TRUE) + ->save(); + // Change translations of user.role.anonymous and user.role.authenticated. $this->translateConfig('user.role.anonymous', 'label', 'Vizitator', 'ro'); + $this->translateConfig('user.role.authenticated', 'label', 'Logat', 'ro'); // Export changes. $this->drush('config:export', [], ['yes' => NULL]); @@ -97,17 +120,51 @@ class ConfigWithTranslationTest extends BrowserTestBase { $this->assertExportedValue('user.role.anonymous', 'label', 'Anonymous user'); // Check that the translated version was not overridden. $this->assertExportedValue('user.role.anonymous', 'label', 'Utilizator anonim', 'ro'); + // Check that user.role.authenticated changes were exported. + $this->assertExportedValue('user.role.authenticated', 'label', 'Authenticated'); + $this->assertExportedValue('user.role.authenticated', 'weight', 1); + $this->assertExportedValue('user.role.authenticated', 'is_admin', FALSE); + // Check that the translated version has been exported too. + $this->assertExportedValue('user.role.authenticated', 'label', 'Logat', 'ro'); + + // Delete user.role.authenticated from sync storage in order to test again + // when the destination is missed. + $sync_storage = $this->getSyncStorage(); + $sync_storage->delete('user.role.authenticated'); + + // Re-export changes. + $this->drush('config:export', [], ['yes' => NULL]); + + $data = $sync_storage->read('user.role.authenticated'); + // Check that weight & is_admin keys were ignored on the new created config. + $this->assertArrayNotHasKey('weight', $data); + $this->assertArrayNotHasKey('is_admin', $data); } /** * Tests config import. */ public function testConfigImport() { + // Add the config_ignore.settings changes in the sync store. Remember that + // the ignore patterns were added only in the active store, in ::setUp(), + // but were never exported in sync. Otherwise the values will be reverted, + // later, in the first config import. + // @see self::setUp() + $this->setConfigSyncValue('config_ignore.settings', 'ignored_config_entities', [ + 'user.role.anonymous', + 'user.role.authenticated:weight', + 'user.role.authenticated:is_admin', + ]); + // Change configurations in the sync store. $this->setConfigSyncValue('user.settings', 'anonymous', 'Visitor'); $this->setConfigSyncValue('user.role.anonymous', 'label', 'Visitor'); - // Change also the translation of user.role.anonymous. + $this->setConfigSyncValue('user.role.authenticated', 'label', 'Authenticated'); + $this->setConfigSyncValue('user.role.authenticated', 'weight', 2); + $this->setConfigSyncValue('user.role.authenticated', 'is_admin', TRUE); + // Change translations of user.role.anonymous and user.role.authenticated. $this->setConfigSyncValue('user.role.anonymous', 'label', 'Vizitator', 'ro'); + $this->setConfigSyncValue('user.role.authenticated', 'label', 'Logat', 'ro'); // Check that user.settings was changed in the sync store. $this->assertExportedValue('user.settings', 'anonymous', 'Visitor'); @@ -115,21 +172,47 @@ class ConfigWithTranslationTest extends BrowserTestBase { $this->assertExportedValue('user.role.anonymous', 'label', 'Visitor'); // Check that the translated override was changed in the sync store. $this->assertExportedValue('user.role.anonymous', 'label', 'Vizitator', 'ro'); + $this->assertExportedValue('user.role.authenticated', 'label', 'Authenticated'); + $this->assertExportedValue('user.role.authenticated', 'weight', 2); + $this->assertExportedValue('user.role.authenticated', 'is_admin', TRUE); // Import changes. $this->drush('config:import', [], ['yes' => NULL]); + // As the tests are running in the same request we manually clear the static + // cache of the config objects. + \Drupal::configFactory()->reset(); // Check that user.settings has been overridden by import. $this->assertSame('Visitor', $this->config('user.settings')->get('anonymous')); // Check that user.role.anonymous has been preserved. $this->assertSame('Anonymous user', $this->config('user.role.anonymous')->get('label')); + // Check that user.role.authenticated has been overridden by import. + $this->assertSame('Authenticated', $this->config('user.role.authenticated')->get('label')); + $this->assertEquals(1, $this->config('user.role.authenticated')->get('weight')); + $this->assertFalse($this->config('user.role.authenticated')->get('is_admin')); + // Check that the user.role.anonymous translation has been also preserved. $language_manager = \Drupal::languageManager(); $original_language = $language_manager->getConfigOverrideLanguage(); /** @var \Drupal\language\Config\LanguageConfigOverride $translated */ $translated = $language_manager->getLanguageConfigOverride('ro', 'user.role.anonymous'); $this->assertSame('Utilizator anonim', $translated->get('label')); + $translated = $language_manager->getLanguageConfigOverride('ro', 'user.role.authenticated'); + $this->assertSame('Logat', $translated->get('label')); $language_manager->setConfigOverrideLanguage($original_language); + + /** @var \Drupal\Core\Config\StorageInterface $active_storage */ + $active_storage = \Drupal::service('config.storage'); + // Remove the config in order to test again when the destination is missed. + $active_storage->delete('user.role.authenticated'); + + // Re-import changes. + $this->drush('config:import', [], ['yes' => NULL]); + \Drupal::configFactory()->reset('user.role.authenticated'); + + $this->assertSame('Authenticated', $this->config('user.role.authenticated')->get('label')); + $this->assertEquals(1, $this->config('user.role.authenticated')->get('weight')); + $this->assertFalse((bool) $this->config('user.role.authenticated')->get('is_admin')); } /** @@ -205,12 +288,13 @@ class ConfigWithTranslationTest extends BrowserTestBase { * Returns the config sync storage. * * @param string|null $langcode - * The language collection language code or NULL for teh default collection. + * (optional) The language collection language code or NULL for the default + * collection. * * @return \Drupal\Core\Config\StorageInterface * The config sync storage. */ - protected function getSyncStorage($langcode) { + protected function getSyncStorage($langcode = NULL) { $sync_storage = \Drupal::service('config.storage.sync'); if ($langcode) { $sync_storage = $sync_storage->createCollection("language.{$langcode}"); diff --git a/tests/src/Unit/ConfigIgnorePatternResolverTest.php b/tests/src/Unit/ConfigIgnorePatternResolverTest.php index 4355f19..d82450e 100644 --- a/tests/src/Unit/ConfigIgnorePatternResolverTest.php +++ b/tests/src/Unit/ConfigIgnorePatternResolverTest.php @@ -162,7 +162,9 @@ class ConfigIgnorePatternResolverTest extends UnitTestCase { $configFactory = $this->prophesize(ConfigFactoryInterface::class); $configFactory->get('config_ignore.settings')->willReturn($configIgnoreSettings->reveal()); - $configFactory->listAll()->willReturn($all_configs); + + $transformation_storage = $this->prophesize(StorageInterface::class); + $transformation_storage->listAll()->willReturn($all_configs); $subscriber = new ConfigIgnoreEventSubscriber( $configFactory->reveal(), @@ -176,7 +178,7 @@ class ConfigIgnorePatternResolverTest extends UnitTestCase { $getIgnoredConfigsMethod = $class->getMethod('getIgnoredConfigs'); $getIgnoredConfigsMethod->setAccessible(TRUE); - return $getIgnoredConfigsMethod->invokeArgs($subscriber, []); + return $getIgnoredConfigsMethod->invokeArgs($subscriber, [$transformation_storage->reveal()]); } } -- GitLab