diff --git a/core/includes/config.inc b/core/includes/config.inc index 8e9ce5e..3f6341a 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -214,7 +214,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/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index 59dbad3..18afe28 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 object. + * + * @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 @@ -406,6 +421,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->getTypedConfigManager()->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(); } @@ -468,4 +494,122 @@ 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)) { + $typed_config_manager = $this->getTypedConfigManager(); + $definition = $typed_config_manager->getDefinition($this->name); + $this->schemaWrapper = $typed_config_manager->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 + */ + 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 (is_scalar($value) || $value === NULL) { + try { + $class = get_class($this->getSchemaForKey($key)); + } + catch(\Exception $e) { + // @todo throw an exception due to an incomplete schema. Only possible + // once https://drupal.org/node/1910624 is complete. + $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; + case 'Drupal\Core\TypedData\Plugin\DataType\Float': + $value = (float) $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->castValue($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 (!isset($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..92e9a90 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 a399583..44dbe1d 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/modules/block/config/schema/block.schema.yml b/core/modules/block/config/schema/block.schema.yml index 61d66e5..b6ba2ae 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 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 eee2445..6281da7 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 7e95109..12ce8e4 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/CKEditorPlugin/DrupalImage.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/CKEditorPlugin/DrupalImage.php @@ -84,7 +84,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/Tests/ConfigEntityTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php index 9acca96..d3d9818 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..9f465d7 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,47 @@ 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, + 'integer' => '100', + 'boolean' => 1, + // In the config schema this doesn't have a type so should cast to string. + 'no_type' => 1, + 'array' => array( + 'string' => 1 + ), + 'float' => '3.14', + // 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' + ), + 'float' => 3.14, + '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..d9851c3 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,23 @@ 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 + float: + type: float + 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.admin.inc b/core/modules/editor/editor.admin.inc index eed9761..a29924b 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 837544f..e5fa4cd 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) ->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 7c09cce..33b8d3e 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 391b968..3f32071 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/migrate_drupal/lib/Drupal/migrate_drupal/Tests/MigrateSystemConfigsTest.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/MigrateSystemConfigsTest.php index c09c390..a878076 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 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..367e4b5 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/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml index 4f3625b..0dabf5e 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 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 ca8d781..b1a0c28 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 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'