diff --git a/core/core.services.yml b/core/core.services.yml index a46d1c706b9d0412c2b71447ddc0a87405024286..8f2965aa08f67466bf1ec11288d4835b1a1d137d 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -93,7 +93,7 @@ services: class: Drupal\Core\Config\ConfigFactory tags: - { name: persist } - arguments: ['@config.storage', '@config.context'] + arguments: ['@config.storage', '@config.context', '@config.typed'] config.storage.staging: class: Drupal\Core\Config\FileStorage factory_class: Drupal\Core\Config\FileStorageFactory diff --git a/core/includes/config.inc b/core/includes/config.inc index 8e9ce5e02790ba7f69bbaea0c9bc00ce6c2ac2ac..d0f1d7bed6fd895e91bc7d5bc171519d9eac9194 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -67,6 +67,7 @@ function ($value) use ($name) { $config_factory = Drupal::service('config.factory'); $context = new FreeConfigContext(Drupal::service('event_dispatcher'), Drupal::service('uuid')); $target_storage = Drupal::service('config.storage'); + $typed_config = Drupal::service('config.typed'); $config_factory->enterContext($context); foreach ($config_to_install as $name) { // Only import new config. @@ -74,7 +75,7 @@ function ($value) use ($name) { continue; } - $new_config = new Config($name, $target_storage, $context); + $new_config = new Config($name, $target_storage, $context, $typed_config); $data = $source_storage->read($name); if ($data !== FALSE) { $new_config->setData($data); @@ -214,7 +215,7 @@ function config_get_entity_type_by_name($name) { * * @see \Drupal\Core\TypedData\TypedDataManager::create() * - * @return \Drupal\Core\TypedData\TypedConfigManager + * @return \Drupal\Core\Config\TypedConfigManager */ function config_typed() { return drupal_container()->get('config.typed'); diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index db9cf588693fb70e1271cd9d6a07cd5ac61df65f..8762f0b1e36140ebbd8a8d81cfe6f865641c1ad4 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -386,9 +386,16 @@ function install_begin_request(&$install_state) { ->setFactoryService(new Reference('config.context.factory')) ->setFactoryMethod('get'); + $container->register('config.storage.schema', 'Drupal\Core\Config\Schema\SchemaStorage'); + + $container->register('config.typed', 'Drupal\Core\Config\TypedConfigManager') + ->addArgument(new Reference('config.storage')) + ->addArgument(new Reference('config.storage.schema')); + $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory') ->addArgument(new Reference('config.storage')) - ->addArgument(new Reference('config.context')); + ->addArgument(new Reference('config.context')) + ->addArgument(new Reference('config.typed')); // Register the 'language_manager' service. $container diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index 807d930b76191a00a65ec6afc7e1f0dd9d647d6c..f04bbe0c69d88293254d59dc9d4ec24f97aec8e5 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -8,8 +8,12 @@ namespace Drupal\Core\Config; use Drupal\Component\Utility\NestedArray; +use Drupal\Component\Utility\String; use Drupal\Core\Config\ConfigNameException; use Drupal\Core\Config\Context\ContextInterface; +use Drupal\Core\TypedData\PrimitiveInterface; +use Drupal\Core\TypedData\Type\FloatInterface; +use Drupal\Core\TypedData\Type\IntegerInterface; /** * Defines the default configuration object. @@ -77,6 +81,20 @@ class Config { */ protected $isLoaded = FALSE; + /** + * The config schema wrapper object for this configuration object. + * + * @var \Drupal\Core\Config\Schema\Element + */ + protected $schemaWrapper; + + /** + * The typed config manager. + * + * @var \Drupal\Core\Config\TypedConfigManager + */ + protected $typedConfigManager; + /** * Constructs a configuration object. * @@ -87,11 +105,14 @@ class Config { * configuration data. * @param \Drupal\Core\Config\Context\ContextInterface $context * The configuration context used for this configuration object. + * @param \Drupal\Core\Config\TypedConfigManager $typed_config + * The typed configuration manager service. */ - public function __construct($name, StorageInterface $storage, ContextInterface $context) { + public function __construct($name, StorageInterface $storage, ContextInterface $context, TypedConfigManager $typed_config) { $this->name = $name; $this->storage = $storage; $this->context = $context; + $this->typedConfigManager = $typed_config; } /** @@ -404,6 +425,17 @@ public function load() { public function save() { // Validate the configuration object name before saving. static::validateName($this->name); + + // If there is a schema for this configuration object, cast all values to + // conform to the schema. + if ($this->typedConfigManager->hasConfigSchema($this->name)) { + // Ensure that the schema wrapper has the latest data. + $this->schemaWrapper = NULL; + foreach ($this->data as $key => $value) { + $this->data[$key] = $this->castValue($key, $value); + } + } + if (!$this->isLoaded) { $this->load(); } @@ -466,4 +498,110 @@ public function merge(array $data_to_merge) { $this->replaceData(NestedArray::mergeDeepArray(array($this->data, $data_to_merge), TRUE)); return $this; } + + /** + * Gets the schema wrapper for the whole configuration object. + * + * The schema wrapper is dependent on the configuration name and the whole + * data structure, so if the name or the data changes in any way, the wrapper + * should be reset. + * + * @return \Drupal\Core\Config\Schema\Element + */ + protected function getSchemaWrapper() { + if (!isset($this->schemaWrapper)) { + $definition = $this->typedConfigManager->getDefinition($this->name); + $this->schemaWrapper = $this->typedConfigManager->create($definition, $this->data); + } + return $this->schemaWrapper; + } + + /** + * Gets the definition for the configuration key. + * + * @param string $key + * A string that maps to a key within the configuration data. + * + * @return \Drupal\Core\Config\Schema\Element + * + * @throws \Drupal\Core\Config\ConfigException + * Thrown when schema is incomplete. + */ + protected function getSchemaForKey($key) { + $parts = explode('.', $key); + $schema_wrapper = $this->getSchemaWrapper(); + if (count($parts) == 1) { + $schema = $schema_wrapper->get($key); + } + else { + $schema = clone $schema_wrapper; + foreach ($parts as $nested_key) { + if (!is_object($schema) || !method_exists($schema, 'get')) { + throw new ConfigException(String::format("Incomplete schema for !key key in configuration object !name.", array('!name' => $this->name, '!key' => $key))); + } + else { + $schema = $schema->get($nested_key); + } + } + } + return $schema; + } + + /** + * Casts the value to correct data type using the configuration schema. + * + * @param string $key + * A string that maps to a key within the configuration data. + * @param string $value + * Value to associate with the key. + * + * @return mixed + * The value cast to the type indicated in the schema. + */ + protected function castValue($key, $value) { + if ($value === NULL) { + $value = NULL; + } + elseif (is_scalar($value)) { + try { + $element = $this->getSchemaForKey($key); + if ($element instanceof PrimitiveInterface) { + // Special handling for integers and floats since the configuration + // system is primarily concerned with saving values from the Form API + // we have to special case the meaning of an empty string for numeric + // types. In PHP this would be casted to a 0 but for the purposes of + // configuration we need to treat this as a NULL. + if ($value === '' && ($element instanceof IntegerInterface || $element instanceof FloatInterface)) { + $value = NULL; + } + else { + $value = $element->getCastedValue(); + } + } + else { + // Config only supports primitive data types. If the config schema + // does define a type $element will be an instance of + // \Drupal\Core\Config\Schema\Property. Convert it to string since it + // is the safest possible type. + $value = $element->getString(); + } + } + catch (\Exception $e) { + // @todo throw an exception due to an incomplete schema. Only possible + // once https://drupal.org/node/1910624 is complete. + } + } + else { + // Any non-scalar value must be an array. + if (!is_array($value)) { + $value = (array) $value; + } + // Recurse into any nested keys. + foreach ($value as $nested_value_key => $nested_value) { + $value[$nested_value_key] = $this->castValue($key . '.' . $nested_value_key, $nested_value); + } + } + return $value; + } + } diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php index 60b3d0d352ac2ac31df61b3d1ef3f997cd2aa7ec..7b1e97ce9df89be21f8e088ea8a0f043df2b5ef7 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactory.php +++ b/core/lib/Drupal/Core/Config/ConfigFactory.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Config; use Drupal\Core\Config\Context\ContextInterface; +use Drupal\Core\Config\TypedConfigManager; /** * Defines the configuration object factory. @@ -51,16 +52,26 @@ class ConfigFactory { */ protected $cache = array(); + /** + * The typed config manager. + * + * @var \Drupal\Core\Config\TypedConfigManager + */ + protected $typedConfigManager; + /** * Constructs the Config factory. * - * @param \Drupal\Core\Config\StorageInterface + * @param \Drupal\Core\Config\StorageInterface $storage * The configuration storage engine. - * @param \Drupal\Core\Config\Context\ContextInterface + * @param \Drupal\Core\Config\Context\ContextInterface $context * Configuration context object. + * @param \Drupal\Core\Config\TypedConfigManager $typed_config + * The typed configuration manager. */ - public function __construct(StorageInterface $storage, ContextInterface $context) { + public function __construct(StorageInterface $storage, ContextInterface $context, TypedConfigManager $typed_config) { $this->storage = $storage; + $this->typedConfigManager = $typed_config; $this->enterContext($context); } @@ -80,7 +91,7 @@ public function get($name) { return $this->cache[$cache_key]; } - $this->cache[$cache_key] = new Config($name, $this->storage, $context); + $this->cache[$cache_key] = new Config($name, $this->storage, $context, $this->typedConfigManager); return $this->cache[$cache_key]->init(); } @@ -115,7 +126,7 @@ public function loadMultiple(array $names) { $storage_data = $this->storage->readMultiple($names); foreach ($storage_data as $name => $data) { $cache_key = $this->getCacheKey($name, $context); - $this->cache[$cache_key] = new Config($name, $this->storage, $context); + $this->cache[$cache_key] = new Config($name, $this->storage, $context, $this->typedConfigManager); $this->cache[$cache_key]->initWithData($data); $list[$name] = $this->cache[$cache_key]; } @@ -172,7 +183,7 @@ public function rename($old_name, $new_name) { } else { // Create the config object if it's not yet loaded into the static cache. - $config = new Config($old_name, $this->storage, $context); + $config = new Config($old_name, $this->storage, $context, $this->typedConfigManager); } $this->cache[$new_cache_key] = $config; diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php index f96d965b3843a7fdd27d45afc1d59abbf2872acd..66f51e452c966ecb5cb03f10432828f62d32a004 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Config; use Drupal\Core\Config\Context\FreeConfigContext; +use Drupal\Core\Config\TypedConfigManager; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Lock\LockBackendInterface; use Drupal\Component\Uuid\UuidInterface; @@ -102,6 +103,13 @@ class ConfigImporter { */ protected $uuidService; + /** + * The typed config manager. + * + * @var \Drupal\Core\Config\TypedConfigManager + */ + protected $typedConfigManager; + /** * Constructs a configuration import object. * @@ -118,14 +126,17 @@ class ConfigImporter { * The lock backend to ensure multiple imports do not occur at the same time. * @param \Drupal\Component\Uuid\UuidInterface $uuid_service * The UUID service. + * @param \Drupal\Core\Config\TypedConfigManager $typed_config + * The typed configuration manager. */ - public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigFactory $config_factory, EntityManagerInterface $entity_manager, LockBackendInterface $lock, UuidInterface $uuid_service) { + public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigFactory $config_factory, EntityManagerInterface $entity_manager, LockBackendInterface $lock, UuidInterface $uuid_service, TypedConfigManager $typed_config) { $this->storageComparer = $storage_comparer; $this->eventDispatcher = $event_dispatcher; $this->configFactory = $config_factory; $this->entityManager = $entity_manager; $this->lock = $lock; $this->uuidService = $uuid_service; + $this->typedConfigManager = $typed_config; $this->processed = $this->storageComparer->getEmptyChangelist(); // Use an override free context for importing so that overrides to do not // pollute the imported data. The context is hard coded to ensure this is @@ -264,7 +275,7 @@ public function validate() { protected function importConfig() { foreach (array('delete', 'create', 'update') as $op) { foreach ($this->getUnprocessed($op) as $name) { - $config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context); + $config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context, $this->typedConfigManager); if ($op == 'delete') { $config->delete(); } @@ -297,11 +308,11 @@ protected function importInvokeOwner() { // Validate the configuration object name before importing it. // Config::validateName($name); if ($entity_type = config_get_entity_type_by_name($name)) { - $old_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context); + $old_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context, $this->typedConfigManager); $old_config->load(); $data = $this->storageComparer->getSourceStorage()->read($name); - $new_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context); + $new_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context, $this->typedConfigManager); if ($data !== FALSE) { $new_config->setData($data); } diff --git a/core/lib/Drupal/Core/Config/Schema/Mapping.php b/core/lib/Drupal/Core/Config/Schema/Mapping.php index 9701df0de0388abb3e0f7e9eb242dbdbd6e22437..92e9a90f53ff5f33c6068cb9f923d4aa12cfd82f 100644 --- a/core/lib/Drupal/Core/Config/Schema/Mapping.php +++ b/core/lib/Drupal/Core/Config/Schema/Mapping.php @@ -27,7 +27,7 @@ class Mapping extends ArrayElement implements ComplexDataInterface { protected function parse() { $elements = array(); foreach ($this->definition['mapping'] as $key => $definition) { - if (isset($this->value[$key])) { + if (isset($this->value[$key]) || array_key_exists($key, $this->value)) { $elements[$key] = $this->parseElement($key, $this->value[$key], $definition); } } diff --git a/core/lib/Drupal/Core/Config/Schema/Sequence.php b/core/lib/Drupal/Core/Config/Schema/Sequence.php index a399583c635eb67311883a844d0f50569b658374..44dbe1d6b2f32066ba94f2f8b9f9854aacf1f8ac 100644 --- a/core/lib/Drupal/Core/Config/Schema/Sequence.php +++ b/core/lib/Drupal/Core/Config/Schema/Sequence.php @@ -49,4 +49,19 @@ public function onChange($delta) { $this->parent->onChange($this->name); } } + + /** + * Gets a typed configuration element from the sequence. + * + * @param string $key + * The key of the sequence to get. + * + * @return \Drupal\Core\Config\Schema\Element + * Typed configuration element. + */ + public function get($key) { + $elements = $this->parse(); + return $elements[$key]; + } + } diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Binary.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Binary.php index 3fc1440512d7a3d3fcb147a427a54998ee1613d8..a1258d44520c7040204794fce7b39b11ac106fa4 100644 --- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Binary.php +++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Binary.php @@ -87,4 +87,11 @@ public function getString() { } return $contents; } + + /** + * @inheritdoc + */ + public function getCastedValue() { + return $this->getValue(); + } } diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Boolean.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Boolean.php index 7acb3dd343e0773177c54e2557a683055f697288..2e5677a565c163be0e63cb71855a10e65ddffb3d 100644 --- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Boolean.php +++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Boolean.php @@ -23,4 +23,10 @@ */ class Boolean extends PrimitiveBase implements BooleanInterface { + /** + * @inheritdoc + */ + public function getCastedValue() { + return (bool) $this->value; + } } diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Float.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Float.php index d1ff6a892f953ec88ccadfa18bd799e5c09db350..5153cb5d12cf2589fdb6d4734a4fda2cd3ab2d44 100644 --- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Float.php +++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Float.php @@ -23,4 +23,10 @@ */ class Float extends PrimitiveBase implements FloatInterface { + /** + * @inheritdoc + */ + public function getCastedValue() { + return (float) $this->value; + } } diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Integer.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Integer.php index 4d89e2fa48eb312e769f21b09e43c1749443565a..d7b1517700b1f69535edf49d4d6693da03612b12 100644 --- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Integer.php +++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Integer.php @@ -23,4 +23,10 @@ */ class Integer extends PrimitiveBase implements IntegerInterface { + /** + * @inheritdoc + */ + public function getCastedValue() { + return (int) $this->value; + } } diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/String.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/String.php index 36696c5023f3c0e9f36b6084ae98696175c255ea..d2d5a17eb7d1800f065ade9784576c902fa9ec4d 100644 --- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/String.php +++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/String.php @@ -23,4 +23,10 @@ */ class String extends PrimitiveBase implements StringInterface { + /** + * @inheritdoc + */ + public function getCastedValue() { + return $this->getString(); + } } diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Uri.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Uri.php index 24fd9b64eb033b6a8833c67eb0e15e0b1467aa5b..e9a08911c1e3a5ef006c06665128e2df2c09cafd 100644 --- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Uri.php +++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Uri.php @@ -21,6 +21,6 @@ * label = @Translation("URI") * ) */ -class Uri extends PrimitiveBase implements UriInterface { +class Uri extends String implements UriInterface { } diff --git a/core/lib/Drupal/Core/TypedData/PrimitiveInterface.php b/core/lib/Drupal/Core/TypedData/PrimitiveInterface.php index b2293e28b0550455f0ef72f69ce9502fb8a43c87..01ad0d9aed1f5ce53831cb88d98724189b664771 100644 --- a/core/lib/Drupal/Core/TypedData/PrimitiveInterface.php +++ b/core/lib/Drupal/Core/TypedData/PrimitiveInterface.php @@ -28,4 +28,10 @@ public function getValue(); */ public function setValue($value); + /** + * Gets the primitive data value casted to the correct PHP type. + * + * @return mixed + */ + public function getCastedValue(); } diff --git a/core/modules/block/config/schema/block.schema.yml b/core/modules/block/config/schema/block.schema.yml index 61d66e59a476b3e5672a2d2d1f6d4ffc45ede8ee..b6ba2aee7cbaab2f30fed1288498729300352614 100644 --- a/core/modules/block/config/schema/block.schema.yml +++ b/core/modules/block/config/schema/block.schema.yml @@ -87,6 +87,9 @@ block.block.*: view_mode: type: string label: 'View mode' + module: + type: string + label: 'Module' langcode: type: string label: 'Default language' diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php index 82827abd8b11c5b502b4fce6da08e9bf6d90d979..99977cbc208590cac870e4749f0b2ea4262d2e4a 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php @@ -96,7 +96,7 @@ protected function createTests() { 'status' => TRUE, 'langcode' => language_default()->id, 'theme' => 'stark', - 'region' => -1, + 'region' => '-1', 'plugin' => 'test_html_id', 'settings' => array( 'cache' => 1, @@ -106,7 +106,7 @@ protected function createTests() { ), 'visibility' => NULL, ); - $this->assertIdentical($actual_properties, $expected_properties, 'The block properties are exported correctly.'); + $this->assertIdentical($actual_properties, $expected_properties); $this->assertTrue($entity->getPlugin() instanceof TestHtmlIdBlock, 'The entity has an instance of the correct block plugin.'); } diff --git a/core/modules/breakpoint/config/schema/breakpoint.schema.yml b/core/modules/breakpoint/config/schema/breakpoint.schema.yml index eee2445ce46f86500d15e1385975a6163506531b..6281da79d938d57982788e0521579ab6392014be 100644 --- a/core/modules/breakpoint/config/schema/breakpoint.schema.yml +++ b/core/modules/breakpoint/config/schema/breakpoint.schema.yml @@ -36,6 +36,9 @@ breakpoint.breakpoint.*.*.*: langcode: type: string label: 'Default language' + status: + type: boolean + label: 'Enabled' breakpoint.breakpoint_group.*.*.*: type: mapping @@ -65,3 +68,9 @@ breakpoint.breakpoint_group.*.*.*: sourceType: type: string label: 'Group source type' + langcode: + type: string + label: 'Default language' + status: + type: boolean + label: 'Enabled' diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/CKEditorPlugin/DrupalImage.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/CKEditorPlugin/DrupalImage.php index 9fd6ec2e59ab4fab014a598c3701d555858665c4..9fc56a2f6a1f5c2a2a0897c1b5ace827cec2d870 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/CKEditorPlugin/DrupalImage.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/CKEditorPlugin/DrupalImage.php @@ -82,7 +82,6 @@ public function settingsForm(array $form, array &$form_state, Editor $editor) { * * @see \Drupal\editor\Form\EditorImageDialog * @see editor_image_upload_settings_form() - * @see editor_image_upload_settings_validate() */ function validateImageUploadSettings(array $element, array &$form_state) { $settings = &$form_state['values']['editor']['settings']['plugins']['drupalimage']['image_upload']; diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php index 5654d45f14dfdd0a48398e6386e69be88c08e8df..4287d867469681ba7a192a8e883bd048d08ea34e 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php @@ -16,6 +16,7 @@ use Drupal\Core\Config\ConfigImporter; use Drupal\Core\Config\ConfigException; use Drupal\Core\Config\ConfigFactory; +use Drupal\Core\Config\TypedConfigManager; use Drupal\Core\Routing\UrlGeneratorInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -72,6 +73,13 @@ class ConfigSync extends FormBase { */ protected $uuidService; + /** + * The typed config manager. + * + * @var \Drupal\Core\Config\TypedConfigManager + */ + protected $typedConfigManager; + /** * Constructs the object. * @@ -90,9 +98,11 @@ class ConfigSync extends FormBase { * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator * The url generator service. * @param \Drupal\Component\Uuid\UuidInterface $uuid_service - * The UUID Service. + * The UUID Service. + * @param \Drupal\Core\Config\TypedConfigManager $typed_config + * The typed configuration manager. */ - public function __construct(StorageInterface $sourceStorage, StorageInterface $targetStorage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigFactory $config_factory, EntityManagerInterface $entity_manager, UrlGeneratorInterface $url_generator, UuidInterface $uuid_service) { + public function __construct(StorageInterface $sourceStorage, StorageInterface $targetStorage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigFactory $config_factory, EntityManagerInterface $entity_manager, UrlGeneratorInterface $url_generator, UuidInterface $uuid_service, TypedConfigManager $typed_config) { $this->sourceStorage = $sourceStorage; $this->targetStorage = $targetStorage; $this->lock = $lock; @@ -101,6 +111,7 @@ public function __construct(StorageInterface $sourceStorage, StorageInterface $t $this->entity_manager = $entity_manager; $this->urlGenerator = $url_generator; $this->uuidService = $uuid_service; + $this->typedConfigManager = $typed_config; } /** @@ -115,7 +126,8 @@ public static function create(ContainerInterface $container) { $container->get('config.factory'), $container->get('entity.manager'), $container->get('url_generator'), - $container->get('uuid') + $container->get('uuid'), + $container->get('config.typed') ); } @@ -222,7 +234,8 @@ public function submitForm(array &$form, array &$form_state) { $this->configFactory, $this->entity_manager, $this->lock, - $this->uuidService + $this->uuidService, + $this->typedConfigManager ); if ($config_importer->alreadyImporting()) { drupal_set_message($this->t('Another request may be synchronizing configuration already.')); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php index cb4aeaae89b72c1e6fd2f18e117e47275077c052..ef9a9ee5f20ea8bb4575a8dbfc01f4981c19d2fb 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php @@ -61,7 +61,8 @@ function setUp() { $this->container->get('config.factory'), $this->container->get('entity.manager'), $this->container->get('lock'), - $this->container->get('uuid') + $this->container->get('uuid'), + $this->container->get('config.typed') ); $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging')); } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php index 3b406b19e0ddbe9dca80de40bb40e874de51f642..663b456f1b91784fbdbb280186ec66c852e287f1 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php @@ -7,7 +7,6 @@ namespace Drupal\config\Tests; -use Drupal\Core\Config\TypedConfig; use Drupal\Core\TypedData\Type\IntegerInterface; use Drupal\Core\TypedData\Type\StringInterface; use Drupal\simpletest\DrupalUnitTestBase; @@ -102,6 +101,8 @@ function testSchemaMapping() { $expected['label'] = 'Image style'; $expected['class'] = '\Drupal\Core\Config\Schema\Mapping'; $expected['mapping']['name']['type'] = 'string'; + $expected['mapping']['uuid']['label'] = 'UUID'; + $expected['mapping']['uuid']['type'] = 'string'; $expected['mapping']['label']['type'] = 'label'; $expected['mapping']['effects']['type'] = 'sequence'; $expected['mapping']['effects']['sequence'][0]['type'] = 'mapping'; @@ -111,6 +112,8 @@ function testSchemaMapping() { $expected['mapping']['effects']['sequence'][0]['mapping']['uuid']['type'] = 'string'; $expected['mapping']['langcode']['label'] = 'Default language'; $expected['mapping']['langcode']['type'] = 'string'; + $expected['mapping']['status']['label'] = 'Enabled'; + $expected['mapping']['status']['type'] = 'boolean'; $this->assertEqual($definition, $expected, 'Retrieved the right metadata for image.style.large'); @@ -242,4 +245,63 @@ function testSchemaData() { $this->assertEqual($site_slogan->getValue(), $new_slogan, 'Successfully updated the contained configuration data'); } + /** + * Test configuration value data type enforcement using schemas. + */ + public function testConfigSaveWithSchema() { + $untyped_values = array( + 'string' => 1, + 'empty_string' => '', + 'null_string' => NULL, + 'integer' => '100', + 'null_integer' => '', + 'boolean' => 1, + // If the config schema doesn't have a type it should be casted to string. + 'no_type' => 1, + 'mapping' => array( + 'string' => 1 + ), + 'float' => '3.14', + 'null_float' => '', + 'sequence' => array (1, 0, 1), + // Not in schema and therefore should be left untouched. + 'not_present_in_schema' => TRUE, + // Test a custom type. + 'config_test_integer' => '1', + 'config_test_integer_empty_string' => '', + ); + $untyped_to_typed = $untyped_values; + + $typed_values = array( + 'string' => '1', + 'empty_string' => '', + 'null_string' => NULL, + 'integer' => 100, + 'null_integer' => NULL, + 'boolean' => TRUE, + 'no_type' => '1', + 'mapping' => array( + 'string' => '1' + ), + 'float' => 3.14, + 'null_float' => NULL, + 'sequence' => array (TRUE, FALSE, TRUE), + 'not_present_in_schema' => TRUE, + 'config_test_integer' => 1, + 'config_test_integer_empty_string' => NULL, + ); + + // Save config which has a schema that enforces types. + \Drupal::config('config_test.schema_data_types') + ->setData($untyped_to_typed) + ->save(); + $this->assertIdentical(\Drupal::config('config_test.schema_data_types')->get(), $typed_values); + + // Save config which does not have a schema that enforces types. + \Drupal::config('config_test.no_schema_data_types') + ->setData($untyped_values) + ->save(); + $this->assertIdentical(\Drupal::config('config_test.no_schema_data_types')->get(), $untyped_values); + } + } diff --git a/core/modules/config/tests/config_test/config/schema/config_test.schema.yml b/core/modules/config/tests/config_test/config/schema/config_test.schema.yml index 04329abf610eb58aac9a50f86f4d8375994e0638..597e9f7e0faef044813b9cce1b8302cb9558563c 100644 --- a/core/modules/config/tests/config_test/config/schema/config_test.schema.yml +++ b/core/modules/config/tests/config_test/config/schema/config_test.schema.yml @@ -91,3 +91,43 @@ config_test.dynamic.*: protected_property: type: string label: 'Protected property' + +config_test_integer: + type: integer + label: 'Config test integer' + +config_test.schema_data_types: + type: mapping + label: 'Config test schema' + mapping: + config_test_integer: + type: config_test_integer + config_test_integer_empty_string: + type: config_test_integer + integer: + type: integer + null_integer: + type: integer + float: + type: float + null_float: + type: float + string: + type: string + null_string: + type: string + empty_string: + type: string + boolean: + type: boolean + no_type: + label: 'No label' + mapping: + type: mapping + mapping: + string: + type: string + sequence: + type: sequence + sequence: + - type: boolean diff --git a/core/modules/editor/editor.admin.inc b/core/modules/editor/editor.admin.inc index eed97610d6e090eedd215a70db62df338712cea2..a29924b2e766a123fa92991f0de64c3d002e0142 100644 --- a/core/modules/editor/editor.admin.inc +++ b/core/modules/editor/editor.admin.inc @@ -128,30 +128,5 @@ function editor_image_upload_settings_form(Editor $editor) { '#states' => $show_if_image_uploads_enabled, ); - $form['#element_validate'] = array( - 'editor_image_upload_settings_validate', - ); - return $form; } - -/** - * #element_validate handler for editor_image_upload_settings_validate(). - * - * Ensures each form item's value is cast to the proper type. - * - * @see \Drupal\editor\Form\EditorImageDialog - * @ingroup forms - */ -function editor_image_upload_settings_validate(array $element, array &$form_state) { - $cast_value = function($type, $element) use (&$form_state) { - $section = $element['#parents']; - $value = NestedArray::getValue($form_state['values'], $section); - settype($value, $type); - NestedArray::setValue($form_state['values'], $section, $value); - }; - - $cast_value('bool', $element['status']); - $cast_value('int', $element['max_dimensions']['width']); - $cast_value('int', $element['max_dimensions']['height']); -} diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module index 1234099b83dd64f7d413f2a9fad2520898bc2b40..affe7111712e7727ee3a048a63912b5e9cd197cb 100644 --- a/core/modules/editor/editor.module +++ b/core/modules/editor/editor.module @@ -552,7 +552,7 @@ function _editor_get_processed_text_fields(ContentEntityInterface $entity) { $settings = Field::fieldInfo() ->getInstance($entity->entityType(), $entity->bundle(), $field) ->getSettings(); - return isset($settings['text_processing']) && $settings['text_processing'] === '1'; + return isset($settings['text_processing']) && $settings['text_processing'] === TRUE; }); } diff --git a/core/modules/field/config/schema/field.schema.yml b/core/modules/field/config/schema/field.schema.yml index 7c09ccea46a3d3d6818e5846999936d36d2dbf78..33b8d3e9da2cbeccca56dd80bac258185749e9af 100644 --- a/core/modules/field/config/schema/field.schema.yml +++ b/core/modules/field/config/schema/field.schema.yml @@ -24,6 +24,9 @@ field.field.*.*: langcode: type: string label: 'Default language' + name: + type: string + label: 'Name' entity_type: type: string label: 'Entity type' diff --git a/core/modules/filter/config/schema/filter.schema.yml b/core/modules/filter/config/schema/filter.schema.yml index 391b968107edee08fb9bfb24f712dc393b08180c..3f32071237391dbdf0ecb09382f46b7e5a670e8f 100644 --- a/core/modules/filter/config/schema/filter.schema.yml +++ b/core/modules/filter/config/schema/filter.schema.yml @@ -21,6 +21,9 @@ filter.format.*: name: type: label label: 'Name' + uuid: + type: string + label: 'UUID' status: type: boolean label: 'Enabled' diff --git a/core/modules/image/config/schema/image.schema.yml b/core/modules/image/config/schema/image.schema.yml index 87a8f1e65ef8dc47d4a413c9243e826bd175843f..b0dfd56f3ac80708521a608301654d84662bc805 100644 --- a/core/modules/image/config/schema/image.schema.yml +++ b/core/modules/image/config/schema/image.schema.yml @@ -6,6 +6,9 @@ image.style.*: mapping: name: type: string + uuid: + type: string + label: 'UUID' label: type: label effects: @@ -24,6 +27,9 @@ image.style.*: langcode: type: string label: 'Default language' + status: + type: boolean + label: 'Enabled' image.effect.image_crop: type: image_size diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/MigrateSystemConfigsTest.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/MigrateSystemConfigsTest.php index c09c390d0d0498179c7a90a3d2b12ed3bed159b3..a878076e111e603500c426a270753a44a9ac0640 100644 --- a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/MigrateSystemConfigsTest.php +++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/MigrateSystemConfigsTest.php @@ -85,8 +85,8 @@ public function testSystemPerformance() { $executable = new MigrateExecutable($migration, new MigrateMessage()); $executable->import(); $config = \Drupal::config('system.performance'); - $this->assertIdentical($config->get('css.preprocess'), 0); - $this->assertIdentical($config->get('js.preprocess'), 0); + $this->assertIdentical($config->get('css.preprocess'), FALSE); + $this->assertIdentical($config->get('js.preprocess'), FALSE); $this->assertIdentical($config->get('cache.page.max_age'), 0); } } diff --git a/core/modules/number/config/schema/number.schema.yml b/core/modules/number/config/schema/number.schema.yml index 087ef8ffb5fbb0ff71d43f863a78307131795cf6..f597c0270eba2221feec9858d486d77d2186471d 100644 --- a/core/modules/number/config/schema/number.schema.yml +++ b/core/modules/number/config/schema/number.schema.yml @@ -51,10 +51,10 @@ field.number_decimal.instance_settings: label: 'Decimal' mapping: min: - type: integer + type: float label: 'Minimum' max: - type: integer + type: float label: 'Maximum' prefix: type: string @@ -71,7 +71,7 @@ field.number_decimal.value: label: 'Default value' mapping: value: - type: integer + type: float label: 'Value' field.number_float.settings: @@ -86,10 +86,10 @@ field.number_float.instance_settings: label: 'Float' mapping: min: - type: integer + type: float label: 'Minimum' max: - type: integer + type: float label: 'Maximum' prefix: type: string @@ -106,5 +106,5 @@ field.number_float.value: label: 'Default value' mapping: value: - type: integer + type: float label: 'Value' diff --git a/core/modules/options/config/schema/options.schema.yml b/core/modules/options/config/schema/options.schema.yml index 8d1481aa1306fc2cf403d2c7c33314220f48b522..367e4b580fb4ebfc122cb2a5942dc92bad83e799 100644 --- a/core/modules/options/config/schema/options.schema.yml +++ b/core/modules/options/config/schema/options.schema.yml @@ -15,11 +15,9 @@ field.list_integer.settings: label: 'Allowed values function' field.list_integer.instance_settings: - type: mapping label: 'List (integer)' - sequence: - - type: string - label: 'setting' + type: mapping + mapping: { } field.list_integer.value: type: sequence @@ -47,11 +45,9 @@ field.list_float.settings: label: 'Allowed values function' field.list_float.instance_settings: - type: mapping label: 'List (float)' - sequence: - - type: string - label: 'setting' + type: mapping + mapping: { } field.list_float.value: type: sequence @@ -79,11 +75,9 @@ field.list_text.settings: label: 'Allowed values function' field.list_text.instance_settings: - type: mapping label: 'List (float)' - sequence: - - type: string - label: 'setting' + type: mapping + mapping: { } field.list_text.value: type: sequence @@ -111,11 +105,9 @@ field.list_boolean.settings: label: 'Allowed values function' field.list_boolean.instance_settings: - type: mapping label: 'List (boolean)' - sequence: - - type: string - label: 'setting' + type: mapping + mapping: { } field.list_boolean.value: type: sequence diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index 882542e97a67567ce2f0476f661b2c0dd0b2f3ce..1e2938416e3e35d50e12c60340a666275546b4c1 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -1400,7 +1400,8 @@ public function configImporter() { $this->container->get('config.factory'), $this->container->get('entity.manager'), $this->container->get('lock'), - $this->container->get('uuid') + $this->container->get('uuid'), + $this->container->get('config.typed') ); } // Always recalculate the changelist when called. diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml index 4f3625b1f31c95c13664669039bb2d6cd85481db..0dabf5e21da9e84a15cec8b3376b454bf24dda88 100644 --- a/core/modules/system/config/schema/system.schema.yml +++ b/core/modules/system/config/schema/system.schema.yml @@ -97,7 +97,7 @@ system.date: default: type: string label: 'Default time zone' - user: + user: type: mapping label: 'User' mapping: @@ -220,7 +220,7 @@ system.performance: type: mapping label: 'Page cache' mapping: - enabled: + use_internal: type: boolean label: 'Cache pages for anonymous users' max_age: @@ -236,6 +236,22 @@ system.performance: gzip: type: boolean label: 'Compress CSS files' + fast_404: + type: mapping + label: 'Fast 404 settings' + mapping: + enabled: + type: boolean + label: 'Fast 404 enabled' + paths: + type: string + label: 'Regular expression to match' + exclude_paths: + type: string + label: 'Regular expression to not match' + html: + type: string + label: 'Fast 404 page html' js: type: mapping label: 'JavaScript performance settings' @@ -295,6 +311,9 @@ system.theme: sequence: - type: string label: 'Theme' + default: + type: string + label: 'Default theme' system.menu.*: type: mapping @@ -303,6 +322,9 @@ system.menu.*: id: type: string label: 'Menu identifier' + uuid: + type: string + label: 'UUID' label: type: label label: 'Menu label' @@ -312,3 +334,9 @@ system.menu.*: langcode: type: string label: 'Default language' + locked: + type: boolean + label: '' + status: + type: boolean + label: '' diff --git a/core/modules/system/config/system.maintenance.yml b/core/modules/system/config/system.maintenance.yml index 5ea379dff2962d6ea360a4657bc00f78eb289de5..40cfeb21a51620c0c34eda703e7e38f1d6eb6554 100644 --- a/core/modules/system/config/system.maintenance.yml +++ b/core/modules/system/config/system.maintenance.yml @@ -1,3 +1,2 @@ -enabled: '0' message: '@site is currently under maintenance. We should be back shortly. Thank you for your patience.' langcode: en diff --git a/core/modules/taxonomy/config/schema/taxonomy.schema.yml b/core/modules/taxonomy/config/schema/taxonomy.schema.yml index 21d1322bfeb9263271a70a20a59e17dff7bf654d..9170280ed1a089b38239e36457e405939ea1885a 100644 --- a/core/modules/taxonomy/config/schema/taxonomy.schema.yml +++ b/core/modules/taxonomy/config/schema/taxonomy.schema.yml @@ -21,6 +21,9 @@ taxonomy.vocabulary.*: vid: type: string label: 'Machine name' + uuid: + type: string + label: 'UUID' name: type: label label: 'Name' @@ -28,7 +31,7 @@ taxonomy.vocabulary.*: type: label label: 'Description' hierarchy: - type: boolean + type: integer label: 'Hierarchy' weight: type: integer diff --git a/core/modules/tour/config/schema/tour.schema.yml b/core/modules/tour/config/schema/tour.schema.yml index ca8d781da0475bac05c0bb735a9cebbe74a3bca2..b1a0c28d8feba79c4b25e8627a56695060c78095 100644 --- a/core/modules/tour/config/schema/tour.schema.yml +++ b/core/modules/tour/config/schema/tour.schema.yml @@ -7,9 +7,18 @@ tour.tour.*: id: type: string label: 'ID' + uuid: + type: string + label: 'UUID' + module: + type: string + label: 'Providing module' label: type: label label: 'Label' + status: + type: boolean + label: 'Enabled' langcode: type: string label: 'Default language' @@ -42,6 +51,9 @@ tour.tip: weight: type: integer label: 'Weight' + location: + type: string + label: 'Location' attributes: type: sequence label: 'Attributes' diff --git a/core/modules/user/config/schema/user.schema.yml b/core/modules/user/config/schema/user.schema.yml index 05155ff3472c1105644ee9eeb85d2a4fdfc17c53..5aec5a2909c7be7ac05bf6980de16933490655fc 100644 --- a/core/modules/user/config/schema/user.schema.yml +++ b/core/modules/user/config/schema/user.schema.yml @@ -137,6 +137,9 @@ user.role.*: sequence: - type: string label: 'Permission' + status: + type: boolean + label: 'Status' langcode: type: string label: 'Default language'