EntityNG.php 24 KB
Newer Older
1
2
3
4
<?php

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

namespace Drupal\Core\Entity;

10
use Drupal\Core\Language\Language;
11
use Drupal\Core\Session\AccountInterface;
12
13
14
15
16
use Drupal\Core\TypedData\TypedDataInterface;

/**
 * Implements Entity Field API specific enhancements to the Entity class.
 *
17
18
19
20
 * Entity(..)NG classes are variants of the Entity(...) classes that implement
 * the next generation (NG) entity field API. They exist during conversion to
 * the new API only and changes will be merged into the respective original
 * classes once the conversion is complete.
21
22
23
24
25
26
 *
 * @todo: Once all entity types have been converted, merge improvements into the
 * Entity class and overhaul the EntityInterface.
 */
class EntityNG extends Entity {

27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
  /**
   * 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;

42
43
44
45
46
47
48
  /**
   * Local cache holding the value of the bundle field.
   *
   * @var string
   */
  protected $bundle;

49
50
51
52
  /**
   * The plain data values of the contained fields.
   *
   * This always holds the original, unchanged values of the entity. The values
53
54
   * are keyed by language code, whereas Language::LANGCODE_DEFAULT is used for
   * values in default language.
55
56
57
58
59
60
   *
   * @todo: Add methods for getting original fields and for determining
   * changes.
   *
   * @var array
   */
61
  protected $values = array();
62
63
64
65
66
67
68
69

  /**
   * The array of fields, each being an instance of FieldInterface.
   *
   * @var array
   */
  protected $fields = array();

70
71
72
73
74
75
76
  /**
   * Local cache for the entity language.
   *
   * @var \Drupal\Core\Language\Language
   */
  protected $language;

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

84
85
  /**
   * Local cache for field definitions.
86
   *
87
   * @see EntityNG::getPropertyDefinitions()
88
89
90
91
92
   *
   * @var array
   */
  protected $fieldDefinitions;

93
94
95
96
97
98
99
  /**
   * Local cache for URI placeholder substitution values.
   *
   * @var array
   */
  protected $uriPlaceholderReplacements;

100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
  /**
   * 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;

  /**
   * 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;

128
129
130
  /**
   * Overrides Entity::__construct().
   */
131
  public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = array()) {
132
133
    $this->entityType = $entity_type;
    $this->bundle = $bundle ? $bundle : $this->entityType;
134
135
    $this->languages = language_list(Language::STATE_ALL);

136
    foreach ($values as $key => $value) {
137
138
      // If the key matches an existing property set the value to the property
      // to ensure non converted properties have the correct value.
139
140
      if (property_exists($this, $key) && isset($value[Language::LANGCODE_DEFAULT])) {
        $this->$key = $value[Language::LANGCODE_DEFAULT];
141
      }
142
143
      $this->values[$key] = $value;
    }
144
145
146
147
148
149
150
151
152
153
154
155
156
157

    // Initialize translations. Ensure we have at least an entry for the entity
    // original language.
    $data = array('status' => static::TRANSLATION_EXISTING);
    $this->translations[Language::LANGCODE_DEFAULT] = $data;
    if ($translations) {
      $default_langcode = $this->language()->id;
      foreach ($translations as $langcode) {
        if ($langcode != $default_langcode && $langcode != Language::LANGCODE_DEFAULT) {
          $this->translations[$langcode] = $data;
        }
      }
    }

158
159
160
161
162
    $this->init();
  }

  /**
   * Initialize the object. Invoked upon construction and wake up.
163
   */
164
165
166
167
  protected function init() {
    // We unset all defined properties, so magic getters apply.
    unset($this->langcode);
  }
168

169
170
171
172
173
174
175
176
177
  /**
   * Clear entity translation object cache to remove stale references.
   */
  protected function clearTranslationCache() {
    foreach ($this->translations as &$translation) {
      unset($translation['entity']);
    }
  }

178
  /**
179
   * Magic __wakeup() implementation.
180
181
182
   */
  public function __wakeup() {
    $this->init();
183
184
185
186
    // @todo This should be done before serializing the entity, but we would
    //   need to provide the full list of data to be serialized. See the
    //   dedicated issue at https://drupal.org/node/2027795.
    $this->clearTranslationCache();
187
  }
