diff --git a/core/modules/options/options.api.php b/core/modules/options/options.api.php index 648d2dd58da31ce2fc80db3f87d5dd0a8ca5de81..67d67a72480eeac41749df09fc11d89a9cc1c6ae 100644 --- a/core/modules/options/options.api.php +++ b/core/modules/options/options.api.php @@ -5,6 +5,9 @@ * Hooks provided by the Options module. */ +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Field\FieldStorageDefinitionInterface; + /** * Alters the list of options to be displayed for a field. * @@ -32,3 +35,55 @@ function hook_options_list_alter(array &$options, array $context) { $options['_none'] = t('== Empty =='); } } + +/** + * Provides the allowed values for an options field or widget. + * + * Callback for options_allowed_values(). + * + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition + * The field storage definition. + * @param \Drupal\Core\Entity\FieldableEntityInterface|NULL $entity + * (optional) A specific entity to use for either restricting the values or + * customizing the labels for particular bundles and entities. NULL when + * there is not a specific entity available, such as for Views filters. + * @param bool $cacheable + * (optional) If $cacheable is FALSE, then the allowed values are not + * statically cached. See options_test_dynamic_values_callback() for an + * example of generating dynamic and uncached values. Defaults to TRUE. + * + * @return array + * 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. If $entity + * is NULL, you should return the list of all the possible allowed values in + * any context so that other code (e.g. Views filters) can support the + * allowed values for all possible entities and bundles. + * + * @see options_allowed_values() + * @see options_test_allowed_values_callback() + * @see options_test_dynamic_values_callback() + */ +function callback_allowed_values_function(FieldStorageDefinitionInterface $definition, FieldableEntityInterface $entity = NULL, $cacheable = TRUE) { + if (isset($entity) && ($entity->bundle() == 'not_a_programmer')) { + $values = array( + 'Group 1' => array( + 1 => 'One', + ), + 'Group 2' => array( + 2 => 'Two', + ), + ); + } + else { + $values = array( + 'Group 1' => array( + 0 => 'Zero', + ), + 'Group 2' => array( + 1 => 'One', + ), + ); + } + + return $values; +} diff --git a/core/modules/options/options.module b/core/modules/options/options.module index ae6760de0ebe5c8886a558bcbfef97d1c668f891..54cf4b3830ae9b6d62f85f4d1a45c29be142cb44 100644 --- a/core/modules/options/options.module +++ b/core/modules/options/options.module @@ -5,9 +5,9 @@ * Defines selection, check box and radio button widgets for text and numeric fields. */ -use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException; -use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\field\FieldStorageConfigInterface; @@ -54,30 +54,41 @@ function options_field_storage_config_delete(FieldStorageConfigInterface $field_ * sanitized through \Drupal\Core\Field\AllowedTagsXssTrait::fieldFilterXss() * before being displayed. * - * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition - * The field definition. - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity object. + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition + * The field storage definition. + * @param \Drupal\Core\Entity\FieldableEntityInterface|NULL $entity + * (optional) The specific entity when this function is called from the + * context of a specific field on a specific entity. This allows custom + * 'allowed_values_function' callbacks to either restrict the values or + * customize the labels for particular bundles and entities. NULL when + * there is not a specific entity available, such as for Views filters. * - * @return + * @return array * 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. + * + * @see callback_allowed_values_function() */ -function options_allowed_values(FieldDefinitionInterface $field_definition, EntityInterface $entity) { +function options_allowed_values(FieldStorageDefinitionInterface $definition, FieldableEntityInterface $entity = NULL) { $allowed_values = &drupal_static(__FUNCTION__, array()); - $cache_id = implode(':', array($entity->getEntityTypeId(), $entity->bundle(), $field_definition->getName())); + $cache_keys = array($definition->getTargetEntityTypeId(), $definition->getName()); + if ($entity) { + $cache_keys[] = 'entity'; + } + $cache_id = implode(':', $cache_keys); + if (!isset($allowed_values[$cache_id])) { - $function = $field_definition->getSetting('allowed_values_function'); + $function = $definition->getSetting('allowed_values_function'); // If $cacheable is FALSE, then the allowed values are not statically // cached. See options_test_dynamic_values_callback() for an example of // generating dynamic and uncached values. $cacheable = TRUE; if (!empty($function)) { - $values = $function($field_definition, $entity, $cacheable); + $values = $function($definition, $entity, $cacheable); } else { - $values = $field_definition->getSetting('allowed_values'); + $values = $definition->getSetting('allowed_values'); } if ($cacheable) { diff --git a/core/modules/options/src/Plugin/Field/FieldFormatter/OptionsDefaultFormatter.php b/core/modules/options/src/Plugin/Field/FieldFormatter/OptionsDefaultFormatter.php index 61fb8b153a053fd27b4ecd75357c61d65311cf97..879e450b055d4f23928ab58828c8e9a72b13e2ef 100644 --- a/core/modules/options/src/Plugin/Field/FieldFormatter/OptionsDefaultFormatter.php +++ b/core/modules/options/src/Plugin/Field/FieldFormatter/OptionsDefaultFormatter.php @@ -34,21 +34,41 @@ class OptionsDefaultFormatter extends FormatterBase { public function viewElements(FieldItemListInterface $items) { $elements = array(); - $entity = $items->getEntity(); - $allowed_values = options_allowed_values($this->fieldDefinition, $entity); + // Only collect allowed options if there are actually items to display. + if ($items->count()) { + $provider = $items->getFieldDefinition() + ->getFieldStorageDefinition() + ->getOptionsProvider('value', $items->getEntity()); + // Flatten the possible options, to support opt groups. + $options = $this->flattenOptions($provider->getPossibleOptions()); - foreach ($items as $delta => $item) { - if (isset($allowed_values[$item->value])) { - $output = $this->fieldFilterXss($allowed_values[$item->value]); + foreach ($items as $delta => $item) { + $value = $item->value; + // If the stored value is in the current set of allowed values, display + // the associated label, otherwise just display the raw value. + $output = isset($options[$value]) ? $options[$value] : $value; + $elements[$delta] = array('#markup' => $this->fieldFilterXss($output)); } - else { - // If no match was found in allowed values, fall back to the key. - $output = $this->fieldFilterXss($item->value); - } - $elements[$delta] = array('#markup' => $output); } return $elements; } + /** + * Flattens an array of allowed values. + * + * @param array $array + * A single or multidimensional array. + * + * @return array + * The flattened array. + * + * @todo Remove it once https://www.drupal.org/node/2392301 landed. + */ + protected function flattenOptions(array $array) { + $result = array(); + array_walk_recursive($array, function($a, $b) use (&$result) { $result[$b] = $a; }); + return $result; + } + } diff --git a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php index 19c42947abbd1f7a2965cbc631812d18bc357c81..bdf063a4c0257d04dd12a01aa333a1be2f58c52d 100644 --- a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php +++ b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php @@ -63,7 +63,7 @@ public function getSettableValues(AccountInterface $account = NULL) { * {@inheritdoc} */ public function getSettableOptions(AccountInterface $account = NULL) { - $allowed_options = options_allowed_values($this->getFieldDefinition(), $this->getEntity()); + $allowed_options = options_allowed_values($this->getFieldDefinition()->getFieldStorageDefinition(), $this->getEntity()); return $allowed_options; } diff --git a/core/modules/options/src/Tests/OptionsDynamicValuesApiTest.php b/core/modules/options/src/Tests/OptionsDynamicValuesApiTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e1df82d441f9cfd743bb272682cca5bc9eafc27e --- /dev/null +++ b/core/modules/options/src/Tests/OptionsDynamicValuesApiTest.php @@ -0,0 +1,39 @@ +fieldStorage); + $this->assertEqual([], $values); + + $values = options_allowed_values($this->fieldStorage, $this->entity); + + $expected_values = array( + $this->entity->label(), + $this->entity->url(), + $this->entity->uuid(), + $this->entity->bundle(), + ); + $expected_values = array_combine($expected_values, $expected_values); + $this->assertEqual($expected_values, $values); + } + +} diff --git a/core/modules/options/src/Tests/OptionsDynamicValuesTestBase.php b/core/modules/options/src/Tests/OptionsDynamicValuesTestBase.php index 43d0bc988231c44674e6e13d94e3662d7ea32405..20f49fd00f531294260e7f430ec24715e9e34577 100644 --- a/core/modules/options/src/Tests/OptionsDynamicValuesTestBase.php +++ b/core/modules/options/src/Tests/OptionsDynamicValuesTestBase.php @@ -28,11 +28,18 @@ abstract class OptionsDynamicValuesTestBase extends FieldTestBase { */ protected $entity; + /** + * The field storage. + * + * @var \Drupal\Core\Field\FieldStorageDefinitionInterface + */ + protected $fieldStorage; + protected function setUp() { parent::setUp(); $this->field_name = 'test_options'; - entity_create('field_storage_config', array( + $this->fieldStorage = entity_create('field_storage_config', array( 'field_name' => $this->field_name, 'entity_type' => 'entity_test_rev', 'type' => 'list_string', @@ -40,7 +47,9 @@ protected function setUp() { 'settings' => array( 'allowed_values_function' => 'options_test_dynamic_values_callback', ), - ))->save(); + )); + $this->fieldStorage->save(); + $this->field = entity_create('field_config', array( 'field_name' => $this->field_name, 'entity_type' => 'entity_test_rev', diff --git a/core/modules/options/tests/options_test/options_test.module b/core/modules/options/tests/options_test/options_test.module index bfd4315d48ba65797f629291accf55d1b0be7335..2d719d39934613a73e5a5b6cec31576ffb17f8ff 100644 --- a/core/modules/options/tests/options_test/options_test.module +++ b/core/modules/options/tests/options_test/options_test.module @@ -5,13 +5,15 @@ * Helper module for the List module tests. */ -use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Field\FieldStorageDefinitionInterface; /** * Allowed values callback. + * + * @see options_allowed_values(). */ -function options_test_allowed_values_callback(FieldDefinitionInterface $field_definition, EntityInterface $entity) { +function options_test_allowed_values_callback(FieldStorageDefinitionInterface $definition, FieldableEntityInterface $entity = NULL) { $values = array( 'Group 1' => array( 0 => 'Zero', @@ -30,15 +32,25 @@ function options_test_allowed_values_callback(FieldDefinitionInterface $field_de /** * An entity-bound allowed values callback. + * + * @todo This function violates the recommendation in options_allowed_values() + * to return a list of all possible values in any context when $items is + * NULL. Since this is not yet used for testing Views integration, that is + * alright for now. Fix this in https://www.drupal.org/node/2012130. + * + * @see options_allowed_values(). */ -function options_test_dynamic_values_callback(FieldDefinitionInterface $field_definition, EntityInterface $entity, &$cacheable) { - $cacheable = FALSE; - $values = array( - $entity->label(), - $entity->url(), - $entity->uuid(), - $entity->bundle(), - ); +function options_test_dynamic_values_callback(FieldStorageDefinitionInterface $definition, FieldableEntityInterface $entity = NULL, &$cacheable = NULL) { + $values = array(); + if (isset($entity)) { + $cacheable = FALSE; + $values = array( + $entity->label(), + $entity->url(), + $entity->uuid(), + $entity->bundle(), + ); + } // We need the values of the entity as keys. return array_combine($values, $values); }