FieldItemList.php 10 KB
Newer Older
1
2
3
4
<?php

/**
 * @file
5
 * Contains \Drupal\Core\Field\FieldItemList.
6
7
 */

8
namespace Drupal\Core\Field;
9

10
use Drupal\Core\Language\LanguageInterface;
11
use Drupal\Core\Entity\ContentEntityInterface;
12
use Drupal\Core\Session\AccountInterface;
13
use Drupal\Core\TypedData\DataDefinitionInterface;
14
use Drupal\Core\TypedData\Plugin\DataType\ItemList;
15
use Drupal\Core\TypedData\TypedDataInterface;
16
17

/**
18
 * Represents an entity field; that is, a list of field item objects.
19
 *
20
21
22
23
 * An entity field is a list of field items, each containing a set of
 * properties. Note that even single-valued entity fields are represented as
 * list of field items, however for easy access to the contained item the entity
 * field delegates __get() and __set() calls directly to the first item.
24
 */
25
class FieldItemList extends ItemList implements FieldItemListInterface {
26
27
28
29
30
31
32
33
34

  /**
   * Numerically indexed array of field items, implementing the
   * FieldItemInterface.
   *
   * @var array
   */
  protected $list = array();

35
36
37
38
39
  /**
   * The langcode of the field values held in the object.
   *
   * @var string
   */
40
  protected $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
41

42
  /**
43
   * {@inheritdoc}
44
   */
45
  public function __construct(DataDefinitionInterface $definition, $name = NULL, TypedDataInterface $parent = NULL) {
46
    parent::__construct($definition, $name, $parent);
47
48
    // Always initialize one empty item as most times a value for at least one
    // item will be present. That way prototypes created by
49
    // \Drupal\Core\TypedData\TypedDataManager::getPropertyInstance() will
50
    // already have this field item ready for use after cloning.
51
52
    $this->list[0] = $this->createItem(0);
  }
53

54
55
56
57
58
59
60
  /**
   * {@inheritdoc}
   */
  public function getEntity() {
    return $this->getParent();
  }

61
62
63
64
65
66
67
68
69
70
71
72
73
74
  /**
   * {@inheritdoc}
   */
  public function setLangcode($langcode) {
    $this->langcode = $langcode;
  }