188
189

  /**
190
   * Implements \Drupal\Core\Entity\EntityInterface::id().
191
192
   */
  public function id() {
193
194
195
196
    return $this->id->value;
  }

  /**
197
   * Implements \Drupal\Core\Entity\EntityInterface::bundle().
198
199
200
   */
  public function bundle() {
    return $this->bundle;
201
202
203
204
205
206
207
208
209
210
  }

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

  /**
211
   * Implements \Drupal\Core\TypedData\ComplexDataInterface::get().
212
213
   */
  public function get($property_name) {
214
215
    if (!isset($this->fields[$property_name][$this->activeLangcode])) {
      return $this->getTranslatedField($property_name, $this->activeLangcode);
216
    }
217
    return $this->fields[$property_name][$this->activeLangcode];
218
219
220
221
222
223
224
225
  }

  /**
   * Gets a translated field.
   *
   * @return \Drupal\Core\Entity\Field\FieldInterface
   */
  protected function getTranslatedField($property_name, $langcode) {
226
227
228
229
    if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
      $message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
      throw new \InvalidArgumentException(format_string($message, array('@langcode' => $this->activeLangcode)));
    }
230
231
    // Populate $this->fields to speed-up further look-ups and to keep track of
    // fields objects, possibly holding changes to field values.
232
233
234
    if (!isset($this->fields[$property_name][$langcode])) {
      $definition = $this->getPropertyDefinition($property_name);
      if (!$definition) {
235
        throw new \InvalidArgumentException('Field ' . check_plain($property_name) . ' is unknown.');
236
      }
237
238
239
240
      // Non-translatable fields are always stored with
      // Language::LANGCODE_DEFAULT as key.
      if ($langcode != Language::LANGCODE_DEFAULT && empty($definition['translatable'])) {
        $this->fields[$property_name][$langcode] = $this->getTranslatedField($property_name, Language::LANGCODE_DEFAULT);
241
242
      }
      else {
243
244
245
246
        $value = NULL;
        if (isset($this->values[$property_name][$langcode])) {
          $value = $this->values[$property_name][$langcode];
        }
247
248
249
        $field = \Drupal::typedData()->getPropertyInstance($this, $property_name, $value);
        $field->setLangcode($langcode);
        $this->fields[$property_name][$langcode] = $field;
250
251
252
253
254
255
      }
    }
    return $this->fields[$property_name][$langcode];
  }

  /**
256
   * Implements \Drupal\Core\TypedData\ComplexDataInterface::set().
257
   */
