ContentEntityBase.php 28 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Contains \Drupal\Core\Entity\ContentEntityBase.
6 7 8 9
 */

namespace Drupal\Core\Entity;

10
use Drupal\Component\Utility\String;
11
use Drupal\Core\Entity\Plugin\DataType\EntityReference;
12
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
13
use Drupal\Core\Language\Language;
14
use Drupal\Core\Session\AccountInterface;
15 16 17 18 19
use Drupal\Core\TypedData\TypedDataInterface;

/**
 * Implements Entity Field API specific enhancements to the Entity class.
 */
20
abstract class ContentEntityBase extends Entity implements \IteratorAggregate, ContentEntityInterface {
21

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
  /**
   * Status code indentifying a removed translation.
   */
  const TRANSLATION_REMOVED = 0;

  /**
   * Status code indentifying an existing translation.
   */
  const TRANSLATION_EXISTING = 1;

  /**
   * Status code indentifying a newly created translation.
   */
  const TRANSLATION_CREATED = 2;

37 38 39 40 41 42 43
  /**
   * Local cache holding the value of the bundle field.
   *
   * @var string
   */
  protected $bundle;

44 45 46 47
  /**
   * The plain data values of the contained fields.
   *
   * This always holds the original, unchanged values of the entity. The values
48 49
   * are keyed by language code, whereas Language::LANGCODE_DEFAULT is used for
   * values in default language.
50 51 52
   *
   * @todo: Add methods for getting original fields and for determining
   * changes.
53
   * @todo: Provide a better way for defining default values.
54 55 56
   *
   * @var array
   */
57
  protected $values = array();
58 59

  /**
60
   * The array of fields, each being an instance of FieldItemListInterface.
61 62 63 64 65
   *
   * @var array
   */
  protected $fields = array();

66 67
  /**
   * Local cache for field definitions.
68
   *
69
   * @see ContentEntityBase::getFieldDefinitions()
70 71 72 73 74
   *
   * @var array
   */
  protected $fieldDefinitions;

75 76 77 78 79 80 81
  /**
   * Local cache for the available language objects.
   *
   * @var array
   */
  protected $languages;

82 83 84 85 86 87 88 89 90 91
  /**
   * Language code identifying the entity active language.
   *
   * This is the language field accessors will use to determine which field
   * values manipulate.
   *
   * @var string
   */
  protected $activeLangcode = Language::LANGCODE_DEFAULT;

92 93 94 95 96 97 98
  /**
   * Local cache for the default language code.
   *
   * @var string
   */
  protected $defaultLangcode;

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
  /**
   * An array of entity translation metadata.
   *
   * An associative array keyed by translation language code. Every value is an
   * array containg the translation status and the translation object, if it has
   * already been instantiated.
   *
   * @var array
   */
  protected $translations = array();

  /**
   * A flag indicating whether a translation object is being initialized.
   *
   * @var bool
   */
  protected $translationInitialize = FALSE;

117 118 119 120 121 122 123 124 125 126 127 128 129 130
  /**
   * Boolean indicating whether a new revision should be created on save.
   *
   * @var bool
   */
  protected $newRevision = FALSE;

  /**
   * Indicates whether this is the default revision.
   *
   * @var bool
   */
  protected $isDefaultRevision = TRUE;

131 132 133
  /**
   * Overrides Entity::__construct().
   */
134
  public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = array()) {
135 136
    $this->entityTypeId = $entity_type;
    $this->bundle = $bundle ? $bundle : $this->entityTypeId;
137
    $this->languages = $this->languageManager()->getLanguages(Language::STATE_ALL);
138

139
    foreach ($values as $key => $value) {
140 141
      // If the key matches an existing property set the value to the property
      // to ensure non converted properties have the correct value.
142 143
      if (property_exists($this, $key) && isset($value[Language::LANGCODE_DEFAULT])) {
        $this->$key = $value[Language::LANGCODE_DEFAULT];
144
      }
145 146
      $this->values[$key] = $value;
    }
147

148 149
    // Initialize translations. Ensure we have at least an entry for the default
    // language.
150 151
    $data = array('status' => static::TRANSLATION_EXISTING);
    $this->translations[Language::LANGCODE_DEFAULT] = $data;
152
    $this->setDefaultLangcode();
153 154
    if ($translations) {
      foreach ($translations as $langcode) {
155
        if ($langcode != $this->defaultLangcode && $langcode != Language::LANGCODE_DEFAULT) {
156 157 158 159
          $this->translations[$langcode] = $data;
        }
      }
    }
160 161
  }

