Security update for Core, with self-updated composer
[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    * @todo Revisit the need when all entity types are converted to NG entities.
101    */
102   public function getValue($include_computed = FALSE) {
103     $values = [];
104     foreach ($this->list as $delta => $item) {
105       $values[$delta] = $item->getValue($include_computed);
106     }
107     return $values;
108   }
109
110   /**
111    * {@inheritdoc}
112    */
113   public function setValue($values, $notify = TRUE) {
114     // Support passing in only the value of the first item, either as a literal
115     // (value of the first property) or as an array of properties.
116     if (isset($values) && (!is_array($values) || (!empty($values) && !is_numeric(current(array_keys($values)))))) {
117       $values = [0 => $values];
118     }
119     parent::setValue($values, $notify);
120   }
121
122   /**
123    * {@inheritdoc}
124    */
125   public function __get($property_name) {
126     // For empty fields, $entity->field->property is NULL.
127     if ($item = $this->first()) {
128       return $item->__get($property_name);
129     }
130   }
131
132   /**
133    * {@inheritdoc}
134    */
135   public function __set($property_name, $value) {
136     // For empty fields, $entity->field->property = $value automatically
137     // creates the item before assigning the value.
138     $item = $this->first() ?: $this->appendItem();
139     $item->__set($property_name, $value);
140   }
141
142   /**
143    * {@inheritdoc}
144    */
145   public function __isset($property_name) {
146     if ($item = $this->first()) {
147       return $item->__isset($property_name);
148     }
149     return FALSE;
150   }
151
152   /**
153    * {@inheritdoc}
154    */
155   public function __unset($property_name) {
156     if ($item = $this->first()) {
157       $item->__unset($property_name);
158     }
159   }
160
161   /**
162    * {@inheritdoc}
163    */
164   public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) {
165     $access_control_handler = \Drupal::entityManager()->getAccessControlHandler($this->getEntity()->getEntityTypeId());
166     return $access_control_handler->fieldAccess($operation, $this->getFieldDefinition(), $account, $this, $return_as_object);
167   }
168
169   /**
170    * {@inheritdoc}
171    */
172   public function defaultAccess($operation = 'view', AccountInterface $account = NULL) {
173     // Grant access per default.
174     return AccessResult::allowed();
175   }
176
177   /**
178    * {@inheritdoc}
179    */
180   public function applyDefaultValue($notify = TRUE) {
181     if ($value = $this->getFieldDefinition()->getDefaultValue($this->getEntity())) {
182       $this->setValue($value, $notify);
183     }
184     else {
185       // Create one field item and give it a chance to apply its defaults.
186       // Remove it if this ended up doing nothing.
187       // @todo Having to create an item in case it wants to set a value is
188       // absurd. Remove that in https://www.drupal.org/node/2356623.
189       $item = $this->first() ?: $this->appendItem();
190       $item->applyDefaultValue(FALSE);
191       $this->filterEmptyItems();
192     }
193     return $this;
194   }
195
196   /**
197    * {@inheritdoc}
198    */
199   public function preSave() {
200     // Filter out empty items.
201     $this->filterEmptyItems();
202
203     $this->delegateMethod('preSave');
204   }
205
206   /**
207    * {@inheritdoc}
208    */
209   public function postSave($update) {
210     $result = $this->delegateMethod('postSave', $update);
211     return (bool) array_filter($result);
212   }
213
214   /**
215    * {@inheritdoc}
216    */
217   public function delete() {
218     $this->delegateMethod('delete');
219   }
220
221   /**
222    * {@inheritdoc}
223    */
224   public function deleteRevision() {
225     $this->delegateMethod('deleteRevision');
226   }
227
228   /**
229    * Calls a method on each FieldItem.
230    *
231    * Any argument passed will be forwarded to the invoked method.
232    *
233    * @param string $method
234    *   The name of the method to be invoked.
235    *
236    * @return array
237    *   An array of results keyed by delta.
238    */
239   protected function delegateMethod($method) {
240     $result = [];
241     $args = array_slice(func_get_args(), 1);
242     foreach ($this->list as $delta => $item) {
243       // call_user_func_array() is way slower than a direct call so we avoid
244       // using it if have no parameters.
245       $result[$delta] = $args ? call_user_func_array([$item, $method], $args) : $item->{$method}();
246     }
247     return $result;
248   }
249
250   /**
251    * {@inheritdoc}
252    */
253   public function view($display_options = []) {
254     $view_builder = \Drupal::entityManager()->getViewBuilder($this->getEntity()->getEntityTypeId());
255     return $view_builder->viewField($this, $display_options);
256   }
257
258   /**
259    * {@inheritdoc}
260    */
261   public function generateSampleItems($count = 1) {
262     $field_definition = $this->getFieldDefinition();
263     $field_type_class = \Drupal::service('plugin.manager.field.field_type')->getPluginClass($field_definition->getType());
264     for ($delta = 0; $delta < $count; $delta++) {
265       $values[$delta] = $field_type_class::generateSampleValue($field_definition);
266     }
267     $this->setValue($values);
268   }
269
270   /**
271    * {@inheritdoc}
272    */
273   public function getConstraints() {
274     $constraints = parent::getConstraints();
275     // Check that the number of values doesn't exceed the field cardinality. For
276     // form submitted values, this can only happen with 'multiple value'
277     // widgets.
278     $cardinality = $this->getFieldDefinition()->getFieldStorageDefinition()->getCardinality();
279     if ($cardinality != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
280       $constraints[] = $this->getTypedDataManager()
281         ->getValidationConstraintManager()
282         ->create('Count', [
283           'max' => $cardinality,
284           'maxMessage' => t('%name: this field cannot hold more than @count values.', ['%name' => $this->getFieldDefinition()->getLabel(), '@count' => $cardinality]),
285         ]);
286     }
287
288     return $constraints;
289   }
290
291   /**
292    * {@inheritdoc}
293    */
294   public function defaultValuesForm(array &$form, FormStateInterface $form_state) {
295     if (empty($this->getFieldDefinition()->getDefaultValueCallback())) {
296       if ($widget = $this->defaultValueWidget($form_state)) {
297         // Place the input in a separate place in the submitted values tree.
298         $element = ['#parents' => ['default_value_input']];
299         $element += $widget->form($this, $element, $form_state);
300
301         return $element;
302       }
303       else {
304         return ['#markup' => $this->t('No widget available for: %type.', ['%type' => $this->getFieldDefinition()->getType()])];
305       }
306     }
307   }
308
309   /**
310    * {@inheritdoc}
311    */
312   public function defaultValuesFormValidate(array $element, array &$form, FormStateInterface $form_state) {
313     // Extract the submitted value, and validate it.
314     if ($widget = $this->defaultValueWidget($form_state)) {
315       $widget->extractFormValues($this, $element, $form_state);
316       // Force a non-required field definition.
317       // @see self::defaultValueWidget().
318       $this->getFieldDefinition()->setRequired(FALSE);
319       $violations = $this->validate();
320
321       // Assign reported errors to the correct form element.
322       if (count($violations)) {
323         $widget->flagErrors($this, $violations, $element, $form_state);
324       }
325     }
326   }
327
328   /**
329    * {@inheritdoc}
330    */
331   public function defaultValuesFormSubmit(array $element, array &$form, FormStateInterface $form_state) {
332     // Extract the submitted value, and return it as an array.
333     if ($widget = $this->defaultValueWidget($form_state)) {
334       $widget->extractFormValues($this, $element, $form_state);
335       return $this->getValue();
336     }
337   }
338
339   /**
340    * {@inheritdoc}
341    */
342   public static function processDefaultValue($default_value, FieldableEntityInterface $entity, FieldDefinitionInterface $definition) {
343     return $default_value;
344   }
345
346   /**
347    * Returns the widget object used in default value form.
348    *
349    * @param \Drupal\Core\Form\FormStateInterface $form_state
350    *   The form state of the (entire) configuration form.
351    *
352    * @return \Drupal\Core\Field\WidgetInterface|null
353    *   A Widget object or NULL if no widget is available.
354    */
355   protected function defaultValueWidget(FormStateInterface $form_state) {
356     if (!$form_state->has('default_value_widget')) {
357       $entity = $this->getEntity();
358
359       // Force a non-required widget.
360       $definition = $this->getFieldDefinition();
361       $definition->setRequired(FALSE);
362       $definition->setDescription('');
363
364       // Use the widget currently configured for the 'default' form mode, or
365       // fallback to the default widget for the field type.
366       $entity_form_display = entity_get_form_display($entity->getEntityTypeId(), $entity->bundle(), 'default');
367       $widget = $entity_form_display->getRenderer($this->getFieldDefinition()->getName());
368       if (!$widget) {
369         $widget = \Drupal::service('plugin.manager.field.widget')->getInstance(['field_definition' => $this->getFieldDefinition()]);
370       }
371
372       $form_state->set('default_value_widget', $widget);
373     }
374
375     return $form_state->get('default_value_widget');
376   }
377
378   /**
379    * {@inheritdoc}
380    */
381   public function equals(FieldItemListInterface $list_to_compare) {
382     $count1 = count($this);
383     $count2 = count($list_to_compare);
384     if ($count1 === 0 && $count2 === 0) {
385       // Both are empty we can safely assume that it did not change.
386       return TRUE;
387     }
388     if ($count1 !== $count2) {
389       // One of them is empty but not the other one so the value changed.
390       return FALSE;
391     }
392     $value1 = $this->getValue();
393     $value2 = $list_to_compare->getValue();
394     if ($value1 === $value2) {
395       return TRUE;
396     }
397     // If the values are not equal ensure a consistent order of field item
398     // properties and remove properties which will not be saved.
399     $property_definitions = $this->getFieldDefinition()->getFieldStorageDefinition()->getPropertyDefinitions();
400     $non_computed_properties = array_filter($property_definitions, function (DataDefinitionInterface $property) {
401       return !$property->isComputed();
402     });
403     $callback = function (&$value) use ($non_computed_properties) {
404       if (is_array($value)) {
405         $value = array_intersect_key($value, $non_computed_properties);
406         ksort($value);
407       }
408     };
409     array_walk($value1, $callback);
410     array_walk($value2, $callback);
411
412     return $value1 == $value2;
413   }
414
415 }