3 namespace Drupal\Core\Field\Plugin\Field\FieldWidget;
5 use Drupal\Core\Entity\FieldableEntityInterface;
6 use Drupal\Core\Field\FieldDefinitionInterface;
7 use Drupal\Core\Field\FieldFilteredMarkup;
8 use Drupal\Core\Field\FieldItemListInterface;
9 use Drupal\Core\Field\WidgetBase;
10 use Drupal\Core\Form\FormStateInterface;
11 use Drupal\Core\Form\OptGroup;
14 * Base class for the 'options_*' widgets.
16 * Field types willing to enable one or several of the widgets defined in
17 * options.module (select, radios/checkboxes, on/off checkbox) need to
18 * implement the OptionsProviderInterface to specify the list of options to
19 * display in the widgets.
21 * @see \Drupal\Core\TypedData\OptionsProviderInterface
23 abstract class OptionsWidgetBase extends WidgetBase {
26 * Abstract over the actual field columns, to allow different field types to
27 * reuse those widgets.
36 public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings) {
37 parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
38 $property_names = $this->fieldDefinition->getFieldStorageDefinition()->getPropertyNames();
39 $this->column = $property_names[0];
45 public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
46 // Prepare some properties for the child methods to build the actual form
48 $this->required = $element['#required'];
49 $this->multiple = $this->fieldDefinition->getFieldStorageDefinition()->isMultiple();
50 $this->has_value = isset($items[0]->{$this->column});
52 // Add our custom validator.
53 $element['#element_validate'][] = [get_class($this), 'validateElement'];
54 $element['#key_column'] = $this->column;
56 // The rest of the $element is built by child method implementations.
62 * Form validation handler for widget elements.
64 * @param array $element
66 * @param \Drupal\Core\Form\FormStateInterface $form_state
69 public static function validateElement(array $element, FormStateInterface $form_state) {
70 if ($element['#required'] && $element['#value'] == '_none') {
71 $form_state->setError($element, t('@name field is required.', ['@name' => $element['#title']]));
74 // Massage submitted form values.
75 // Drupal\Core\Field\WidgetBase::submit() expects values as
76 // an array of values keyed by delta first, then by column, while our
77 // widgets return the opposite.
79 if (is_array($element['#value'])) {
80 $values = array_values($element['#value']);
83 $values = [$element['#value']];
86 // Filter out the 'none' option. Use a strict comparison, because
88 $index = array_search('_none', $values, TRUE);
89 if ($index !== FALSE) {
90 unset($values[$index]);
93 // Transpose selections from field => delta to delta => field.
95 foreach ($values as $value) {
96 $items[] = [$element['#key_column'] => $value];
98 $form_state->setValueForElement($element, $items);
102 * Returns the array of options for the widget.
104 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
105 * The entity for which to return options.
108 * The array of options for the widget.
110 protected function getOptions(FieldableEntityInterface $entity) {
111 if (!isset($this->options)) {
112 // Limit the settable options for the current user account.
113 $options = $this->fieldDefinition
114 ->getFieldStorageDefinition()
115 ->getOptionsProvider($this->column, $entity)
116 ->getSettableOptions(\Drupal::currentUser());
118 // Add an empty option if the widget needs one.
119 if ($empty_label = $this->getEmptyLabel()) {
120 $options = ['_none' => $empty_label] + $options;
123 $module_handler = \Drupal::moduleHandler();
125 'fieldDefinition' => $this->fieldDefinition,
128 $module_handler->alter('options_list', $options, $context);
130 array_walk_recursive($options, [$this, 'sanitizeLabel']);
132 // Options might be nested ("optgroups"). If the widget does not support
133 // nested options, flatten the list.
134 if (!$this->supportsGroups()) {
135 $options = OptGroup::flattenOptions($options);
138 $this->options = $options;
140 return $this->options;
144 * Determines selected options from the incoming field values.
146 * @param \Drupal\Core\Field\FieldItemListInterface $items
150 * The array of corresponding selected options.
152 protected function getSelectedOptions(FieldItemListInterface $items) {
153 // We need to check against a flat list of options.
154 $flat_options = OptGroup::flattenOptions($this->getOptions($items->getEntity()));
156 $selected_options = [];
157 foreach ($items as $item) {
158 $value = $item->{$this->column};
159 // Keep the value if it actually is in the list of options (needs to be
160 // checked against the flat list).
161 if (isset($flat_options[$value])) {
162 $selected_options[] = $value;
166 return $selected_options;
170 * Indicates whether the widgets support optgroups.
173 * TRUE if the widget supports optgroups, FALSE otherwise.
175 protected function supportsGroups() {
180 * Sanitizes a string label to display as an option.
182 * @param string $label
183 * The label to sanitize.
185 protected function sanitizeLabel(&$label) {
186 // Allow a limited set of HTML tags.
187 $label = FieldFilteredMarkup::create($label);
191 * Returns the empty option label to add to the list of options, if any.
193 * @return string|null
194 * Either a label of the empty option, or NULL.
196 protected function getEmptyLabel() { }