162 163 164 165 166 167 168 169 170
  /**
   * Returns the typed data manager.
   *
   * @return \Drupal\Core\TypedData\TypedDataManager
   */
  protected function typedDataManager() {
    return \Drupal::typedDataManager();
  }

171 172 173 174 175 176 177 178 179 180 181
  /**
   * {@inheritdoc}
   */
  public function setNewRevision($value = TRUE) {
    $this->newRevision = $value;
  }

  /**
   * {@inheritdoc}
   */
  public function isNewRevision() {
182
    return $this->newRevision || ($this->getEntityType()->hasKey('revision') && !$this->getRevisionId());
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
  }

  /**
   * {@inheritdoc}
   */
  public function isDefaultRevision($new_value = NULL) {
    $return = $this->isDefaultRevision;
    if (isset($new_value)) {
      $this->isDefaultRevision = (bool) $new_value;
    }
    return $return;
  }

  /**
   * {@inheritdoc}
   */
  public function getRevisionId() {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function isTranslatable() {
207
    $bundles = $this->entityManager()->getBundleInfo($this->entityTypeId);
208 209 210 211 212 213 214 215 216 217 218 219
    return !empty($bundles[$this->bundle()]['translatable']);
  }

  /**
   * {@inheritdoc}
   */
  public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) {
  }

  /**
   * {@inheritdoc}
   */
