diff --git a/core/modules/options/config/schema/options.schema.yml b/core/modules/options/config/schema/options.schema.yml index 8c90839..b81080e 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' @@ -95,8 +119,15 @@ field.list_boolean.settings: type: sequence label: 'Allowed values list' sequence: - - type: string - label: 'Value' + - type: mapping + label: 'Allowed value with label' + mapping: + value: + type: boolean + label: 'Value' + label: + type: label + label: 'Label' allowed_values_function: type: string label: 'Allowed values function' diff --git a/core/modules/options/options.module b/core/modules/options/options.module index e149477..e9574e6 100644 --- a/core/modules/options/options.module +++ b/core/modules/options/options.module @@ -10,6 +10,7 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\field\FieldConfigInterface; use Drupal\field\FieldConfigUpdateForbiddenException; +use Drupal\options\Plugin\Field\FieldType\ListItemBase; /** * Implements hook_help(). @@ -59,8 +60,9 @@ function options_field_config_delete(FieldConfigInterface $field) { * The entity object. * * @return - * The array of allowed values. Keys of the array are the raw stored values - * (number or text), values of the array are the display labels. + * The array of allowed values. Each array element is an associative + * array with 'value' and 'label' keys. The 'value' key stores the raw stored + * value (number or text), the 'label' key stores the display labels. */ function options_allowed_values(FieldDefinitionInterface $field_definition, EntityInterface $entity) { $allowed_values = &drupal_static(__FUNCTION__, array()); @@ -98,7 +100,10 @@ function options_field_config_update_forbid(FieldConfigInterface $field, FieldCo // Forbid any update that removes allowed values with actual data. $allowed_values = $field->getSetting('allowed_values'); $prior_allowed_values = $prior_field->getSetting('allowed_values'); - $lost_keys = array_diff(array_keys($prior_allowed_values), array_keys($allowed_values)); + $lost_keys = array_diff( + array_keys(ListItemBase::simplifyAllowedValues($prior_allowed_values)), + array_keys(ListItemBase::simplifyAllowedValues($allowed_values)) + ); if (_options_values_in_use($field->entity_type, $field->getName(), $lost_keys)) { throw new FieldConfigUpdateForbiddenException(t('A list field (@field_name) with existing data cannot have its keys changed.', array('@field_name' => $field->getName()))); } diff --git a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php index d61aef1..ea1b6e5 100644 --- a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php +++ b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php @@ -129,8 +129,8 @@ public static function validateAllowedValues($element, &$form_state) { } else { // Check that keys are valid for the field type. - foreach ($values as $key => $value) { - if ($error = static::validateAllowedValue($key)) { + foreach ($values as $item) { + if ($error = static::validateAllowedValue($item['value'])) { \Drupal::formBuilder()->setError($element, $form_state, $error); break; } @@ -138,7 +138,10 @@ public static function validateAllowedValues($element, &$form_state) { // Prevent removing values currently in use. if ($element['#field_has_data']) { - $lost_keys = array_diff(array_keys($element['#allowed_values']), array_keys($values)); + $lost_keys = array_diff( + array_keys(static::simplifyAllowedValues($element['#allowed_values'])), + array_keys(static::simplifyAllowedValues($values)) + ); if (_options_values_in_use($element['#entity_type'], $element['#field_name'], $lost_keys)) { \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: some values are being removed while currently in use.')); } @@ -193,7 +196,7 @@ protected static function extractAllowedValues($string, $has_data) { return; } - $values[$key] = $value; + $values[] = array('value' => $key, 'label' => $value); } // We generate keys only if the list contains no explicit key at all. @@ -205,6 +208,25 @@ protected static function extractAllowedValues($string, $has_data) { } /** + * 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. + */ + public static function simplifyAllowedValues(array $structured_values) { + $values = array(); + foreach ($structured_values as $item) { + $values[$item['value']] = $item['label']; + } + return $values; + } + + /** * Checks whether a candidate allowed value is valid. * * @param string $option @@ -220,19 +242,19 @@ protected static function validateAllowedValue($option) { } * * This string format is suitable for edition in a textarea. * - * @param array $values - * An array of values, where array keys are values and array values are - * labels. + * @param array $structured_values + * An array of values, where elements are arrays with 'value' and 'label' + * keys. * * @return string * The string representation of the $values array: * - Values are separated by a carriage return. - * - Each value is in the format "value|label" or "value". + * - Each value is in the format "value|label". */ - protected function allowedValuesString($values) { + protected function allowedValuesString($structured_values) { $lines = array(); - foreach ($values as $key => $value) { - $lines[] = "$key|$value"; + foreach ($structured_values as $item) { + $lines[] = $item['value'] . '|' . $item['label']; } return implode("\n", $lines); } diff --git a/core/modules/options/src/Tests/OptionsFieldUITest.php b/core/modules/options/src/Tests/OptionsFieldUITest.php index baecfca..84f5256 100644 --- a/core/modules/options/src/Tests/OptionsFieldUITest.php +++ b/core/modules/options/src/Tests/OptionsFieldUITest.php @@ -60,15 +60,24 @@ function testOptionsAllowedValuesInteger() { // Flat list of textual values. $string = "Zero\nOne"; - $array = array('0' => 'Zero', '1' => 'One'); + $array = array( + array('value' => '0', 'label' => 'Zero'), + array('value' => '1', 'label' => 'One'), + ); $this->assertAllowedValuesInput($string, $array, 'Unkeyed lists are accepted.'); // Explicit integer keys. $string = "0|Zero\n2|Two"; - $array = array('0' => 'Zero', '2' => 'Two'); + $array = array( + array('value' => '0', 'label' => 'Zero'), + array('value' => '2', 'label' => 'Two'), + ); $this->assertAllowedValuesInput($string, $array, 'Integer keys are accepted.'); // Check that values can be added and removed. $string = "0|Zero\n1|One"; - $array = array('0' => 'Zero', '1' => 'One'); + $array = array( + array('value' => '0', 'label' => 'Zero'), + array('value' => '1', 'label' => 'One'), + ); $this->assertAllowedValuesInput($string, $array, 'Values can be added and removed.'); // Non-integer keys. $this->assertAllowedValuesInput("1.1|One", 'keys must be integers', 'Non integer keys are rejected.'); @@ -88,17 +97,26 @@ function testOptionsAllowedValuesInteger() { // Check that values can be added but values in use cannot be removed. $string = "0|Zero\n1|One\n2|Two"; - $array = array('0' => 'Zero', '1' => 'One', '2' => 'Two'); + $array = array( + array('value' => '0', 'label' => 'Zero'), + array('value' => '1', 'label' => 'One'), + array('value' => '2', 'label' => 'Two'), + ); $this->assertAllowedValuesInput($string, $array, 'Values can be added.'); $string = "0|Zero\n1|One"; - $array = array('0' => 'Zero', '1' => 'One'); + $array = array( + array('value' => '0', 'label' => 'Zero'), + array('value' => '1', 'label' => 'One'), + ); $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.'); $this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', 'Values in use cannot be removed.'); // Delete the node, remove the value. $node->delete(); $string = "0|Zero"; - $array = array('0' => 'Zero'); + $array = array( + array('value' => '0', 'label' => 'Zero'), + ); $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.'); } @@ -111,15 +129,25 @@ function testOptionsAllowedValuesFloat() { // Flat list of textual values. $string = "Zero\nOne"; - $array = array('0' => 'Zero', '1' => 'One'); + $array = array( + array('value' => '0', 'label' => 'Zero'), + array('value' => '1', 'label' => 'One'), + ); $this->assertAllowedValuesInput($string, $array, 'Unkeyed lists are accepted.'); // Explicit numeric keys. $string = "0|Zero\n.5|Point five"; - $array = array('0' => 'Zero', '0.5' => 'Point five'); + $array = array( + array('value' => '0', 'label' => 'Zero'), + array('value' => '0.5', 'label' => 'Point five'), + ); $this->assertAllowedValuesInput($string, $array, 'Integer keys are accepted.'); // Check that values can be added and removed. $string = "0|Zero\n.5|Point five\n1.0|One"; - $array = array('0' => 'Zero', '0.5' => 'Point five', '1' => 'One'); + $array = array( + array('value' => '0', 'label' => 'Zero'), + array('value' => '0.5', 'label' => 'Point five'), + array('value' => '1', 'label' => 'One'), + ); $this->assertAllowedValuesInput($string, $array, 'Values can be added and removed.'); // Non-numeric keys. $this->assertAllowedValuesInput("abc|abc\n", 'each key must be a valid integer or decimal', 'Non numeric keys are rejected.'); @@ -138,17 +166,26 @@ function testOptionsAllowedValuesFloat() { // Check that values can be added but values in use cannot be removed. $string = "0|Zero\n.5|Point five\n2|Two"; - $array = array('0' => 'Zero', '0.5' => 'Point five', '2' => 'Two'); + $array = array( + array('value' => '0', 'label' => 'Zero'), + array('value' => '0.5', 'label' => 'Point five'), + array('value' => '2', 'label' => 'Two'), + ); $this->assertAllowedValuesInput($string, $array, 'Values can be added.'); $string = "0|Zero\n.5|Point five"; - $array = array('0' => 'Zero', '0.5' => 'Point five'); + $array = array( + array('value' => '0', 'label' => 'Zero'), + array('value' => '0.5', 'label' => 'Point five'), + ); $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.'); $this->assertAllowedValuesInput("0|Zero", 'some values are being removed while currently in use', 'Values in use cannot be removed.'); // Delete the node, remove the value. $node->delete(); $string = "0|Zero"; - $array = array('0' => 'Zero'); + $array = array( + array('value' => '0', 'label' => 'Zero'), + ); $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.'); } @@ -161,19 +198,31 @@ function testOptionsAllowedValuesText() { // Flat list of textual values. $string = "Zero\nOne"; - $array = array('Zero' => 'Zero', 'One' => 'One'); + $array = array( + array('value' => 'Zero', 'label' => 'Zero'), + array('value' => 'One', 'label' => 'One'), + ); $this->assertAllowedValuesInput($string, $array, 'Unkeyed lists are accepted.'); // Explicit keys. $string = "zero|Zero\none|One"; - $array = array('zero' => 'Zero', 'one' => 'One'); + $array = array( + array('value' => 'zero', 'label' => 'Zero'), + array('value' => 'one', 'label' => 'One'), + ); $this->assertAllowedValuesInput($string, $array, 'Explicit keys are accepted.'); // Check that values can be added and removed. $string = "zero|Zero\ntwo|Two"; - $array = array('zero' => 'Zero', 'two' => 'Two'); + $array = array( + array('value' => 'zero', 'label' => 'Zero'), + array('value' => 'two', 'label' => 'Two'), + ); $this->assertAllowedValuesInput($string, $array, 'Values can be added and removed.'); // Mixed list of keyed and unkeyed values. $string = "zero|Zero\nOne\n"; - $array = array('zero' => 'Zero', 'One' => 'One'); + $array = array( + array('value' => 'zero', 'label' => 'Zero'), + array('value' => 'One', 'label' => 'One'), + ); $this->assertAllowedValuesInput($string, $array, 'Mixed lists are accepted.'); // Overly long keys. $this->assertAllowedValuesInput("zero|Zero\n" . $this->randomName(256) . "|One", 'each key must be a string at most 255 characters long', 'Overly long keys are rejected.'); @@ -188,22 +237,34 @@ function testOptionsAllowedValuesText() { // Check that flat lists of values are still accepted once the field has // data. $string = "Zero\nOne"; - $array = array('Zero' => 'Zero', 'One' => 'One'); + $array = array( + array('value' => 'Zero', 'label' => 'Zero'), + array('value' => 'One', 'label' => 'One'), + ); $this->assertAllowedValuesInput($string, $array, 'Unkeyed lists are still accepted once the field has data.'); // Check that values can be added but values in use cannot be removed. $string = "Zero\nOne\nTwo"; - $array = array('Zero' => 'Zero', 'One' => 'One', 'Two' => 'Two'); + $array = array( + array('value' => 'Zero', 'label' => 'Zero'), + array('value' => 'One', 'label' => 'One'), + array('value' => 'Two', 'label' => 'Two'), + ); $this->assertAllowedValuesInput($string, $array, 'Values can be added.'); $string = "Zero\nOne"; - $array = array('Zero' => 'Zero', 'One' => 'One'); + $array = array( + array('value' => 'Zero', 'label' => 'Zero'), + array('value' => 'One', 'label' => 'One'), + ); $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.'); $this->assertAllowedValuesInput("Zero", 'some values are being removed while currently in use', 'Values in use cannot be removed.'); // Delete the node, remove the value. $node->delete(); $string = "Zero"; - $array = array('Zero' => 'Zero'); + $array = array( + array('value' => 'Zero', 'label' => 'Zero'), + ); $this->assertAllowedValuesInput($string, $array, 'Values not in use can be removed.'); } @@ -244,7 +305,10 @@ function testOptionsTrimmedValuesText() { // Explicit keys. $string = "zero |Zero\none | One"; - $array = array('zero' => 'Zero', 'one' => 'One'); + $array = array( + array('value' => 'zero', 'label' => 'Zero'), + array('value' => 'one', 'label' => 'One'), + ); $this->assertAllowedValuesInput($string, $array, 'Explicit keys are accepted and trimmed.'); }