Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Core\Field;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Component\Utility\Html;
|
Chris@0
|
6 use Drupal\Component\Utility\NestedArray;
|
Chris@0
|
7 use Drupal\Component\Utility\SortArray;
|
Chris@0
|
8 use Drupal\Core\Form\FormStateInterface;
|
Chris@0
|
9 use Drupal\Core\Render\Element;
|
Chris@0
|
10 use Symfony\Component\Validator\ConstraintViolationInterface;
|
Chris@0
|
11 use Symfony\Component\Validator\ConstraintViolationListInterface;
|
Chris@0
|
12
|
Chris@0
|
13 /**
|
Chris@0
|
14 * Base class for 'Field widget' plugin implementations.
|
Chris@0
|
15 *
|
Chris@0
|
16 * @ingroup field_widget
|
Chris@0
|
17 */
|
Chris@0
|
18 abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface {
|
Chris@0
|
19
|
Chris@0
|
20 use AllowedTagsXssTrait;
|
Chris@0
|
21
|
Chris@0
|
22 /**
|
Chris@0
|
23 * The field definition.
|
Chris@0
|
24 *
|
Chris@0
|
25 * @var \Drupal\Core\Field\FieldDefinitionInterface
|
Chris@0
|
26 */
|
Chris@0
|
27 protected $fieldDefinition;
|
Chris@0
|
28
|
Chris@0
|
29 /**
|
Chris@0
|
30 * The widget settings.
|
Chris@0
|
31 *
|
Chris@0
|
32 * @var array
|
Chris@0
|
33 */
|
Chris@0
|
34 protected $settings;
|
Chris@0
|
35
|
Chris@0
|
36 /**
|
Chris@0
|
37 * Constructs a WidgetBase object.
|
Chris@0
|
38 *
|
Chris@0
|
39 * @param string $plugin_id
|
Chris@0
|
40 * The plugin_id for the widget.
|
Chris@0
|
41 * @param mixed $plugin_definition
|
Chris@0
|
42 * The plugin implementation definition.
|
Chris@0
|
43 * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
|
Chris@0
|
44 * The definition of the field to which the widget is associated.
|
Chris@0
|
45 * @param array $settings
|
Chris@0
|
46 * The widget settings.
|
Chris@0
|
47 * @param array $third_party_settings
|
Chris@0
|
48 * Any third party settings.
|
Chris@0
|
49 */
|
Chris@0
|
50 public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings) {
|
Chris@0
|
51 parent::__construct([], $plugin_id, $plugin_definition);
|
Chris@0
|
52 $this->fieldDefinition = $field_definition;
|
Chris@0
|
53 $this->settings = $settings;
|
Chris@0
|
54 $this->thirdPartySettings = $third_party_settings;
|
Chris@0
|
55 }
|
Chris@0
|
56
|
Chris@0
|
57 /**
|
Chris@0
|
58 * {@inheritdoc}
|
Chris@0
|
59 */
|
Chris@0
|
60 public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) {
|
Chris@0
|
61 $field_name = $this->fieldDefinition->getName();
|
Chris@0
|
62 $parents = $form['#parents'];
|
Chris@0
|
63
|
Chris@0
|
64 // Store field information in $form_state.
|
Chris@0
|
65 if (!static::getWidgetState($parents, $field_name, $form_state)) {
|
Chris@0
|
66 $field_state = [
|
Chris@0
|
67 'items_count' => count($items),
|
Chris@0
|
68 'array_parents' => [],
|
Chris@0
|
69 ];
|
Chris@0
|
70 static::setWidgetState($parents, $field_name, $form_state, $field_state);
|
Chris@0
|
71 }
|
Chris@0
|
72
|
Chris@0
|
73 // Collect widget elements.
|
Chris@0
|
74 $elements = [];
|
Chris@0
|
75
|
Chris@0
|
76 // If the widget is handling multiple values (e.g Options), or if we are
|
Chris@0
|
77 // displaying an individual element, just get a single form element and make
|
Chris@0
|
78 // it the $delta value.
|
Chris@0
|
79 if ($this->handlesMultipleValues() || isset($get_delta)) {
|
Chris@0
|
80 $delta = isset($get_delta) ? $get_delta : 0;
|
Chris@0
|
81 $element = [
|
Chris@0
|
82 '#title' => $this->fieldDefinition->getLabel(),
|
Chris@0
|
83 '#description' => FieldFilteredMarkup::create(\Drupal::token()->replace($this->fieldDefinition->getDescription())),
|
Chris@0
|
84 ];
|
Chris@0
|
85 $element = $this->formSingleElement($items, $delta, $element, $form, $form_state);
|
Chris@0
|
86
|
Chris@0
|
87 if ($element) {
|
Chris@0
|
88 if (isset($get_delta)) {
|
Chris@0
|
89 // If we are processing a specific delta value for a field where the
|
Chris@0
|
90 // field module handles multiples, set the delta in the result.
|
Chris@0
|
91 $elements[$delta] = $element;
|
Chris@0
|
92 }
|
Chris@0
|
93 else {
|
Chris@0
|
94 // For fields that handle their own processing, we cannot make
|
Chris@0
|
95 // assumptions about how the field is structured, just merge in the
|
Chris@0
|
96 // returned element.
|
Chris@0
|
97 $elements = $element;
|
Chris@0
|
98 }
|
Chris@0
|
99 }
|
Chris@0
|
100 }
|
Chris@0
|
101 // If the widget does not handle multiple values itself, (and we are not
|
Chris@0
|
102 // displaying an individual element), process the multiple value form.
|
Chris@0
|
103 else {
|
Chris@0
|
104 $elements = $this->formMultipleElements($items, $form, $form_state);
|
Chris@0
|
105 }
|
Chris@0
|
106
|
Chris@14
|
107 // Allow modules to alter the field multi-value widget form element.
|
Chris@14
|
108 // This hook can also be used for single-value fields.
|
Chris@14
|
109 $context = [
|
Chris@14
|
110 'form' => $form,
|
Chris@14
|
111 'widget' => $this,
|
Chris@14
|
112 'items' => $items,
|
Chris@14
|
113 'default' => $this->isDefaultValueWidget($form_state),
|
Chris@14
|
114 ];
|
Chris@14
|
115 \Drupal::moduleHandler()->alter([
|
Chris@14
|
116 'field_widget_multivalue_form',
|
Chris@14
|
117 'field_widget_multivalue_' . $this->getPluginId() . '_form',
|
Chris@14
|
118 ], $elements, $form_state, $context);
|
Chris@14
|
119
|
Chris@0
|
120 // Populate the 'array_parents' information in $form_state->get('field')
|
Chris@0
|
121 // after the form is built, so that we catch changes in the form structure
|
Chris@0
|
122 // performed in alter() hooks.
|
Chris@0
|
123 $elements['#after_build'][] = [get_class($this), 'afterBuild'];
|
Chris@0
|
124 $elements['#field_name'] = $field_name;
|
Chris@0
|
125 $elements['#field_parents'] = $parents;
|
Chris@0
|
126 // Enforce the structure of submitted values.
|
Chris@0
|
127 $elements['#parents'] = array_merge($parents, [$field_name]);
|
Chris@0
|
128 // Most widgets need their internal structure preserved in submitted values.
|
Chris@0
|
129 $elements += ['#tree' => TRUE];
|
Chris@0
|
130
|
Chris@0
|
131 return [
|
Chris@0
|
132 // Aid in theming of widgets by rendering a classified container.
|
Chris@0
|
133 '#type' => 'container',
|
Chris@0
|
134 // Assign a different parent, to keep the main id for the widget itself.
|
Chris@0
|
135 '#parents' => array_merge($parents, [$field_name . '_wrapper']),
|
Chris@0
|
136 '#attributes' => [
|
Chris@0
|
137 'class' => [
|
Chris@0
|
138 'field--type-' . Html::getClass($this->fieldDefinition->getType()),
|
Chris@0
|
139 'field--name-' . Html::getClass($field_name),
|
Chris@0
|
140 'field--widget-' . Html::getClass($this->getPluginId()),
|
Chris@0
|
141 ],
|
Chris@0
|
142 ],
|
Chris@0
|
143 'widget' => $elements,
|
Chris@0
|
144 ];
|
Chris@0
|
145 }
|
Chris@0
|
146
|
Chris@0
|
147 /**
|
Chris@0
|
148 * Special handling to create form elements for multiple values.
|
Chris@0
|
149 *
|
Chris@0
|
150 * Handles generic features for multiple fields:
|
Chris@0
|
151 * - number of widgets
|
Chris@0
|
152 * - AHAH-'add more' button
|
Chris@0
|
153 * - table display and drag-n-drop value reordering
|
Chris@0
|
154 */
|
Chris@0
|
155 protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
|
Chris@0
|
156 $field_name = $this->fieldDefinition->getName();
|
Chris@0
|
157 $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
|
Chris@0
|
158 $parents = $form['#parents'];
|
Chris@0
|
159
|
Chris@0
|
160 // Determine the number of widgets to display.
|
Chris@0
|
161 switch ($cardinality) {
|
Chris@0
|
162 case FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED:
|
Chris@0
|
163 $field_state = static::getWidgetState($parents, $field_name, $form_state);
|
Chris@0
|
164 $max = $field_state['items_count'];
|
Chris@0
|
165 $is_multiple = TRUE;
|
Chris@0
|
166 break;
|
Chris@0
|
167
|
Chris@0
|
168 default:
|
Chris@0
|
169 $max = $cardinality - 1;
|
Chris@0
|
170 $is_multiple = ($cardinality > 1);
|
Chris@0
|
171 break;
|
Chris@0
|
172 }
|
Chris@0
|
173
|
Chris@0
|
174 $title = $this->fieldDefinition->getLabel();
|
Chris@0
|
175 $description = FieldFilteredMarkup::create(\Drupal::token()->replace($this->fieldDefinition->getDescription()));
|
Chris@0
|
176
|
Chris@0
|
177 $elements = [];
|
Chris@0
|
178
|
Chris@0
|
179 for ($delta = 0; $delta <= $max; $delta++) {
|
Chris@0
|
180 // Add a new empty item if it doesn't exist yet at this delta.
|
Chris@0
|
181 if (!isset($items[$delta])) {
|
Chris@0
|
182 $items->appendItem();
|
Chris@0
|
183 }
|
Chris@0
|
184
|
Chris@0
|
185 // For multiple fields, title and description are handled by the wrapping
|
Chris@0
|
186 // table.
|
Chris@0
|
187 if ($is_multiple) {
|
Chris@0
|
188 $element = [
|
Chris@0
|
189 '#title' => $this->t('@title (value @number)', ['@title' => $title, '@number' => $delta + 1]),
|
Chris@0
|
190 '#title_display' => 'invisible',
|
Chris@0
|
191 '#description' => '',
|
Chris@0
|
192 ];
|
Chris@0
|
193 }
|
Chris@0
|
194 else {
|
Chris@0
|
195 $element = [
|
Chris@0
|
196 '#title' => $title,
|
Chris@0
|
197 '#title_display' => 'before',
|
Chris@0
|
198 '#description' => $description,
|
Chris@0
|
199 ];
|
Chris@0
|
200 }
|
Chris@0
|
201
|
Chris@0
|
202 $element = $this->formSingleElement($items, $delta, $element, $form, $form_state);
|
Chris@0
|
203
|
Chris@0
|
204 if ($element) {
|
Chris@0
|
205 // Input field for the delta (drag-n-drop reordering).
|
Chris@0
|
206 if ($is_multiple) {
|
Chris@0
|
207 // We name the element '_weight' to avoid clashing with elements
|
Chris@0
|
208 // defined by widget.
|
Chris@0
|
209 $element['_weight'] = [
|
Chris@0
|
210 '#type' => 'weight',
|
Chris@0
|
211 '#title' => $this->t('Weight for row @number', ['@number' => $delta + 1]),
|
Chris@0
|
212 '#title_display' => 'invisible',
|
Chris@0
|
213 // Note: this 'delta' is the FAPI #type 'weight' element's property.
|
Chris@0
|
214 '#delta' => $max,
|
Chris@0
|
215 '#default_value' => $items[$delta]->_weight ?: $delta,
|
Chris@0
|
216 '#weight' => 100,
|
Chris@0
|
217 ];
|
Chris@0
|
218 }
|
Chris@0
|
219
|
Chris@0
|
220 $elements[$delta] = $element;
|
Chris@0
|
221 }
|
Chris@0
|
222 }
|
Chris@0
|
223
|
Chris@0
|
224 if ($elements) {
|
Chris@0
|
225 $elements += [
|
Chris@0
|
226 '#theme' => 'field_multiple_value_form',
|
Chris@0
|
227 '#field_name' => $field_name,
|
Chris@0
|
228 '#cardinality' => $cardinality,
|
Chris@0
|
229 '#cardinality_multiple' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(),
|
Chris@0
|
230 '#required' => $this->fieldDefinition->isRequired(),
|
Chris@0
|
231 '#title' => $title,
|
Chris@0
|
232 '#description' => $description,
|
Chris@0
|
233 '#max_delta' => $max,
|
Chris@0
|
234 ];
|
Chris@0
|
235
|
Chris@0
|
236 // Add 'add more' button, if not working with a programmed form.
|
Chris@0
|
237 if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && !$form_state->isProgrammed()) {
|
Chris@0
|
238 $id_prefix = implode('-', array_merge($parents, [$field_name]));
|
Chris@0
|
239 $wrapper_id = Html::getUniqueId($id_prefix . '-add-more-wrapper');
|
Chris@0
|
240 $elements['#prefix'] = '<div id="' . $wrapper_id . '">';
|
Chris@0
|
241 $elements['#suffix'] = '</div>';
|
Chris@0
|
242
|
Chris@0
|
243 $elements['add_more'] = [
|
Chris@0
|
244 '#type' => 'submit',
|
Chris@0
|
245 '#name' => strtr($id_prefix, '-', '_') . '_add_more',
|
Chris@0
|
246 '#value' => t('Add another item'),
|
Chris@0
|
247 '#attributes' => ['class' => ['field-add-more-submit']],
|
Chris@0
|
248 '#limit_validation_errors' => [array_merge($parents, [$field_name])],
|
Chris@0
|
249 '#submit' => [[get_class($this), 'addMoreSubmit']],
|
Chris@0
|
250 '#ajax' => [
|
Chris@0
|
251 'callback' => [get_class($this), 'addMoreAjax'],
|
Chris@0
|
252 'wrapper' => $wrapper_id,
|
Chris@0
|
253 'effect' => 'fade',
|
Chris@0
|
254 ],
|
Chris@0
|
255 ];
|
Chris@0
|
256 }
|
Chris@0
|
257 }
|
Chris@0
|
258
|
Chris@0
|
259 return $elements;
|
Chris@0
|
260 }
|
Chris@0
|
261
|
Chris@0
|
262 /**
|
Chris@0
|
263 * After-build handler for field elements in a form.
|
Chris@0
|
264 *
|
Chris@0
|
265 * This stores the final location of the field within the form structure so
|
Chris@0
|
266 * that flagErrors() can assign validation errors to the right form element.
|
Chris@0
|
267 */
|
Chris@0
|
268 public static function afterBuild(array $element, FormStateInterface $form_state) {
|
Chris@0
|
269 $parents = $element['#field_parents'];
|
Chris@0
|
270 $field_name = $element['#field_name'];
|
Chris@0
|
271
|
Chris@0
|
272 $field_state = static::getWidgetState($parents, $field_name, $form_state);
|
Chris@0
|
273 $field_state['array_parents'] = $element['#array_parents'];
|
Chris@0
|
274 static::setWidgetState($parents, $field_name, $form_state, $field_state);
|
Chris@0
|
275
|
Chris@0
|
276 return $element;
|
Chris@0
|
277 }
|
Chris@0
|
278
|
Chris@0
|
279 /**
|
Chris@0
|
280 * Submission handler for the "Add another item" button.
|
Chris@0
|
281 */
|
Chris@0
|
282 public static function addMoreSubmit(array $form, FormStateInterface $form_state) {
|
Chris@0
|
283 $button = $form_state->getTriggeringElement();
|
Chris@0
|
284
|
Chris@0
|
285 // Go one level up in the form, to the widgets container.
|
Chris@0
|
286 $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
|
Chris@0
|
287 $field_name = $element['#field_name'];
|
Chris@0
|
288 $parents = $element['#field_parents'];
|
Chris@0
|
289
|
Chris@0
|
290 // Increment the items count.
|
Chris@0
|
291 $field_state = static::getWidgetState($parents, $field_name, $form_state);
|
Chris@0
|
292 $field_state['items_count']++;
|
Chris@0
|
293 static::setWidgetState($parents, $field_name, $form_state, $field_state);
|
Chris@0
|
294
|
Chris@0
|
295 $form_state->setRebuild();
|
Chris@0
|
296 }
|
Chris@0
|
297
|
Chris@0
|
298 /**
|
Chris@0
|
299 * Ajax callback for the "Add another item" button.
|
Chris@0
|
300 *
|
Chris@0
|
301 * This returns the new page content to replace the page content made obsolete
|
Chris@0
|
302 * by the form submission.
|
Chris@0
|
303 */
|
Chris@0
|
304 public static function addMoreAjax(array $form, FormStateInterface $form_state) {
|
Chris@0
|
305 $button = $form_state->getTriggeringElement();
|
Chris@0
|
306
|
Chris@0
|
307 // Go one level up in the form, to the widgets container.
|
Chris@0
|
308 $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
|
Chris@0
|
309
|
Chris@0
|
310 // Ensure the widget allows adding additional items.
|
Chris@0
|
311 if ($element['#cardinality'] != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
|
Chris@0
|
312 return;
|
Chris@0
|
313 }
|
Chris@0
|
314
|
Chris@0
|
315 // Add a DIV around the delta receiving the Ajax effect.
|
Chris@0
|
316 $delta = $element['#max_delta'];
|
Chris@0
|
317 $element[$delta]['#prefix'] = '<div class="ajax-new-content">' . (isset($element[$delta]['#prefix']) ? $element[$delta]['#prefix'] : '');
|
Chris@0
|
318 $element[$delta]['#suffix'] = (isset($element[$delta]['#suffix']) ? $element[$delta]['#suffix'] : '') . '</div>';
|
Chris@0
|
319
|
Chris@0
|
320 return $element;
|
Chris@0
|
321 }
|
Chris@0
|
322
|
Chris@0
|
323 /**
|
Chris@0
|
324 * Generates the form element for a single copy of the widget.
|
Chris@0
|
325 */
|
Chris@0
|
326 protected function formSingleElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
|
Chris@0
|
327 $element += [
|
Chris@0
|
328 '#field_parents' => $form['#parents'],
|
Chris@0
|
329 // Only the first widget should be required.
|
Chris@0
|
330 '#required' => $delta == 0 && $this->fieldDefinition->isRequired(),
|
Chris@0
|
331 '#delta' => $delta,
|
Chris@0
|
332 '#weight' => $delta,
|
Chris@0
|
333 ];
|
Chris@0
|
334
|
Chris@0
|
335 $element = $this->formElement($items, $delta, $element, $form, $form_state);
|
Chris@0
|
336
|
Chris@0
|
337 if ($element) {
|
Chris@0
|
338 // Allow modules to alter the field widget form element.
|
Chris@0
|
339 $context = [
|
Chris@0
|
340 'form' => $form,
|
Chris@0
|
341 'widget' => $this,
|
Chris@0
|
342 'items' => $items,
|
Chris@0
|
343 'delta' => $delta,
|
Chris@0
|
344 'default' => $this->isDefaultValueWidget($form_state),
|
Chris@0
|
345 ];
|
Chris@0
|
346 \Drupal::moduleHandler()->alter(['field_widget_form', 'field_widget_' . $this->getPluginId() . '_form'], $element, $form_state, $context);
|
Chris@0
|
347 }
|
Chris@0
|
348
|
Chris@0
|
349 return $element;
|
Chris@0
|
350 }
|
Chris@0
|
351
|
Chris@0
|
352 /**
|
Chris@0
|
353 * {@inheritdoc}
|
Chris@0
|
354 */
|
Chris@0
|
355 public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
|
Chris@0
|
356 $field_name = $this->fieldDefinition->getName();
|
Chris@0
|
357
|
Chris@0
|
358 // Extract the values from $form_state->getValues().
|
Chris@0
|
359 $path = array_merge($form['#parents'], [$field_name]);
|
Chris@0
|
360 $key_exists = NULL;
|
Chris@0
|
361 $values = NestedArray::getValue($form_state->getValues(), $path, $key_exists);
|
Chris@0
|
362
|
Chris@0
|
363 if ($key_exists) {
|
Chris@0
|
364 // Account for drag-and-drop reordering if needed.
|
Chris@0
|
365 if (!$this->handlesMultipleValues()) {
|
Chris@0
|
366 // Remove the 'value' of the 'add more' button.
|
Chris@0
|
367 unset($values['add_more']);
|
Chris@0
|
368
|
Chris@0
|
369 // The original delta, before drag-and-drop reordering, is needed to
|
Chris@0
|
370 // route errors to the correct form element.
|
Chris@0
|
371 foreach ($values as $delta => &$value) {
|
Chris@0
|
372 $value['_original_delta'] = $delta;
|
Chris@0
|
373 }
|
Chris@0
|
374
|
Chris@0
|
375 usort($values, function ($a, $b) {
|
Chris@0
|
376 return SortArray::sortByKeyInt($a, $b, '_weight');
|
Chris@0
|
377 });
|
Chris@0
|
378 }
|
Chris@0
|
379
|
Chris@0
|
380 // Let the widget massage the submitted values.
|
Chris@0
|
381 $values = $this->massageFormValues($values, $form, $form_state);
|
Chris@0
|
382
|
Chris@0
|
383 // Assign the values and remove the empty ones.
|
Chris@0
|
384 $items->setValue($values);
|
Chris@0
|
385 $items->filterEmptyItems();
|
Chris@0
|
386
|
Chris@0
|
387 // Put delta mapping in $form_state, so that flagErrors() can use it.
|
Chris@0
|
388 $field_state = static::getWidgetState($form['#parents'], $field_name, $form_state);
|
Chris@0
|
389 foreach ($items as $delta => $item) {
|
Chris@0
|
390 $field_state['original_deltas'][$delta] = isset($item->_original_delta) ? $item->_original_delta : $delta;
|
Chris@0
|
391 unset($item->_original_delta, $item->_weight);
|
Chris@0
|
392 }
|
Chris@0
|
393 static::setWidgetState($form['#parents'], $field_name, $form_state, $field_state);
|
Chris@0
|
394 }
|
Chris@0
|
395 }
|
Chris@0
|
396
|
Chris@0
|
397 /**
|
Chris@0
|
398 * {@inheritdoc}
|
Chris@0
|
399 */
|
Chris@0
|
400 public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
|
Chris@0
|
401 $field_name = $this->fieldDefinition->getName();
|
Chris@0
|
402
|
Chris@0
|
403 $field_state = static::getWidgetState($form['#parents'], $field_name, $form_state);
|
Chris@0
|
404
|
Chris@0
|
405 if ($violations->count()) {
|
Chris@0
|
406 // Locate the correct element in the form.
|
Chris@0
|
407 $element = NestedArray::getValue($form_state->getCompleteForm(), $field_state['array_parents']);
|
Chris@0
|
408
|
Chris@0
|
409 // Do not report entity-level validation errors if Form API errors have
|
Chris@0
|
410 // already been reported for the field.
|
Chris@0
|
411 // @todo Field validation should not be run on fields with FAPI errors to
|
Chris@0
|
412 // begin with. See https://www.drupal.org/node/2070429.
|
Chris@0
|
413 $element_path = implode('][', $element['#parents']);
|
Chris@0
|
414 if ($reported_errors = $form_state->getErrors()) {
|
Chris@0
|
415 foreach (array_keys($reported_errors) as $error_path) {
|
Chris@0
|
416 if (strpos($error_path, $element_path) === 0) {
|
Chris@0
|
417 return;
|
Chris@0
|
418 }
|
Chris@0
|
419 }
|
Chris@0
|
420 }
|
Chris@0
|
421
|
Chris@0
|
422 // Only set errors if the element is visible.
|
Chris@0
|
423 if (Element::isVisibleElement($element)) {
|
Chris@0
|
424 $handles_multiple = $this->handlesMultipleValues();
|
Chris@0
|
425
|
Chris@14
|
426 $violations_by_delta = $item_list_violations = [];
|
Chris@0
|
427 foreach ($violations as $violation) {
|
Chris@0
|
428 // Separate violations by delta.
|
Chris@0
|
429 $property_path = explode('.', $violation->getPropertyPath());
|
Chris@0
|
430 $delta = array_shift($property_path);
|
Chris@14
|
431 if (is_numeric($delta)) {
|
Chris@14
|
432 $violations_by_delta[$delta][] = $violation;
|
Chris@14
|
433 }
|
Chris@14
|
434 // Violations at the ItemList level are not associated to any delta.
|
Chris@14
|
435 else {
|
Chris@14
|
436 $item_list_violations[] = $violation;
|
Chris@14
|
437 }
|
Chris@0
|
438 $violation->arrayPropertyPath = $property_path;
|
Chris@0
|
439 }
|
Chris@0
|
440
|
Chris@0
|
441 /** @var \Symfony\Component\Validator\ConstraintViolationInterface[] $delta_violations */
|
Chris@0
|
442 foreach ($violations_by_delta as $delta => $delta_violations) {
|
Chris@14
|
443 // Pass violations to the main element if this is a multiple-value
|
Chris@14
|
444 // widget.
|
Chris@14
|
445 if ($handles_multiple) {
|
Chris@0
|
446 $delta_element = $element;
|
Chris@0
|
447 }
|
Chris@0
|
448 // Otherwise, pass errors by delta to the corresponding sub-element.
|
Chris@0
|
449 else {
|
Chris@0
|
450 $original_delta = $field_state['original_deltas'][$delta];
|
Chris@0
|
451 $delta_element = $element[$original_delta];
|
Chris@0
|
452 }
|
Chris@0
|
453 foreach ($delta_violations as $violation) {
|
Chris@0
|
454 // @todo: Pass $violation->arrayPropertyPath as property path.
|
Chris@0
|
455 $error_element = $this->errorElement($delta_element, $violation, $form, $form_state);
|
Chris@0
|
456 if ($error_element !== FALSE) {
|
Chris@0
|
457 $form_state->setError($error_element, $violation->getMessage());
|
Chris@0
|
458 }
|
Chris@0
|
459 }
|
Chris@0
|
460 }
|
Chris@14
|
461
|
Chris@14
|
462 /** @var \Symfony\Component\Validator\ConstraintViolationInterface[] $item_list_violations */
|
Chris@14
|
463 // Pass violations to the main element without going through
|
Chris@14
|
464 // errorElement() if the violations are at the ItemList level.
|
Chris@14
|
465 foreach ($item_list_violations as $violation) {
|
Chris@14
|
466 $form_state->setError($element, $violation->getMessage());
|
Chris@14
|
467 }
|
Chris@0
|
468 }
|
Chris@0
|
469 }
|
Chris@0
|
470 }
|
Chris@0
|
471
|
Chris@0
|
472 /**
|
Chris@0
|
473 * {@inheritdoc}
|
Chris@0
|
474 */
|
Chris@0
|
475 public static function getWidgetState(array $parents, $field_name, FormStateInterface $form_state) {
|
Chris@0
|
476 return NestedArray::getValue($form_state->getStorage(), static::getWidgetStateParents($parents, $field_name));
|
Chris@0
|
477 }
|
Chris@0
|
478
|
Chris@0
|
479 /**
|
Chris@0
|
480 * {@inheritdoc}
|
Chris@0
|
481 */
|
Chris@0
|
482 public static function setWidgetState(array $parents, $field_name, FormStateInterface $form_state, array $field_state) {
|
Chris@0
|
483 NestedArray::setValue($form_state->getStorage(), static::getWidgetStateParents($parents, $field_name), $field_state);
|
Chris@0
|
484 }
|
Chris@0
|
485
|
Chris@0
|
486 /**
|
Chris@0
|
487 * Returns the location of processing information within $form_state.
|
Chris@0
|
488 *
|
Chris@0
|
489 * @param array $parents
|
Chris@0
|
490 * The array of #parents where the widget lives in the form.
|
Chris@0
|
491 * @param string $field_name
|
Chris@0
|
492 * The field name.
|
Chris@0
|
493 *
|
Chris@0
|
494 * @return array
|
Chris@0
|
495 * The location of processing information within $form_state.
|
Chris@0
|
496 */
|
Chris@0
|
497 protected static function getWidgetStateParents(array $parents, $field_name) {
|
Chris@0
|
498 // Field processing data is placed at
|
Chris@0
|
499 // $form_state->get(['field_storage', '#parents', ...$parents..., '#fields', $field_name]),
|
Chris@0
|
500 // to avoid clashes between field names and $parents parts.
|
Chris@0
|
501 return array_merge(['field_storage', '#parents'], $parents, ['#fields', $field_name]);
|
Chris@0
|
502 }
|
Chris@0
|
503
|
Chris@0
|
504 /**
|
Chris@0
|
505 * {@inheritdoc}
|
Chris@0
|
506 */
|
Chris@0
|
507 public function settingsForm(array $form, FormStateInterface $form_state) {
|
Chris@0
|
508 return [];
|
Chris@0
|
509 }
|
Chris@0
|
510
|
Chris@0
|
511 /**
|
Chris@0
|
512 * {@inheritdoc}
|
Chris@0
|
513 */
|
Chris@0
|
514 public function settingsSummary() {
|
Chris@0
|
515 return [];
|
Chris@0
|
516 }
|
Chris@0
|
517
|
Chris@0
|
518 /**
|
Chris@0
|
519 * {@inheritdoc}
|
Chris@0
|
520 */
|
Chris@0
|
521 public function errorElement(array $element, ConstraintViolationInterface $error, array $form, FormStateInterface $form_state) {
|
Chris@0
|
522 return $element;
|
Chris@0
|
523 }
|
Chris@0
|
524
|
Chris@0
|
525 /**
|
Chris@0
|
526 * {@inheritdoc}
|
Chris@0
|
527 */
|
Chris@0
|
528 public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
|
Chris@0
|
529 return $values;
|
Chris@0
|
530 }
|
Chris@0
|
531
|
Chris@0
|
532 /**
|
Chris@0
|
533 * Returns the array of field settings.
|
Chris@0
|
534 *
|
Chris@0
|
535 * @return array
|
Chris@0
|
536 * The array of settings.
|
Chris@0
|
537 */
|
Chris@0
|
538 protected function getFieldSettings() {
|
Chris@0
|
539 return $this->fieldDefinition->getSettings();
|
Chris@0
|
540 }
|
Chris@0
|
541
|
Chris@0
|
542 /**
|
Chris@0
|
543 * Returns the value of a field setting.
|
Chris@0
|
544 *
|
Chris@0
|
545 * @param string $setting_name
|
Chris@0
|
546 * The setting name.
|
Chris@0
|
547 *
|
Chris@0
|
548 * @return mixed
|
Chris@0
|
549 * The setting value.
|
Chris@0
|
550 */
|
Chris@0
|
551 protected function getFieldSetting($setting_name) {
|
Chris@0
|
552 return $this->fieldDefinition->getSetting($setting_name);
|
Chris@0
|
553 }
|
Chris@0
|
554
|
Chris@0
|
555 /**
|
Chris@0
|
556 * Returns whether the widget handles multiple values.
|
Chris@0
|
557 *
|
Chris@0
|
558 * @return bool
|
Chris@0
|
559 * TRUE if a single copy of formElement() can handle multiple field values,
|
Chris@0
|
560 * FALSE if multiple values require separate copies of formElement().
|
Chris@0
|
561 */
|
Chris@0
|
562 protected function handlesMultipleValues() {
|
Chris@0
|
563 $definition = $this->getPluginDefinition();
|
Chris@0
|
564 return $definition['multiple_values'];
|
Chris@0
|
565 }
|
Chris@0
|
566
|
Chris@0
|
567 /**
|
Chris@0
|
568 * {@inheritdoc}
|
Chris@0
|
569 */
|
Chris@0
|
570 public static function isApplicable(FieldDefinitionInterface $field_definition) {
|
Chris@0
|
571 // By default, widgets are available for all fields.
|
Chris@0
|
572 return TRUE;
|
Chris@0
|
573 }
|
Chris@0
|
574
|
Chris@0
|
575 /**
|
Chris@0
|
576 * Returns whether the widget used for default value form.
|
Chris@0
|
577 *
|
Chris@0
|
578 * @param \Drupal\Core\Form\FormStateInterface $form_state
|
Chris@0
|
579 * The current state of the form.
|
Chris@0
|
580 *
|
Chris@0
|
581 * @return bool
|
Chris@0
|
582 * TRUE if a widget used to input default value, FALSE otherwise.
|
Chris@0
|
583 */
|
Chris@0
|
584 protected function isDefaultValueWidget(FormStateInterface $form_state) {
|
Chris@0
|
585 return (bool) $form_state->get('default_value_widget');
|
Chris@0
|
586 }
|
Chris@0
|
587
|
Chris@0
|
588 /**
|
Chris@0
|
589 * Returns the filtered field description.
|
Chris@0
|
590 *
|
Chris@0
|
591 * @return \Drupal\Core\Field\FieldFilteredMarkup
|
Chris@0
|
592 * The filtered field description, with tokens replaced.
|
Chris@0
|
593 */
|
Chris@0
|
594 protected function getFilteredDescription() {
|
Chris@0
|
595 return FieldFilteredMarkup::create(\Drupal::token()->replace($this->fieldDefinition->getDescription()));
|
Chris@0
|
596 }
|
Chris@0
|
597
|
Chris@0
|
598 }
|