Commit dd8c0e6b authored by catch's avatar catch

Issue #2625258 by alexpott: LocaleConfigManager::updateConfigTranslations()...

Issue #2625258 by alexpott: LocaleConfigManager::updateConfigTranslations() deletes translations if a config object's name happens to match that of a shipped configuration object
parent cf3a70a2
......@@ -87,12 +87,22 @@ color_hex:
# Complex extended data types:
# Root of a configuration object.
_core_config_info:
type: mapping
mapping:
default_config_hash:
type: string
label: 'Default configuration hash'
config_object:
type: mapping
mapping:
langcode:
type: string
label: 'Language code'
_core:
type: _core_config_info
# Mail text with subject and body parts.
mail:
......@@ -281,6 +291,8 @@ config_entity:
label: 'Third party settings'
sequence:
type: '[%parent.%parent.%type].third_party.[%key]'
_core:
type: _core_config_info
block_settings:
type: mapping
......
......@@ -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;
......@@ -279,6 +280,12 @@ protected function createConfiguration($collection, array $config_to_create) {
}
if ($config_to_create[$name] !== FALSE) {
$new_config->setData($config_to_create[$name]);
// Add a hash to configuration created through the installer so it is
// possible to know if the configuration was created by installing an
// extension and to track which version of the default config was used.
if (!$this->isSyncing() && $collection == StorageInterface::DEFAULT_COLLECTION) {
$new_config->set('_core.default_config_hash', Crypt::hashBase64(serialize($config_to_create[$name])));
}
}
if ($collection == StorageInterface::DEFAULT_COLLECTION && $entity_type = $this->configManager->getEntityTypeIdByName($name)) {
// If we are syncing do not create configuration entities. Pluggable
......
......@@ -103,6 +103,17 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
*/
protected $third_party_settings = array();
/**
* Information maintained by Drupal core about configuration.
*
* Keys:
* - default_config_hash: A hash calculated by the config.installer service
* and added during installation.
*
* @var array
*/
protected $_core = [];
/**
* Trust supplied data and not use configuration schema on save.
*
......@@ -296,6 +307,9 @@ public function toArray() {
if (empty($this->third_party_settings)) {
unset($properties['third_party_settings']);
}
if (empty($this->_core)) {
unset($properties['_core']);
}
return $properties;
}
......
......@@ -157,6 +157,7 @@ public function getPropertiesToExport() {
'status' => 'status',
'dependencies' => 'dependencies',
'third_party_settings' => 'third_party_settings',
'_core' => '_core',
];
foreach ($this->config_export as $property => $name) {
if (is_numeric($property)) {
......
......@@ -7,6 +7,7 @@
namespace Drupal\config\Tests;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\ConfigNameException;
use Drupal\Core\Config\ConfigValueException;
......@@ -263,6 +264,7 @@ public function testDataTypes() {
'string' => 'string',
'string_int' => '1',
);
$data['_core']['default_config_hash'] = Crypt::hashBase64(serialize($data));
$this->assertIdentical($config->get(), $data);
// Re-set each key using Config::set().
......
......@@ -35,13 +35,10 @@ function testDiff() {
$add_key = 'biff';
$add_data = 'bangpow';
$change_data = 'foobar';
$original_data = array(
'foo' => 'bar',
'404' => 'herp',
);
// Install the default config.
$this->installConfig(array('config_test'));
$original_data = \Drupal::config($config_name)->get();
// Change a configuration value in sync.
$sync_data = $original_data;
......
......@@ -7,6 +7,7 @@
namespace Drupal\config\Tests;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Config\InstallStorage;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Config\FileStorage;
......@@ -48,6 +49,7 @@ function testInstallProfileConfigOverwrite() {
'requirements_error' => 1209600,
),
);
$expected_profile_data['_core']['default_config_hash'] = Crypt::hashBase64(serialize($expected_profile_data));
// Verify that the original data matches. We have to read the module config
// file directly, because the install profile default system.cron.yml
......
......@@ -63,6 +63,7 @@ function testSchemaMapping() {
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
$expected['mapping']['langcode']['type'] = 'string';
$expected['mapping']['langcode']['label'] = 'Language code';
$expected['mapping']['_core']['type'] = '_core_config_info';
$expected['mapping']['testitem'] = array('label' => 'Test item');
$expected['mapping']['testlist'] = array('label' => 'Test list');
$expected['type'] = 'config_schema_test.someschema';
......@@ -106,6 +107,7 @@ function testSchemaMapping() {
'label' => 'Language code',
'type' => 'string',
);
$expected['mapping']['_core']['type'] = '_core_config_info';
$expected['type'] = 'system.maintenance';
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for system.maintenance');
......@@ -120,6 +122,7 @@ function testSchemaMapping() {
'type' => 'string',
'label' => 'Language code',
);
$expected['mapping']['_core']['type'] = '_core_config_info';
$expected['mapping']['label'] = array(
'label' => 'Label',
'type' => 'label',
......@@ -179,6 +182,7 @@ 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']['_core']['type'] = '_core_config_info';
$expected['type'] = 'image.style.*';
$this->assertEqual($definition, $expected);
......@@ -231,6 +235,7 @@ function testSchemaMapping() {
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
$expected['mapping']['langcode']['type'] = 'string';
$expected['mapping']['langcode']['label'] = 'Language code';
$expected['mapping']['_core']['type'] = '_core_config_info';
$expected['mapping']['testid']['type'] = 'string';
$expected['mapping']['testid']['label'] = 'ID';
$expected['mapping']['testdescription']['type'] = 'text';
......@@ -386,7 +391,9 @@ public function testConfigSaveWithSchema() {
$extension_path = drupal_get_path('module', 'config_schema_test');
$install_storage = new FileStorage($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY);
$original_data = $install_storage->read('config_schema_test.ignore');
$this->assertIdentical($this->config('config_schema_test.ignore')->get(), $original_data);
$installed_data = $this->config('config_schema_test.ignore')->get();
unset($installed_data['_core']);
$this->assertIdentical($installed_data, $original_data);
}
/**
......@@ -401,6 +408,7 @@ function testSchemaFallback() {
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
$expected['mapping']['langcode']['type'] = 'string';
$expected['mapping']['langcode']['label'] = 'Language code';
$expected['mapping']['_core']['type'] = '_core_config_info';
$expected['mapping']['testid']['type'] = 'string';
$expected['mapping']['testid']['label'] = 'ID';
$expected['mapping']['testdescription']['type'] = 'text';
......
......@@ -202,7 +202,7 @@ public function testExport() {
$this->assertIdentical($expected_options, array_intersect($expected_options, $options), 'The expected configuration files are listed.');
$this->drupalGet('admin/config/development/configuration/single/export/system.simple/system.image');
$this->assertFieldByXPath('//textarea[@name="export"]', "toolkit: gd\n", 'The expected system configuration is displayed.');
$this->assertFieldByXPath('//textarea[@name="export"]', "toolkit: gd\n_core:\n default_config_hash: durWHaKeBaq4d9Wpi4RqwADj1OufDepcnJuhVLmKN24\n", 'The expected system configuration is displayed.');
$this->drupalGet('admin/config/development/configuration/single/export/date_format');
$this->assertFieldByXPath('//select[@name="config_type"]//option[@selected="selected"]', t('Date format'), 'The date format entity type is selected when specified in the URL.');
......
......@@ -6,6 +6,8 @@ services:
locale.config_manager:
class: Drupal\locale\LocaleConfigManager
arguments: ['@config.storage', '@locale.storage', '@config.factory', '@config.typed', '@language_manager', '@locale.default.config.storage']
calls:
- [_setConfigManager, ['@config.manager']]
locale.storage:
class: Drupal\locale\StringDatabaseStorage
arguments: ['@database']
......
......@@ -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;
......@@ -95,6 +96,17 @@ class LocaleConfigManager {
*/
protected $defaultConfigStorage;
/**
* The configuration manager.
*
* @var \Drupal\Core\Config\ConfigManagerInterface
*
* @internal
* Will be made protected and renamed to $configManager in 8.1.0.
* https://www.drupal.org/node/2628132
*/
private $_configManager;
/**
* Creates a new typed configuration manager.
*
......@@ -120,6 +132,36 @@ public function __construct(StorageInterface $config_storage, StringStorageInter
$this->defaultConfigStorage = $default_config_storage;
}
/**
* Sets the configuration manager service.
*
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
*
* @internal
* Will be replaced by constructor injection in 8.1.0.
* https://www.drupal.org/node/2628132
*/
public function _setConfigManager(ConfigManagerInterface $config_manager) {
$this->_configManager = $config_manager;
}
/**
* Gets the configuration manager service.
*
* @return \Drupal\Core\Config\ConfigManagerInterface
* The config manager
*
* @internal
* Will be replaced by constructor injection in 8.1.0.
* https://www.drupal.org/node/2628132
*/
private final function _getConfigManager() {
if (!isset($this->_configManager)) {
$this->_configManager = \Drupal::service('config.manager');
}
return $this->_configManager;
}
/**
* Gets array of translated strings for Locale translatable configuration.
*
......@@ -477,10 +519,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->_getConfigManager()->getEntityTypeIdByName($name);
if (!$config_entity_type || $config_entity_type === 'configurable_language'
|| !empty($this->configFactory->get($name)->get('_core.default_config_hash'))
) {
$shipped = $this->defaultConfigStorage->read($name);
if (!empty($shipped)) {
return !empty($shipped['langcode']) ? $shipped['langcode'] : 'en';
}
}
return NULL;
}
/**
......
......@@ -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,14 @@ class LocaleConfigManagerTest extends KernelTestBase {
*
* @var array
*/
public static $modules = array('language', 'locale', 'locale_test');
public static $modules = array('system', 'language', 'locale', 'locale_test', 'block');
/**
* This test creates simple config on the fly breaking schema checking.
*
* @var bool
*/
protected $strictConfigSchema = FALSE;
/**
* Tests hasTranslation().
......@@ -72,6 +80,49 @@ 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.');
$simple_config = \Drupal::configFactory()->getEditable('locale_test_translate.simple_config_extra');
$simple_config->set('foo', 'bar')->save();
$this->assertNull(\Drupal::service('locale.config_manager')->getDefaultConfigLangcode($simple_config->getName()), 'Simple config created through the API is not treated as shipped configuration.');
$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->assertNull(\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->assertNull(\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.');
}
}
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: { }
......@@ -35,6 +35,9 @@ public function testMigration() {
'ip_window' => 7200,
'user_limit' => 22,
'user_window' => 86400,
'_core' => [
'default_config_hash' => 'UYfMzeP1S8jKm9PSvxf7nQNe8DsNS-3bc2WSNNXBQWs',
],
];
$this->assertIdentical($expected, $this->config('user.flood')->get());
}
......
......@@ -74,6 +74,10 @@ protected function assertConfigDiff(Diff $result, $config_name, array $skipped_c
}
break;
case 'Drupal\Component\Diff\Engine\DiffOpAdd':
// The _core property does not exist in the default config.
if ($op->closing[0] === '_core:') {
continue;
}
foreach ($op->closing as $closing) {
// The UUIDs don't exist in the default config.
if (strpos($closing, 'uuid: ') === 0) {
......
......@@ -163,6 +163,7 @@ public function providerGetPropertiesToExport() {
'status' => 'status',
'dependencies' => 'dependencies',
'third_party_settings' => 'third_party_settings',
'_core' => '_core',
'id' => 'id',
'custom_property' => 'customProperty',
],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment