diff --git a/core/includes/config.inc b/core/includes/config.inc index 99774ef..5e51f65 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -174,10 +174,18 @@ function config_sync_get_changes(StorageInterface $source_storage, StorageInterf * Retrieves metadata for a configuration object or key. * * The metadata has the same structure as the configuration object itself but - * the configuration values are replaced by an array containing exactly one - * element. The key is .definition and the value is a $definition array to - * be passed to typed_data()->create() or - * \Drupal\Core\TypedData\TypedDataManager::create(). + * the configuration values are replaced by an array containing some special + * elements, whose keys are prefixed by a dot: + * - '.definition', array containing the data type definition of the element. + * The value is a $definition array to be passed to typed_data()->create() + * or \Drupal\Core\TypedData\TypedDataManager::create(). + * - '.include', name of additional metadata file to be added. The current + * metadata will then be merged on top of it using + * NestedArray::mergeRecursive() + * - '.list', contains an array of metadata to be used for each of the children + * elements when there's no other metadata for them. This will also imply + * that the type definition's 'list' property for the parent element will be + * set to TRUE. * * For example, for the config file core/modules/system/config/system.site.yml: * @code @@ -239,26 +247,24 @@ function config_sync_get_changes(StorageInterface $source_storage, StorageInterf * @code * .definition: * label: 'User mails' - * list: '1' - * list settings: - * elements name: 'user.mail.%' - * @endcode - * - * For every element, meta/user.mail.%.yml is read as specified by the - * 'elements name' list settings: - * @code - * .definition: - * label: 'Mail text' - * subject: + * .list: * .definition: - * label: 'Subject' - * type: 'text' - * body: - * .definition: - * label: 'Body' - * type: 'text' + * label: 'Mail text' + * subject: + * .definition: + * label: 'Subject' + * type: 'text' + * body: + * .definition: + * label: 'Body' + * type: 'text' + * @endcode * + * For every element in the list of user mails, the metadata is specified by + * the '.list' key which contains an array of metadata to be used for each of + * the nested configuration elements. + * * As said above, the type for list elements is config_element, so the * $definition array for the status_blocked array is: * @code @@ -268,6 +274,14 @@ function config_sync_get_changes(StorageInterface $source_storage, StorageInterf * ); * @endcode * + * While the $definition array for the 'subject' nested property of it is: + * @code + * array( + * 'label' => 'Subject', + * 'type' => 'text', + * ); + * @endcode + * * The % in meta/user.mail.%.yml is a placeholder and the array key can be * used for more specific metadata. So, meta/user.mail.status_canceled.yml * could be used to specify some metadata only for the status_canceled list @@ -275,8 +289,8 @@ function config_sync_get_changes(StorageInterface $source_storage, StorageInterf * in meta/user.mail.%.yml as described in * \Drupal\Component\Utility\NestedArray::mergeDeep(). * - * Other lists use a property inside a list element for this key. For example, - * core/modules/image/config/image.style.medium.yml: + * Other lists use a property inside a the '.include' element for this key. + * For example, core/modules/image/config/image.style.medium.yml: * @code * name: medium * label: Medium (220x220) @@ -304,20 +318,62 @@ function config_sync_get_changes(StorageInterface $source_storage, StorageInterf * effects: * .definition: * label: 'Style effects' - * list: '1' - * list settings: - * elements name: 'image.style.effects.%' - * elements key: 'name' + * .list: + * .include: 'image.effect.[name]' + * .definition: + * label: 'Image style effect' + * weight: + * .definition: + * label: 'Weight' + * type: integer + * ieid: + * .definition: + * label: 'IEID' * @endcode * + * For this example, for each image effect, we have the metadata under the + * '.list' key. Each of the effects will have: + * - Some common properties that depend on the image style (weight, ieid) and + * since they are specific of image styles (not image effects) they are + * defined right in this element. + * - Some common properties for all image effects that are defined in + * image.effect.%.yml + * - Some properties specific of a given image effect, like the ones in + * image.effect.image_scale.yml + * * In this case we want to reuse the metadata for 'image_scale' every time - * that effect is used. So the elements key is set to name, which in this + * that effect is used. So the '[name]' variable in the include is replaced + * by the element's 'name' value, which in this * example will indeed be 'image_scale' so to find the metadata for the * bddf0d06-42f9-4c75-a700-a33cafa25ea0 list element, - * meta/image.style.effects.image_scale.yml is used if it exists and - * meta/image.style.effects.%.yml if it does not. + * meta/image.effect.image_scale.yml is used if it exists and + * meta/image.effect.%.yml if it does not. + * + * For example here is meta/image.effect.image_scale.yml: + * @code + * .include: 'image.effect.%' + * .definition: + * label: 'Image scale' + * data: + * width: + * .definition: + * label: 'Width' + * type: 'integer' + * height: + * .definition: + * label: 'Height' + * type: 'integer' + * upscale: + * .definition: + * label: 'Upscale' + * type: 'boolean' + * @endcode * - * For example here is meta/image.style.effects.%.yml: + * Note there is another '.include' at the top of the file which means that + * file is included first and then this one adds or overrides properties in + * image.effect.%.yml + * + * And here is meta/image.effect.%.yml: * @code * .definition: * label: 'Image style effect' @@ -336,27 +392,11 @@ function config_sync_get_changes(StorageInterface $source_storage, StorageInterf * .definition: * label: 'IEID' * @endcode - * And meta/image.style.effects.image_scale.yml: - * @code - * .definition: - * label: 'Image scale' - * data: - * width: - * .definition: - * label: 'Width' - * type: 'integer' - * height: - * .definition: - * label: 'Height' - * type: 'integer' - * upscale: - * .definition: - * label: 'Upscale' - * type: 'boolean' - * @endcode + * + * * In this example, it's visible how the top-level label is overridden in - * meta/image.style.effects.image_scale.yml. + * meta/image.effect.image_scale.yml. * * @see \Drupal\Core\TypedData\TypedDataManager::create() * @see \Drupal\Core\Config\Metadata\ElementBase diff --git a/core/lib/Drupal/Core/Config/Metadata/ElementBase.php b/core/lib/Drupal/Core/Config/Metadata/ElementBase.php index 81b75cd..3f8e68d 100644 --- a/core/lib/Drupal/Core/Config/Metadata/ElementBase.php +++ b/core/lib/Drupal/Core/Config/Metadata/ElementBase.php @@ -7,8 +7,10 @@ namespace Drupal\Core\Config\Metadata; +use Drupal\Component\Utility\NestedArray; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\TypedData\Type\TypedData; + use ArrayIterator; use IteratorAggregate; @@ -78,25 +80,15 @@ protected function getElement($key) { */ protected function getElementMetadata($key) { // Metadata for nested elements must be built before. - if (!isset($this->metadata[$key])) { - $this->metadata[$key] = $this->buildElementMetadata($key, $this->value[$key]); + if (isset($this->metadata[$key])) { + return $this->metadata[$key]; + } + elseif (isset($this->metadata['.list'])) { + return $this->metadata['.list']; + } + else { + return array(); } - return $this->metadata[$key]; - } - - /** - * Builds metadata for a child element. - * - * @param string $key - * Element's key. - * @param mixed $data - * Configuration data for the element. - * - * @return array - * Configuration metadata for the element. - */ - protected function buildElementMetadata($key, $data) { - return isset($this->metadata[$key]) ? $this->metadata[$key] : array(); } /** @@ -149,6 +141,22 @@ public function getMetadata() { * Implements Drupal\Core\Config\Metadata\ElementInterface::setMetadata(). */ public function setMetadata($metadata) { + // Process '.include' directive if present. + if (isset($metadata['.include'])) { + $replace = array(); + // If this element has a key (is not the parent), add it to the variables + // for replacement under '[%key]' + if (isset($this->key)) { + $replace['[%key]'] = $this->key; + } + // Get other variable replacements from the data, only scalar values. + if (is_array($this->value)) { + foreach (array_filter($this->value, 'is_scalar') as $key => $value) { + $replace['[' . $key . ']'] = $value; + } + } + $metadata = $this->processInclude($metadata, $replace); + } $this->metadata = $metadata; } @@ -277,10 +285,13 @@ public static function buildElement($value, $metadata, $key, $parent = NULL) { */ public static function buildDefinition($data, $metadata) { $definition = isset($metadata['.definition']) ? $metadata['.definition'] : array(); - unset($metadata['.definition']); - // The default type will depend on whether we've got children or not. + // If we have a '.list' directive we assume this is a list. + if (isset($metadata['.list'])) { + $definition += array('list' => TRUE); + } + // The default type will depend on whether data is an array. if (!isset($definition['type'])) { - if (!empty($metadata) || is_array($data) || !empty($definition['list']) || !empty($definition['list settings'])) { + if (is_array($data) || !empty($definition['list']) || !empty($definition['list settings'])) { $definition['type'] = 'config_element'; } else { @@ -290,4 +301,24 @@ public static function buildDefinition($data, $metadata) { return $definition; } + /** + * Processes '.include' keyword applying variable replacements. + * + * This method is called recursively in case the included metadata has also + * an '.include' keyword. + * + * @param array $metadata + * Metadata array for the element. + * @param array $replace + * Variable replacements for the include value. + */ + protected function processInclude($metadata, $replace) { + if (isset($metadata['.include']) && $include = config_metadata(strtr($metadata['.include'], $replace))) { + // There may be nested '.include' directives, we need to process them too. + $include = $this->processInclude($include, $replace); + $metadata = NestedArray::mergeDeep($include, $metadata); + } + return $metadata; + } + } diff --git a/core/lib/Drupal/Core/Config/Metadata/ListElement.php b/core/lib/Drupal/Core/Config/Metadata/ListElement.php index 659628c..4cb494d 100644 --- a/core/lib/Drupal/Core/Config/Metadata/ListElement.php +++ b/core/lib/Drupal/Core/Config/Metadata/ListElement.php @@ -7,7 +7,6 @@ namespace Drupal\Core\Config\Metadata; -use Drupal\Component\Utility\NestedArray; use Drupal\Core\TypedData\ListInterface; /** @@ -15,52 +14,8 @@ * * This is a configuration element that wraps a list of nested elements in the * configuration data. - * - * Supported list settings (below the definition's 'list settings' key) are: - * - 'elements name', Configuration name for each of the nested elements, may - * contain the placeholder '%' to replace by the element's key. - * - 'elements key', Name of the configuration data property to be used as the - * key for name replacement. */ class ListElement extends ElementBase implements ListInterface { - - /** - * Overrides Drupal\Core\Config\Metadata\ElementBase::buildElementMetadata(). - */ - protected function buildElementMetadata($key, $data) { - $meta = parent::buildElementMetadata($key, $data); - // When the parent element is a list, elements may have special settings. - if (isset($this->definition['list settings'])) { - $settings = $this->definition['list settings']; - if (isset($settings['elements_base']) && $base = config_metadata($settings['elements_base'])) { - // Load common base metadata for all elements. - $meta = NestedArray::mergeDeep($base, $meta); - } - if (isset($settings['elements name'])) { - // Load specific metadata for each element. - $name = $settings['elements name']; - $element_key = $key; - if (isset($settings['elements key'])) { - // The search key for this one is in the data itself. - $element_key = isset($data[$settings['elements key']]) ? $data[$settings['elements key']] : NULL; - } - if ($element_key) { - $name = str_replace('%', $element_key, $name); - } - if ($merge = config_metadata($name)) { - $meta = NestedArray::mergeDeep($meta, $merge); - } - } - if (isset($settings['elements_label']) && !isset($meta['.label'])) { - // Extract element's label from configuration data. - if (!empty($data[$settings['elements_label']])) { - $meta['.label'] = $data[$settings['elements_label']]; - } - } - } - return $meta; - } - /** * Implements Drupal\Core\TypedData\ListInterface::isEmpty(). */ diff --git a/core/lib/Drupal/Core/Config/Metadata/MetadataLookup.php b/core/lib/Drupal/Core/Config/Metadata/MetadataLookup.php index 529ecb0..779005b 100644 --- a/core/lib/Drupal/Core/Config/Metadata/MetadataLookup.php +++ b/core/lib/Drupal/Core/Config/Metadata/MetadataLookup.php @@ -54,20 +54,17 @@ public function __construct(MetadataStorage $metadata_storage) { * Overrides DrupalCacheArray::resolveCacheMiss(). */ protected function resolveCacheMiss($offset) { - $metadata = array(); - // First read the base data. - if (($base_name = $this->getBaseName($offset)) && ($base_data = $this->offsetGet($base_name))) { - $metadata = $base_data; + $metadata = $this->metadataStorage->read($offset); + // If no metadata with this exact name, try the fallback name. + if ($metadata === FALSE && ($basename = $this->getFallbackName($offset))) { + $metadata = $this->offsetGet($basename); } - if ($override = $this->metadataStorage->read($offset)) { - $metadata = NestedArray::mergeDeep($metadata, $override); - } - $this->storage[$offset] = $metadata; + $this->storage[$offset] = $metadata ? $metadata : array(); return $this->storage[$offset]; } /** - * Gets base metadata name. + * Gets fallback metadata name. * * @param string $name * Configuration name or key. @@ -75,7 +72,7 @@ protected function resolveCacheMiss($offset) { * @return string * Same name with the last part replaced by the filesystem marker. */ - protected static function getBaseName($name) { + protected static function getFallbackName($name) { $replaced = preg_replace('/\.[^.]+$/', '.' . self::BASE_MARK, $name); if ($replaced != $name) { return $replaced; diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigMetadataTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigMetadataTest.php index 947603a..1807505 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigMetadataTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigMetadataTest.php @@ -51,7 +51,6 @@ function testBasicMetadata() { $expected = array(); $expected['name']['.definition'] = array( 'label' => 'Machine name', - 'type' => 'string', ); $expected['label']['.definition'] = array( 'label' => 'Label', @@ -59,13 +58,42 @@ function testBasicMetadata() { ); $expected['effects']['.definition'] = array( 'label' => 'Style effects', - 'list' => '1', - 'list settings' => array( - 'elements name' => 'image.style.effects.%', - 'elements key' => 'name' - ), + ); + $expected['effects']['.list']['.include'] = 'image.effect.[name]'; + $expected['effects']['.list']['weight']['.definition'] = array( + 'label' => 'Weight', + 'type' => 'integer' + ); + $expected['effects']['.list']['ieid']['.definition'] = array( + 'label' => 'IEID' ); $this->assertEqual($metadata, $expected, 'Retrieved the right metadata for image.style.large'); + + // Most complex case, metadata from actual configuration element using includes. + $metadata = config_wrapper('image.style.medium')->get('effects.bddf0d06-42f9-4c75-a700-a33cafa25ea0')->getMetadata(); + $expected = array(); + $expected['.include'] = 'image.effect.[name]'; + $expected['.definition']['label'] = 'Image scale'; + $expected['data']['width']['.definition'] = array( + 'label' => 'Width', + 'type' => 'integer', + ); + $expected['data']['height']['.definition'] = array( + 'label' => 'Height', + 'type' => 'integer', + ); + $expected['data']['upscale']['.definition'] = array( + 'label' => 'Upscale', + 'type' => 'boolean', + ); + $expected['weight']['.definition'] = array( + 'label' => 'Weight', + 'type' => 'integer' + ); + $expected['ieid']['.definition'] = array( + 'label' => 'IEID' + ); + $this->assertEqual($metadata, $expected, 'Retrieved the right metadata for the first effect of image.style.medium'); } /** diff --git a/core/modules/contact/meta/contact.category.%.yml b/core/modules/contact/meta/contact.category.%.yml index 6a772e3..3e6b077 100644 --- a/core/modules/contact/meta/contact.category.%.yml +++ b/core/modules/contact/meta/contact.category.%.yml @@ -1,9 +1,5 @@ -.definition: - settings: - label: label id: .definition: - type: string label: 'Id' label: .definition: @@ -11,7 +7,6 @@ label: label: 'Label' recipients: .definition: - list: '1' label: 'Recipients' reply: .definition: diff --git a/core/modules/image/meta/image.effect.%.yml b/core/modules/image/meta/image.effect.%.yml new file mode 100644 index 0000000..cc6bddc --- /dev/null +++ b/core/modules/image/meta/image.effect.%.yml @@ -0,0 +1,8 @@ +.definition: + label: 'Image effect' +name: + .definition: + label: 'Machine name' +data: + .definition: + label: 'Data' diff --git a/core/modules/image/meta/image.effect.image_scale.yml b/core/modules/image/meta/image.effect.image_scale.yml new file mode 100644 index 0000000..c17c55d --- /dev/null +++ b/core/modules/image/meta/image.effect.image_scale.yml @@ -0,0 +1,16 @@ +.include: image.effect.%.yml +.definition: + label: 'Image scale' +data: + width: + .definition: + label: 'Width' + type: integer + height: + .definition: + label: 'Height' + type: integer + upscale: + .definition: + label: 'Upscale' + type: boolean diff --git a/core/modules/image/meta/image.style.%.yml b/core/modules/image/meta/image.style.%.yml index 6975590..31852b8 100644 --- a/core/modules/image/meta/image.style.%.yml +++ b/core/modules/image/meta/image.style.%.yml @@ -1,7 +1,6 @@ name: .definition: label: 'Machine name' - type: string label: .definition: label: 'Label' @@ -9,7 +8,12 @@ label: effects: .definition: label: 'Style effects' - list: '1' - list settings: - elements name: 'image.style.effects.%' - elements key: 'name' + .list: + .include: 'image.effect.[name]' + weight: + .definition: + label: 'Weight' + type: integer + ieid: + .definition: + label: 'IEID' \ No newline at end of file diff --git a/core/modules/image/meta/image.style.effects.%.yml b/core/modules/image/meta/image.style.effects.%.yml deleted file mode 100644 index c0d4e95..0000000 --- a/core/modules/image/meta/image.style.effects.%.yml +++ /dev/null @@ -1,16 +0,0 @@ -.definition: - label: 'Image style effect' -name: - .definition: - label: 'Machine name' - type: string -data: - .definition: - label: 'Data' -weight: - .definition: - label: 'Weight' - type: integer -ieid: - .definition: - label: 'IEID' diff --git a/core/modules/image/meta/image.style.effects.image_scale.yml b/core/modules/image/meta/image.style.effects.image_scale.yml deleted file mode 100644 index 7f893bf..0000000 --- a/core/modules/image/meta/image.style.effects.image_scale.yml +++ /dev/null @@ -1,15 +0,0 @@ -.definition: - label: 'Image scale' -data: - width: - .definition: - label: 'Width' - type: 'integer' - height: - .definition: - label: 'Height' - type: 'integer' - upscale: - .definition: - label: 'Upscale' - type: 'boolean' diff --git a/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php b/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php index 6108a74..1e72c64 100644 --- a/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php +++ b/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php @@ -179,8 +179,10 @@ protected function getTranslatedData($elements, $options) { * Translates element if it fits our translation criteria. * * For an element to be translatable by locale module it needs to be of type - * 'text'. Translatable elements may define the string context by using the - * 'locale context' key under the element definition's 'constraints'. + * 'text'. Translatable elements may use these additional keys in their data + * definition: + * - 'translatable', FALSE to opt out of translation. + * - 'locale context', to define the string context. * * @param \Drupal\Core\TypedData\TypedDataInterface $element * Configuration element. @@ -194,8 +196,8 @@ public function translateElement($element, $options) { if ($this->canTranslate($options['source'], $options['target'])) { $definition = $element->getDefinition(); $value = $element->getValue(); - if ($value && is_string($value) && $element->getType() == 'text') { - $context = isset($definition['constraints']['locale context']) ? $definition['constraints']['locale context'] : ''; + if ($value && is_string($value) && $element->getType() == 'text' && (!isset($definition['translatable']) || $definition['translatable'])) { + $context = isset($definition['locale context']) ? $definition['locale context'] : ''; if ($translation = $this->translateString($options['target'], $value, $context)) { $element->setValue($translation); return TRUE; diff --git a/core/modules/user/meta/user.mail.%.yml b/core/modules/user/meta/user.mail.%.yml deleted file mode 100644 index 36ee7f3..0000000 --- a/core/modules/user/meta/user.mail.%.yml +++ /dev/null @@ -1,10 +0,0 @@ -.definition: - label: 'Mail text' -subject: - .definition: - label: 'Subject' - type: 'text' -body: - .definition: - label: 'Body' - type: 'text' diff --git a/core/modules/user/meta/user.mail.yml b/core/modules/user/meta/user.mail.yml index 91e9030..cd710a9 100644 --- a/core/modules/user/meta/user.mail.yml +++ b/core/modules/user/meta/user.mail.yml @@ -1,5 +1,13 @@ .definition: label: 'User mails' - list: '1' - list settings: - elements name: 'user.mail.%' +.list: + .definition: + label: 'Mail text' + subject: + .definition: + label: 'Subject' + type: text + body: + .definition: + label: 'Body' + type: text \ No newline at end of file