FieldItemList.php 9.96 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\Session\AccountInterface;
11
use Drupal\Core\TypedData\DataDefinitionInterface;
12
use Drupal\Core\TypedData\TypedDataInterface;
13
use Drupal\Core\TypedData\Plugin\DataType\ItemList;
14
use Drupal\Core\Language\Language;
15 16

/**
17
 * Represents an entity field; that is, a list of field item objects.
18
 *
19 20 21 22
 * 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.
23
 */
24
class FieldItemList extends ItemList implements FieldItemListInterface {
25 26 27 28 29 30 31 32 33

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

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

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

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

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

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

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

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

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

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

106 107 108 109 110 111 112 113 114 115 116 117 118 119
  /**
   * {@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;
    }
  }

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

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

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

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

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

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

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

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

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

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

207 208 209
    // 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))) {
210
      // Create one field item and apply defaults.
211
      $this->first()->applyDefaultValue(FALSE);
212
    }
213 214 215
    else {
      $this->setValue($value, $notify);
    }
216 217 218
    return $this;
  }

219 220 221 222 223 224 225
  /**
   * Returns the default value for the field.
   *
   * @return array
   *   The default value for the field.
   */
  protected function getDefaultValue() {
226
    return $this->getFieldDefinition()->getDefaultValue($this->getEntity());
227
  }
228 229 230 231 232 233

  /**
   * {@inheritdoc}
   */
  public function preSave() {
    // Filter out empty items.
234
    $this->filterEmptyItems();
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 272 273 274 275 276 277 278 279 280

    $this->delegateMethod('presave');
  }

  /**
   * {@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}();
      }
    }
  }

281 282 283 284 285 286 287 288
  /**
   * {@inheritdoc}
   */
  public function view($display_options = array()) {
    $view_builder = \Drupal::entityManager()->getViewBuilder($this->getEntity()->getEntityTypeId());
    return $view_builder->viewField($this, $display_options);
  }

289 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 325 326 327 328 329 330 331 332 333
  /**
   * {@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.
    $cardinality = $this->getFieldDefinition()->getCardinality();
    if ($cardinality != FieldDefinitionInterface::CARDINALITY_UNLIMITED) {
      $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();

334
    // Assign reported errors to the correct form element.
335
    if (count($violations)) {
336
      $widget->flagErrors($this, $violations, $element, $form_state);
337 338 339 340 341 342 343 344 345 346 347 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 379 380
    }
  }

  /**
   * {@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();
  }

  /**
   * 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'];
  }

381
}