  /**
   * {@inheritdoc}
   */
  public function getLangcode() {
    return $this->langcode;
  }

75
76
77
78
  /**
   * {@inheritdoc}
   */
  public function getFieldDefinition() {
79
    return $this->definition;
80
81
  }

82
83
84
85
86
87
88
89
90
91
92
93
94
95
  /**
   * {@inheritdoc}
   */
  public function getSettings() {
    return $this->definition->getSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function getSetting($setting_name) {
    return $this->definition->getSetting($setting_name);
  }

96
  /**
97
   * {@inheritdoc}
98
   */
99
  public function filterEmptyItems() {
100
    if (isset($this->list)) {
101
102
103
      $this->list = array_values(array_filter($this->list, function($item) {
        return !$item->isEmpty();
      }));
104
105
106
    }
  }

107
108
109
110
111
112
113
114
115
116
117
118
119
120
  /**
   * {@inheritdoc}
   * @todo Revisit the need when all entity types are converted to NG entities.
   */
  public function getValue($include_computed = FALSE) {
    if (isset($this->list)) {
      $values = array();
      foreach ($this->list as $delta => $item) {
        $values[$delta] = $item->getValue($include_computed);
      }
      return $values;
    }
  }

121
  /**
122
   * {@inheritdoc}
123
   */
124
  public function setValue($values, $notify = TRUE) {
125
126
127
128
    if (!isset($values) || $values === array()) {
      $this->list = $values;
    }
    else {
129
130
131
132
      // Support passing in only the value of the first item.
      if (!is_array($values) || !is_numeric(current(array_keys($values)))) {
        $values = array(0 => $values);
      }
133
134

      // Clear the values of properties for which no value has been passed.
135
136
      if (isset($this->list)) {
        $this->list = array_intersect_key($this->list, $values);
137
138
139
140
141
      }

      // Set the values.
      foreach ($values as $delta => $value) {
        if (!is_numeric($delta)) {
142
          throw new \InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.');
143
144
        }
        elseif (!isset($this->list[$delta])) {
145
          $this->list[$delta] = $this->createItem($delta, $value);
146
147
        }
        else {
148
          $this->list[$delta]->setValue($value, FALSE);
149
150
151
        }
      }
    }
152
153
154
155
    // Notify the parent of any changes.
    if ($notify && isset($this->parent)) {
      $this->parent->onChange($this->name);
    }
156
157
  }

158
  /**
159
   * {@inheritdoc}
160
161
   */
  public function __get($property_name) {
162
    return $this->first()->__get($property_name);
163
164
165
  }

  /**
166
   * {@inheritdoc}
167
168
   */
  public function __set($property_name, $value) {
169
    $this->first()->__set($property_name, $value);
170
171
172
  }

  /**
173
   * {@inheritdoc}
174
175
   */
  public function __isset($property_name) {
176
    return $this->first()->__isset($property_name);
177
178
179
  }

  /**
180
   * {@inheritdoc}
181
182
   */
  public function __unset($property_name) {
183
    return $this->first()->__unset($property_name);
184
185
186
  }

  /**
187
   * {@inheritdoc}
188
   */
189
  public function access($operation = 'view', AccountInterface $account = NULL) {
190
    $access_controller = \Drupal::entityManager()->getAccessController($this->getEntity()->getEntityTypeId());
191
    return $access_controller->fieldAccess($operation, $this->getFieldDefinition(), $account, $this);
192
193
194
  }

  /**
195
   * {@inheritdoc}
196
   */
197
  public function defaultAccess($operation = 'view', AccountInterface $account = NULL) {
198
199
    // Grant access per default.
    return TRUE;
200
  }
201

202
203
204
205
  /**
   * {@inheritdoc}
   */
  public function applyDefaultValue($notify = TRUE) {
206
    $value = $this->getFieldDefinition()->getDefaultValue($this->getEntity());
207

208
209
210
    // NULL or array() mean "no default value", but  0, '0' and the empty string
    // are valid default values.
    if (!isset($value) || (is_array($value) && empty($value))) {
211
      // Create one field item and apply defaults.
212
      $this->first()->applyDefaultValue(FALSE);
213
    }
214
215
216
    else {
      $this->setValue($value, $notify);
    }
217
218
219
    return $this;
  }

220
221
222
223
224
  /**
   * {@inheritdoc}
   */
  public function preSave() {
    // Filter out empty items.
225
    $this->filterEmptyItems();
226

227
    $this->delegateMethod('preSave');
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
  }

  /**
   * {@inheritdoc}
   */
  public function insert() {
    $this->delegateMethod('insert');
  }

  /**
   * {@inheritdoc}
   */
  public function update() {
    $this->delegateMethod('update');
  }

  /**
   * {@inheritdoc}
   */
  public function delete() {
    $this->delegateMethod('delete');
  }

  /**
   * {@inheritdoc}
   */
  public function deleteRevision() {
    $this->delegateMethod('deleteRevision');
  }

  /**
   * Calls a method on each FieldItem.
   *
   * @param string $method
   *   The name of the method.
   */
  protected function delegateMethod($method) {
    if (isset($this->list)) {
      foreach ($this->list as $item) {
        $item->{$method}();
      }
    }
  }

272
273
274
275
276
277
278
279
  /**
   * {@inheritdoc}
   */
  public function view($display_options = array()) {
    $view_builder = \Drupal::entityManager()->getViewBuilder($this->getEntity()->getEntityTypeId());
    return $view_builder->viewField($this, $display_options);
  }

280
281
282
283
284
285
286
287
  /**
   * {@inheritdoc}
   */
  public function getConstraints() {
    $constraints = parent::getConstraints();
    // Check that the number of values doesn't exceed the field cardinality. For
    // form submitted values, this can only happen with 'multiple value'
    // widgets.
288
289
    $cardinality = $this->getFieldDefinition()->getFieldStorageDefinition()->getCardinality();
    if ($cardinality != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
      $constraints[] = \Drupal::typedDataManager()
        ->getValidationConstraintManager()
        ->create('Count', array(
          'max' => $cardinality,
          'maxMessage' => t('%name: this field cannot hold more than @count values.', array('%name' => $this->getFieldDefinition()->getLabel(), '@count' => $cardinality)),
        ));
    }

    return $constraints;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultValuesForm(array &$form, array &$form_state) {
    if (empty($this->getFieldDefinition()->default_value_function)) {
      // Place the input in a separate place in the submitted values tree.
      $widget = $this->defaultValueWidget($form_state);

      $element = array('#parents' => array('default_value_input'));
      $element += $widget->form($this, $element, $form_state);

      return $element;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function defaultValuesFormValidate(array $element, array &$form, array &$form_state) {
    // Extract the submitted value, and validate it.
    $widget = $this->defaultValueWidget($form_state);
    $widget->extractFormValues($this, $element, $form_state);
    $violations = $this->validate();

325
    // Assign reported errors to the correct form element.
326
    if (count($violations)) {
327
      $widget->flagErrors($this, $violations, $element, $form_state);
328
329
330
331
332
333
334
335
336
337
338
339
340
    }
  }

  /**
   * {@inheritdoc}
   */
  public function defaultValuesFormSubmit(array $element, array &$form, array &$form_state) {
    // Extract the submitted value, and return it as an array.
    $widget = $this->defaultValueWidget($form_state);
    $widget->extractFormValues($this, $element, $form_state);
    return $this->getValue();
  }

341
342
343
344
345
346
347
  /**
   * {@inheritdoc}
   */
  public static function processDefaultValue($default_value, ContentEntityInterface $entity, FieldDefinitionInterface $definition) {
    return $default_value;
  }

348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
  /**
   * Returns the widget object used in default value form.
   *
   * @param array $form_state
   *   The form state of the (entire) configuration form.
   *
   * @return \Drupal\Core\Field\WidgetInterface
   *   A Widget object.
   */
  protected function defaultValueWidget(array &$form_state) {
    if (!isset($form_state['default_value_widget'])) {
      $entity = $this->getEntity();

      // Force a non-required widget.
      $this->getFieldDefinition()->required = FALSE;
      $this->getFieldDefinition()->description = '';

      // Use the widget currently configured for the 'default' form mode, or
      // fallback to the default widget for the field type.
      $entity_form_display = entity_get_form_display($entity->getEntityTypeId(), $entity->bundle(), 'default');
      $widget = $entity_form_display->getRenderer($this->getFieldDefinition()->getName());
      if (!$widget) {
        $widget = \Drupal::service('plugin.manager.field.widget')->getInstance(array('field_definition' => $this->getFieldDefinition()));
      }

      $form_state['default_value_widget'] = $widget;
    }

    return $form_state['default_value_widget'];
  }

379
}