diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index bdeeba1..b3b51c2 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -110,7 +110,7 @@ public function get($key = '') { * {@inheritdoc} */ public function setData(array $data) { - $this->data = $data; + parent::setData($data); $this->resetOverriddenData(); return $this; } diff --git a/core/lib/Drupal/Core/Config/ConfigBase.php b/core/lib/Drupal/Core/Config/ConfigBase.php index 3508611..37e0d80 100644 --- a/core/lib/Drupal/Core/Config/ConfigBase.php +++ b/core/lib/Drupal/Core/Config/ConfigBase.php @@ -160,13 +160,39 @@ public function get($key = '') { * * @return $this * The configuration object. + * + * @throws \Drupal\Core\Config\ConfigValueException + * If any key in $data in any depth contains a dot. */ public function setData(array $data) { + $this->validateKeys($data); $this->data = $data; return $this; } /** + * Validate all keys in a passed in config array structure. + * + * @param array $data + * Configuration array structure. + * + * @return null + * + * @throws \Drupal\Core\Config\ConfigValueException + * If any key in $data in any depth contains a dot. + */ + protected function validateKeys(array $data) { + foreach ($data as $key => $value) { + if (strpos($key, '.') !== FALSE) { + throw new ConfigValueException(String::format('@key key contains a dot which is not supported.', array('@key' => $key))); + } + if (is_array($value)) { + $this->validateKeys($value); + } + } + } + + /** * Sets a value in this configuration object. * * @param string $key @@ -176,10 +202,16 @@ public function setData(array $data) { * * @return $this * The configuration object. + * + * @throws \Drupal\Core\Config\ConfigValueException + * If $value is an array and any of its keys in any depth contains a dot. */ public function set($key, $value) { // The dot/period is a reserved character; it may appear between keys, but // not within keys. + if (is_array($value)) { + $this->validateKeys($value); + } $parts = explode('.', $key); if (count($parts) == 1) { $this->data[$key] = $value; diff --git a/core/lib/Drupal/Core/Config/ConfigValueException.php b/core/lib/Drupal/Core/Config/ConfigValueException.php new file mode 100644 index 0000000..9802228 --- /dev/null +++ b/core/lib/Drupal/Core/Config/ConfigValueException.php @@ -0,0 +1,13 @@ +configFactory->loadMultiple($names) as $config) { - $records[$config->get($this->idKey)] = $config->get(); + $records[$config->get($this->idKey)] = $this->postLoadData($config->get()); } return $this->mapFromStorageRecords($records); } @@ -237,7 +237,7 @@ protected function doSave($id, EntityInterface $entity) { } // Retrieve the desired properties and set them in config. - foreach ($entity->toArray() as $key => $value) { + foreach ($this->preSaveData($entity->toArray(), $entity) as $key => $value) { $config->set($key, $value); } $config->save(); @@ -246,6 +246,34 @@ protected function doSave($id, EntityInterface $entity) { } /** + * Alter data for the storage environment right before saving. + * + * @param array $data + * Raw configuration data being saved. + * @param \Drupal\Core\Entity\EntityInterface $entity + * Entity being saved. + * + * @return array + * Data to save with any modifications necessary for storage performed. + */ + protected function preSaveData(array $data, EntityInterface $entity) { + return $data; + } + + /** + * Alter data from the storage environment right after loading. + * + * @param array $data + * Raw configuration data as loaded. + * + * @return array + * Transformed data with any modifications necessary for instantiation. + */ + protected function postLoadData(array $data) { + return $data; + } + + /** * {@inheritdoc} */ protected function has($id, EntityInterface $entity) { diff --git a/core/lib/Drupal/Core/Field/FieldItemBase.php b/core/lib/Drupal/Core/Field/FieldItemBase.php index 296c468..8ab3ad6 100644 --- a/core/lib/Drupal/Core/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Field/FieldItemBase.php @@ -255,4 +255,14 @@ public function instanceSettingsForm(array $form, array &$form_state) { return array(); } + /** + * {@inheritdoc} + */ + public static function preSaveSettings(array &$settings) { } + + /** + * {@inheritdoc} + */ + public static function postLoadSettings(array &$settings) { } + } diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php index ee27996..f63d36a 100644 --- a/core/lib/Drupal/Core/Field/FieldItemInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php @@ -183,6 +183,27 @@ public function view($display_options = array()); public function preSave(); /** + * Defines behavior for transforming field settings before being saved. + * + * May be used to alter settings to transform them to a storage-friendly form. + * + * @param array $settings + * Field settings. + */ + public static function preSaveSettings(array &$settings); + + /** + * Defines custom behavior for transforming field settings when loading. + * + * May be used to alter settings to transform them from a storage-friendly + * form. + * + * @param array $settings + * Field settings. + */ + public static function postLoadSettings(array &$settings); + + /** * Defines custom insert behavior for field values. * * This method is called during the process of inserting an entity, just diff --git a/core/modules/config/src/Tests/ConfigCRUDTest.php b/core/modules/config/src/Tests/ConfigCRUDTest.php index 122843e..c38d3f6 100644 --- a/core/modules/config/src/Tests/ConfigCRUDTest.php +++ b/core/modules/config/src/Tests/ConfigCRUDTest.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\String; use Drupal\Core\Config\ConfigNameException; +use Drupal\Core\Config\ConfigValueException; use Drupal\Core\Config\InstallStorage; use Drupal\simpletest\DrupalUnitTestBase; use Drupal\Core\Config\FileStorage; @@ -184,6 +185,31 @@ function testNameValidation() { } /** + * Tests the validation of configuration object values. + */ + function testValueValidation() { + // Verify that setData() will catch dotted keys. + $message = 'Expected ConfigValueException was thrown from setData() for value with dotted keys.'; + try { + \Drupal::config('namespace.object')->setData(array('key.value' => 12))->save(); + $this->fail($message); + } + catch (ConfigValueException $e) { + $this->pass($message); + } + + // Verify that set() will catch dotted keys. + $message = 'Expected ConfigValueException was thrown from set() for value with dotted keys.'; + try { + \Drupal::config('namespace.object')->set('foo', array('key.value' => 12))->save(); + $this->fail($message); + } + catch (ConfigValueException $e) { + $this->pass($message); + } + } + + /** * Tests data type handling. */ public function testDataTypes() { diff --git a/core/modules/field/src/FieldConfigStorage.php b/core/modules/field/src/FieldConfigStorage.php index 8419424..77d0daa 100644 --- a/core/modules/field/src/FieldConfigStorage.php +++ b/core/modules/field/src/FieldConfigStorage.php @@ -10,6 +10,7 @@ use Drupal\Component\Uuid\UuidInterface; use Drupal\Core\Config\Config; use Drupal\Core\Config\Entity\ConfigEntityStorage; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\Query\QueryFactory; @@ -156,4 +157,27 @@ public function loadByProperties(array $conditions = array()) { return $matching_fields; } + + /** + * {@inheritdoc} + */ + protected function postLoadData(array $data) { + // @todo Makes too much assumptions but it needs to. + $type_definition = \Drupal::typedDataManager() + ->getDefinition('field_item:' . $data['type']); + $type_definition['class']::postLoadSettings($data['settings']); + return $data; + } + + /** + * {@inheritdoc} + */ + protected function preSaveData(array $data, EntityInterface $entity) { + // @todo Makes too much assumptions but it needs to. + $type_definition = \Drupal::typedDataManager() + ->getDefinition('field_item:' . $data['type']); + $type_definition['class']::preSaveSettings($data['settings']); + return $data; + } + } diff --git a/core/modules/options/config/schema/options.schema.yml b/core/modules/options/config/schema/options.schema.yml index 39ed439..7a639cb 100644 --- a/core/modules/options/config/schema/options.schema.yml +++ b/core/modules/options/config/schema/options.schema.yml @@ -8,8 +8,15 @@ field.list_integer.settings: type: sequence label: 'Allowed values list' sequence: - - type: string - label: 'Value' + - type: mapping + label: 'Allowed value with label' + mapping: + value: + type: integer + label: 'Value' + label: + type: label + label: 'Label' allowed_values_function: type: string label: 'Allowed values function' @@ -35,8 +42,18 @@ field.list_float.settings: label: 'List (float) settings' mapping: allowed_values: - type: ignore + type: sequence label: 'Allowed values list' + sequence: + - type: mapping + label: 'Allowed value with label' + mapping: + value: + type: float + label: 'Value' + label: + type: label + label: 'Label' allowed_values_function: type: string label: 'Allowed values function' @@ -65,8 +82,15 @@ field.list_text.settings: type: sequence label: 'Allowed values list' sequence: - - type: string - label: 'Value' + - type: mapping + label: 'Allowed value with label' + mapping: + value: + type: string + label: 'Value' + label: + type: label + label: 'Label' allowed_values_function: type: string label: 'Allowed values function' diff --git a/core/modules/options/src/Plugin/Field/FieldType/ListFloatItem.php b/core/modules/options/src/Plugin/Field/FieldType/ListFloatItem.php index 30cd382..1b2d323 100644 --- a/core/modules/options/src/Plugin/Field/FieldType/ListFloatItem.php +++ b/core/modules/options/src/Plugin/Field/FieldType/ListFloatItem.php @@ -90,4 +90,24 @@ protected static function validateAllowedValue($option) { } } + /** + * {@inheritdoc} + */ + public static function simplifyAllowedValues(array $structured_values) { + $values = array(); + foreach ($structured_values as $item) { + // Nested elements are embedded in the label. + // See ListItemBase::structureAllowedValues(). + if (is_array($item['label'])) { + $item['label'] = static::simplifyAllowedValues($item['label']); + } + // Cast the value to a float first so that .5 and 0.5 are the same value + // and then cast to a string so that values like 0.5 can be used as array + // keys. + // @see http://php.net/manual/en/language.types.array.php + $values[(string) (float) $item['value']] = $item['label']; + } + return $values; + } + } diff --git a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php index d61aef1..f63b17f 100644 --- a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php +++ b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php @@ -237,4 +237,71 @@ protected function allowedValuesString($values) { return implode("\n", $lines); } + /** + * Defined in \Drupal\Core\Field\FieldItemInterface. + */ + public static function preSaveSettings(array &$settings) { + if (isset($settings['allowed_values'])) { + $settings['allowed_values'] = static::structureAllowedValues($settings['allowed_values']); + } + } + + /** + * Defined in \Drupal\Core\Field\FieldItemInterface. + */ + public static function postLoadSettings(array &$settings) { + if (isset($settings['allowed_values'])) { + $settings['allowed_values'] = static::simplifyAllowedValues($settings['allowed_values']); + } + } + + /** + * Simplify allowed values to a key-value array from the structured array. + * + * @param array $structured_values + * Array of items with a 'value' and 'label' key each for the allowed + * values. + * + * @return array + * Allowed values were the array key is the 'value' value, the value is + * the 'label' value. + */ + protected static function simplifyAllowedValues(array $structured_values) { + $values = array(); + foreach ($structured_values as $item) { + if (is_array($item['label'])) { + // Nested elements are embedded in the label. + // See structureAllowedValues(). + $item['label'] = static::simplifyAllowedValues($item['label']); + } + $values[$item['value']] = $item['label']; + } + return $values; + } + + /** + * Creates a structured array of allowed values from a key-value array. + * + * @param array $values + * Allowed values were the array key is the 'value' value, the value is + * the 'label' value. + * + * @return array + * Array of items with a 'value' and 'label' key each for the allowed + * values. + */ + protected static function structureAllowedValues(array $values) { + $structured_values = array(); + foreach ($values as $value => $label) { + if (is_array($label)) { + $label = static::structureAllowedValues($label); + } + $structured_values[] = array( + 'value' => $value, + 'label' => $label, + ); + } + return $structured_values; + } + }