Mercurial > hg > isophonics-drupal-site
comparison core/lib/Drupal/Core/Entity/ContentEntityBase.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 7a779792577d |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\Core\Entity; | |
4 | |
5 use Drupal\Component\Utility\SafeMarkup; | |
6 use Drupal\Core\Entity\Plugin\DataType\EntityReference; | |
7 use Drupal\Core\Field\BaseFieldDefinition; | |
8 use Drupal\Core\Field\ChangedFieldItemList; | |
9 use Drupal\Core\Language\Language; | |
10 use Drupal\Core\Language\LanguageInterface; | |
11 use Drupal\Core\Session\AccountInterface; | |
12 use Drupal\Core\StringTranslation\TranslatableMarkup; | |
13 use Drupal\Core\TypedData\TranslationStatusInterface; | |
14 use Drupal\Core\TypedData\TypedDataInterface; | |
15 | |
16 /** | |
17 * Implements Entity Field API specific enhancements to the Entity class. | |
18 * | |
19 * @ingroup entity_api | |
20 */ | |
21 abstract class ContentEntityBase extends Entity implements \IteratorAggregate, ContentEntityInterface, TranslationStatusInterface { | |
22 | |
23 /** | |
24 * The plain data values of the contained fields. | |
25 * | |
26 * This always holds the original, unchanged values of the entity. The values | |
27 * are keyed by language code, whereas LanguageInterface::LANGCODE_DEFAULT | |
28 * is used for values in default language. | |
29 * | |
30 * @todo: Add methods for getting original fields and for determining | |
31 * changes. | |
32 * @todo: Provide a better way for defining default values. | |
33 * | |
34 * @var array | |
35 */ | |
36 protected $values = []; | |
37 | |
38 /** | |
39 * The array of fields, each being an instance of FieldItemListInterface. | |
40 * | |
41 * @var array | |
42 */ | |
43 protected $fields = []; | |
44 | |
45 /** | |
46 * Local cache for field definitions. | |
47 * | |
48 * @see ContentEntityBase::getFieldDefinitions() | |
49 * | |
50 * @var array | |
51 */ | |
52 protected $fieldDefinitions; | |
53 | |
54 /** | |
55 * Local cache for the available language objects. | |
56 * | |
57 * @var \Drupal\Core\Language\LanguageInterface[] | |
58 */ | |
59 protected $languages; | |
60 | |
61 /** | |
62 * The language entity key. | |
63 * | |
64 * @var string | |
65 */ | |
66 protected $langcodeKey; | |
67 | |
68 /** | |
69 * The default langcode entity key. | |
70 * | |
71 * @var string | |
72 */ | |
73 protected $defaultLangcodeKey; | |
74 | |
75 /** | |
76 * Language code identifying the entity active language. | |
77 * | |
78 * This is the language field accessors will use to determine which field | |
79 * values manipulate. | |
80 * | |
81 * @var string | |
82 */ | |
83 protected $activeLangcode = LanguageInterface::LANGCODE_DEFAULT; | |
84 | |
85 /** | |
86 * Local cache for the default language code. | |
87 * | |
88 * @var string | |
89 */ | |
90 protected $defaultLangcode; | |
91 | |
92 /** | |
93 * An array of entity translation metadata. | |
94 * | |
95 * An associative array keyed by translation language code. Every value is an | |
96 * array containing the translation status and the translation object, if it has | |
97 * already been instantiated. | |
98 * | |
99 * @var array | |
100 */ | |
101 protected $translations = []; | |
102 | |
103 /** | |
104 * A flag indicating whether a translation object is being initialized. | |
105 * | |
106 * @var bool | |
107 */ | |
108 protected $translationInitialize = FALSE; | |
109 | |
110 /** | |
111 * Boolean indicating whether a new revision should be created on save. | |
112 * | |
113 * @var bool | |
114 */ | |
115 protected $newRevision = FALSE; | |
116 | |
117 /** | |
118 * Indicates whether this is the default revision. | |
119 * | |
120 * @var bool | |
121 */ | |
122 protected $isDefaultRevision = TRUE; | |
123 | |
124 /** | |
125 * Holds untranslatable entity keys such as the ID, bundle, and revision ID. | |
126 * | |
127 * @var array | |
128 */ | |
129 protected $entityKeys = []; | |
130 | |
131 /** | |
132 * Holds translatable entity keys such as the label. | |
133 * | |
134 * @var array | |
135 */ | |
136 protected $translatableEntityKeys = []; | |
137 | |
138 /** | |
139 * Whether entity validation was performed. | |
140 * | |
141 * @var bool | |
142 */ | |
143 protected $validated = FALSE; | |
144 | |
145 /** | |
146 * Whether entity validation is required before saving the entity. | |
147 * | |
148 * @var bool | |
149 */ | |
150 protected $validationRequired = FALSE; | |
151 | |
152 /** | |
153 * The loaded revision ID before the new revision was set. | |
154 * | |
155 * @var int | |
156 */ | |
157 protected $loadedRevisionId; | |
158 | |
159 /** | |
160 * {@inheritdoc} | |
161 */ | |
162 public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = []) { | |
163 $this->entityTypeId = $entity_type; | |
164 $this->entityKeys['bundle'] = $bundle ? $bundle : $this->entityTypeId; | |
165 $this->langcodeKey = $this->getEntityType()->getKey('langcode'); | |
166 $this->defaultLangcodeKey = $this->getEntityType()->getKey('default_langcode'); | |
167 | |
168 foreach ($values as $key => $value) { | |
169 // If the key matches an existing property set the value to the property | |
170 // to set properties like isDefaultRevision. | |
171 // @todo: Should this be converted somehow? | |
172 if (property_exists($this, $key) && isset($value[LanguageInterface::LANGCODE_DEFAULT])) { | |
173 $this->$key = $value[LanguageInterface::LANGCODE_DEFAULT]; | |
174 } | |
175 } | |
176 | |
177 $this->values = $values; | |
178 foreach ($this->getEntityType()->getKeys() as $key => $field_name) { | |
179 if (isset($this->values[$field_name])) { | |
180 if (is_array($this->values[$field_name])) { | |
181 // We store untranslatable fields into an entity key without using a | |
182 // langcode key. | |
183 if (!$this->getFieldDefinition($field_name)->isTranslatable()) { | |
184 if (isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) { | |
185 if (is_array($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) { | |
186 if (isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'])) { | |
187 $this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value']; | |
188 } | |
189 } | |
190 else { | |
191 $this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT]; | |
192 } | |
193 } | |
194 } | |
195 else { | |
196 // We save translatable fields such as the publishing status of a node | |
197 // into an entity key array keyed by langcode as a performance | |
198 // optimization, so we don't have to go through TypedData when we | |
199 // need these values. | |
200 foreach ($this->values[$field_name] as $langcode => $field_value) { | |
201 if (is_array($this->values[$field_name][$langcode])) { | |
202 if (isset($this->values[$field_name][$langcode][0]['value'])) { | |
203 $this->translatableEntityKeys[$key][$langcode] = $this->values[$field_name][$langcode][0]['value']; | |
204 } | |
205 } | |
206 else { | |
207 $this->translatableEntityKeys[$key][$langcode] = $this->values[$field_name][$langcode]; | |
208 } | |
209 } | |
210 } | |
211 } | |
212 } | |
213 } | |
214 | |
215 // Initialize translations. Ensure we have at least an entry for the default | |
216 // language. | |
217 // We determine if the entity is new by checking in the entity values for | |
218 // the presence of the id entity key, as the usage of ::isNew() is not | |
219 // possible in the constructor. | |
220 $data = isset($values[$this->getEntityType()->getKey('id')]) ? ['status' => static::TRANSLATION_EXISTING] : ['status' => static::TRANSLATION_CREATED]; | |
221 $this->translations[LanguageInterface::LANGCODE_DEFAULT] = $data; | |
222 $this->setDefaultLangcode(); | |
223 if ($translations) { | |
224 foreach ($translations as $langcode) { | |
225 if ($langcode != $this->defaultLangcode && $langcode != LanguageInterface::LANGCODE_DEFAULT) { | |
226 $this->translations[$langcode] = $data; | |
227 } | |
228 } | |
229 } | |
230 if ($this->getEntityType()->isRevisionable()) { | |
231 // Store the loaded revision ID the entity has been loaded with to | |
232 // keep it safe from changes. | |
233 $this->updateLoadedRevisionId(); | |
234 } | |
235 } | |
236 | |
237 /** | |
238 * {@inheritdoc} | |
239 */ | |
240 protected function getLanguages() { | |
241 if (empty($this->languages)) { | |
242 $this->languages = $this->languageManager()->getLanguages(LanguageInterface::STATE_ALL); | |
243 // If the entity references a language that is not or no longer available, | |
244 // we return a mock language object to avoid disrupting the consuming | |
245 // code. | |
246 if (!isset($this->languages[$this->defaultLangcode])) { | |
247 $this->languages[$this->defaultLangcode] = new Language(['id' => $this->defaultLangcode]); | |
248 } | |
249 } | |
250 return $this->languages; | |
251 } | |
252 | |
253 /** | |
254 * {@inheritdoc} | |
255 */ | |
256 public function postCreate(EntityStorageInterface $storage) { | |
257 $this->newRevision = TRUE; | |
258 } | |
259 | |
260 /** | |
261 * {@inheritdoc} | |
262 */ | |
263 public function setNewRevision($value = TRUE) { | |
264 if (!$this->getEntityType()->hasKey('revision')) { | |
265 throw new \LogicException("Entity type {$this->getEntityTypeId()} does not support revisions."); | |
266 } | |
267 | |
268 if ($value && !$this->newRevision) { | |
269 // When saving a new revision, set any existing revision ID to NULL so as | |
270 // to ensure that a new revision will actually be created. | |
271 $this->set($this->getEntityType()->getKey('revision'), NULL); | |
272 | |
273 // Make sure that the flag tracking which translations are affected by the | |
274 // current revision is reset. | |
275 foreach ($this->translations as $langcode => $data) { | |
276 // But skip removed translations. | |
277 if ($this->hasTranslation($langcode)) { | |
278 $this->getTranslation($langcode)->setRevisionTranslationAffected(NULL); | |
279 } | |
280 } | |
281 } | |
282 | |
283 $this->newRevision = $value; | |
284 } | |
285 | |
286 /** | |
287 * {@inheritdoc} | |
288 */ | |
289 public function getLoadedRevisionId() { | |
290 return $this->loadedRevisionId; | |
291 } | |
292 | |
293 /** | |
294 * {@inheritdoc} | |
295 */ | |
296 public function updateLoadedRevisionId() { | |
297 $this->loadedRevisionId = $this->getRevisionId() ?: $this->loadedRevisionId; | |
298 return $this; | |
299 } | |
300 | |
301 /** | |
302 * {@inheritdoc} | |
303 */ | |
304 public function isNewRevision() { | |
305 return $this->newRevision || ($this->getEntityType()->hasKey('revision') && !$this->getRevisionId()); | |
306 } | |
307 | |
308 /** | |
309 * {@inheritdoc} | |
310 */ | |
311 public function isDefaultRevision($new_value = NULL) { | |
312 $return = $this->isDefaultRevision; | |
313 if (isset($new_value)) { | |
314 $this->isDefaultRevision = (bool) $new_value; | |
315 } | |
316 // New entities should always ensure at least one default revision exists, | |
317 // creating an entity without a default revision is an invalid state. | |
318 return $this->isNew() || $return; | |
319 } | |
320 | |
321 /** | |
322 * {@inheritdoc} | |
323 */ | |
324 public function isRevisionTranslationAffected() { | |
325 $field_name = $this->getEntityType()->getKey('revision_translation_affected'); | |
326 return $this->hasField($field_name) ? $this->get($field_name)->value : TRUE; | |
327 } | |
328 | |
329 /** | |
330 * {@inheritdoc} | |
331 */ | |
332 public function setRevisionTranslationAffected($affected) { | |
333 $field_name = $this->getEntityType()->getKey('revision_translation_affected'); | |
334 if ($this->hasField($field_name)) { | |
335 $this->set($field_name, $affected); | |
336 } | |
337 return $this; | |
338 } | |
339 | |
340 /** | |
341 * {@inheritdoc} | |
342 */ | |
343 public function isDefaultTranslation() { | |
344 return $this->activeLangcode === LanguageInterface::LANGCODE_DEFAULT; | |
345 } | |
346 | |
347 /** | |
348 * {@inheritdoc} | |
349 */ | |
350 public function getRevisionId() { | |
351 return $this->getEntityKey('revision'); | |
352 } | |
353 | |
354 /** | |
355 * {@inheritdoc} | |
356 */ | |
357 public function isTranslatable() { | |
358 // Check that the bundle is translatable, the entity has a language defined | |
359 // and if we have more than one language on the site. | |
360 $bundles = $this->entityManager()->getBundleInfo($this->entityTypeId); | |
361 return !empty($bundles[$this->bundle()]['translatable']) && !$this->getUntranslated()->language()->isLocked() && $this->languageManager()->isMultilingual(); | |
362 } | |
363 | |
364 /** | |
365 * {@inheritdoc} | |
366 */ | |
367 public function preSave(EntityStorageInterface $storage) { | |
368 // An entity requiring validation should not be saved if it has not been | |
369 // actually validated. | |
370 if ($this->validationRequired && !$this->validated) { | |
371 // @todo Make this an assertion in https://www.drupal.org/node/2408013. | |
372 throw new \LogicException('Entity validation was skipped.'); | |
373 } | |
374 else { | |
375 $this->validated = FALSE; | |
376 } | |
377 | |
378 parent::preSave($storage); | |
379 } | |
380 | |
381 /** | |
382 * {@inheritdoc} | |
383 */ | |
384 public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) { | |
385 } | |
386 | |
387 /** | |
388 * {@inheritdoc} | |
389 */ | |
390 public function postSave(EntityStorageInterface $storage, $update = TRUE) { | |
391 parent::postSave($storage, $update); | |
392 | |
393 // Update the status of all saved translations. | |
394 $removed = []; | |
395 foreach ($this->translations as $langcode => &$data) { | |
396 if ($data['status'] == static::TRANSLATION_REMOVED) { | |
397 $removed[$langcode] = TRUE; | |
398 } | |
399 else { | |
400 $data['status'] = static::TRANSLATION_EXISTING; | |
401 } | |
402 } | |
403 $this->translations = array_diff_key($this->translations, $removed); | |
404 } | |
405 | |
406 /** | |
407 * {@inheritdoc} | |
408 */ | |
409 public function validate() { | |
410 $this->validated = TRUE; | |
411 $violations = $this->getTypedData()->validate(); | |
412 return new EntityConstraintViolationList($this, iterator_to_array($violations)); | |
413 } | |
414 | |
415 /** | |
416 * {@inheritdoc} | |
417 */ | |
418 public function isValidationRequired() { | |
419 return (bool) $this->validationRequired; | |
420 } | |
421 | |
422 /** | |
423 * {@inheritdoc} | |
424 */ | |
425 public function setValidationRequired($required) { | |
426 $this->validationRequired = $required; | |
427 return $this; | |
428 } | |
429 | |
430 /** | |
431 * Clear entity translation object cache to remove stale references. | |
432 */ | |
433 protected function clearTranslationCache() { | |
434 foreach ($this->translations as &$translation) { | |
435 unset($translation['entity']); | |
436 } | |
437 } | |
438 | |
439 /** | |
440 * {@inheritdoc} | |
441 */ | |
442 public function __sleep() { | |
443 // Get the values of instantiated field objects, only serialize the values. | |
444 foreach ($this->fields as $name => $fields) { | |
445 foreach ($fields as $langcode => $field) { | |
446 $this->values[$name][$langcode] = $field->getValue(); | |
447 } | |
448 } | |
449 $this->fields = []; | |
450 $this->fieldDefinitions = NULL; | |
451 $this->languages = NULL; | |
452 $this->clearTranslationCache(); | |
453 | |
454 return parent::__sleep(); | |
455 } | |
456 | |
457 /** | |
458 * {@inheritdoc} | |
459 */ | |
460 public function id() { | |
461 return $this->getEntityKey('id'); | |
462 } | |
463 | |
464 /** | |
465 * {@inheritdoc} | |
466 */ | |
467 public function bundle() { | |
468 return $this->getEntityKey('bundle'); | |
469 } | |
470 | |
471 /** | |
472 * {@inheritdoc} | |
473 */ | |
474 public function uuid() { | |
475 return $this->getEntityKey('uuid'); | |
476 } | |
477 | |
478 /** | |
479 * {@inheritdoc} | |
480 */ | |
481 public function hasField($field_name) { | |
482 return (bool) $this->getFieldDefinition($field_name); | |
483 } | |
484 | |
485 /** | |
486 * {@inheritdoc} | |
487 */ | |
488 public function get($field_name) { | |
489 if (!isset($this->fields[$field_name][$this->activeLangcode])) { | |
490 return $this->getTranslatedField($field_name, $this->activeLangcode); | |
491 } | |
492 return $this->fields[$field_name][$this->activeLangcode]; | |
493 } | |
494 | |
495 /** | |
496 * Gets a translated field. | |
497 * | |
498 * @return \Drupal\Core\Field\FieldItemListInterface | |
499 */ | |
500 protected function getTranslatedField($name, $langcode) { | |
501 if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) { | |
502 throw new \InvalidArgumentException("The entity object refers to a removed translation ({$this->activeLangcode}) and cannot be manipulated."); | |
503 } | |
504 // Populate $this->fields to speed-up further look-ups and to keep track of | |
505 // fields objects, possibly holding changes to field values. | |
506 if (!isset($this->fields[$name][$langcode])) { | |
507 $definition = $this->getFieldDefinition($name); | |
508 if (!$definition) { | |
509 throw new \InvalidArgumentException("Field $name is unknown."); | |
510 } | |
511 // Non-translatable fields are always stored with | |
512 // LanguageInterface::LANGCODE_DEFAULT as key. | |
513 | |
514 $default = $langcode == LanguageInterface::LANGCODE_DEFAULT; | |
515 if (!$default && !$definition->isTranslatable()) { | |
516 if (!isset($this->fields[$name][LanguageInterface::LANGCODE_DEFAULT])) { | |
517 $this->fields[$name][LanguageInterface::LANGCODE_DEFAULT] = $this->getTranslatedField($name, LanguageInterface::LANGCODE_DEFAULT); | |
518 } | |
519 $this->fields[$name][$langcode] = &$this->fields[$name][LanguageInterface::LANGCODE_DEFAULT]; | |
520 } | |
521 else { | |
522 $value = NULL; | |
523 if (isset($this->values[$name][$langcode])) { | |
524 $value = $this->values[$name][$langcode]; | |
525 } | |
526 $field = \Drupal::service('plugin.manager.field.field_type')->createFieldItemList($this->getTranslation($langcode), $name, $value); | |
527 if ($default) { | |
528 // $this->defaultLangcode might not be set if we are initializing the | |
529 // default language code cache, in which case there is no valid | |
530 // langcode to assign. | |
531 $field_langcode = isset($this->defaultLangcode) ? $this->defaultLangcode : LanguageInterface::LANGCODE_NOT_SPECIFIED; | |
532 } | |
533 else { | |
534 $field_langcode = $langcode; | |
535 } | |
536 $field->setLangcode($field_langcode); | |
537 $this->fields[$name][$langcode] = $field; | |
538 } | |
539 } | |
540 return $this->fields[$name][$langcode]; | |
541 } | |
542 | |
543 /** | |
544 * {@inheritdoc} | |
545 */ | |
546 public function set($name, $value, $notify = TRUE) { | |
547 // Assign the value on the child and overrule notify such that we get | |
548 // notified to handle changes afterwards. We can ignore notify as there is | |
549 // no parent to notify anyway. | |
550 $this->get($name)->setValue($value, TRUE); | |
551 return $this; | |
552 } | |
553 | |
554 /** | |
555 * {@inheritdoc} | |
556 */ | |
557 public function getFields($include_computed = TRUE) { | |
558 $fields = []; | |
559 foreach ($this->getFieldDefinitions() as $name => $definition) { | |
560 if ($include_computed || !$definition->isComputed()) { | |
561 $fields[$name] = $this->get($name); | |
562 } | |
563 } | |
564 return $fields; | |
565 } | |
566 | |
567 /** | |
568 * {@inheritdoc} | |
569 */ | |
570 public function getTranslatableFields($include_computed = TRUE) { | |
571 $fields = []; | |
572 foreach ($this->getFieldDefinitions() as $name => $definition) { | |
573 if (($include_computed || !$definition->isComputed()) && $definition->isTranslatable()) { | |
574 $fields[$name] = $this->get($name); | |
575 } | |
576 } | |
577 return $fields; | |
578 } | |
579 | |
580 /** | |
581 * {@inheritdoc} | |
582 */ | |
583 public function getIterator() { | |
584 return new \ArrayIterator($this->getFields()); | |
585 } | |
586 | |
587 /** | |
588 * {@inheritdoc} | |
589 */ | |
590 public function getFieldDefinition($name) { | |
591 if (!isset($this->fieldDefinitions)) { | |
592 $this->getFieldDefinitions(); | |
593 } | |
594 if (isset($this->fieldDefinitions[$name])) { | |
595 return $this->fieldDefinitions[$name]; | |
596 } | |
597 } | |
598 | |
599 /** | |
600 * {@inheritdoc} | |
601 */ | |
602 public function getFieldDefinitions() { | |
603 if (!isset($this->fieldDefinitions)) { | |
604 $this->fieldDefinitions = $this->entityManager()->getFieldDefinitions($this->entityTypeId, $this->bundle()); | |
605 } | |
606 return $this->fieldDefinitions; | |
607 } | |
608 | |
609 /** | |
610 * {@inheritdoc} | |
611 */ | |
612 public function toArray() { | |
613 $values = []; | |
614 foreach ($this->getFields() as $name => $property) { | |
615 $values[$name] = $property->getValue(); | |
616 } | |
617 return $values; | |
618 } | |
619 | |
620 /** | |
621 * {@inheritdoc} | |
622 */ | |
623 public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) { | |
624 if ($operation == 'create') { | |
625 return $this->entityManager() | |
626 ->getAccessControlHandler($this->entityTypeId) | |
627 ->createAccess($this->bundle(), $account, [], $return_as_object); | |
628 } | |
629 return $this->entityManager() | |
630 ->getAccessControlHandler($this->entityTypeId) | |
631 ->access($this, $operation, $account, $return_as_object); | |
632 } | |
633 | |
634 /** | |
635 * {@inheritdoc} | |
636 */ | |
637 public function language() { | |
638 $language = NULL; | |
639 if ($this->activeLangcode != LanguageInterface::LANGCODE_DEFAULT) { | |
640 if (!isset($this->languages[$this->activeLangcode])) { | |
641 $this->getLanguages(); | |
642 } | |
643 $language = $this->languages[$this->activeLangcode]; | |
644 } | |
645 else { | |
646 // @todo Avoid this check by getting the language from the language | |
647 // manager directly in https://www.drupal.org/node/2303877. | |
648 if (!isset($this->languages[$this->defaultLangcode])) { | |
649 $this->getLanguages(); | |
650 } | |
651 $language = $this->languages[$this->defaultLangcode]; | |
652 } | |
653 return $language; | |
654 } | |
655 | |
656 /** | |
657 * Populates the local cache for the default language code. | |
658 */ | |
659 protected function setDefaultLangcode() { | |
660 // Get the language code if the property exists. | |
661 // Try to read the value directly from the list of entity keys which got | |
662 // initialized in __construct(). This avoids creating a field item object. | |
663 if (isset($this->translatableEntityKeys['langcode'][$this->activeLangcode])) { | |
664 $this->defaultLangcode = $this->translatableEntityKeys['langcode'][$this->activeLangcode]; | |
665 } | |
666 elseif ($this->hasField($this->langcodeKey) && ($item = $this->get($this->langcodeKey)) && isset($item->language)) { | |
667 $this->defaultLangcode = $item->language->getId(); | |
668 $this->translatableEntityKeys['langcode'][$this->activeLangcode] = $this->defaultLangcode; | |
669 } | |
670 | |
671 if (empty($this->defaultLangcode)) { | |
672 // Make sure we return a proper language object, if the entity has a | |
673 // langcode field, default to the site's default language. | |
674 if ($this->hasField($this->langcodeKey)) { | |
675 $this->defaultLangcode = $this->languageManager()->getDefaultLanguage()->getId(); | |
676 } | |
677 else { | |
678 $this->defaultLangcode = LanguageInterface::LANGCODE_NOT_SPECIFIED; | |
679 } | |
680 } | |
681 | |
682 // This needs to be initialized manually as it is skipped when instantiating | |
683 // the language field object to avoid infinite recursion. | |
684 if (!empty($this->fields[$this->langcodeKey])) { | |
685 $this->fields[$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT]->setLangcode($this->defaultLangcode); | |
686 } | |
687 } | |
688 | |
689 /** | |
690 * Updates language for already instantiated fields. | |
691 */ | |
692 protected function updateFieldLangcodes($langcode) { | |
693 foreach ($this->fields as $name => $items) { | |
694 if (!empty($items[LanguageInterface::LANGCODE_DEFAULT])) { | |
695 $items[LanguageInterface::LANGCODE_DEFAULT]->setLangcode($langcode); | |
696 } | |
697 } | |
698 } | |
699 | |
700 /** | |
701 * {@inheritdoc} | |
702 */ | |
703 public function onChange($name) { | |
704 // Check if the changed name is the value of an entity key and if the value | |
705 // of that is currently cached, if so, reset it. Exclude the bundle from | |
706 // that check, as it ready only and must not change, unsetting it could | |
707 // lead to recursions. | |
708 if ($key = array_search($name, $this->getEntityType()->getKeys())) { | |
709 if ($key != 'bundle') { | |
710 if (isset($this->entityKeys[$key])) { | |
711 unset($this->entityKeys[$key]); | |
712 } | |
713 elseif (isset($this->translatableEntityKeys[$key][$this->activeLangcode])) { | |
714 unset($this->translatableEntityKeys[$key][$this->activeLangcode]); | |
715 } | |
716 } | |
717 } | |
718 | |
719 switch ($name) { | |
720 case $this->langcodeKey: | |
721 if ($this->isDefaultTranslation()) { | |
722 // Update the default internal language cache. | |
723 $this->setDefaultLangcode(); | |
724 if (isset($this->translations[$this->defaultLangcode])) { | |
725 $message = SafeMarkup::format('A translation already exists for the specified language (@langcode).', ['@langcode' => $this->defaultLangcode]); | |
726 throw new \InvalidArgumentException($message); | |
727 } | |
728 $this->updateFieldLangcodes($this->defaultLangcode); | |
729 } | |
730 else { | |
731 // @todo Allow the translation language to be changed. See | |
732 // https://www.drupal.org/node/2443989. | |
733 $items = $this->get($this->langcodeKey); | |
734 if ($items->value != $this->activeLangcode) { | |
735 $items->setValue($this->activeLangcode, FALSE); | |
736 $message = SafeMarkup::format('The translation language cannot be changed (@langcode).', ['@langcode' => $this->activeLangcode]); | |
737 throw new \LogicException($message); | |
738 } | |
739 } | |
740 break; | |
741 | |
742 case $this->defaultLangcodeKey: | |
743 // @todo Use a standard method to make the default_langcode field | |
744 // read-only. See https://www.drupal.org/node/2443991. | |
745 if (isset($this->values[$this->defaultLangcodeKey]) && $this->get($this->defaultLangcodeKey)->value != $this->isDefaultTranslation()) { | |
746 $this->get($this->defaultLangcodeKey)->setValue($this->isDefaultTranslation(), FALSE); | |
747 $message = SafeMarkup::format('The default translation flag cannot be changed (@langcode).', ['@langcode' => $this->activeLangcode]); | |
748 throw new \LogicException($message); | |
749 } | |
750 break; | |
751 } | |
752 } | |
753 | |
754 /** | |
755 * {@inheritdoc} | |
756 */ | |
757 public function getTranslation($langcode) { | |
758 // Ensure we always use the default language code when dealing with the | |
759 // original entity language. | |
760 if ($langcode != LanguageInterface::LANGCODE_DEFAULT && $langcode == $this->defaultLangcode) { | |
761 $langcode = LanguageInterface::LANGCODE_DEFAULT; | |
762 } | |
763 | |
764 // Populate entity translation object cache so it will be available for all | |
765 // translation objects. | |
766 if ($langcode == $this->activeLangcode) { | |
767 $this->translations[$langcode]['entity'] = $this; | |
768 } | |
769 | |
770 // If we already have a translation object for the specified language we can | |
771 // just return it. | |
772 if (isset($this->translations[$langcode]['entity'])) { | |
773 $translation = $this->translations[$langcode]['entity']; | |
774 } | |
775 // Otherwise if an existing translation language was specified we need to | |
776 // instantiate the related translation. | |
777 elseif (isset($this->translations[$langcode])) { | |
778 $translation = $this->initializeTranslation($langcode); | |
779 $this->translations[$langcode]['entity'] = $translation; | |
780 } | |
781 | |
782 if (empty($translation)) { | |
783 throw new \InvalidArgumentException("Invalid translation language ($langcode) specified."); | |
784 } | |
785 | |
786 return $translation; | |
787 } | |
788 | |
789 /** | |
790 * {@inheritdoc} | |
791 */ | |
792 public function getUntranslated() { | |
793 return $this->getTranslation(LanguageInterface::LANGCODE_DEFAULT); | |
794 } | |
795 | |
796 /** | |
797 * Instantiates a translation object for an existing translation. | |
798 * | |
799 * The translated entity will be a clone of the current entity with the | |
800 * specified $langcode. All translations share the same field data structures | |
801 * to ensure that all of them deal with fresh data. | |
802 * | |
803 * @param string $langcode | |
804 * The language code for the requested translation. | |
805 * | |
806 * @return \Drupal\Core\Entity\EntityInterface | |
807 * The translation object. The content properties of the translation object | |
808 * are stored as references to the main entity. | |
809 */ | |
810 protected function initializeTranslation($langcode) { | |
811 // If the requested translation is valid, clone it with the current language | |
812 // as the active language. The $translationInitialize flag triggers a | |
813 // shallow (non-recursive) clone. | |
814 $this->translationInitialize = TRUE; | |
815 $translation = clone $this; | |
816 $this->translationInitialize = FALSE; | |
817 | |
818 $translation->activeLangcode = $langcode; | |
819 | |
820 // Ensure that changes to fields, values and translations are propagated | |
821 // to all the translation objects. | |
822 // @todo Consider converting these to ArrayObject. | |
823 $translation->values = &$this->values; | |
824 $translation->fields = &$this->fields; | |
825 $translation->translations = &$this->translations; | |
826 $translation->enforceIsNew = &$this->enforceIsNew; | |
827 $translation->newRevision = &$this->newRevision; | |
828 $translation->entityKeys = &$this->entityKeys; | |
829 $translation->translatableEntityKeys = &$this->translatableEntityKeys; | |
830 $translation->translationInitialize = FALSE; | |
831 $translation->typedData = NULL; | |
832 $translation->loadedRevisionId = &$this->loadedRevisionId; | |
833 $translation->isDefaultRevision = &$this->isDefaultRevision; | |
834 | |
835 return $translation; | |
836 } | |
837 | |
838 /** | |
839 * {@inheritdoc} | |
840 */ | |
841 public function hasTranslation($langcode) { | |
842 if ($langcode == $this->defaultLangcode) { | |
843 $langcode = LanguageInterface::LANGCODE_DEFAULT; | |
844 } | |
845 return !empty($this->translations[$langcode]['status']); | |
846 } | |
847 | |
848 /** | |
849 * {@inheritdoc} | |
850 */ | |
851 public function isNewTranslation() { | |
852 return $this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_CREATED; | |
853 } | |
854 | |
855 /** | |
856 * {@inheritdoc} | |
857 */ | |
858 public function addTranslation($langcode, array $values = []) { | |
859 // Make sure we do not attempt to create a translation if an invalid | |
860 // language is specified or the entity cannot be translated. | |
861 $this->getLanguages(); | |
862 if (!isset($this->languages[$langcode]) || $this->hasTranslation($langcode) || $this->languages[$langcode]->isLocked()) { | |
863 throw new \InvalidArgumentException("Invalid translation language ($langcode) specified."); | |
864 } | |
865 if ($this->languages[$this->defaultLangcode]->isLocked()) { | |
866 throw new \InvalidArgumentException("The entity cannot be translated since it is language neutral ({$this->defaultLangcode})."); | |
867 } | |
868 | |
869 // Initialize the translation object. | |
870 /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */ | |
871 $storage = $this->entityManager()->getStorage($this->getEntityTypeId()); | |
872 $this->translations[$langcode]['status'] = !isset($this->translations[$langcode]['status_existed']) ? static::TRANSLATION_CREATED : static::TRANSLATION_EXISTING; | |
873 return $storage->createTranslation($this, $langcode, $values); | |
874 } | |
875 | |
876 /** | |
877 * {@inheritdoc} | |
878 */ | |
879 public function removeTranslation($langcode) { | |
880 if (isset($this->translations[$langcode]) && $langcode != LanguageInterface::LANGCODE_DEFAULT && $langcode != $this->defaultLangcode) { | |
881 foreach ($this->getFieldDefinitions() as $name => $definition) { | |
882 if ($definition->isTranslatable()) { | |
883 unset($this->values[$name][$langcode]); | |
884 unset($this->fields[$name][$langcode]); | |
885 } | |
886 } | |
887 // If removing a translation which has not been saved yet, then we have | |
888 // to remove it completely so that ::getTranslationStatus returns the | |
889 // proper status. | |
890 if ($this->translations[$langcode]['status'] == static::TRANSLATION_CREATED) { | |
891 unset($this->translations[$langcode]); | |
892 } | |
893 else { | |
894 if ($this->translations[$langcode]['status'] == static::TRANSLATION_EXISTING) { | |
895 $this->translations[$langcode]['status_existed'] = TRUE; | |
896 } | |
897 $this->translations[$langcode]['status'] = static::TRANSLATION_REMOVED; | |
898 } | |
899 } | |
900 else { | |
901 throw new \InvalidArgumentException("The specified translation ($langcode) cannot be removed."); | |
902 } | |
903 } | |
904 | |
905 /** | |
906 * {@inheritdoc} | |
907 */ | |
908 public function getTranslationStatus($langcode) { | |
909 if ($langcode == $this->defaultLangcode) { | |
910 $langcode = LanguageInterface::LANGCODE_DEFAULT; | |
911 } | |
912 return isset($this->translations[$langcode]) ? $this->translations[$langcode]['status'] : NULL; | |
913 } | |
914 | |
915 /** | |
916 * {@inheritdoc} | |
917 */ | |
918 public function getTranslationLanguages($include_default = TRUE) { | |
919 $translations = array_filter($this->translations, function ($translation) { | |
920 return $translation['status']; | |
921 }); | |
922 unset($translations[LanguageInterface::LANGCODE_DEFAULT]); | |
923 | |
924 if ($include_default) { | |
925 $translations[$this->defaultLangcode] = TRUE; | |
926 } | |
927 | |
928 // Now load language objects based upon translation langcodes. | |
929 return array_intersect_key($this->getLanguages(), $translations); | |
930 } | |
931 | |
932 /** | |
933 * Updates the original values with the interim changes. | |
934 */ | |
935 public function updateOriginalValues() { | |
936 if (!$this->fields) { | |
937 return; | |
938 } | |
939 foreach ($this->getFieldDefinitions() as $name => $definition) { | |
940 if (!$definition->isComputed() && !empty($this->fields[$name])) { | |
941 foreach ($this->fields[$name] as $langcode => $item) { | |
942 $item->filterEmptyItems(); | |
943 $this->values[$name][$langcode] = $item->getValue(); | |
944 } | |
945 } | |
946 } | |
947 } | |
948 | |
949 /** | |
950 * Implements the magic method for getting object properties. | |
951 * | |
952 * @todo: A lot of code still uses non-fields (e.g. $entity->content in view | |
953 * builders) by reference. Clean that up. | |
954 */ | |
955 public function &__get($name) { | |
956 // If this is an entity field, handle it accordingly. We first check whether | |
957 // a field object has been already created. If not, we create one. | |
958 if (isset($this->fields[$name][$this->activeLangcode])) { | |
959 return $this->fields[$name][$this->activeLangcode]; | |
960 } | |
961 // Inline getFieldDefinition() to speed things up. | |
962 if (!isset($this->fieldDefinitions)) { | |
963 $this->getFieldDefinitions(); | |
964 } | |
965 if (isset($this->fieldDefinitions[$name])) { | |
966 $return = $this->getTranslatedField($name, $this->activeLangcode); | |
967 return $return; | |
968 } | |
969 // Else directly read/write plain values. That way, non-field entity | |
970 // properties can always be accessed directly. | |
971 if (!isset($this->values[$name])) { | |
972 $this->values[$name] = NULL; | |
973 } | |
974 return $this->values[$name]; | |
975 } | |
976 | |
977 /** | |
978 * Implements the magic method for setting object properties. | |
979 * | |
980 * Uses default language always. | |
981 */ | |
982 public function __set($name, $value) { | |
983 // Inline getFieldDefinition() to speed things up. | |
984 if (!isset($this->fieldDefinitions)) { | |
985 $this->getFieldDefinitions(); | |
986 } | |
987 // Handle Field API fields. | |
988 if (isset($this->fieldDefinitions[$name])) { | |
989 // Support setting values via property objects. | |
990 if ($value instanceof TypedDataInterface) { | |
991 $value = $value->getValue(); | |
992 } | |
993 // If a FieldItemList object already exists, set its value. | |
994 if (isset($this->fields[$name][$this->activeLangcode])) { | |
995 $this->fields[$name][$this->activeLangcode]->setValue($value); | |
996 } | |
997 // If not, create one. | |
998 else { | |
999 $this->getTranslatedField($name, $this->activeLangcode)->setValue($value); | |
1000 } | |
1001 } | |
1002 // The translations array is unset when cloning the entity object, we just | |
1003 // need to restore it. | |
1004 elseif ($name == 'translations') { | |
1005 $this->translations = $value; | |
1006 } | |
1007 // Directly write non-field values. | |
1008 else { | |
1009 $this->values[$name] = $value; | |
1010 } | |
1011 } | |
1012 | |
1013 /** | |
1014 * Implements the magic method for isset(). | |
1015 */ | |
1016 public function __isset($name) { | |
1017 // "Official" Field API fields are always set. For non-field properties, | |
1018 // check the internal values. | |
1019 return $this->hasField($name) ? TRUE : isset($this->values[$name]); | |
1020 } | |
1021 | |
1022 /** | |
1023 * Implements the magic method for unset(). | |
1024 */ | |
1025 public function __unset($name) { | |
1026 // Unsetting a field means emptying it. | |
1027 if ($this->hasField($name)) { | |
1028 $this->get($name)->setValue([]); | |
1029 } | |
1030 // For non-field properties, unset the internal value. | |
1031 else { | |
1032 unset($this->values[$name]); | |
1033 } | |
1034 } | |
1035 | |
1036 /** | |
1037 * {@inheritdoc} | |
1038 */ | |
1039 public function createDuplicate() { | |
1040 if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) { | |
1041 throw new \InvalidArgumentException("The entity object refers to a removed translation ({$this->activeLangcode}) and cannot be manipulated."); | |
1042 } | |
1043 | |
1044 $duplicate = clone $this; | |
1045 $entity_type = $this->getEntityType(); | |
1046 $duplicate->{$entity_type->getKey('id')}->value = NULL; | |
1047 $duplicate->enforceIsNew(); | |
1048 | |
1049 // Check if the entity type supports UUIDs and generate a new one if so. | |
1050 if ($entity_type->hasKey('uuid')) { | |
1051 $duplicate->{$entity_type->getKey('uuid')}->value = $this->uuidGenerator()->generate(); | |
1052 } | |
1053 | |
1054 // Check whether the entity type supports revisions and initialize it if so. | |
1055 if ($entity_type->isRevisionable()) { | |
1056 $duplicate->{$entity_type->getKey('revision')}->value = NULL; | |
1057 $duplicate->loadedRevisionId = NULL; | |
1058 } | |
1059 | |
1060 return $duplicate; | |
1061 } | |
1062 | |
1063 /** | |
1064 * Magic method: Implements a deep clone. | |
1065 */ | |
1066 public function __clone() { | |
1067 // Avoid deep-cloning when we are initializing a translation object, since | |
1068 // it will represent the same entity, only with a different active language. | |
1069 if ($this->translationInitialize) { | |
1070 return; | |
1071 } | |
1072 | |
1073 // The translation is a different object, and needs its own TypedData | |
1074 // adapter object. | |
1075 $this->typedData = NULL; | |
1076 $definitions = $this->getFieldDefinitions(); | |
1077 | |
1078 // The translation cache has to be cleared before cloning the fields | |
1079 // below so that the call to getTranslation() does not re-use the | |
1080 // translation objects of the old entity but instead creates new | |
1081 // translation objects from the newly cloned entity. Otherwise the newly | |
1082 // cloned field item lists would hold references to the old translation | |
1083 // objects in their $parent property after the call to setContext(). | |
1084 $this->clearTranslationCache(); | |
1085 | |
1086 // Because the new translation objects that are created below are | |
1087 // themselves created by *cloning* the newly cloned entity we need to | |
1088 // make sure that the references to property values are properly cloned | |
1089 // before cloning the fields. Otherwise calling | |
1090 // $items->getEntity()->isNew(), for example, would return the | |
1091 // $enforceIsNew value of the old entity. | |
1092 | |
1093 // Ensure the translations array is actually cloned by overwriting the | |
1094 // original reference with one pointing to a copy of the array. | |
1095 $translations = $this->translations; | |
1096 $this->translations = &$translations; | |
1097 | |
1098 // Ensure that the following properties are actually cloned by | |
1099 // overwriting the original references with ones pointing to copies of | |
1100 // them: enforceIsNew, newRevision, loadedRevisionId, fields, entityKeys, | |
1101 // translatableEntityKeys, values and isDefaultRevision. | |
1102 $enforce_is_new = $this->enforceIsNew; | |
1103 $this->enforceIsNew = &$enforce_is_new; | |
1104 | |
1105 $new_revision = $this->newRevision; | |
1106 $this->newRevision = &$new_revision; | |
1107 | |
1108 $original_revision_id = $this->loadedRevisionId; | |
1109 $this->loadedRevisionId = &$original_revision_id; | |
1110 | |
1111 $fields = $this->fields; | |
1112 $this->fields = &$fields; | |
1113 | |
1114 $entity_keys = $this->entityKeys; | |
1115 $this->entityKeys = &$entity_keys; | |
1116 | |
1117 $translatable_entity_keys = $this->translatableEntityKeys; | |
1118 $this->translatableEntityKeys = &$translatable_entity_keys; | |
1119 | |
1120 $values = $this->values; | |
1121 $this->values = &$values; | |
1122 | |
1123 $default_revision = $this->isDefaultRevision; | |
1124 $this->isDefaultRevision = &$default_revision; | |
1125 | |
1126 foreach ($this->fields as $name => $fields_by_langcode) { | |
1127 $this->fields[$name] = []; | |
1128 // Untranslatable fields may have multiple references for the same field | |
1129 // object keyed by language. To avoid creating different field objects | |
1130 // we retain just the original value, as references will be recreated | |
1131 // later as needed. | |
1132 if (!$definitions[$name]->isTranslatable() && count($fields_by_langcode) > 1) { | |
1133 $fields_by_langcode = array_intersect_key($fields_by_langcode, [LanguageInterface::LANGCODE_DEFAULT => TRUE]); | |
1134 } | |
1135 foreach ($fields_by_langcode as $langcode => $items) { | |
1136 $this->fields[$name][$langcode] = clone $items; | |
1137 $this->fields[$name][$langcode]->setContext($name, $this->getTranslation($langcode)->getTypedData()); | |
1138 } | |
1139 } | |
1140 } | |
1141 | |
1142 /** | |
1143 * {@inheritdoc} | |
1144 */ | |
1145 public function label() { | |
1146 $label = NULL; | |
1147 $entity_type = $this->getEntityType(); | |
1148 if (($label_callback = $entity_type->getLabelCallback()) && is_callable($label_callback)) { | |
1149 $label = call_user_func($label_callback, $this); | |
1150 } | |
1151 elseif (($label_key = $entity_type->getKey('label'))) { | |
1152 $label = $this->getEntityKey('label'); | |
1153 } | |
1154 return $label; | |
1155 } | |
1156 | |
1157 /** | |
1158 * {@inheritdoc} | |
1159 */ | |
1160 public function referencedEntities() { | |
1161 $referenced_entities = []; | |
1162 | |
1163 // Gather a list of referenced entities. | |
1164 foreach ($this->getFields() as $field_items) { | |
1165 foreach ($field_items as $field_item) { | |
1166 // Loop over all properties of a field item. | |
1167 foreach ($field_item->getProperties(TRUE) as $property) { | |
1168 if ($property instanceof EntityReference && $entity = $property->getValue()) { | |
1169 $referenced_entities[] = $entity; | |
1170 } | |
1171 } | |
1172 } | |
1173 } | |
1174 | |
1175 return $referenced_entities; | |
1176 } | |
1177 | |
1178 /** | |
1179 * Gets the value of the given entity key, if defined. | |
1180 * | |
1181 * @param string $key | |
1182 * Name of the entity key, for example id, revision or bundle. | |
1183 * | |
1184 * @return mixed | |
1185 * The value of the entity key, NULL if not defined. | |
1186 */ | |
1187 protected function getEntityKey($key) { | |
1188 // If the value is known already, return it. | |
1189 if (isset($this->entityKeys[$key])) { | |
1190 return $this->entityKeys[$key]; | |
1191 } | |
1192 if (isset($this->translatableEntityKeys[$key][$this->activeLangcode])) { | |
1193 return $this->translatableEntityKeys[$key][$this->activeLangcode]; | |
1194 } | |
1195 | |
1196 // Otherwise fetch the value by creating a field object. | |
1197 $value = NULL; | |
1198 if ($this->getEntityType()->hasKey($key)) { | |
1199 $field_name = $this->getEntityType()->getKey($key); | |
1200 $definition = $this->getFieldDefinition($field_name); | |
1201 $property = $definition->getFieldStorageDefinition()->getMainPropertyName(); | |
1202 $value = $this->get($field_name)->$property; | |
1203 | |
1204 // Put it in the right array, depending on whether it is translatable. | |
1205 if ($definition->isTranslatable()) { | |
1206 $this->translatableEntityKeys[$key][$this->activeLangcode] = $value; | |
1207 } | |
1208 else { | |
1209 $this->entityKeys[$key] = $value; | |
1210 } | |
1211 } | |
1212 else { | |
1213 $this->entityKeys[$key] = $value; | |
1214 } | |
1215 return $value; | |
1216 } | |
1217 | |
1218 /** | |
1219 * {@inheritdoc} | |
1220 */ | |
1221 public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { | |
1222 $fields = []; | |
1223 if ($entity_type->hasKey('id')) { | |
1224 $fields[$entity_type->getKey('id')] = BaseFieldDefinition::create('integer') | |
1225 ->setLabel(new TranslatableMarkup('ID')) | |
1226 ->setReadOnly(TRUE) | |
1227 ->setSetting('unsigned', TRUE); | |
1228 } | |
1229 if ($entity_type->hasKey('uuid')) { | |
1230 $fields[$entity_type->getKey('uuid')] = BaseFieldDefinition::create('uuid') | |
1231 ->setLabel(new TranslatableMarkup('UUID')) | |
1232 ->setReadOnly(TRUE); | |
1233 } | |
1234 if ($entity_type->hasKey('revision')) { | |
1235 $fields[$entity_type->getKey('revision')] = BaseFieldDefinition::create('integer') | |
1236 ->setLabel(new TranslatableMarkup('Revision ID')) | |
1237 ->setReadOnly(TRUE) | |
1238 ->setSetting('unsigned', TRUE); | |
1239 } | |
1240 if ($entity_type->hasKey('langcode')) { | |
1241 $fields[$entity_type->getKey('langcode')] = BaseFieldDefinition::create('language') | |
1242 ->setLabel(new TranslatableMarkup('Language')) | |
1243 ->setDisplayOptions('view', [ | |
1244 'region' => 'hidden', | |
1245 ]) | |
1246 ->setDisplayOptions('form', [ | |
1247 'type' => 'language_select', | |
1248 'weight' => 2, | |
1249 ]); | |
1250 if ($entity_type->isRevisionable()) { | |
1251 $fields[$entity_type->getKey('langcode')]->setRevisionable(TRUE); | |
1252 } | |
1253 if ($entity_type->isTranslatable()) { | |
1254 $fields[$entity_type->getKey('langcode')]->setTranslatable(TRUE); | |
1255 } | |
1256 } | |
1257 if ($entity_type->hasKey('bundle')) { | |
1258 if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) { | |
1259 $fields[$entity_type->getKey('bundle')] = BaseFieldDefinition::create('entity_reference') | |
1260 ->setLabel($entity_type->getBundleLabel()) | |
1261 ->setSetting('target_type', $bundle_entity_type_id) | |
1262 ->setRequired(TRUE) | |
1263 ->setReadOnly(TRUE); | |
1264 } | |
1265 else { | |
1266 $fields[$entity_type->getKey('bundle')] = BaseFieldDefinition::create('string') | |
1267 ->setLabel($entity_type->getBundleLabel()) | |
1268 ->setRequired(TRUE) | |
1269 ->setReadOnly(TRUE); | |
1270 } | |
1271 } | |
1272 | |
1273 return $fields; | |
1274 } | |
1275 | |
1276 /** | |
1277 * {@inheritdoc} | |
1278 */ | |
1279 public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) { | |
1280 return []; | |
1281 } | |
1282 | |
1283 /** | |
1284 * Returns an array of field names to skip in ::hasTranslationChanges. | |
1285 * | |
1286 * @return array | |
1287 * An array of field names. | |
1288 */ | |
1289 protected function getFieldsToSkipFromTranslationChangesCheck() { | |
1290 /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */ | |
1291 $entity_type = $this->getEntityType(); | |
1292 // A list of known revision metadata fields which should be skipped from | |
1293 // the comparision. | |
1294 $fields = [ | |
1295 $entity_type->getKey('revision'), | |
1296 'revision_translation_affected', | |
1297 ]; | |
1298 $fields = array_merge($fields, array_values($entity_type->getRevisionMetadataKeys())); | |
1299 | |
1300 return $fields; | |
1301 } | |
1302 | |
1303 /** | |
1304 * {@inheritdoc} | |
1305 */ | |
1306 public function hasTranslationChanges() { | |
1307 if ($this->isNew()) { | |
1308 return TRUE; | |
1309 } | |
1310 | |
1311 // $this->original only exists during save. See | |
1312 // \Drupal\Core\Entity\EntityStorageBase::save(). If it exists we re-use it | |
1313 // here for performance reasons. | |
1314 /** @var \Drupal\Core\Entity\ContentEntityBase $original */ | |
1315 $original = $this->original ? $this->original : NULL; | |
1316 | |
1317 if (!$original) { | |
1318 $id = $this->getOriginalId() !== NULL ? $this->getOriginalId() : $this->id(); | |
1319 $original = $this->entityManager()->getStorage($this->getEntityTypeId())->loadUnchanged($id); | |
1320 } | |
1321 | |
1322 // If the current translation has just been added, we have a change. | |
1323 $translated = count($this->translations) > 1; | |
1324 if ($translated && !$original->hasTranslation($this->activeLangcode)) { | |
1325 return TRUE; | |
1326 } | |
1327 | |
1328 // Compare field item current values with the original ones to determine | |
1329 // whether we have changes. If a field is not translatable and the entity is | |
1330 // translated we skip it because, depending on the use case, it would make | |
1331 // sense to mark all translations as changed or none of them. We skip also | |
1332 // computed fields as comparing them with their original values might not be | |
1333 // possible or be meaningless. | |
1334 /** @var \Drupal\Core\Entity\ContentEntityBase $translation */ | |
1335 $translation = $original->getTranslation($this->activeLangcode); | |
1336 | |
1337 // The list of fields to skip from the comparision. | |
1338 $skip_fields = $this->getFieldsToSkipFromTranslationChangesCheck(); | |
1339 | |
1340 foreach ($this->getFieldDefinitions() as $field_name => $definition) { | |
1341 // @todo Avoid special-casing the following fields. See | |
1342 // https://www.drupal.org/node/2329253. | |
1343 if (in_array($field_name, $skip_fields, TRUE)) { | |
1344 continue; | |
1345 } | |
1346 $field = $this->get($field_name); | |
1347 // When saving entities in the user interface, the changed timestamp is | |
1348 // automatically incremented by ContentEntityForm::submitForm() even if | |
1349 // nothing was actually changed. Thus, the changed time needs to be | |
1350 // ignored when determining whether there are any actual changes in the | |
1351 // entity. | |
1352 if (!($field instanceof ChangedFieldItemList) && !$definition->isComputed()) { | |
1353 $items = $field->filterEmptyItems(); | |
1354 $original_items = $translation->get($field_name)->filterEmptyItems(); | |
1355 if (!$items->equals($original_items)) { | |
1356 return TRUE; | |
1357 } | |
1358 } | |
1359 } | |
1360 | |
1361 return FALSE; | |
1362 } | |
1363 | |
1364 } |