258
259
  public function set($property_name, $value, $notify = TRUE) {
    $this->get($property_name)->setValue($value, FALSE);
260
261
262
  }

  /**
263
   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getProperties().
264
265
266
267
268
269
270
271
272
273
274
275
   */
  public function getProperties($include_computed = FALSE) {
    $properties = array();
    foreach ($this->getPropertyDefinitions() as $name => $definition) {
      if ($include_computed || empty($definition['computed'])) {
        $properties[$name] = $this->get($name);
      }
    }
    return $properties;
  }

  /**
276
   * Implements \IteratorAggregate::getIterator().
277
278
   */
  public function getIterator() {
279
    return new \ArrayIterator($this->getProperties());
280
281
282
  }

  /**
283
   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinition().
284
285
   */
  public function getPropertyDefinition($name) {
286
287
288
289
290
    if (!isset($this->fieldDefinitions)) {
      $this->getPropertyDefinitions();
    }
    if (isset($this->fieldDefinitions[$name])) {
      return $this->fieldDefinitions[$name];
291
    }
292
293
    else {
      return FALSE;
294
295
296
297
    }
  }

  /**
298
   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
299
300
   */
  public function getPropertyDefinitions() {
301
    if (!isset($this->fieldDefinitions)) {
302
303
      $bundle = $this->bundle != $this->entityType ? $this->bundle : NULL;
      $this->fieldDefinitions = \Drupal::entityManager()->getFieldDefinitions($this->entityType, $bundle);
304
305
    }
    return $this->fieldDefinitions;
306
307
308
  }

  /**
309
   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyValues().
310
311
312
313
314
315
316
317
318
319
   */
  public function getPropertyValues() {
    $values = array();
    foreach ($this->getProperties() as $name => $property) {
      $values[$name] = $property->getValue();
    }
    return $values;
  }

  /**
320
   * Implements \Drupal\Core\TypedData\ComplexDataInterface::setPropertyValues().
321
322
323
324
325
326
327
328
   */
  public function setPropertyValues($values) {
    foreach ($values as $name => $value) {
      $this->get($name)->setValue($value);
    }
  }

  /**
329
   * Implements \Drupal\Core\TypedData\ComplexDataInterface::isEmpty().
330
331
332
333
334
335
336
337
338
339
340
341
342
343
   */
  public function isEmpty() {
    if (!$this->isNew()) {
      return FALSE;
    }
    foreach ($this->getProperties() as $property) {
      if ($property->getValue() !== NULL) {
        return FALSE;
      }
    }
    return TRUE;
  }

  /**
344
345
346
   * {@inheritdoc}
   */
  public function access($operation = 'view', AccountInterface $account = NULL) {
347
    if ($operation == 'create') {
348
349
350
      return \Drupal::entityManager()
        ->getAccessController($this->entityType)
        ->createAccess($this->bundle(), $account);
351
    }
352
353
354
    return \Drupal::entityManager()
      ->getAccessController($this->entityType)
      ->access($this, $operation, $this->activeLangcode, $account);
355
356
357
358
  }

  /**
   * {@inheritdoc}
359
360
   */
  public function language() {
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
    if ($this->activeLangcode != Language::LANGCODE_DEFAULT) {
      if (!isset($this->languages[$this->activeLangcode])) {
        $this->languages += language_list(Language::STATE_ALL);
      }
      return $this->languages[$this->activeLangcode];
    }
    else {
      return $this->language ?: $this->getDefaultLanguage();
    }
  }

  /**
   * Returns the entity original language.
   *
   * @return \Drupal\Core\Language\Language
   *   A language object.
   */
  protected function getDefaultLanguage() {
379
380
381
382
    // Keep a local cache of the language object and clear it if the langcode
    // gets changed, see EntityNG::onChange().
    if (!isset($this->language)) {
      // Get the language code if the property exists.
383
384
      if ($this->getPropertyDefinition('langcode') && ($item = $this->get('langcode')) && isset($item->language)) {
        $this->language = $item->language;
385
386
387
      }
      if (empty($this->language)) {
        // Make sure we return a proper language object.
388
        $this->language = new Language(array('id' => Language::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE));
389
      }
390
    }
391
392
393
394
395
396
397
398
399
400
401
    return $this->language;
  }

  /**
   * {@inheritdoc}
   */
  public function onChange($property_name) {
    if ($property_name == 'langcode') {
      // Avoid using unset as this unnecessarily triggers magic methods later
      // on.
      $this->language = NULL;
402
    }
403
404
405
  }

  /**
406
   * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslation().
407
   *
408
409
410
411
412
413
414
415
416
417
   * @return \Drupal\Core\Entity\EntityInterface
   */
  public function getTranslation($langcode) {
    // Ensure we always use the default language code when dealing with the
    // original entity language.
    if ($langcode != Language::LANGCODE_DEFAULT) {
      $default_language = $this->language ?: $this->getDefaultLanguage();
      if ($langcode == $default_language->id) {
        $langcode = Language::LANGCODE_DEFAULT;
      }
418
    }
419
420
421
422
423

    // Populate entity translation object cache so it will be available for all
    // translation objects.
    if ($langcode == $this->activeLangcode) {
      $this->translations[$langcode]['entity'] = $this;
424
    }
425
426
427
428
429
430
431
432
433
434

    // 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;
435
      }
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
      else {
        // If we were given a valid language and there is no translation for it,
        // we return a new one.
        $languages = language_list(Language::STATE_ALL);
        if (isset($languages[$langcode])) {
          // 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.
          $translation = empty($this->getDefaultLanguage()->locked) && empty($languages[$langcode]->locked) ? $this->addTranslation($langcode) : $this;
        }
      }
    }

    if (empty($translation)) {
      $message = 'Invalid translation language (@langcode) specified.';
      throw new \InvalidArgumentException(format_string($message, array('@langcode' => $langcode)));
452
    }
453

