1<?php 2 3namespace Drupal\Core\Field; 4 5use Drupal\Core\Access\AccessResult; 6use Drupal\Core\Entity\FieldableEntityInterface; 7use Drupal\Core\Form\FormStateInterface; 8use Drupal\Core\Language\LanguageInterface; 9use Drupal\Core\Session\AccountInterface; 10use Drupal\Core\TypedData\DataDefinitionInterface; 11use 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 */ 21class 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::entityTypeManager()->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::entityTypeManager()->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 return []; 326 } 327 328 /** 329 * {@inheritdoc} 330 */ 331 public static function processDefaultValue($default_value, FieldableEntityInterface $entity, FieldDefinitionInterface $definition) { 332 return $default_value; 333 } 334 335 /** 336 * Returns the widget object used in default value form. 337 * 338 * @param \Drupal\Core\Form\FormStateInterface $form_state 339 * The form state of the (entire) configuration form. 340 * 341 * @return \Drupal\Core\Field\WidgetInterface|null 342 * A Widget object or NULL if no widget is available. 343 */ 344 protected function defaultValueWidget(FormStateInterface $form_state) { 345 if (!$form_state->has('default_value_widget')) { 346 $entity = $this->getEntity(); 347 348 // Force a non-required widget. 349 $definition = $this->getFieldDefinition(); 350 $definition->setRequired(FALSE); 351 $definition->setDescription(''); 352 353 // Use the widget currently configured for the 'default' form mode, or 354 // fallback to the default widget for the field type. 355 $entity_form_display = \Drupal::service('entity_display.repository') 356 ->getFormDisplay($entity->getEntityTypeId(), $entity->bundle()); 357 $widget = $entity_form_display->getRenderer($this->getFieldDefinition()->getName()); 358 if (!$widget) { 359 $widget = \Drupal::service('plugin.manager.field.widget')->getInstance(['field_definition' => $this->getFieldDefinition()]); 360 } 361 362 $form_state->set('default_value_widget', $widget); 363 } 364 365 return $form_state->get('default_value_widget'); 366 } 367 368 /** 369 * {@inheritdoc} 370 */ 371 public function equals(FieldItemListInterface $list_to_compare) { 372 $count1 = count($this); 373 $count2 = count($list_to_compare); 374 if ($count1 === 0 && $count2 === 0) { 375 // Both are empty we can safely assume that it did not change. 376 return TRUE; 377 } 378 if ($count1 !== $count2) { 379 // One of them is empty but not the other one so the value changed. 380 return FALSE; 381 } 382 $value1 = $this->getValue(); 383 $value2 = $list_to_compare->getValue(); 384 if ($value1 === $value2) { 385 return TRUE; 386 } 387 // If the values are not equal ensure a consistent order of field item 388 // properties and remove properties which will not be saved. 389 $property_definitions = $this->getFieldDefinition()->getFieldStorageDefinition()->getPropertyDefinitions(); 390 $non_computed_properties = array_filter($property_definitions, function (DataDefinitionInterface $property) { 391 return !$property->isComputed(); 392 }); 393 $callback = function (&$value) use ($non_computed_properties) { 394 if (is_array($value)) { 395 $value = array_intersect_key($value, $non_computed_properties); 396 397 // Also filter out properties with a NULL value as they might exist in 398 // one field item and not in the other, depending on how the values are 399 // set. Do not filter out empty strings or other false-y values as e.g. 400 // a NULL or FALSE in a boolean field is not the same. 401 $value = array_filter($value, function ($property) { 402 return $property !== NULL; 403 }); 404 405 ksort($value); 406 } 407 }; 408 array_walk($value1, $callback); 409 array_walk($value2, $callback); 410 411 return $value1 == $value2; 412 } 413 414 /** 415 * {@inheritdoc} 416 */ 417 public function hasAffectingChanges(FieldItemListInterface $original_items, $langcode) { 418 return !$this->equals($original_items); 419 } 420 421} 422