Newer
Older
Dries Buytaert
committed
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentEntityBase.
Dries Buytaert
committed
*/
namespace Drupal\Core\Entity;
Angie Byron
committed
use Drupal\Component\Utility\String;
use Drupal\Core\Entity\Plugin\DataType\EntityReference;
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
Dries Buytaert
committed
use Drupal\Core\TypedData\TypedDataInterface;
/**
* Implements Entity Field API specific enhancements to the Entity class.
Angie Byron
committed
*
* @ingroup entity_api
Dries Buytaert
committed
*/
abstract class ContentEntityBase extends Entity implements \IteratorAggregate, ContentEntityInterface {
Dries Buytaert
committed
/**
* 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;
Dries Buytaert
committed
/**
* The plain data values of the contained fields.
*
* This always holds the original, unchanged values of the entity. The values
* are keyed by language code, whereas LanguageInterface::LANGCODE_DEFAULT
* is used for values in default language.
Dries Buytaert
committed
*
* @todo: Add methods for getting original fields and for determining
* changes.
* @todo: Provide a better way for defining default values.
Dries Buytaert
committed
*
* @var array
*/
protected $values = array();
Dries Buytaert
committed
/**
* The array of fields, each being an instance of FieldItemListInterface.
Dries Buytaert
committed
*
* @var array
*/
protected $fields = array();
/**
* Local cache for field definitions.
Dries Buytaert
committed
*
* @see ContentEntityBase::getFieldDefinitions()
*
* @var array
*/
protected $fieldDefinitions;
/**
* Local cache for the available language objects.
*
* @var array
*/
protected $languages;
/**
* 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 = LanguageInterface::LANGCODE_DEFAULT;
/**
* Local cache for the default language code.
*
* @var string
*/
protected $defaultLangcode;
/**
* 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;
/**
* 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;
Alex Pott
committed
/**
* Holds entity keys like the ID, bundle and revision ID.
*
* @var array
*/
protected $entityKeys = array();
/**
* The instantiated entity data definition.
*
* @var \Drupal\Core\Entity\TypedData\EntityDataDefinition
*/
protected $dataDefinition;
/**
* Overrides Entity::__construct().
*/
public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = array()) {
$this->entityTypeId = $entity_type;
Alex Pott
committed
$this->entityKeys['bundle'] = $bundle ? $bundle : $this->entityTypeId;
$this->languages = $this->languageManager()->getLanguages(LanguageInterface::STATE_ALL);
foreach ($values as $key => $value) {
Angie Byron
committed
// If the key matches an existing property set the value to the property
Alex Pott
committed
// to set properties like isDefaultRevision.
// @todo: Should this be converted somehow?
if (property_exists($this, $key) && isset($value[LanguageInterface::LANGCODE_DEFAULT])) {
$this->$key = $value[LanguageInterface::LANGCODE_DEFAULT];
Angie Byron
committed
}
Alex Pott
committed
}
$this->values = $values;
foreach ($this->getEntityType()->getKeys() as $key => $field_name) {
if (isset($this->values[$field_name])) {
if (is_array($this->values[$field_name]) && isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) {
if (is_array($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) {
if (isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'])) {
$this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'];
}
}
else {
$this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT];
}
Alex Pott
committed
}
}
}
// Initialize translations. Ensure we have at least an entry for the default
// language.
$data = array('status' => static::TRANSLATION_EXISTING);
$this->translations[LanguageInterface::LANGCODE_DEFAULT] = $data;
$this->setDefaultLangcode();
if ($translations) {
foreach ($translations as $langcode) {
if ($langcode != $this->defaultLangcode && $langcode != LanguageInterface::LANGCODE_DEFAULT) {
$this->translations[$langcode] = $data;
}
}
}
}
Alex Pott
committed
/**
* Returns the typed data manager.
*
* @return \Drupal\Core\TypedData\TypedDataManager
*/
protected function typedDataManager() {
return \Drupal::typedDataManager();
}
catch
committed
/**
* {@inheritdoc}
*/
Angie Byron
committed
public function postCreate(EntityStorageInterface $storage) {
catch
committed
$this->newRevision = TRUE;
}
/**
* {@inheritdoc}
*/
public function setNewRevision($value = TRUE) {
catch
committed
if (!$this->getEntityType()->hasKey('revision')) {
throw new \LogicException(String::format('Entity type @entity_type does support revisions.'));
}
if ($value && !$this->newRevision) {
// When saving a new revision, set any existing revision ID to NULL so as
// to ensure that a new revision will actually be created.
$this->set($this->getEntityType()->getKey('revision'), NULL);
}
$this->newRevision = $value;
}
/**
* {@inheritdoc}
*/
public function isNewRevision() {
return $this->newRevision || ($this->getEntityType()->hasKey('revision') && !$this->getRevisionId());
}
/**
* {@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() {
Alex Pott
committed
return $this->getEntityKey('revision');
}
/**
* {@inheritdoc}
*/
public function isTranslatable() {
Alex Pott
committed
$bundles = $this->entityManager()->getBundleInfo($this->entityTypeId);
return !empty($bundles[$this->bundle()]['translatable']);
}
/**
* {@inheritdoc}
*/
catch
committed
public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
}
/**
* {@inheritdoc}
*/
public function getDataDefinition() {
Alex Pott
committed
if (!$this->dataDefinition) {
$this->dataDefinition = EntityDataDefinition::create($this->getEntityTypeId());
$this->dataDefinition->setBundles(array($this->bundle()));
}
return $this->dataDefinition;
}
/**
* {@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->toArray();
}
/**
* {@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.
foreach ($value as $field_name => $field_value) {
$this->set($field_name, $field_value, $notify);
}
}
/**
* {@inheritdoc}
*/
public function getString() {
Alex Pott
committed
return (string) $this->label();
}
/**
* {@inheritdoc}
*/
public function validate() {
Alex Pott
committed
return $this->typedDataManager()->getValidator()->validate($this);
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
}
/**
* {@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.
}
/**
* Clear entity translation object cache to remove stale references.
*/
protected function clearTranslationCache() {
foreach ($this->translations as &$translation) {
unset($translation['entity']);
}
}
/**
* {@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;
Alex Pott
committed
$this->dataDefinition = NULL;
$this->clearTranslationCache();
Alex Pott
committed
return parent::__sleep();
}
Dries Buytaert
committed
/**
* {@inheritdoc}
Dries Buytaert
committed
*/
public function id() {
Alex Pott
committed
return $this->getEntityKey('id');
}
/**
* {@inheritdoc}
*/
public function bundle() {
Alex Pott
committed
return $this->getEntityKey('bundle');
Dries Buytaert
committed
}
/**
Alex Pott
committed
* {inheritdoc}
Dries Buytaert
committed
*/
public function uuid() {
Alex Pott
committed
return $this->getEntityKey('uuid');
Dries Buytaert
committed
}
/**
* {@inheritdoc}
*/
public function hasField($field_name) {
return (bool) $this->getFieldDefinition($field_name);
}
Dries Buytaert
committed
/**
* {@inheritdoc}
Dries Buytaert
committed
*/
public function get($property_name) {
if (!isset($this->fields[$property_name][$this->activeLangcode])) {
return $this->getTranslatedField($property_name, $this->activeLangcode);
Dries Buytaert
committed
}
return $this->fields[$property_name][$this->activeLangcode];
Dries Buytaert
committed
}
/**
* Gets a translated field.
*
Alex Pott
committed
* @return \Drupal\Core\Field\FieldItemListInterface
Dries Buytaert
committed
*/
protected function getTranslatedField($name, $langcode) {
if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
$message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
Alex Pott
committed
throw new \InvalidArgumentException(String::format($message, array('@langcode' => $this->activeLangcode)));
}
// Populate $this->fields to speed-up further look-ups and to keep track of
// fields objects, possibly holding changes to field values.
if (!isset($this->fields[$name][$langcode])) {
$definition = $this->getFieldDefinition($name);
Dries Buytaert
committed
if (!$definition) {
Angie Byron
committed
throw new \InvalidArgumentException('Field ' . String::checkPlain($name) . ' is unknown.');
Dries Buytaert
committed
}
// Non-translatable fields are always stored with
// LanguageInterface::LANGCODE_DEFAULT as key.
Alex Pott
committed
$default = $langcode == LanguageInterface::LANGCODE_DEFAULT;
Alex Pott
committed
if (!$default && !$definition->isTranslatable()) {
if (!isset($this->fields[$name][LanguageInterface::LANGCODE_DEFAULT])) {
$this->fields[$name][LanguageInterface::LANGCODE_DEFAULT] = $this->getTranslatedField($name, LanguageInterface::LANGCODE_DEFAULT);
}
$this->fields[$name][$langcode] = &$this->fields[$name][LanguageInterface::LANGCODE_DEFAULT];
Dries Buytaert
committed
}
else {
$value = NULL;
if (isset($this->values[$name][$langcode])) {
$value = $this->values[$name][$langcode];
}
$field = \Drupal::typedDataManager()->getPropertyInstance($this, $name, $value);
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 : LanguageInterface::LANGCODE_NOT_SPECIFIED;
}
else {
$field_langcode = $langcode;
}
$field->setLangcode($field_langcode);
$this->fields[$name][$langcode] = $field;
Dries Buytaert
committed
}
}
return $this->fields[$name][$langcode];
Dries Buytaert
committed
}
/**
* {@inheritdoc}
Dries Buytaert
committed
*/
public function set($name, $value, $notify = TRUE) {
Alex Pott
committed
// If default language or an entity key changes we need to react to that.
$notify = $name == 'langcode' || in_array($name, $this->getEntityType()->getKeys());
$this->get($name)->setValue($value, $notify);
Dries Buytaert
committed
}
/**
* {@inheritdoc}
Dries Buytaert
committed
*/
public function getProperties($include_computed = FALSE) {
$properties = array();
foreach ($this->getFieldDefinitions() as $name => $definition) {
Angie Byron
committed
if ($include_computed || !$definition->isComputed()) {
Dries Buytaert
committed
$properties[$name] = $this->get($name);
}
}
return $properties;
}
/**
* {@inheritdoc}
Dries Buytaert
committed
*/
public function getIterator() {
Angie Byron
committed
return new \ArrayIterator($this->getProperties());
Dries Buytaert
committed
}
/**
* {@inheritdoc}
Dries Buytaert
committed
*/
public function getFieldDefinition($name) {
if (!isset($this->fieldDefinitions)) {
$this->getFieldDefinitions();
}
if (isset($this->fieldDefinitions[$name])) {
return $this->fieldDefinitions[$name];
Dries Buytaert
committed
}
}
/**
* {@inheritdoc}
Dries Buytaert
committed
*/
public function getFieldDefinitions() {
if (!isset($this->fieldDefinitions)) {
$this->fieldDefinitions = \Drupal::entityManager()->getFieldDefinitions($this->entityTypeId, $this->bundle());
}
return $this->fieldDefinitions;
Dries Buytaert
committed
}
/**
* {@inheritdoc}
Dries Buytaert
committed
*/
public function toArray() {
Dries Buytaert
committed
$values = array();
foreach ($this->getProperties() as $name => $property) {
$values[$name] = $property->getValue();
}
return $values;
}
/**
* {@inheritdoc}
Dries Buytaert
committed
*/
public function isEmpty() {
if (!$this->isNew()) {
return FALSE;
}
foreach ($this->getProperties() as $property) {
if ($property->getValue() !== NULL) {
return FALSE;
}
}
return TRUE;
}
/**
* {@inheritdoc}
*/
Alex Pott
committed
public function access($operation, AccountInterface $account = NULL) {
if ($operation == 'create') {
Alex Pott
committed
return $this->entityManager()
->getAccessControlHandler($this->entityTypeId)
Angie Byron
committed
->createAccess($this->bundle(), $account);
}
Alex Pott
committed
return $this->entityManager()
->getAccessControlHandler($this->entityTypeId)
Angie Byron
committed
->access($this, $operation, $this->activeLangcode, $account);
}
/**
* {@inheritdoc}
Dries Buytaert
committed
*/
public function language() {
$language = NULL;
if ($this->activeLangcode != LanguageInterface::LANGCODE_DEFAULT) {
if (!isset($this->languages[$this->activeLangcode])) {
$this->languages += $this->languageManager()->getLanguages(LanguageInterface::STATE_ALL);
}
$language = $this->languages[$this->activeLangcode];
}
else {
// @todo Avoid this check by getting the language from the language
// manager directly in https://www.drupal.org/node/2303877.
if (!isset($this->languages[$this->defaultLangcode])) {
$this->languages += $this->languageManager()->getLanguages(LanguageInterface::STATE_ALL);
}
$language = $this->languages[$this->defaultLangcode];
}
return $language;
}
/**
* Populates the local cache for the default language code.
*/
protected function setDefaultLangcode() {
// Get the language code if the property exists.
if ($this->hasField('langcode') && ($item = $this->get('langcode')) && isset($item->language)) {
$this->defaultLangcode = $item->language->id;
}
if (empty($this->defaultLangcode)) {
// Make sure we return a proper language object, if the entity has a
// langcode field, default to the site's default language.
if ($this->hasField('langcode')) {
$this->defaultLangcode = $this->languageManager()->getDefaultLanguage()->getId();
}
else {
$this->defaultLangcode = LanguageInterface::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'][LanguageInterface::LANGCODE_DEFAULT]->setLangcode($this->defaultLangcode);
}
}
/**
* Updates language for already instantiated fields.
*/
protected function updateFieldLangcodes($langcode) {
foreach ($this->fields as $name => $items) {
if (!empty($items[LanguageInterface::LANGCODE_DEFAULT])) {
$items[LanguageInterface::LANGCODE_DEFAULT]->setLangcode($langcode);
}
}
}
/**
* {@inheritdoc}
*/
public function onChange($name) {
if ($name == 'langcode') {
$this->setDefaultLangcode();
if (isset($this->translations[$this->defaultLangcode])) {
Alex Pott
committed
$message = String::format('A translation already exists for the specified language (@langcode).', array('@langcode' => $this->defaultLangcode));
throw new \InvalidArgumentException($message);
}
$this->updateFieldLangcodes($this->defaultLangcode);
}
Alex Pott
committed
// Check if the changed name is the value of an entity key and if the value
// of that is currently cached, if so, reset it. Exclude the bundle from
// that check, as it ready only and must not change, unsetting it could
// lead to recursions.
if ($key = array_search($name, $this->getEntityType()->getKeys())) {
if (isset($this->entityKeys[$key]) && $key != 'bundle') {
unset($this->entityKeys[$key]);
}
}
Dries Buytaert
committed
}
/**
* {@inheritdoc}
Dries Buytaert
committed
*
* @return \Drupal\Core\Entity\ContentEntityInterface
*/
public function getTranslation($langcode) {
// Ensure we always use the default language code when dealing with the
// original entity language.
if ($langcode != LanguageInterface::LANGCODE_DEFAULT && $langcode == $this->defaultLangcode) {
$langcode = LanguageInterface::LANGCODE_DEFAULT;
Dries Buytaert
committed
}
// Populate entity translation object cache so it will be available for all
// translation objects.
if ($langcode == $this->activeLangcode) {
$this->translations[$langcode]['entity'] = $this;
Dries Buytaert
committed
}
// 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;
Dries Buytaert
committed
}
else {
// If we were given a valid language and there is no translation for it,
// we return a new one.
if (isset($this->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->languages[$this->defaultLangcode]->locked) && empty($this->languages[$langcode]->locked) ? $this->addTranslation($langcode) : $this;
}
}
}
if (empty($translation)) {
$message = 'Invalid translation language (@langcode) specified.';
Alex Pott
committed
throw new \InvalidArgumentException(String::format($message, array('@langcode' => $langcode)));
Dries Buytaert
committed
}
Dries Buytaert
committed
return $translation;
}
/**
* {@inheritdoc}
Dries Buytaert
committed
*/
public function getUntranslated() {
return $this->getTranslation(LanguageInterface::LANGCODE_DEFAULT);
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
}
/**
* 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->enforceIsNew = &$this->enforceIsNew;
$translation->translationInitialize = FALSE;
Alex Pott
committed
// The label is the only entity key that can change based on the language,
// so unset that in case it is currently set.
unset($translation->entityKeys['label']);
return $translation;
}
/**
* {@inheritdoc}
*/
public function hasTranslation($langcode) {
if ($langcode == $this->defaultLangcode) {
$langcode = LanguageInterface::LANGCODE_DEFAULT;
return !empty($this->translations[$langcode]['status']);
}
/**
* {@inheritdoc}
*/
public function addTranslation($langcode, array $values = array()) {
if (!isset($this->languages[$langcode]) || $this->hasTranslation($langcode)) {
$message = 'Invalid translation language (@langcode) specified.';
Alex Pott
committed
throw new \InvalidArgumentException(String::format($message, array('@langcode' => $langcode)));
}
// Instantiate a new empty entity so default values will be populated in the
// specified language.
Alex Pott
committed
$entity_type = $this->getEntityType();
Alex Pott
committed
$default_values = array($entity_type->getKey('bundle') => $this->bundle(), 'langcode' => $langcode);
Alex Pott
committed
$entity = $this->entityManager()
catch
committed
->getStorage($this->getEntityTypeId())
->create($default_values);
foreach ($entity as $name => $field) {
if (!isset($values[$name]) && !$field->isEmpty()) {
$values[$name] = $field->value;
}
}
$this->translations[$langcode]['status'] = static::TRANSLATION_CREATED;
$translation = $this->getTranslation($langcode);
$definitions = $translation->getFieldDefinitions();
foreach ($values as $name => $value) {
Alex Pott
committed
if (isset($definitions[$name]) && $definitions[$name]->isTranslatable()) {
$translation->$name = $value;
}
}
return $translation;
}
/**
* {@inheritdoc}
*/
public function removeTranslation($langcode) {
if (isset($this->translations[$langcode]) && $langcode != LanguageInterface::LANGCODE_DEFAULT && $langcode != $this->defaultLangcode) {
foreach ($this->getFieldDefinitions() as $name => $definition) {
Alex Pott
committed
if ($definition->isTranslatable()) {
unset($this->values[$name][$langcode]);
unset($this->fields[$name][$langcode]);
}
Dries Buytaert
committed
}
$this->translations[$langcode]['status'] = static::TRANSLATION_REMOVED;
}
else {
$message = 'The specified translation (@langcode) cannot be removed.';
Alex Pott
committed
throw new \InvalidArgumentException(String::format($message, array('@langcode' => $langcode)));
}
}
/**
* {@inheritdoc}
*/
public function initTranslation($langcode) {
if ($langcode != LanguageInterface::LANGCODE_DEFAULT && $langcode != $this->defaultLangcode) {
$this->translations[$langcode]['status'] = static::TRANSLATION_EXISTING;
Dries Buytaert
committed
}
}
/**
* {@inheritdoc}
*/
public function getTranslationLanguages($include_default = TRUE) {
$translations = array_filter($this->translations, function($translation) { return $translation['status']; });
unset($translations[LanguageInterface::LANGCODE_DEFAULT]);
Dries Buytaert
committed
if ($include_default) {
$translations[$this->defaultLangcode] = TRUE;
Dries Buytaert
committed
}
// Now load language objects based upon translation langcodes.
return array_intersect_key($this->languages, $translations);
Dries Buytaert
committed
}
/**
* Overrides Entity::translations().
Dries Buytaert
committed
*
* @todo: Remove once Entity::translations() gets removed.
Dries Buytaert
committed
*/
public function translations() {
return $this->getTranslationLanguages(FALSE);
Dries Buytaert
committed
}
/**
* Updates the original values with the interim changes.
*/
public function updateOriginalValues() {
if (!$this->fields) {
return;
}
foreach ($this->getFieldDefinitions() as $name => $definition) {
Angie Byron
committed
if (!$definition->isComputed() && !empty($this->fields[$name])) {
foreach ($this->fields[$name] as $langcode => $item) {
$item->filterEmptyItems();
$this->values[$name][$langcode] = $item->getValue();
}
Dries Buytaert
committed
}
}
}
/**
* Implements the magic method for getting object properties.
Dries Buytaert
committed
*
Alex Pott
committed
* @todo: A lot of code still uses non-fields (e.g. $entity->content in view
* builders) by reference. Clean that up.
Dries Buytaert
committed
*/
public function &__get($name) {
// 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.
if (isset($this->fields[$name][$this->activeLangcode])) {
return $this->fields[$name][$this->activeLangcode];
Dries Buytaert
committed
}
// Inline getFieldDefinition() to speed up things.
if (!isset($this->fieldDefinitions)) {
$this->getFieldDefinitions();
}
if (isset($this->fieldDefinitions[$name])) {
$return = $this->getTranslatedField($name, $this->activeLangcode);
Dries Buytaert
committed
return $return;
}
// Else directly read/write plain values. That way, non-field entity
// properties can always be accessed directly.
if (!isset($this->values[$name])) {
$this->values[$name] = NULL;
Dries Buytaert
committed
}
return $this->values[$name];
Dries Buytaert
committed
}
/**
* Implements the magic method for setting object properties.
*
* Uses default language always.
Dries Buytaert
committed
*/
public function __set($name, $value) {
// Support setting values via property objects.
catch
committed
if ($value instanceof TypedDataInterface && !$value instanceof EntityInterface) {
Dries Buytaert
committed
$value = $value->getValue();
}
// 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.
if (isset($this->fields[$name][$this->activeLangcode])) {
$this->fields[$name][$this->activeLangcode]->setValue($value);
Dries Buytaert
committed
}
elseif ($this->hasField($name)) {
$this->getTranslatedField($name, $this->activeLangcode)->setValue($value);
Dries Buytaert
committed
}
// The translations array is unset when cloning the entity object, we just
// need to restore it.
elseif ($name == 'translations') {
$this->translations = $value;
}
// Else directly read/write plain values. That way, fields not yet converted
// to the entity field API can always be directly accessed.
Dries Buytaert
committed
else {
$this->values[$name] = $value;
Dries Buytaert
committed
}
}
/**
* Implements the magic method for isset().
Dries Buytaert
committed
*/
public function __isset($name) {
if ($this->hasField($name)) {
return $this->get($name)->getValue() !== NULL;
Dries Buytaert
committed
}
else {
return isset($this->values[$name]);
Dries Buytaert
committed
}
}
/**
* Implements the magic method for unset.
Dries Buytaert
committed
*/
public function __unset($name) {
if ($this->hasField($name)) {
$this->get($name)->setValue(NULL);
Dries Buytaert
committed
}
else {
unset($this->values[$name]);
Dries Buytaert
committed
}
}
/**
* Overrides Entity::createDuplicate().
*/
public function createDuplicate() {
if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
$message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
Alex Pott
committed
throw new \InvalidArgumentException(String::format($message, array('@langcode' => $this->activeLangcode)));
}
Dries Buytaert
committed
$duplicate = clone $this;
Alex Pott
committed
$entity_type = $this->getEntityType();
$duplicate->{$entity_type->getKey('id')}->value = NULL;
Alex Pott
committed
$duplicate->enforceIsNew();
Dries Buytaert
committed
// Check if the entity type supports UUIDs and generate a new one if so.
Alex Pott
committed
if ($entity_type->hasKey('uuid')) {
Alex Pott
committed
$duplicate->{$entity_type->getKey('uuid')}->value = $this->uuidGenerator()->generate();
Dries Buytaert
committed
}
Angie Byron
committed
// Check whether the entity type supports revisions and initialize it if so.
catch
committed
if ($entity_type->isRevisionable()) {
Alex Pott
committed
$duplicate->{$entity_type->getKey('revision')}->value = NULL;
Angie Byron
committed
}
Alex Pott
committed
$duplicate->entityKeys = array();
Dries Buytaert
committed
return $duplicate;
}
/**
* Magic method: Implements a deep clone.
Dries Buytaert
committed
*/
public function __clone() {
// 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) {
$definitions = $this->getFieldDefinitions();
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.
Alex Pott
committed
if (!$definitions[$name]->isTranslatable() && count($values) > 1) {
$values = array_intersect_key($values, array(LanguageInterface::LANGCODE_DEFAULT => TRUE));
}
foreach ($values as $langcode => $items) {
$this->fields[$name][$langcode] = clone $items;
$this->fields[$name][$langcode]->setContext($name, $this);
}
Dries Buytaert
committed
}
// Ensure the translations array is actually cloned by overwriting the
// original reference with one pointing to a copy of the array.
$this->clearTranslationCache();
$translations = $this->translations;
$this->translations = &$translations;
Dries Buytaert
committed
}
}
/**
* {@inheritdoc}
*/
public function label() {
$label = NULL;
Alex Pott
committed
$entity_type = $this->getEntityType();
Alex Pott
committed
if (($label_callback = $entity_type->getLabelCallback()) && is_callable($label_callback)) {