diff --git a/core/includes/config.inc b/core/includes/config.inc index 8e9ce5e..2df436f 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -212,9 +212,9 @@ function config_get_entity_type_by_name($name) { * * Use the typed data manager service for creating typed configuration objects. * - * @see \Drupal\Core\TypedData\TypedDataManager::create() + * @see \Drupal\Core\Config\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/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index 83366b1..3f117fc 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -8,6 +8,7 @@ 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; @@ -78,6 +79,20 @@ class Config { protected $isLoaded = FALSE; /** + * The config schema wrapper object for this configuration. + * + * @var \Drupal\Core\Config\Schema\Element + */ + protected $schemaWrapper; + + /** + * The typed config manager. + * + * @var \Drupal\Core\Config\TypedConfigManager + */ + protected $typedConfigManager; + + /** * Constructs a configuration object. * * @param string $name @@ -400,6 +415,15 @@ public function load() { public function save() { // Validate the configuration object name before saving. static::validateName($this->name); + + if ($this->getTypedConfigManager()->hasConfigSchema($this->name)) { + // Ensure that the schema wrapper has the latest data. + $this->setSchemaWrapper(); + foreach ($this->data as $key => $value) { + $this->data[$key] = $this->getTypedValue($key, $value); + } + } + if (!$this->isLoaded) { $this->load(); } @@ -459,4 +483,125 @@ public function merge(array $data_to_merge) { $this->replaceData(NestedArray::mergeDeepArray(array($this->data, $data_to_merge), TRUE)); return $this; } + + /** + * Gets the schema for the whole configuration object. + * + * @return \Drupal\Core\Config\Schema\Element + */ + public function getSchemaWrapper() { + if (empty($this->schemaWrapper)) { + $this->setSchemaWrapper(); + } + return $this->schemaWrapper; + } + + /** + * Sets the schema wrapper property using the current configuration data + * + * @return \Drupal\Core\Config\Config + * The configuration object. + */ + public function setSchemaWrapper() { + $typed_config_manager = $this->getTypedConfigManager(); + $definition = $typed_config_manager->getDefinition($this->name); + $this->schemaWrapper = $typed_config_manager->create($definition, $this->data); + return $this; + } + + /** + * Gets the definition for the configuration key. + * + * @param $key + * Identifier to store value in config. + * + * @return \Drupal\Core\Config\Schema\Element + */ + public 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; + } + + /** + * Gets the value with the correct data type for this config object. + * + * @param string $key + * Identifier to store value in config. + * @param string $value + * Value to associate with identifier. + * + * @return mixed + * The value cast to the type indicated in the schema. + */ + public function getTypedValue($key, $value) { + if (is_scalar($value) || $value === NULL) { + try { + $class = get_class($this->getSchemaForKey($key)); + } + catch(\Exception $e) { + // @todo throw an exception due to an incomplete schema + $class = FALSE; + } + switch ($class) { + case 'Drupal\Core\TypedData\Plugin\DataType\String': + case 'Drupal\Core\TypedData\Plugin\DataType\Uri': + case 'Drupal\Core\TypedData\Plugin\DataType\Email': + case 'Drupal\Core\Config\Schema\Property': + $value = (string) $value; + break; + case 'Drupal\Core\TypedData\Plugin\DataType\Integer': + $value = (int) $value; + break; + case 'Drupal\Core\TypedData\Plugin\DataType\Boolean': + $value = (bool) $value; + break; + default: + // Don't change the value. + break; + } + } + 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->getTypedValue($key . '.' . $nested_value_key, $nested_value); + } + } + return $value; + } + + + /** + * Returns the typed config manager service. + * + * Use the typed data manager service for creating typed configuration objects. + * + * @see \Drupal\Core\Config\TypedDataManager::create() + * + * @return \Drupal\Core\Config\TypedConfigManager + */ + protected function getTypedConfigManager() { + if (empty($this->typedConfigManager)) { + $this->typedConfigManager = \Drupal::service('config.typed'); + } + return $this->typedConfigManager; + } } diff --git a/core/lib/Drupal/Core/Config/Schema/Mapping.php b/core/lib/Drupal/Core/Config/Schema/Mapping.php index 9701df0..72de972 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 (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 a399583..832c5ec 100644 --- a/core/lib/Drupal/Core/Config/Schema/Sequence.php +++ b/core/lib/Drupal/Core/Config/Schema/Sequence.php @@ -49,4 +49,18 @@ public function onChange($delta) { $this->parent->onChange($this->name); } } + + /** + * Gets a typed configuration element from the sequence. + * + * @param $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/modules/block/config/schema/block.schema.yml b/core/modules/block/config/schema/block.schema.yml index 64c9a3a..3eeb65b 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: 'Language code' diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php index 82827ab..732dfb9 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php @@ -92,11 +92,11 @@ protected function createTests() { // Ensure that default values are filled in. $expected_properties = array( 'id' => 'test_block', - 'weight' => NULL, + 'weight' => 0, '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 0f4184e..30a7c66 100644 --- a/core/modules/breakpoint/config/schema/breakpoint.schema.yml +++ b/core/modules/breakpoint/config/schema/breakpoint.schema.yml @@ -48,6 +48,9 @@ breakpoint.breakpoint.*.*.*: langcode: type: string label: 'Language' + status: + type: boolean + label: 'Enabled' breakpoint.breakpoint_group.*.*.*: type: mapping @@ -77,3 +80,9 @@ breakpoint.breakpoint_group.*.*.*: sourceType: type: string label: 'Group source type' + langcode: + type: string + label: 'Language' + status: + type: boolean + label: 'Enabled' diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php index f04b518..a0d7951 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php @@ -146,7 +146,7 @@ function testCRUD() { // Verify that the entity was overwritten. $same_id = entity_load('config_test', $config_test->id()); $this->assertIdentical($same_id->id(), $config_test->id()); - $this->assertIdentical($same_id->label(), NULL); + $this->assertIdentical($same_id->label(), ''); $this->assertNotEqual($same_id->uuid(), $config_test->uuid()); // Delete the overridden entity first. diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php index 3b406b1..9eca659 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php @@ -102,6 +102,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 +113,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 +246,44 @@ function testSchemaData() { $this->assertEqual($site_slogan->getValue(), $new_slogan, 'Successfully updated the contained configuration data'); } + /** + * Test configuration value data type enforcement using schemas. + */ + protected function testConfigSaveWithSchema () { + $untyped_values = array( + 'string' => 1, + 'integer' => '100', + 'boolean' => 1, + // In the config schema this does not have a type so should cast to string. + 'no_type' => 1, + 'array' => array( + 'string' => 1 + ), + // Not in schema and therefore should be left untouched. + 'not_present_in_schema' => TRUE, + ); + + $typed_values = array( + 'string' => '1', + 'integer' => 100, + 'boolean' => TRUE, + 'no_type' => '1', + 'array' => array( + 'string' => '1' + ), + 'not_present_in_schema' => TRUE, + ); + + // Save config which has a schema that enforces types. + \Drupal::config('config_test.schema_data_types') + ->setData($untyped_values) + ->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 04329ab..faba694 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,21 @@ config_test.dynamic.*: protected_property: type: string label: 'Protected property' + +config_test.schema_data_types: + type: mapping + label: 'Config test schema' + mapping: + integer: + type: integer + string: + type: string + boolean: + type: boolean + no_type: + label: 'No label' + array: + type: mapping + mapping: + string: + type: string diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module index c1bffd4..fbfe4aa 100644 --- a/core/modules/editor/editor.module +++ b/core/modules/editor/editor.module @@ -568,7 +568,7 @@ function _editor_get_processed_text_fields(ContentEntityInterface $entity) { $settings = Field::fieldInfo() ->getInstance($entity->entityType(), $entity->bundle(), $field) ->getFieldSettings(); - 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 424132b..d1699c1 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 6a36034..d13c777 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 a6bd186..191b0cb 100644 --- a/core/modules/image/config/schema/image.schema.yml +++ b/core/modules/image/config/schema/image.schema.yml @@ -19,6 +19,9 @@ image.style.*: mapping: name: type: string + uuid: + type: string + label: 'UUID' label: type: label effects: @@ -37,6 +40,9 @@ image.style.*: langcode: type: string label: 'Default language' + status: + type: boolean + label: 'Enabled' # Image effects plugins: image.effect.% # These are used in image styles. diff --git a/core/modules/number/config/schema/number.schema.yml b/core/modules/number/config/schema/number.schema.yml index 087ef8f..9aafca5 100644 --- a/core/modules/number/config/schema/number.schema.yml +++ b/core/modules/number/config/schema/number.schema.yml @@ -12,10 +12,10 @@ field.number_integer.instance_settings: label: 'Integer' mapping: min: - type: integer + type: string label: 'Minimum' max: - type: integer + type: string label: 'Maximum' prefix: type: string @@ -51,10 +51,10 @@ field.number_decimal.instance_settings: label: 'Decimal' mapping: min: - type: integer + type: string label: 'Minimum' max: - type: integer + type: string label: 'Maximum' prefix: type: string @@ -71,7 +71,7 @@ field.number_decimal.value: label: 'Default value' mapping: value: - type: integer + type: string label: 'Value' field.number_float.settings: @@ -86,10 +86,10 @@ field.number_float.instance_settings: label: 'Float' mapping: min: - type: integer + type: string label: 'Minimum' max: - type: integer + type: string label: 'Maximum' prefix: type: string @@ -106,5 +106,5 @@ field.number_float.value: label: 'Default value' mapping: value: - type: integer + type: string label: 'Value' diff --git a/core/modules/options/config/schema/options.schema.yml b/core/modules/options/config/schema/options.schema.yml index 8d1481a..ac4f62d 100644 --- a/core/modules/options/config/schema/options.schema.yml +++ b/core/modules/options/config/schema/options.schema.yml @@ -15,11 +15,8 @@ field.list_integer.settings: label: 'Allowed values function' field.list_integer.instance_settings: - type: mapping label: 'List (integer)' - sequence: - - type: string - label: 'setting' + type: sequence field.list_integer.value: type: sequence @@ -47,11 +44,8 @@ field.list_float.settings: label: 'Allowed values function' field.list_float.instance_settings: - type: mapping label: 'List (float)' - sequence: - - type: string - label: 'setting' + type: sequence field.list_float.value: type: sequence @@ -79,11 +73,8 @@ field.list_text.settings: label: 'Allowed values function' field.list_text.instance_settings: - type: mapping label: 'List (float)' - sequence: - - type: string - label: 'setting' + type: sequence field.list_text.value: type: sequence @@ -111,11 +102,8 @@ field.list_boolean.settings: label: 'Allowed values function' field.list_boolean.instance_settings: - type: mapping label: 'List (boolean)' - sequence: - - type: string - label: 'setting' + type: sequence field.list_boolean.value: type: sequence diff --git a/core/modules/picture/config/schema/picture.schema.yml b/core/modules/picture/config/schema/picture.schema.yml index 0650f26..4dfcf6e 100644 --- a/core/modules/picture/config/schema/picture.schema.yml +++ b/core/modules/picture/config/schema/picture.schema.yml @@ -1,33 +1,33 @@ # Schema for the configuration files of the Picture module. -picture.mappings.*: - type: mapping - label: 'Picture mapping' - mapping: - id: - type: string - label: 'Machine-readable name' - uuid: - type: string - label: 'UUID' - label: - type: label - label: 'Label' - mappings: - type: sequence - label: 'Mappings' - sequence: - - type: sequence - label: 'Mapping' - sequence: - - type: string - label: 'Image style' - breakpointGroup: - type: string - label: 'Breakpoint group' - status: - type: boolean - label: 'Status' - langcode: - type: string - label: 'Default language' +#picture.mappings.*: +# type: mapping +# label: 'Picture mapping' +# mapping: +# id: +# type: string +# label: 'Machine-readable name' +# uuid: +# type: string +# label: 'UUID' +# label: +# type: label +# label: 'Label' +# mappings: +# type: sequence +# label: 'Mappings' +# sequence: +# - type: sequence +# label: 'Mapping' +# sequence: +# - type: string +# label: 'Image style' +# breakpointGroup: +# type: string +# label: 'Breakpoint group' +# status: +# type: boolean +# label: 'Status' +# langcode: +# type: string +# label: 'Default language' diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml index 020e9d3..bd446f8 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: @@ -226,7 +226,7 @@ system.performance: type: mapping label: 'Page cache' mapping: - enabled: + use_internal: type: boolean label: 'Cache pages for anonymous users' max_age: @@ -242,6 +242,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' @@ -304,6 +320,9 @@ system.theme: sequence: - type: string label: 'Theme' + default: + type: string + label: 'Default theme' system.menu.*: type: mapping @@ -312,6 +331,9 @@ system.menu.*: id: type: string label: 'Menu identifier' + uuid: + type: string + label: 'UUID' label: type: label label: 'Menu label' @@ -321,3 +343,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 5ea379d..40cfeb2 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 21d1322..9170280 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 3975fb4..2645e73 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: '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 05155ff..5aec5a2 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'