220 221
  public function getDataDefinition() {
    $definition = EntityDataDefinition::create($this->getEntityTypeId());
222
    $definition->setBundles(array($this->bundle()));
223
    return $definition;
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
  }

  /**
   * {@inheritdoc}
   */
  public function getValue() {
    // @todo: This does not make much sense, so remove once TypedDataInterface
    // is removed. See https://drupal.org/node/2002138.
    return $this->getPropertyValues();
  }

  /**
   * {@inheritdoc}
   */
  public function setValue($value, $notify = TRUE) {
    // @todo: This does not make much sense, so remove once TypedDataInterface
    // is removed. See https://drupal.org/node/2002138.
    $this->setPropertyValues($value);
  }

  /**
   * {@inheritdoc}
   */
  public function getString() {
248
    return (string) $this->label();
249 250 251 252 253 254
  }

  /**
   * {@inheritdoc}
   */
  public function validate() {
255
    return $this->typedDataManager()->getValidator()->validate($this);
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 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
  }

  /**
   * {@inheritdoc}
   */
  public function applyDefaultValue($notify = TRUE) {
    foreach ($this->getProperties() as $property) {
      $property->applyDefaultValue(FALSE);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getConstraints() {
    return array();
  }

  /**
   * {@inheritdoc}
   */
  public function getName() {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getRoot() {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getPropertyPath() {
    return '';
  }

  /**
   * {@inheritdoc}
   */
  public function getParent() {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setContext($name = NULL, TypedDataInterface $parent = NULL) {
    // As entities are always the root of the tree of typed data, we do not need
    // to set any parent or name.
  }

310 311 312 313 314 315 316 317 318
  /**
   * Clear entity translation object cache to remove stale references.
   */
  protected function clearTranslationCache() {
    foreach ($this->translations as &$translation) {
      unset($translation['entity']);
    }
  }

319 320 321 322 323 324 325 326 327 328 329 330 331
  /**
   * {@inheritdoc}
   */
  public function __sleep() {
    // Get the values of instantiated field objects, only serialize the values.
    foreach ($this->fields as $name => $fields) {
      foreach ($fields as $langcode => $field) {
        $this->values[$name][$langcode] = $field->getValue();
      }
    }
    $this->fields = array();
    $this->fieldDefinitions = NULL;
    $this->clearTranslationCache();
332

333
    return parent::__sleep();
334 335
  }

336
  /**
337
   * {@inheritdoc}
338 339
   */
  public function id() {
340 341 342 343
    return $this->id->value;
  }

  /**
344
   * {@inheritdoc}
345 346 347
   */
  public function bundle() {
    return $this->bundle;
348 349 350 351 352 353 354 355 356
  }

  /**
   * Overrides Entity::uuid().
   */
  public function uuid() {
    return $this->get('uuid')->value;
  }

357 358 359 360
  /**
   * {@inheritdoc}
   */
  public function hasField($field_name) {
361
    return (bool) $this->getFieldDefinition($field_name);
362 363
  }

364
  /**
365
   * {@inheritdoc}
366 367
   */
  public function get($property_name) {
368 369
    if (!isset($this->fields[$property_name][$this->activeLangcode])) {
      return $this->getTranslatedField($property_name, $this->activeLangcode);
370
    }
371
    return $this->fields[$property_name][$this->activeLangcode];
372 373 374 375 376
  }

  /**
   * Gets a translated field.
   *
377
   * @return \Drupal\Core\Field\FieldItemListInterface
378
   */
379
  protected function getTranslatedField($name, $langcode) {
380 381
    if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
      $message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
382
      throw new \InvalidArgumentException(String::format($message, array('@langcode' => $this->activeLangcode)));
383
    }
384 385
    // Populate $this->fields to speed-up further look-ups and to keep track of
    // fields objects, possibly holding changes to field values.
386
    if (!isset($this->fields[$name][$langcode])) {
387
      $definition = $this->getFieldDefinition($name);
388
      if (!$definition) {
389
        throw new \InvalidArgumentException('Field ' . String::checkPlain($name) . ' is unknown.');
390
      }
391 392
      // Non-translatable fields are always stored with
      // Language::LANGCODE_DEFAULT as key.
393

394
      $default = $langcode == Language::LANGCODE_DEFAULT;
395
      if (!$default && !$definition->isTranslatable()) {
396 397
        if (!isset($this->fields[$name][Language::LANGCODE_DEFAULT])) {
          $this->fields[$name][Language::LANGCODE_DEFAULT] = $this->getTranslatedField($name, Language::LANGCODE_DEFAULT);
398
        }
399
        $this->fields[$name][$langcode] = &$this->fields[$name][Language::LANGCODE_DEFAULT];
400 401
      }
      else {
402
        $value = NULL;
403 404 405
        if (isset($this->values[$name][$langcode])) {
          $value = $this->values[$name][$langcode];
        }
406
        $field = \Drupal::typedDataManager()->getPropertyInstance($this, $name, $value);
407 408 409 410 411
        if ($default) {
          // $this->defaultLangcode might not be set if we are initializing the
          // default language code cache, in which case there is no valid
          // langcode to assign.
          $field_langcode = isset($this->defaultLangcode) ? $this->defaultLangcode : Language::LANGCODE_NOT_SPECIFIED;
412
        }
413 414 415 416 417
        else {
          $field_langcode = $langcode;
        }
        $field->setLangcode($field_langcode);
        $this->fields[$name][$langcode] = $field;
418 419
      }
    }
420
    return $this->fields[$name][$langcode];
421 422 423
  }

  /**
424
   * {@inheritdoc}
425
   */
426 427 428 429
  public function set($name, $value, $notify = TRUE) {
    // If default language changes we need to react to that.
    $notify = $name == 'langcode';
    $this->get($name)->setValue($value, $notify);
430 431 432
  }

  /**
433
   * {@inheritdoc}
434 435 436
   */
  public function getProperties($include_computed = FALSE) {
    $properties = array();
437
    foreach ($this->getFieldDefinitions() as $name => $definition) {
438
      if ($include_computed || !$definition->isComputed()) {
439 440 441 442 443 444 445
        $properties[$name] = $this->get($name);
      }
    }
    return $properties;
  }

  /**
446
   * {@inheritdoc}
447 448
   */
  public function getIterator() {
449
    return new \ArrayIterator($this->getProperties());
450 451 452
  }

  /**
453
   * {@inheritdoc}
454
   */
455
  public function getFieldDefinition($name) {
456
    if (!isset($this->fieldDefinitions)) {
457
      $this->getFieldDefinitions();
458 459 460
    }
    if (isset($this->fieldDefinitions[$name])) {
      return $this->fieldDefinitions[$name];
461
    }
462 463
    else {
      return FALSE;
464 465 466 467
    }
  }

  /**
468
   * {@inheritdoc}
469
   */
470
  public function getFieldDefinitions() {
471
    if (!isset($this->fieldDefinitions)) {
472
      $this->fieldDefinitions = \Drupal::entityManager()->getFieldDefinitions($this->entityTypeId, $this->bundle());
473 474
    }
    return $this->fieldDefinitions;
475 476 477
  }

  /**
478
   * {@inheritdoc}
479 480 481 482 483 484 485 486 487 488
   */
  public function getPropertyValues() {
    $values = array();
    foreach ($this->getProperties() as $name => $property) {
      $values[$name] = $property->getValue();
    }
    return $values;
  }

  /**
489
   * {@inheritdoc}
490 491 492 493 494 495 496 497
   */
  public function setPropertyValues($values) {
    foreach ($values as $name => $value) {
      $this->get($name)->setValue($value);
    }
  }

  /**
498
   * {@inheritdoc}
499 500 501 502 503 504 505 506 507 508 509 510 511 512
   */
  public function isEmpty() {
    if (!$this->isNew()) {
      return FALSE;
    }
    foreach ($this->getProperties() as $property) {
      if ($property->getValue() !== NULL) {
        return FALSE;
      }
    }
    return TRUE;
  }

  /**
513 514
   * {@inheritdoc}
   */
515
  public function access($operation, AccountInterface $account = NULL) {
516
    if ($operation == 'create') {
517
      return $this->entityManager()
518
        ->getAccessController($this->entityTypeId)
519
        ->createAccess($this->bundle(), $account);
520
    }
521
    return $this->entityManager()
522
      ->getAccessController($this->entityTypeId)
523
      ->access($this, $operation, $this->activeLangcode, $account);
524 525 526 527
  }

  /**
   * {@inheritdoc}
528 529
   */
  public function language() {
530
    $language = NULL;
531 532
    if ($this->activeLangcode != Language::LANGCODE_DEFAULT) {
      if (!isset($this->languages[$this->activeLangcode])) {
533
        $this->languages += $this->languageManager()->getLanguages(Language::STATE_ALL);
534
      }
535
      $language = $this->languages[$this->activeLangcode];
536 537
    }
    else {
538
      $language = $this->languages[$this->defaultLangcode];
539
    }
540
    return $language;
541 542 543
  }

  /**
544 545 546 547
   * Populates the local cache for the default language code.
   */
  protected function setDefaultLangcode() {
    // Get the language code if the property exists.
548
    if ($this->hasField('langcode') && ($item = $this->get('langcode')) && isset($item->language)) {
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
      $this->defaultLangcode = $item->language->id;
    }
    if (empty($this->defaultLangcode)) {
      // Make sure we return a proper language object.
      $this->defaultLangcode = Language::LANGCODE_NOT_SPECIFIED;
    }
    // This needs to be initialized manually as it is skipped when instantiating
    // the language field object to avoid infinite recursion.
    if (!empty($this->fields['langcode'])) {
      $this->fields['langcode'][Language::LANGCODE_DEFAULT]->setLangcode($this->defaultLangcode);
    }
  }

  /**
   * Updates language for already instantiated fields.
564 565 566 567
   *
   * @return \Drupal\Core\Language\Language
   *   A language object.
   */
568 569 570 571
  protected function updateFieldLangcodes($langcode) {
    foreach ($this->fields as $name => $items) {
      if (!empty($items[Language::LANGCODE_DEFAULT])) {
        $items[Language::LANGCODE_DEFAULT]->setLangcode($langcode);
572
      }
573
    }
574 575 576 577 578
  }

  /**
   * {@inheritdoc}
   */
579 580 581 582
  public function onChange($name) {
    if ($name == 'langcode') {
      $this->setDefaultLangcode();
      if (isset($this->translations[$this->defaultLangcode])) {
583
        $message = String::format('A translation already exists for the specified language (@langcode).', array('@langcode' => $this->defaultLangcode));
584 585 586
        throw new \InvalidArgumentException($message);
      }
      $this->updateFieldLangcodes($this->defaultLangcode);
587
    }
588 589 590
  }

  /**
591
   * {@inheritdoc}
592
   *
593
   * @return \Drupal\Core\Entity\ContentEntityInterface
594 595 596 597
   */
  public function getTranslation($langcode) {
    // Ensure we always use the default language code when dealing with the
    // original entity language.
598 599
    if ($langcode != Language::LANGCODE_DEFAULT && $langcode == $this->defaultLangcode) {
      $langcode = Language::LANGCODE_DEFAULT;
600
    }
601 602 603 604 605

    // Populate entity translation object cache so it will be available for all
    // translation objects.
    if ($langcode == $this->activeLangcode) {
      $this->translations[$langcode]['entity'] = $this;
606
    }
607 608 609 610 611 612 613 614 615 616

    // If we already have a translation object for the specified language we can
    // just return it.
    if (isset($this->translations[$langcode]['entity'])) {
      $translation = $this->translations[$langcode]['entity'];
    }
    else {
      if (isset($this->translations[$langcode])) {
        $translation = $this->initializeTranslation($langcode);
        $this->translations[$langcode]['entity'] = $translation;
617
      }
618 619 620
      else {
        // If we were given a valid language and there is no translation for it,
        // we return a new one.
621
        if (isset($this->languages[$langcode])) {
622 623 624
          // If the entity or the requested language  is not a configured
          // language, we fall back to the entity itself, since in this case it
          // cannot have translations.
625
          $translation = empty($this->languages[$this->defaultLangcode]->locked) && empty($this->languages[$langcode]->locked) ? $this->addTranslation($langcode) : $this;
626 627 628 629 630 631
        }
      }
    }

    if (empty($translation)) {
      $message = 'Invalid translation language (@langcode) specified.';
632
      throw new \InvalidArgumentException(String::format($message, array('@langcode' => $langcode)));
633
    }
634

635 636 637 638
    return $translation;
  }

  /**
639
   * {@inheritdoc}
640
   */
641
  public function getUntranslated() {
642
    return $this->getTranslation(Language::LANGCODE_DEFAULT);
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
  }

  /**
   * Instantiates a translation object for an existing translation.
   *
   * The translated entity will be a clone of the current entity with the
   * specified $langcode. All translations share the same field data structures
   * to ensure that all of them deal with fresh data.
   *
   * @param string $langcode
   *   The language code for the requested translation.
   *
   * @return \Drupal\Core\Entity\EntityInterface
   *   The translation object. The content properties of the translation object
   *   are stored as references to the main entity.
   */
  protected function initializeTranslation($langcode) {
    // If the requested translation is valid, clone it with the current language
    // as the active language. The $translationInitialize flag triggers a
    // shallow (non-recursive) clone.
    $this->translationInitialize = TRUE;
    $translation = clone $this;
    $this->translationInitialize = FALSE;

    $translation->activeLangcode = $langcode;

    // Ensure that changes to fields, values and translations are propagated
    // to all the translation objects.
    // @todo Consider converting these to ArrayObject.
    $translation->values = &$this->values;
    $translation->fields = &$this->fields;
    $translation->translations = &$this->translations;
675
    $translation->enforceIsNew = &$this->enforceIsNew;
676 677 678 679 680 681 682 683 684
    $translation->translationInitialize = FALSE;

    return $translation;
  }

  /**
   * {@inheritdoc}
   */
  public function hasTranslation($langcode) {
685
    if ($langcode == $this->defaultLangcode) {
686 687
      $langcode = Language::LANGCODE_DEFAULT;
    }
688 689 690 691 692 693 694
    return !empty($this->translations[$langcode]['status']);
  }

  /**
   * {@inheritdoc}
   */
  public function addTranslation($langcode, array $values = array()) {
695
    if (!isset($this->languages[$langcode]) || $this->hasTranslation($langcode)) {
696
      $message = 'Invalid translation language (@langcode) specified.';
697
      throw new \InvalidArgumentException(String::format($message, array('@langcode' => $langcode)));
698 699 700 701
    }

    // Instantiate a new empty entity so default values will be populated in the
    // specified language.
702 703
    $entity_type = $this->getEntityType();
    $default_values = array($entity_type->getKey('bundle') => $this->bundle, 'langcode' => $langcode);
704
    $entity = $this->entityManager()
705
      ->getStorageController($this->getEntityTypeId())
706 707 708 709 710
      ->create($default_values);

    foreach ($entity as $name => $field) {
      if (!isset($values[$name]) && !$field->isEmpty()) {
        $values[$name] = $field->value;
711
      }
712 713 714 715
    }

    $this->translations[$langcode]['status'] = static::TRANSLATION_CREATED;
    $translation = $this->getTranslation($langcode);
716
    $definitions = $translation->getFieldDefinitions();
717 718

    foreach ($values as $name => $value) {
719
      if (isset($definitions[$name]) && $definitions[$name]->isTranslatable()) {
720 721 722 723 724 725 726 727 728 729 730
        $translation->$name = $value;
      }
    }

    return $translation;
  }

  /**
   * {@inheritdoc}
   */
  public function removeTranslation($langcode) {
731
    if (isset($this->translations[$langcode]) && $langcode != Language::LANGCODE_DEFAULT && $langcode != $this->defaultLangcode) {
732
      foreach ($this->getFieldDefinitions() as $name => $definition) {
733
        if ($definition->isTranslatable()) {
734 735
          unset($this->values[$name][$langcode]);
          unset($this->fields[$name][$langcode]);
736
        }
737
      }
738 739 740 741
      $this->translations[$langcode]['status'] = static::TRANSLATION_REMOVED;
    }
    else {
      $message = 'The specified translation (@langcode) cannot be removed.';
742
      throw new \InvalidArgumentException(String::format($message, array('@langcode' => $langcode)));
743 744 745 746 747 748 749
    }
  }

  /**
   * {@inheritdoc}
   */
  public function initTranslation($langcode) {
750
    if ($langcode != Language::LANGCODE_DEFAULT && $langcode != $this->defaultLangcode) {
751
      $this->translations[$langcode]['status'] = static::TRANSLATION_EXISTING;
752
    }
753 754 755 756 757 758 759
  }

  /**
   * {@inheritdoc}
   */
  public function getTranslationLanguages($include_default = TRUE) {
    $translations = array_filter($this->translations, function($translation) { return $translation['status']; });
760
    unset($translations[Language::LANGCODE_DEFAULT]);
761 762

    if ($include_default) {
763
      $translations[$this->defaultLangcode] = TRUE;
764
    }
765

766
    // Now load language objects based upon translation langcodes.
767
    return array_intersect_key($this->languages, $translations);
768 769 770
  }

  /**
771
   * Overrides Entity::translations().
772
   *
773
   * @todo: Remove once Entity::translations() gets removed.
774
   */
775 776
  public function translations() {
    return $this->getTranslationLanguages(FALSE);
777 778 779 780 781 782
  }

  /**
   * Updates the original values with the interim changes.
   */
  public function updateOriginalValues() {
783 784 785
    if (!$this->fields) {
      return;
    }
786
    foreach ($this->getFieldDefinitions() as $name => $definition) {
787
      if (!$definition->isComputed() && !empty($this->fields[$name])) {
788 789 790
        foreach ($this->fields[$name] as $langcode => $item) {
          $item->filterEmptyItems();
          $this->values[$name][$langcode] = $item->getValue();
791
        }
792 793 794 795 796
      }
    }
  }

  /**
797
   * Implements the magic method for getting object properties.
798
   *
799
   * @todo: A lot of code still uses non-fields (e.g. $entity->content in render
800
   *   controllers) by reference. Clean that up.
801 802
   */
  public function &__get($name) {
803 804
    // If this is an entity field, handle it accordingly. We first check whether
    // a field object has been already created. If not, we create one.
805 806
    if (isset($this->fields[$name][$this->activeLangcode])) {
      return $this->fields[$name][$this->activeLangcode];
807
    }
808
    // Inline getFieldDefinition() to speed up things.
809
    if (!isset($this->fieldDefinitions)) {
810
      $this->getFieldDefinitions();
811 812
    }
    if (isset($this->fieldDefinitions[$name])) {
813
      $return = $this->getTranslatedField($name, $this->activeLangcode);
814 815
      return $return;
    }
816 817
    // Else directly read/write plain values. That way, non-field entity
    // properties can always be accessed directly.
818 819
    if (!isset($this->values[$name])) {
      $this->values[$name] = NULL;
820
    }
821
    return $this->values[$name];
822 823 824
  }

  /**
825 826 827
   * Implements the magic method for setting object properties.
   *
   * Uses default language always.
828 829 830
   */
  public function __set($name, $value) {
    // Support setting values via property objects.
831
    if ($value instanceof TypedDataInterface && !$value instanceof EntityInterface) {
832 833
      $value = $value->getValue();
    }
834 835
    // If this is an entity field, handle it accordingly. We first check whether
    // a field object has been already created. If not, we create one.
836 837
    if (isset($this->fields[$name][$this->activeLangcode])) {
      $this->fields[$name][$this->activeLangcode]->setValue($value);
838
    }
839
    elseif ($this->hasField($name)) {
840
      $this->getTranslatedField($name, $this->activeLangcode)->setValue($value);
841
    }
842 843 844 845 846
    // The translations array is unset when cloning the entity object, we just
    // need to restore it.
    elseif ($name == 'translations') {
      $this->translations = $value;
    }
847 848
    // Else directly read/write plain values. That way, fields not yet converted
    // to the entity field API can always be directly accessed.
849
    else {
850
      $this->values[$name] = $value;
851 852 853 854
    }
  }

  /**
855
   * Implements the magic method for isset().
856 857
   */
  public function __isset($name) {
858
    if ($this->hasField($name)) {
859
      return $this->get($name)->getValue() !== NULL;
860
    }
861 862
    else {
      return isset($this->values[$name]);
863 864 865 866
    }
  }

  /**
867
   * Implements the magic method for unset.
868 869
   */
  public function __unset($name) {
870
    if ($this->hasField($name)) {
871
      $this->get($name)->setValue(NULL);
872
    }
873 874
    else {
      unset($this->values[$name]);
875 876 877 878 879 880 881
    }
  }

  /**
   * Overrides Entity::createDuplicate().
   */
  public function createDuplicate() {
882 883
    if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
      $message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
884
      throw new \InvalidArgumentException(String::format($message, array('@langcode' => $this->activeLangcode)));
885 886
    }

887
    $duplicate = clone $this;
888 889
    $entity_type = $this->getEntityType();
    $duplicate->{$entity_type->getKey('id')}->value = NULL;
890 891

    // Check if the entity type supports UUIDs and generate a new one if so.
892
    if ($entity_type->hasKey('uuid')) {
893
      $duplicate->{$entity_type->getKey('uuid')}->value = $this->uuidGenerator()->generate();
894
    }
895 896

    // Check whether the entity type supports revisions and initialize it if so.
897 898
    if ($entity_type->hasKey('revision')) {
      $duplicate->{$entity_type->getKey('revision')}->value = NULL;
899 900
    }

901 902 903 904
    return $duplicate;
  }

  /**
905
   * Magic method: Implements a deep clone.
906 907
   */
  public function __clone() {
908 909 910
    // Avoid deep-cloning when we are initializing a translation object, since
    // it will represent the same entity, only with a different active language.
    if (!$this->translationInitialize) {
911
      $definitions = $this->getFieldDefinitions();
912 913 914 915 916 917
      foreach ($this->fields as $name => $values) {
        $this->fields[$name] = array();
        // Untranslatable fields may have multiple references for the same field
        // object keyed by language. To avoid creating different field objects
        // we retain just the original value, as references will be recreated
        // later as needed.
918
        if (!$definitions[$name]->isTranslatable() && count($values) > 1) {
919 920 921 922
          $values = array_intersect_key($values, array(Language::LANGCODE_DEFAULT => TRUE));
        }
        foreach ($values as $langcode => $items) {
          $this->fields[$name][$langcode] = clone $items;
923 924
          $this->fields[$name][$langcode]->setContext($name, $this);
        }
925
      }
926

927 928
      // Ensure the translations array is actually cloned by overwriting the
      // original reference with one pointing to a copy of the array.
929
      $this->clearTranslationCache();
930
      $translations = $this->translations;
931
      $this->translations = &$translations;
932 933
    }
  }
934 935

  /**
936
   * {@inheritdoc}
937
   */
938
  public function label() {
939
    $label = NULL;
940
    $entity_type = $this->getEntityType();
941 942
    if (($label_callback = $entity_type->getLabelCallback()) && is_callable($label_callback)) {
      $label = call_user_func($label_callback, $this);
943
    }
944
    elseif (($label_key = $entity_type->getKey('label')) && isset($this->{$label_key})) {
945
      $label = $this->{$label_key}->value;
946 947 948
    }
    return $label;
  }
949

950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970
  /**
   * {@inheritdoc}
   */
  public function referencedEntities() {
    $referenced_entities = array();

    // Gather a list of referenced entities.
    foreach ($this->getProperties() as $field_items) {
      foreach ($field_items as $field_item) {
        // Loop over all properties of a field item.
        foreach ($field_item->getProperties(TRUE) as $property) {
          if ($property instanceof EntityReference && $entity = $property->getTarget()) {
            $referenced_entities[] = $entity;
          }
        }
      }
    }

    return $referenced_entities;
  }

971 972 973 974 975 976 977
  /**
   * {@inheritdoc}
   */
  public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
    return array();
  }

978
}