diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 3edb8057..95d6658 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -281,6 +281,9 @@ config_entity: label: 'Third party settings' sequence: type: '[%parent.%parent.%type].third_party.[%key]' + default_config_hash: + type: string + label: 'Default configuration hash' block_settings: type: mapping diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php index ae12c91..b34dc80 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstaller.php +++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Config; +use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\Entity\ConfigDependencyManager; use Drupal\Core\Config\Entity\ConfigEntityDependency; @@ -305,7 +306,10 @@ protected function createConfiguration($collection, array $config_to_create) { $entity = $entity_storage->createFromStorageRecord($new_config->get()); } if ($entity->isInstallable()) { - $entity->trustData()->save(); + $entity + ->set('default_config_hash', Crypt::hashBase64(serialize($new_config->get()))) + ->trustData() + ->save(); } } else { diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index bb396e6..70144d2 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -104,6 +104,11 @@ protected $third_party_settings = array(); /** + * @var string + */ + protected $default_config_hash = ''; + + /** * Trust supplied data and not use configuration schema on save. * * @var bool @@ -296,6 +301,9 @@ public function toArray() { if (empty($this->third_party_settings)) { unset($properties['third_party_settings']); } + if (empty($this->default_config_hash)) { + unset($properties['default_config_hash']); + } return $properties; } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php index 527a336..b29f803 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php @@ -157,6 +157,7 @@ public function getPropertiesToExport() { 'status' => 'status', 'dependencies' => 'dependencies', 'third_party_settings' => 'third_party_settings', + 'default_config_hash' => 'default_config_hash', ]; foreach ($this->config_export as $property => $name) { if (is_numeric($property)) { diff --git a/core/modules/config/src/Tests/ConfigSchemaTest.php b/core/modules/config/src/Tests/ConfigSchemaTest.php index a94e26a..3c7980c 100644 --- a/core/modules/config/src/Tests/ConfigSchemaTest.php +++ b/core/modules/config/src/Tests/ConfigSchemaTest.php @@ -179,6 +179,8 @@ function testSchemaMapping() { $expected['mapping']['third_party_settings']['type'] = 'sequence'; $expected['mapping']['third_party_settings']['label'] = 'Third party settings'; $expected['mapping']['third_party_settings']['sequence']['type'] = '[%parent.%parent.%type].third_party.[%key]'; + $expected['mapping']['default_config_hash']['type'] = 'string'; + $expected['mapping']['default_config_hash']['label'] = 'Default configuration hash'; $expected['type'] = 'image.style.*'; $this->assertEqual($definition, $expected); diff --git a/core/modules/locale/locale.services.yml b/core/modules/locale/locale.services.yml index 29603ee..e37cb48 100644 --- a/core/modules/locale/locale.services.yml +++ b/core/modules/locale/locale.services.yml @@ -5,7 +5,7 @@ services: public: false locale.config_manager: class: Drupal\locale\LocaleConfigManager - arguments: ['@config.storage', '@locale.storage', '@config.factory', '@config.typed', '@language_manager', '@locale.default.config.storage'] + arguments: ['@config.storage', '@locale.storage', '@config.factory', '@config.typed', '@language_manager', '@locale.default.config.storage', '@config.manager'] locale.storage: class: Drupal\locale\StringDatabaseStorage arguments: ['@database'] diff --git a/core/modules/locale/src/LocaleConfigManager.php b/core/modules/locale/src/LocaleConfigManager.php index cab8816..de09fc3 100644 --- a/core/modules/locale/src/LocaleConfigManager.php +++ b/core/modules/locale/src/LocaleConfigManager.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ConfigManagerInterface; use Drupal\Core\Config\StorageInterface; use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -96,6 +97,13 @@ class LocaleConfigManager { protected $defaultConfigStorage; /** + * The configuration manager. + * + * @var \Drupal\Core\Config\ConfigManagerInterface + */ + protected $configManager; + + /** * Creates a new typed configuration manager. * * @param \Drupal\Core\Config\StorageInterface $config_storage @@ -111,13 +119,14 @@ class LocaleConfigManager { * @param \Drupal\locale\LocaleDefaultConfigStorage $default_config_storage * The locale default configuration storage. */ - public function __construct(StorageInterface $config_storage, StringStorageInterface $locale_storage, ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config, ConfigurableLanguageManagerInterface $language_manager, LocaleDefaultConfigStorage $default_config_storage) { + public function __construct(StorageInterface $config_storage, StringStorageInterface $locale_storage, ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config, ConfigurableLanguageManagerInterface $language_manager, LocaleDefaultConfigStorage $default_config_storage, ConfigManagerInterface $config_manager) { $this->configStorage = $config_storage; $this->localeStorage = $locale_storage; $this->configFactory = $config_factory; $this->typedConfigManager = $typed_config; $this->languageManager = $language_manager; $this->defaultConfigStorage = $default_config_storage; + $this->configManager = $config_manager; } /** @@ -477,10 +486,21 @@ public function hasTranslation($name, $langcode) { * configuration exists. */ public function getDefaultConfigLangcode($name) { - $shipped = $this->defaultConfigStorage->read($name); - if (!empty($shipped)) { - return !empty($shipped['langcode']) ? $shipped['langcode'] : 'en'; + // Config entities that do not have the 'default_config_hash' cannot be + // shipped configuration regardless of whether there is a name match. + // configurable_language entities are a special case since they can be + // translated regardless of whether they are shipped if they in the standard + // language list. + $config_entity_type = $this->configManager->getEntityTypeIdByName($name); + if (!$config_entity_type || $config_entity_type === 'configurable_language' + || !empty($this->configFactory->get($name)->get('default_config_hash')) + ) { + $shipped = $this->defaultConfigStorage->read($name); + if (!empty($shipped)) { + return !empty($shipped['langcode']) ? $shipped['langcode'] : 'en'; + } } + return NULL; } /** diff --git a/core/modules/locale/src/Tests/LocaleConfigManagerTest.php b/core/modules/locale/src/Tests/LocaleConfigManagerTest.php index 39f14cb..3e3e697 100644 --- a/core/modules/locale/src/Tests/LocaleConfigManagerTest.php +++ b/core/modules/locale/src/Tests/LocaleConfigManagerTest.php @@ -7,6 +7,7 @@ namespace Drupal\locale\Tests; +use Drupal\block\Entity\Block; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\simpletest\KernelTestBase; @@ -22,7 +23,7 @@ class LocaleConfigManagerTest extends KernelTestBase { * * @var array */ - public static $modules = array('language', 'locale', 'locale_test'); + public static $modules = array('system', 'language', 'locale', 'locale_test', 'block'); /** * Tests hasTranslation(). @@ -72,6 +73,45 @@ public function testGetDefaultConfigLangcode() { $this->assertNull(\Drupal::service('locale.config_manager')->getDefaultConfigLangcode('locale_test_translate.settings'), 'Before installing a module the locale config manager can not access the shipped configuration.'); \Drupal::service('module_installer')->install(['locale_test_translate']); $this->assertEqual('en', \Drupal::service('locale.config_manager')->getDefaultConfigLangcode('locale_test_translate.settings'), 'After installing a module the locale config manager can get the shipped configuration langcode.'); + + $block = Block::create(array( + 'id' => 'test_default_config', + 'theme' => 'classy', + 'status' => TRUE, + 'region' => 'content', + 'plugin' => 'local_tasks_block', + 'settings' => [ + 'id' => 'local_tasks_block', + 'label' => $this->randomMachineName(), + 'provider' => 'core', + 'label_display' => FALSE, + 'primary' => TRUE, + 'secondary' => TRUE, + ], + )); + $block->save(); + + // Install the theme after creating the block as installing the theme will + // install the block provided by the locale_test module. + \Drupal::service('theme_installer')->install(['classy']); + + // The test_default_config block provided by the locale_test module will not + // be installed because a block with the same ID already exists. + $this->installConfig(['locale_test']); + $this->assertEqual(NULL, \Drupal::service('locale.config_manager')->getDefaultConfigLangcode('block.block.test_default_config'), 'The block.block.test_default_config is not shipped configuration.'); + // Delete the block so we can install the one provided by the locale_test + // module. + $block->delete(); + $this->installConfig(['locale_test']); + $this->assertEqual('en', \Drupal::service('locale.config_manager')->getDefaultConfigLangcode('block.block.test_default_config'), 'The block.block.test_default_config is shipped configuration.'); + + // Test the special case for configurable_language config entities. + $fr_language = ConfigurableLanguage::createFromLangcode('fr'); + $fr_language->save(); + $this->assertEqual('en', \Drupal::service('locale.config_manager')->getDefaultConfigLangcode('language.entity.fr'), 'The language.entity.fr is treated as shipped configuration because it is a configurable_language config entity and in the standard language list.'); + $custom_language = ConfigurableLanguage::createFromLangcode('custom'); + $custom_language->save(); + $this->assertEqual(NULL, \Drupal::service('locale.config_manager')->getDefaultConfigLangcode('language.entity.custom'), 'The language.entity.custom is not shipped configuration because it is not in the standard language list.'); } } diff --git a/core/modules/locale/tests/modules/locale_test/config/optional/block.block.test_default_config.yml b/core/modules/locale/tests/modules/locale_test/config/optional/block.block.test_default_config.yml new file mode 100644 index 0000000..15f0807 --- /dev/null +++ b/core/modules/locale/tests/modules/locale_test/config/optional/block.block.test_default_config.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + theme: + - classy +id: test_default_config +theme: classy +region: content +weight: -40 +provider: null +plugin: local_tasks_block +settings: + id: local_tasks_block + label: Tabs + provider: core + label_display: '0' + primary: true + secondary: true +visibility: { } diff --git a/core/tests/Drupal/KernelTests/AssertConfigTrait.php b/core/tests/Drupal/KernelTests/AssertConfigTrait.php index 24d393e..8489ae7 100644 --- a/core/tests/Drupal/KernelTests/AssertConfigTrait.php +++ b/core/tests/Drupal/KernelTests/AssertConfigTrait.php @@ -79,6 +79,10 @@ protected function assertConfigDiff(Diff $result, $config_name, array $skipped_c if (strpos($closing, 'uuid: ') === 0) { continue; } + // The UUIDs don't exist in the default config. + if (strpos($closing, 'default_config_hash: ') === 0) { + continue; + } throw new \Exception($config_name . ': ' . var_export($op, TRUE)); } break; diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityTypeTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityTypeTest.php index 97bf172..44eae9a 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityTypeTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityTypeTest.php @@ -163,6 +163,7 @@ public function providerGetPropertiesToExport() { 'status' => 'status', 'dependencies' => 'dependencies', 'third_party_settings' => 'third_party_settings', + 'default_config_hash' => 'default_config_hash', 'id' => 'id', 'custom_property' => 'customProperty', ],