52bf9c9d23df01eb33b411361fec96214c513071
[yaffs-website] / web / core / lib / Drupal / Core / Field / FieldItemList.php
1 <?php
2
3 namespace Drupal\Core\Field;
4
5 use Drupal\Core\Access\AccessResult;
6 use Drupal\Core\Entity\FieldableEntityInterface;
7 use Drupal\Core\Form\FormStateInterface;
8 use Drupal\Core\Language\LanguageInterface;
9 use Drupal\Core\Session\AccountInterface;
10 use Drupal\Core\TypedData\DataDefinitionInterface;
11 use Drupal\Core\TypedData\Plugin\DataType\ItemList;
12
13 /**
14  * Represents an entity field; that is, a list of field item objects.
15  *
16  * An entity field is a list of field items, each containing a set of
17  * properties. Note that even single-valued entity fields are represented as
18  * list of field items, however for easy access to the contained item the entity
19  * field delegates __get() and __set() calls directly to the first item.
20  */
21 class FieldItemList extends ItemList implements FieldItemListInterface {
22
23   /**
24    * Numerically indexed array of field items.
25    *
26    * @var \Drupal\Core\Field\FieldItemInterface[]
27    */
28   protected $list = [];
29
30   /**
31    * The langcode of the field values held in the object.
32    *
33    * @var string
34    */
35   protected $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
36
37   /**
38    * {@inheritdoc}
39    */
40   protected function createItem($offset = 0, $value = NULL) {
41     return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($this, $offset, $value);
42   }
43
44   /**
45    * {@inheritdoc}
46    */
47   public function getEntity() {
48     // The "parent" is the TypedData object for the entity, we need to unwrap
49     // the actual entity.
50     return $this->getParent()->getValue();
51   }
52
53   /**
54    * {@inheritdoc}
55    */
56   public function setLangcode($langcode) {
57     $this->langcode = $langcode;
58   }
59
60   /**
61    * {@inheritdoc}
62    */
63   public function getLangcode() {
64     return $this->langcode;
65   }
66
67   /**
68    * {@inheritdoc}
69    */
70   public function getFieldDefinition() {
71     return $this->definition;
72   }
73
74   /**
75    * {@inheritdoc}
76    */
77   public function getSettings() {
78     return $this->definition->getSettings();
79   }
80
81   /**
82    * {@inheritdoc}
83    */
84   public function getSetting($setting_name) {
85     return $this->definition->getSetting($setting_name);
86   }
87
88   /**
89    * {@inheritdoc}
90    */
91   public function filterEmptyItems() {
92     $this->filter(function ($item) {
93       return !$item->isEmpty();
94     });
95     return $this;
96   }
97
98   /**
99    * {@inheritdoc}
100    */
101   public function setValue($values, $notify = TRUE) {
102     // Support passing in only the value of the first item, either as a literal
103     // (value of the first property) or as an array of properties.
104     if (isset($values) && (!is_array($values) || (!empty($values) && !is_numeric(current(array_keys($values)))))) {
105       $values = [0 => $values];
106     }
107     parent::setValue($values, $notify);
108   }
109
110   /**
111    * {@inheritdoc}
112    */
113   public function __get($property_name) {
114     // For empty fields, $entity->field->property is NULL.
115     if ($item = $this->first()) {
116       return $item->__get($property_name);
117     }
118   }
119
120   /**
121    * {@inheritdoc}
122    */
123   public function __set($property_name, $value) {
124     // For empty fields, $entity->field->property = $value automatically
125     // creates the item before assigning the value.
126     $item = $this->first() ?: $this->appendItem();
127     $item->__set($property_name, $value);
128   }
129
130   /**
131    * {@inheritdoc}
132    */
133   public function __isset($property_name) {
134     if ($item = $this->first()) {
135       return $item->__isset($property_name);
136     }
137     return FALSE;
138   }
139
140   /**
141    * {@inheritdoc}
142    */
143   public function __unset($property_name) {
144     if ($item = $this->first()) {
145       $item->__unset($property_name);
146     }
147   }
148
149   /**
150    * {@inheritdoc}
151    */
152   public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) {
153     $access_control_handler = \Drupal::entityManager()->getAccessControlHandler($this->getEntity()->getEntityTypeId());
154     return $access_control_handler->fieldAccess($operation, $this->getFieldDefinition(), $account, $this, $return_as_object);
155   }
156
157   /**
158    * {@inheritdoc}
159    */
160   public function defaultAccess($operation = 'view', AccountInterface $account = NULL) {
161     // Grant access per default.
162     return AccessResult::allowed();
163   }
164
165   /**
166    * {@inheritdoc}
167    */
168   public function applyDefaultValue($notify = TRUE) {
169     if ($value = $this->getFieldDefinition()->getDefaultValue($this->getEntity())) {
170       $this->setValue($value, $notify);
171     }
172     else {
173       // Create one field item and give it a chance to apply its defaults.
174       // Remove it if this ended up doing nothing.
175       // @todo Having to create an item in case it wants to set a value is
176       // absurd. Remove that in https://www.drupal.org/node/2356623.
177       $item = $this->first() ?: $this->appendItem();
178       $item->applyDefaultValue(FALSE);
179       $this->filterEmptyItems();
180     }
181     return $this;
182   }
183
184   /**
185    * {@inheritdoc}
186    */
187   public function preSave() {
188     // Filter out empty items.
189     $this->filterEmptyItems();
190
191     $this->delegateMethod('preSave');
192   }
193
194   /**
195    * {@inheritdoc}
196    */
197   public function postSave($update) {
198     $result = $this->delegateMethod('postSave', $update);
199     return (bool) array_filter($result);
200   }
201
202   /**
203    * {@inheritdoc}
204    */
205   public function delete() {
206     $this->delegateMethod('delete');
207   }
208
209   /**
210    * {@inheritdoc}
211    */
212   public function deleteRevision() {
213     $this->delegateMethod('deleteRevision');
214   }
215
216   /**
217    * Calls a method on each FieldItem.
218    *
219    * Any argument passed will be forwarded to the invoked method.
220    *
221    * @param string $method
222    *   The name of the method to be invoked.
223    *
224    * @return array
225    *   An array of results keyed by delta.
226    */
227   protected function delegateMethod($method) {
228     $result = [];
229     $args = array_slice(func_get_args(), 1);
230     foreach ($this->list as $delta => $item) {
231       // call_user_func_array() is way slower than a direct call so we avoid
232       // using it if have no parameters.
233       $result[$delta] = $args ? call_user_func_array([$item, $method], $args) : $item->{$method}();
234     }
235     return $result;
236   }
237
238   /**
239    * {@inheritdoc}
240    */
241   public function view($display_options = []) {
242     $view_builder = \Drupal::entityManager()->getViewBuilder($this->getEntity()->getEntityTypeId());
243     return $view_builder->viewField($this, $display_options);
244   }
245
246   /**
247    * {@inheritdoc}
248    */
249   public function generateSampleItems($count = 1) {
250     $field_definition = $this->getFieldDefinition();
251     $field_type_class = $field_definition->getItemDefinition()->getClass();
252     for ($delta = 0; $delta < $count; $delta++) {
253       $values[$delta] = $field_type_class::generateSampleValue($field_definition);
254     }
255     $this->setValue($values);
256   }
257
258   /**
259    * {@inheritdoc}
260    */
261   public function getConstraints() {
262     $constraints = parent::getConstraints();
263     // Check that the number of values doesn't exceed the field cardinality. For
264     // form submitted values, this can only happen with 'multiple value'
265     // widgets.
266     $cardinality = $this->getFieldDefinition()->getFieldStorageDefinition()->getCardinality();
267     if ($cardinality != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
268       $constraints[] = $this->getTypedDataManager()
269         ->getValidationConstraintManager()
270         ->create('Count', [
271           'max' => $cardinality,
272           'maxMessage' => t('%name: this field cannot hold more than @count values.', ['%name' => $this->getFieldDefinition()->getLabel(), '@count' => $cardinality]),
273         ]);
274     }
275
276     return $constraints;
277   }
278
279   /**
280    * {@inheritdoc}
281    */
282   public function defaultValuesForm(array &$form, FormStateInterface $form_state) {
283     if (empty($this->getFieldDefinition()->getDefaultValueCallback())) {
284       if ($widget = $this->defaultValueWidget($form_state)) {
285         // Place the input in a separate place in the submitted values tree.
286         $element = ['#parents' => ['default_value_input']];
287         $element += $widget->form($this, $element, $form_state);
288
289         return $element;
290       }
291       else {
292         return ['#markup' => $this->t('No widget available for: %type.', ['%type' => $this->getFieldDefinition()->getType()])];
293       }
294     }
295   }
296
297   /**
298    * {@inheritdoc}
299    */
300   public function defaultValuesFormValidate(array $element, array &$form, FormStateInterface $form_state) {
301     // Extract the submitted value, and validate it.
302     if ($widget = $this->defaultValueWidget($form_state)) {
303       $widget->extractFormValues($this, $element, $form_state);
304       // Force a non-required field definition.
305       // @see self::defaultValueWidget().
306       $this->getFieldDefinition()->setRequired(FALSE);
307       $violations = $this->validate();
308
309       // Assign reported errors to the correct form element.
310       if (count($violations)) {
311         $widget->flagErrors($this, $violations, $element, $form_state);
312       }
313     }
314   }
315
316   /**
317    * {@inheritdoc}
318    */
319   public function defaultValuesFormSubmit(array $element, array &$form, FormStateInterface $form_state) {
320     // Extract the submitted value, and return it as an array.
321     if ($widget = $this->defaultValueWidget($form_state)) {
322       $widget->extractFormValues($this, $element, $form_state);
323       return $this->getValue();
324     }
325   }
326
327   /**
328    * {@inheritdoc}
329    */
330   public static function processDefaultValue($default_value, FieldableEntityInterface $entity, FieldDefinitionInterface $definition) {
331     return $default_value;
332   }
333
334   /**
335    * Returns the widget object used in default value form.
336    *
337    * @param \Drupal\Core\Form\FormStateInterface $form_state
338    *   The form state of the (entire) configuration form.
339    *
340    * @return \Drupal\Core\Field\WidgetInterface|null
341    *   A Widget object or NULL if no widget is available.
342    */
343   protected function defaultValueWidget(FormStateInterface $form_state) {
344     if (!$form_state->has('default_value_widget')) {
345       $entity = $this->getEntity();
346
347       // Force a non-required widget.
348       $definition = $this->getFieldDefinition();
349       $definition->setRequired(FALSE);
350       $definition->setDescription('');
351
352       // Use the widget currently configured for the 'default' form mode, or
353       // fallback to the default widget for the field type.
354       $entity_form_display = entity_get_form_display($entity->getEntityTypeId(), $entity->bundle(), 'default');
355       $widget = $entity_form_display->getRenderer($this->getFieldDefinition()->getName());
356       if (!$widget) {
357         $widget = \Drupal::service('plugin.manager.field.widget')->getInstance(['field_definition' => $this->getFieldDefinition()]);
358       }
359
360       $form_state->set('default_value_widget', $widget);
361     }
362
363     return $form_state->get('default_value_widget');
364   }
365
366   /**
367    * {@inheritdoc}
368    */
369   public function equals(FieldItemListInterface $list_to_compare) {
370     $count1 = count($this);
371     $count2 = count($list_to_compare);
372     if ($count1 === 0 && $count2 === 0) {
373       // Both are empty we can safely assume that it did not change.
374       return TRUE;
375     }
376     if ($count1 !== $count2) {
377       // One of them is empty but not the other one so the value changed.
378       return FALSE;
379     }
380     $value1 = $this->getValue();
381     $value2 = $list_to_compare->getValue();
382     if ($value1 === $value2) {
383       return TRUE;
384     }
385     // If the values are not equal ensure a consistent order of field item
386     // properties and remove properties which will not be saved.
387     $property_definitions = $this->getFieldDefinition()->getFieldStorageDefinition()->getPropertyDefinitions();
388     $non_computed_properties = array_filter($property_definitions, function (DataDefinitionInterface $property) {
389       return !$property->isComputed();
390     });
391     $callback = function (&$value) use ($non_computed_properties) {
392       if (is_array($value)) {
393         $value = array_intersect_key($value, $non_computed_properties);
394         ksort($value);
395       }
396     };
397     array_walk($value1, $callback);
398     array_walk($value2, $callback);
399
400     return $value1 == $value2;
401   }
402
403 }