454
455
456
457
    return $translation;
  }

  /**
458
   * {@inheritdoc}
459
   */
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
  public function getUntranslated() {
    $langcode = Language::LANGCODE_DEFAULT;
    return isset($this->translations[$langcode]['entity']) ? $this->translations[$langcode]['entity'] : $this->getTranslation($langcode);
  }

  /**
   * 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;
    $translation->translationInitialize = FALSE;

    return $translation;
  }

  /**
   * {@inheritdoc}
   */
  public function hasTranslation($langcode) {
504
505
506
507
    $default_language = $this->language ?: $this->getDefaultLanguage();
    if ($langcode == $default_language->id) {
      $langcode = Language::LANGCODE_DEFAULT;
    }
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
    return !empty($this->translations[$langcode]['status']);
  }

  /**
   * {@inheritdoc}
   */
  public function addTranslation($langcode, array $values = array()) {
    $languages = language_list(Language::STATE_ALL);
    if (!isset($languages[$langcode]) || $this->hasTranslation($langcode)) {
      $message = 'Invalid translation language (@langcode) specified.';
      throw new \InvalidArgumentException(format_string($message, array('@langcode' => $langcode)));
    }

    // Instantiate a new empty entity so default values will be populated in the
    // specified language.
    $info = $this->entityInfo();
    $default_values = array($info['entity_keys']['bundle'] => $this->bundle, 'langcode' => $langcode);
    $entity = \Drupal::entityManager()
      ->getStorageController($this->entityType())
      ->create($default_values);

    foreach ($entity as $name => $field) {
      if (!isset($values[$name]) && !$field->isEmpty()) {
        $values[$name] = $field->value;
532
      }
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
    }

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

    foreach ($values as $name => $value) {
      if (isset($definitions[$name]) && !empty($definitions[$name]['translatable'])) {
        $translation->$name = $value;
      }
    }

    return $translation;
  }

  /**
   * {@inheritdoc}
   */
  public function removeTranslation($langcode) {
    if (isset($this->translations[$langcode]) && $langcode != Language::LANGCODE_DEFAULT && $langcode != $this->getDefaultLanguage()->id) {
553
      foreach ($this->getPropertyDefinitions() as $name => $definition) {
554
        if (!empty($definition['translatable'])) {
555
556
          unset($this->values[$name][$langcode]);
          unset($this->fields[$name][$langcode]);
557
        }
558
      }
559
560
561
562
563
564
565
566
567
568
569
570
571
572
      $this->translations[$langcode]['status'] = static::TRANSLATION_REMOVED;
    }
    else {
      $message = 'The specified translation (@langcode) cannot be removed.';
      throw new \InvalidArgumentException(format_string($message, array('@langcode' => $langcode)));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function initTranslation($langcode) {
    if ($langcode != Language::LANGCODE_DEFAULT && $langcode != $this->getDefaultLanguage()->id) {
      $this->translations[$langcode]['status'] = static::TRANSLATION_EXISTING;
573
    }
574
575
576
577
578
579
580
  }

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

    if ($include_default) {
584
585
      $langcode = $this->getDefaultLanguage()->id;
      $translations[$langcode] = TRUE;
586
    }
587

588
    // Now load language objects based upon translation langcodes.
589
    return array_intersect_key(language_list(Language::STATE_ALL), $translations);
590
591
592
  }

  /**
593
   * Overrides Entity::translations().
594
   *
595
   * @todo: Remove once Entity::translations() gets removed.
596
   */
597
598
  public function translations() {
    return $this->getTranslationLanguages(FALSE);
599
600
601
602
603
604
  }

  /**
   * Updates the original values with the interim changes.
   */
  public function updateOriginalValues() {
605
606
607
608
609
610
    if (!$this->fields) {
      return;
    }
    foreach ($this->getPropertyDefinitions() as $name => $definition) {
      if (empty($definition['computed']) && !empty($this->fields[$name])) {
        foreach ($this->fields[$name] as $langcode => $field) {
611
          $field->filterEmptyValues();
612
613
          $this->values[$name][$langcode] = $field->getValue();
        }
614
615
616
617
618
      }
    }
  }

  /**
619
   * Implements the magic method for setting object properties.
620
   *
621
   * @todo: A lot of code still uses non-fields (e.g. $entity->content in render
622
   *   controllers) by reference. Clean that up.
623
624
   */
  public function &__get($name) {
625
626
    // 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.
627
628
    if (isset($this->fields[$name][$this->activeLangcode])) {
      return $this->fields[$name][$this->activeLangcode];
629
    }
630
631
632
633
634
    // Inline getPropertyDefinition() to speed up things.
    if (!isset($this->fieldDefinitions)) {
      $this->getPropertyDefinitions();
    }
    if (isset($this->fieldDefinitions[$name])) {
635
      $return = $this->getTranslatedField($name, $this->activeLangcode);
636
637
      return $return;
    }
638
639
    // Else directly read/write plain values. That way, non-field entity
    // properties can always be accessed directly.
640
641
    if (!isset($this->values[$name])) {
      $this->values[$name] = NULL;
642
    }
643
    return $this->values[$name];
644
645
646
  }

  /**
647
648
649
   * Implements the magic method for setting object properties.
   *
   * Uses default language always.
650
651
652
   */
  public function __set($name, $value) {
    // Support setting values via property objects.
653
    if ($value instanceof TypedDataInterface && !$value instanceof EntityInterface) {
654
655
      $value = $value->getValue();
    }
656
657
    // 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.
658
659
    if (isset($this->fields[$name][$this->activeLangcode])) {
      $this->fields[$name][$this->activeLangcode]->setValue($value);
660
661
    }
    elseif ($this->getPropertyDefinition($name)) {
662
      $this->getTranslatedField($name, $this->activeLangcode)->setValue($value);
663
    }
664
665
666
667
668
    // The translations array is unset when cloning the entity object, we just
    // need to restore it.
    elseif ($name == 'translations') {
      $this->translations = $value;
    }
669
670
    // Else directly read/write plain values. That way, fields not yet converted
    // to the entity field API can always be directly accessed.
671
    else {
672
      $this->values[$name] = $value;
673
674
675
676
    }
  }

  /**
677
   * Implements the magic method for isset().
678
679
   */
  public function __isset($name) {
680
681
    if ($this->getPropertyDefinition($name)) {
      return $this->get($name)->getValue() !== NULL;
682
    }
683
684
    else {
      return isset($this->values[$name]);
685
686
687
688
    }
  }

  /**
689
   * Implements the magic method for unset.
690
691
   */
  public function __unset($name) {
692
693
    if ($this->getPropertyDefinition($name)) {
      $this->get($name)->setValue(NULL);
694
    }
695
696
    else {
      unset($this->values[$name]);
697
698
699
700
701
702
703
    }
  }

  /**
   * Overrides Entity::createDuplicate().
   */
  public function createDuplicate() {
704
705
706
707
708
    if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
      $message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
      throw new \InvalidArgumentException(format_string($message, array('@langcode' => $this->activeLangcode)));
    }

709
710
    $duplicate = clone $this;
    $entity_info = $this->entityInfo();
711
    $duplicate->{$entity_info['entity_keys']['id']}->value = NULL;
712
713

    // Check if the entity type supports UUIDs and generate a new one if so.
714
    if (!empty($entity_info['entity_keys']['uuid'])) {
715
      $duplicate->{$entity_info['entity_keys']['uuid']}->applyDefaultValue();
716
    }
717
718
719
720
721
722

    // Check whether the entity type supports revisions and initialize it if so.
    if (!empty($entity_info['entity_keys']['revision'])) {
      $duplicate->{$entity_info['entity_keys']['revision']}->value = NULL;
    }

723
724
725
726
    return $duplicate;
  }

  /**
727
   * Magic method: Implements a deep clone.
728
729
   */
  public function __clone() {
730
731
732
733
734
735
736
737
    // 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) {
      foreach ($this->fields as $name => $properties) {
        foreach ($properties as $langcode => $property) {
          $this->fields[$name][$langcode] = clone $property;
          $this->fields[$name][$langcode]->setContext($name, $this);
        }
738
      }
739
740
741

      // Ensure the translations array is actually cloned by removing the
      // original reference and re-creating its values.
742
      $this->clearTranslationCache();
743
744
745
746
747
      $translations = $this->translations;
      unset($this->translations);
      // This will trigger the magic setter as the translations array is
      // undefined now.
      $this->translations = $translations;
748
749
    }
  }
750
751
752
753
754
755
756

  /**
   * Overrides Entity::label() to access the label field with the new API.
   */
  public function label($langcode = NULL) {
    $label = NULL;
    $entity_info = $this->entityInfo();
757
758
759
    if (!isset($langcode)) {
      $langcode = $this->activeLangcode;
    }
760
761
762
763
764
765
766
767
    if (isset($entity_info['label_callback']) && function_exists($entity_info['label_callback'])) {
      $label = $entity_info['label_callback']($this->entityType, $this, $langcode);
    }
    elseif (!empty($entity_info['entity_keys']['label']) && isset($this->{$entity_info['entity_keys']['label']})) {
      $label = $this->{$entity_info['entity_keys']['label']}->value;
    }
    return $label;
  }
768

769
}