Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / core / lib / Drupal / Core / Field / Plugin / Field / FieldWidget / OptionsWidgetBase.php
1 <?php
2
3 namespace Drupal\Core\Field\Plugin\Field\FieldWidget;
4
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;
12
13 /**
14  * Base class for the 'options_*' widgets.
15  *
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.
20  *
21  * @see \Drupal\Core\TypedData\OptionsProviderInterface
22  */
23 abstract class OptionsWidgetBase extends WidgetBase {
24
25   /**
26    * Abstract over the actual field columns, to allow different field types to
27    * reuse those widgets.
28    *
29    * @var string
30    */
31   protected $column;
32
33   /**
34    * {@inheritdoc}
35    */
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];
40   }
41
42   /**
43    * {@inheritdoc}
44    */
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
47     // element.
48     $this->required = $element['#required'];
49     $this->multiple = $this->fieldDefinition->getFieldStorageDefinition()->isMultiple();
50     $this->has_value = isset($items[0]->{$this->column});
51
52     // Add our custom validator.
53     $element['#element_validate'][] = [get_class($this), 'validateElement'];
54     $element['#key_column'] = $this->column;
55
56     // The rest of the $element is built by child method implementations.
57
58     return $element;
59   }
60
61   /**
62    * Form validation handler for widget elements.
63    *
64    * @param array $element
65    *   The form element.
66    * @param \Drupal\Core\Form\FormStateInterface $form_state
67    *   The form state.
68    */
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']]));
72     }
73
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.
78
79     if (is_array($element['#value'])) {
80       $values = array_values($element['#value']);
81     }
82     else {
83       $values = [$element['#value']];
84     }
85
86     // Filter out the 'none' option. Use a strict comparison, because
87     // 0 == 'any string'.
88     $index = array_search('_none', $values, TRUE);
89     if ($index !== FALSE) {
90       unset($values[$index]);
91     }
92
93     // Transpose selections from field => delta to delta => field.
94     $items = [];
95     foreach ($values as $value) {
96       $items[] = [$element['#key_column'] => $value];
97     }
98     $form_state->setValueForElement($element, $items);
99   }
100
101   /**
102    * Returns the array of options for the widget.
103    *
104    * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
105    *   The entity for which to return options.
106    *
107    * @return array
108    *   The array of options for the widget.
109    */
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());
117
118       // Add an empty option if the widget needs one.
119       if ($empty_label = $this->getEmptyLabel()) {
120         $options = ['_none' => $empty_label] + $options;
121       }
122
123       $module_handler = \Drupal::moduleHandler();
124       $context = [
125         'fieldDefinition' => $this->fieldDefinition,
126         'entity' => $entity,
127       ];
128       $module_handler->alter('options_list', $options, $context);
129
130       array_walk_recursive($options, [$this, 'sanitizeLabel']);
131
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);
136       }
137
138       $this->options = $options;
139     }
140     return $this->options;
141   }
142
143   /**
144    * Determines selected options from the incoming field values.
145    *
146    * @param \Drupal\Core\Field\FieldItemListInterface $items
147    *   The field values.
148    *
149    * @return array
150    *   The array of corresponding selected options.
151    */
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()));
155
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;
163       }
164     }
165
166     return $selected_options;
167   }
168
169   /**
170    * Indicates whether the widgets support optgroups.
171    *
172    * @return bool
173    *   TRUE if the widget supports optgroups, FALSE otherwise.
174    */
175   protected function supportsGroups() {
176     return FALSE;
177   }
178
179   /**
180    * Sanitizes a string label to display as an option.
181    *
182    * @param string $label
183    *   The label to sanitize.
184    */
185   protected function sanitizeLabel(&$label) {
186     // Allow a limited set of HTML tags.
187     $label = FieldFilteredMarkup::create($label);
188   }
189
190   /**
191    * Returns the empty option label to add to the list of options, if any.
192    *
193    * @return string|null
194    *   Either a label of the empty option, or NULL.
195    */
196   protected function getEmptyLabel() {}
197
198 }