abstract class ContentEntityBase in Zircon Profile 8
Same name and namespace in other branches
- 8.0 core/lib/Drupal/Core/Entity/ContentEntityBase.php \Drupal\Core\Entity\ContentEntityBase
Implements Entity Field API specific enhancements to the Entity class.
Hierarchy
- class \Drupal\Core\Entity\Entity implements EntityInterface uses RefinableCacheableDependencyTrait, DependencySerializationTrait
- class \Drupal\Core\Entity\ContentEntityBase implements \Drupal\Core\Entity\IteratorAggregate, ContentEntityInterface
Expanded class hierarchy of ContentEntityBase
Related topics
13 files declare their use of ContentEntityBase
- BlockContent.php in core/
modules/ block_content/ src/ Entity/ BlockContent.php - Contains \Drupal\block_content\Entity\BlockContent.
- Comment.php in core/
modules/ comment/ src/ Entity/ Comment.php - Contains \Drupal\comment\Entity\Comment.
- EntityTest.php in core/
modules/ system/ tests/ modules/ entity_test/ src/ Entity/ EntityTest.php - Contains \Drupal\entity_test\Entity\EntityTest.
- Feed.php in core/
modules/ aggregator/ src/ Entity/ Feed.php - Contains \Drupal\aggregator\Entity\Feed.
- File.php in core/
modules/ file/ src/ Entity/ File.php - Contains \Drupal\file\Entity\File.
File
- core/
lib/ Drupal/ Core/ Entity/ ContentEntityBase.php, line 22 - Contains \Drupal\Core\Entity\ContentEntityBase.
Namespace
Drupal\Core\EntityView source
abstract class ContentEntityBase extends Entity implements \IteratorAggregate, ContentEntityInterface {
/**
* Status code identifying a removed translation.
*/
const TRANSLATION_REMOVED = 0;
/**
* Status code identifying an existing translation.
*/
const TRANSLATION_EXISTING = 1;
/**
* Status code identifying a newly created translation.
*/
const TRANSLATION_CREATED = 2;
/**
* 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.
*
* @todo: Add methods for getting original fields and for determining
* changes.
* @todo: Provide a better way for defining default values.
*
* @var array
*/
protected $values = array();
/**
* The array of fields, each being an instance of FieldItemListInterface.
*
* @var array
*/
protected $fields = array();
/**
* Local cache for field definitions.
*
* @see ContentEntityBase::getFieldDefinitions()
*
* @var array
*/
protected $fieldDefinitions;
/**
* Local cache for the available language objects.
*
* @var \Drupal\Core\Language\LanguageInterface[]
*/
protected $languages;
/**
* The language entity key.
*
* @var string
*/
protected $langcodeKey;
/**
* The default langcode entity key.
*
* @var string
*/
protected $defaultLangcodeKey;
/**
* 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 containing 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;
/**
* Holds untranslatable entity keys such as the ID, bundle, and revision ID.
*
* @var array
*/
protected $entityKeys = array();
/**
* Holds translatable entity keys such as the label.
*
* @var array
*/
protected $translatableEntityKeys = array();
/**
* Whether entity validation was performed.
*
* @var bool
*/
protected $validated = FALSE;
/**
* Whether entity validation is required before saving the entity.
*
* @var bool
*/
protected $validationRequired = FALSE;
/**
* {@inheritdoc}
*/
public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = array()) {
$this->entityTypeId = $entity_type;
$this->entityKeys['bundle'] = $bundle ? $bundle : $this->entityTypeId;
$this->langcodeKey = $this
->getEntityType()
->getKey('langcode');
$this->defaultLangcodeKey = $this
->getEntityType()
->getKey('default_langcode');
foreach ($values as $key => $value) {
// If the key matches an existing property set the value to the property
// 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];
}
}
$this->values = $values;
foreach ($this
->getEntityType()
->getKeys() as $key => $field_name) {
if (isset($this->values[$field_name])) {
if (is_array($this->values[$field_name])) {
// We store untranslatable fields into an entity key without using a
// langcode key.
if (!$this
->getFieldDefinition($field_name)
->isTranslatable()) {
if (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];
}
}
}
else {
// We save translatable fields such as the publishing status of a node
// into an entity key array keyed by langcode as a performance
// optimization, so we don't have to go through TypedData when we
// need these values.
foreach ($this->values[$field_name] as $langcode => $field_value) {
if (is_array($this->values[$field_name][$langcode])) {
if (isset($this->values[$field_name][$langcode][0]['value'])) {
$this->translatableEntityKeys[$key][$langcode] = $this->values[$field_name][$langcode][0]['value'];
}
}
else {
$this->translatableEntityKeys[$key][$langcode] = $this->values[$field_name][$langcode];
}
}
}
}
}
}
// 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;
}
}
}
}
/**
* {@inheritdoc}
*/
protected function getLanguages() {
if (empty($this->languages)) {
$this->languages = $this
->languageManager()
->getLanguages(LanguageInterface::STATE_ALL);
// If the entity references a language that is not or no longer available,
// we return a mock language object to avoid disrupting the consuming
// code.
if (!isset($this->languages[$this->defaultLangcode])) {
$this->languages[$this->defaultLangcode] = new Language(array(
'id' => $this->defaultLangcode,
));
}
}
return $this->languages;
}
/**
* {@inheritdoc}
*/
public function postCreate(EntityStorageInterface $storage) {
$this->newRevision = TRUE;
}
/**
* {@inheritdoc}
*/
public function setNewRevision($value = TRUE) {
if (!$this
->getEntityType()
->hasKey('revision')) {
throw new \LogicException("Entity type {$this->getEntityTypeId()} does not 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);
// Make sure that the flag tracking which translations are affected by the
// current revision is reset.
foreach ($this->translations as $langcode => $data) {
// But skip removed translations.
if ($this
->hasTranslation($langcode)) {
$this
->getTranslation($langcode)
->setRevisionTranslationAffected(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 isRevisionTranslationAffected() {
$field_name = 'revision_translation_affected';
return $this
->hasField($field_name) ? $this
->get($field_name)->value : TRUE;
}
/**
* {@inheritdoc}
*/
public function setRevisionTranslationAffected($affected) {
$field_name = 'revision_translation_affected';
if ($this
->hasField($field_name)) {
$this
->set($field_name, $affected);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function isDefaultTranslation() {
return $this->activeLangcode === LanguageInterface::LANGCODE_DEFAULT;
}
/**
* {@inheritdoc}
*/
public function getRevisionId() {
return $this
->getEntityKey('revision');
}
/**
* {@inheritdoc}
*/
public function isTranslatable() {
// Check that the bundle is translatable, the entity has a language defined
// and if we have more than one language on the site.
$bundles = $this
->entityManager()
->getBundleInfo($this->entityTypeId);
return !empty($bundles[$this
->bundle()]['translatable']) && !$this
->getUntranslated()
->language()
->isLocked() && $this
->languageManager()
->isMultilingual();
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
// An entity requiring validation should not be saved if it has not been
// actually validated.
if ($this->validationRequired && !$this->validated) {
// @todo Make this an assertion in https://www.drupal.org/node/2408013.
throw new \LogicException('Entity validation was skipped.');
}
else {
$this->validated = FALSE;
}
parent::preSave($storage);
}
/**
* {@inheritdoc}
*/
public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
}
/**
* {@inheritdoc}
*/
public function validate() {
$this->validated = TRUE;
$violations = $this
->getTypedData()
->validate();
return new EntityConstraintViolationList($this, iterator_to_array($violations));
}
/**
* {@inheritdoc}
*/
public function isValidationRequired() {
return (bool) $this->validationRequired;
}
/**
* {@inheritdoc}
*/
public function setValidationRequired($required) {
$this->validationRequired = $required;
return $this;
}
/**
* 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;
$this->languages = NULL;
$this
->clearTranslationCache();
return parent::__sleep();
}
/**
* {@inheritdoc}
*/
public function id() {
return $this
->getEntityKey('id');
}
/**
* {@inheritdoc}
*/
public function bundle() {
return $this
->getEntityKey('bundle');
}
/**
* {@inheritdoc}
*/
public function uuid() {
return $this
->getEntityKey('uuid');
}
/**
* {@inheritdoc}
*/
public function hasField($field_name) {
return (bool) $this
->getFieldDefinition($field_name);
}
/**
* {@inheritdoc}
*/
public function get($field_name) {
if (!isset($this->fields[$field_name][$this->activeLangcode])) {
return $this
->getTranslatedField($field_name, $this->activeLangcode);
}
return $this->fields[$field_name][$this->activeLangcode];
}
/**
* Gets a translated field.
*
* @return \Drupal\Core\Field\FieldItemListInterface
*/
protected function getTranslatedField($name, $langcode) {
if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
throw new \InvalidArgumentException("The entity object refers to a removed translation ({$this->activeLangcode}) and cannot be manipulated.");
}
// 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);
if (!$definition) {
throw new \InvalidArgumentException("Field {$name} is unknown.");
}
// Non-translatable fields are always stored with
// LanguageInterface::LANGCODE_DEFAULT as key.
$default = $langcode == LanguageInterface::LANGCODE_DEFAULT;
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];
}
else {
$value = NULL;
if (isset($this->values[$name][$langcode])) {
$value = $this->values[$name][$langcode];
}
$field = \Drupal::service('plugin.manager.field.field_type')
->createFieldItemList($this
->getTranslation($langcode), $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;
}
}
return $this->fields[$name][$langcode];
}
/**
* {@inheritdoc}
*/
public function set($name, $value, $notify = TRUE) {
// Assign the value on the child and overrule notify such that we get
// notified to handle changes afterwards. We can ignore notify as there is
// no parent to notify anyway.
$this
->get($name)
->setValue($value, TRUE);
return $this;
}
/**
* {@inheritdoc}
*/
public function getFields($include_computed = TRUE) {
$fields = array();
foreach ($this
->getFieldDefinitions() as $name => $definition) {
if ($include_computed || !$definition
->isComputed()) {
$fields[$name] = $this
->get($name);
}
}
return $fields;
}
/**
* {@inheritdoc}
*/
public function getTranslatableFields($include_computed = TRUE) {
$fields = [];
foreach ($this
->getFieldDefinitions() as $name => $definition) {
if (($include_computed || !$definition
->isComputed()) && $definition
->isTranslatable()) {
$fields[$name] = $this
->get($name);
}
}
return $fields;
}
/**
* {@inheritdoc}
*/
public function getIterator() {
return new \ArrayIterator($this
->getFields());
}
/**
* {@inheritdoc}
*/
public function getFieldDefinition($name) {
if (!isset($this->fieldDefinitions)) {
$this
->getFieldDefinitions();
}
if (isset($this->fieldDefinitions[$name])) {
return $this->fieldDefinitions[$name];
}
}
/**
* {@inheritdoc}
*/
public function getFieldDefinitions() {
if (!isset($this->fieldDefinitions)) {
$this->fieldDefinitions = $this
->entityManager()
->getFieldDefinitions($this->entityTypeId, $this
->bundle());
}
return $this->fieldDefinitions;
}
/**
* {@inheritdoc}
*/
public function toArray() {
$values = array();
foreach ($this
->getFields() as $name => $property) {
$values[$name] = $property
->getValue();
}
return $values;
}
/**
* {@inheritdoc}
*/
public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
if ($operation == 'create') {
return $this
->entityManager()
->getAccessControlHandler($this->entityTypeId)
->createAccess($this
->bundle(), $account, [], $return_as_object);
}
return $this
->entityManager()
->getAccessControlHandler($this->entityTypeId)
->access($this, $operation, $account, $return_as_object);
}
/**
* {@inheritdoc}
*/
public function language() {
$language = NULL;
if ($this->activeLangcode != LanguageInterface::LANGCODE_DEFAULT) {
if (!isset($this->languages[$this->activeLangcode])) {
$this
->getLanguages();
}
$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
->getLanguages();
}
$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.
// Try to read the value directly from the list of entity keys which got
// initialized in __construct(). This avoids creating a field item object.
if (isset($this->translatableEntityKeys['langcode'][$this->activeLangcode])) {
$this->defaultLangcode = $this->translatableEntityKeys['langcode'][$this->activeLangcode];
}
elseif ($this
->hasField($this->langcodeKey) && ($item = $this
->get($this->langcodeKey)) && isset($item->language)) {
$this->defaultLangcode = $item->language
->getId();
$this->translatableEntityKeys['langcode'][$this->activeLangcode] = $this->defaultLangcode;
}
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($this->langcodeKey)) {
$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[$this->langcodeKey])) {
$this->fields[$this->langcodeKey][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) {
// 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 ($key != 'bundle') {
if (isset($this->entityKeys[$key])) {
unset($this->entityKeys[$key]);
}
elseif (isset($this->translatableEntityKeys[$key][$this->activeLangcode])) {
unset($this->translatableEntityKeys[$key][$this->activeLangcode]);
}
}
}
switch ($name) {
case $this->langcodeKey:
if ($this
->isDefaultTranslation()) {
// Update the default internal language cache.
$this
->setDefaultLangcode();
if (isset($this->translations[$this->defaultLangcode])) {
$message = SafeMarkup::format('A translation already exists for the specified language (@langcode).', array(
'@langcode' => $this->defaultLangcode,
));
throw new \InvalidArgumentException($message);
}
$this
->updateFieldLangcodes($this->defaultLangcode);
}
else {
// @todo Allow the translation language to be changed. See
// https://www.drupal.org/node/2443989.
$items = $this
->get($this->langcodeKey);
if ($items->value != $this->activeLangcode) {
$items
->setValue($this->activeLangcode, FALSE);
$message = SafeMarkup::format('The translation language cannot be changed (@langcode).', array(
'@langcode' => $this->activeLangcode,
));
throw new \LogicException($message);
}
}
break;
case $this->defaultLangcodeKey:
// @todo Use a standard method to make the default_langcode field
// read-only. See https://www.drupal.org/node/2443991.
if (isset($this->values[$this->defaultLangcodeKey]) && $this
->get($this->defaultLangcodeKey)->value != $this
->isDefaultTranslation()) {
$this
->get($this->defaultLangcodeKey)
->setValue($this
->isDefaultTranslation(), FALSE);
$message = SafeMarkup::format('The default translation flag cannot be changed (@langcode).', array(
'@langcode' => $this->activeLangcode,
));
throw new \LogicException($message);
}
break;
}
}
/**
* {@inheritdoc}
*/
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;
}
// Populate entity translation object cache so it will be available for all
// translation objects.
if ($langcode == $this->activeLangcode) {
$this->translations[$langcode]['entity'] = $this;
}
// 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'];
}
elseif (isset($this->translations[$langcode])) {
$translation = $this
->initializeTranslation($langcode);
$this->translations[$langcode]['entity'] = $translation;
}
if (empty($translation)) {
throw new \InvalidArgumentException("Invalid translation language ({$langcode}) specified.");
}
return $translation;
}
/**
* {@inheritdoc}
*/
public function getUntranslated() {
return $this
->getTranslation(LanguageInterface::LANGCODE_DEFAULT);
}
/**
* 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->newRevision =& $this->newRevision;
$translation->entityKeys =& $this->entityKeys;
$translation->translatableEntityKeys =& $this->translatableEntityKeys;
$translation->translationInitialize = FALSE;
$translation->typedData = NULL;
return $translation;
}
/**
* {@inheritdoc}
*/
public function hasTranslation($langcode) {
if ($langcode == $this->defaultLangcode) {
$langcode = LanguageInterface::LANGCODE_DEFAULT;
}
return !empty($this->translations[$langcode]['status']);
}
/**
* {@inheritdoc}
*/
public function isNewTranslation() {
return $this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_CREATED;
}
/**
* {@inheritdoc}
*/
public function addTranslation($langcode, array $values = array()) {
// Make sure we do not attempt to create a translation if an invalid
// language is specified or the entity cannot be translated.
$this
->getLanguages();
if (!isset($this->languages[$langcode]) || $this
->hasTranslation($langcode) || $this->languages[$langcode]
->isLocked()) {
throw new \InvalidArgumentException("Invalid translation language ({$langcode}) specified.");
}
if ($this->languages[$this->defaultLangcode]
->isLocked()) {
throw new \InvalidArgumentException("The entity cannot be translated since it is language neutral ({$this->defaultLangcode}).");
}
// Initialize the translation object.
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$storage = $this
->entityManager()
->getStorage($this
->getEntityTypeId());
$this->translations[$langcode]['status'] = static::TRANSLATION_CREATED;
return $storage
->createTranslation($this, $langcode, $values);
}
/**
* {@inheritdoc}
*/
public function removeTranslation($langcode) {
if (isset($this->translations[$langcode]) && $langcode != LanguageInterface::LANGCODE_DEFAULT && $langcode != $this->defaultLangcode) {
foreach ($this
->getFieldDefinitions() as $name => $definition) {
if ($definition
->isTranslatable()) {
unset($this->values[$name][$langcode]);
unset($this->fields[$name][$langcode]);
}
}
$this->translations[$langcode]['status'] = static::TRANSLATION_REMOVED;
}
else {
throw new \InvalidArgumentException("The specified translation ({$langcode}) cannot be removed.");
}
}
/**
* {@inheritdoc}
*/
public function getTranslationLanguages($include_default = TRUE) {
$translations = array_filter($this->translations, function ($translation) {
return $translation['status'];
});
unset($translations[LanguageInterface::LANGCODE_DEFAULT]);
if ($include_default) {
$translations[$this->defaultLangcode] = TRUE;
}
// Now load language objects based upon translation langcodes.
return array_intersect_key($this
->getLanguages(), $translations);
}
/**
* Updates the original values with the interim changes.
*/
public function updateOriginalValues() {
if (!$this->fields) {
return;
}
foreach ($this
->getFieldDefinitions() as $name => $definition) {
if (!$definition
->isComputed() && !empty($this->fields[$name])) {
foreach ($this->fields[$name] as $langcode => $item) {
$item
->filterEmptyItems();
$this->values[$name][$langcode] = $item
->getValue();
}
}
}
}
/**
* Implements the magic method for getting object properties.
*
* @todo: A lot of code still uses non-fields (e.g. $entity->content in view
* builders) by reference. Clean that up.
*/
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];
}
// Inline getFieldDefinition() to speed things up.
if (!isset($this->fieldDefinitions)) {
$this
->getFieldDefinitions();
}
if (isset($this->fieldDefinitions[$name])) {
$return = $this
->getTranslatedField($name, $this->activeLangcode);
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;
}
return $this->values[$name];
}
/**
* Implements the magic method for setting object properties.
*
* Uses default language always.
*/
public function __set($name, $value) {
// Inline getFieldDefinition() to speed things up.
if (!isset($this->fieldDefinitions)) {
$this
->getFieldDefinitions();
}
// Handle Field API fields.
if (isset($this->fieldDefinitions[$name])) {
// Support setting values via property objects.
if ($value instanceof TypedDataInterface) {
$value = $value
->getValue();
}
// If a FieldItemList object already exists, set its value.
if (isset($this->fields[$name][$this->activeLangcode])) {
$this->fields[$name][$this->activeLangcode]
->setValue($value);
}
else {
$this
->getTranslatedField($name, $this->activeLangcode)
->setValue($value);
}
}
elseif ($name == 'translations') {
$this->translations = $value;
}
else {
$this->values[$name] = $value;
}
}
/**
* Implements the magic method for isset().
*/
public function __isset($name) {
// "Official" Field API fields are always set.
if ($this
->hasField($name)) {
return TRUE;
}
else {
return isset($this->values[$name]);
}
}
/**
* Implements the magic method for unset().
*/
public function __unset($name) {
// Unsetting a field means emptying it.
if ($this
->hasField($name)) {
$this
->get($name)
->setValue(array());
}
else {
unset($this->values[$name]);
}
}
/**
* {@inheritdoc}
*/
public function createDuplicate() {
if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
throw new \InvalidArgumentException("The entity object refers to a removed translation ({$this->activeLangcode}) and cannot be manipulated.");
}
$duplicate = clone $this;
$entity_type = $this
->getEntityType();
$duplicate->{$entity_type
->getKey('id')}->value = NULL;
$duplicate
->enforceIsNew();
// Check if the entity type supports UUIDs and generate a new one if so.
if ($entity_type
->hasKey('uuid')) {
$duplicate->{$entity_type
->getKey('uuid')}->value = $this
->uuidGenerator()
->generate();
}
// Check whether the entity type supports revisions and initialize it if so.
if ($entity_type
->isRevisionable()) {
$duplicate->{$entity_type
->getKey('revision')}->value = NULL;
}
return $duplicate;
}
/**
* Magic method: Implements a deep clone.
*/
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) {
// The translation is a different object, and needs its own TypedData
// adapter object.
$this->typedData = NULL;
$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.
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
->getTranslation($langcode)
->getTypedData());
}
}
// 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;
}
}
/**
* {@inheritdoc}
*/
public function label() {
$label = NULL;
$entity_type = $this
->getEntityType();
if (($label_callback = $entity_type
->getLabelCallback()) && is_callable($label_callback)) {
$label = call_user_func($label_callback, $this);
}
elseif ($label_key = $entity_type
->getKey('label')) {
$label = $this
->getEntityKey('label');
}
return $label;
}
/**
* {@inheritdoc}
*/
public function referencedEntities() {
$referenced_entities = array();
// Gather a list of referenced entities.
foreach ($this
->getFields() as $field_items) {
foreach ($field_items as $field_item) {
// Loop over all properties of a field item.
foreach ($field_item
->getProperties(TRUE) as $property) {
if ($property instanceof EntityReference && ($entity = $property
->getValue())) {
$referenced_entities[] = $entity;
}
}
}
}
return $referenced_entities;
}
/**
* Gets the value of the given entity key, if defined.
*
* @param string $key
* Name of the entity key, for example id, revision or bundle.
*
* @return mixed
* The value of the entity key, NULL if not defined.
*/
protected function getEntityKey($key) {
// If the value is known already, return it.
if (isset($this->entityKeys[$key])) {
return $this->entityKeys[$key];
}
if (isset($this->translatableEntityKeys[$key][$this->activeLangcode])) {
return $this->translatableEntityKeys[$key][$this->activeLangcode];
}
// Otherwise fetch the value by creating a field object.
$value = NULL;
if ($this
->getEntityType()
->hasKey($key)) {
$field_name = $this
->getEntityType()
->getKey($key);
$definition = $this
->getFieldDefinition($field_name);
$property = $definition
->getFieldStorageDefinition()
->getMainPropertyName();
$value = $this
->get($field_name)->{$property};
// Put it in the right array, depending on whether it is translatable.
if ($definition
->isTranslatable()) {
$this->translatableEntityKeys[$key][$this->activeLangcode] = $value;
}
else {
$this->entityKeys[$key] = $value;
}
}
else {
$this->entityKeys[$key] = $value;
}
return $value;
}
/**
* {@inheritdoc}
*/
public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
return array();
}
/**
* {@inheritdoc}
*/
public function hasTranslationChanges() {
if ($this
->isNew()) {
return TRUE;
}
// $this->original only exists during save. See
// \Drupal\Core\Entity\EntityStorageBase::save(). If it exists we re-use it
// here for performance reasons.
/** @var \Drupal\Core\Entity\ContentEntityBase $original */
$original = $this->original ? $this->original : NULL;
if (!$original) {
$id = $this
->getOriginalId() !== NULL ? $this
->getOriginalId() : $this
->id();
$original = $this
->entityManager()
->getStorage($this
->getEntityTypeId())
->loadUnchanged($id);
}
// If the current translation has just been added, we have a change.
$translated = count($this->translations) > 1;
if ($translated && !$original
->hasTranslation($this->activeLangcode)) {
return TRUE;
}
// Compare field item current values with the original ones to determine
// whether we have changes. If a field is not translatable and the entity is
// translated we skip it because, depending on the use case, it would make
// sense to mark all translations as changed or none of them. We skip also
// computed fields as comparing them with their original values might not be
// possible or be meaningless.
/** @var \Drupal\Core\Entity\ContentEntityBase $translation */
$translation = $original
->getTranslation($this->activeLangcode);
foreach ($this
->getFieldDefinitions() as $field_name => $definition) {
// @todo Avoid special-casing the following fields. See
// https://www.drupal.org/node/2329253.
if ($field_name == 'revision_translation_affected' || $field_name == 'revision_id') {
continue;
}
if (!$definition
->isComputed() && (!$translated || $definition
->isTranslatable())) {
$items = $this
->get($field_name)
->filterEmptyItems();
$original_items = $translation
->get($field_name)
->filterEmptyItems();
if (!$items
->equals($original_items)) {
return TRUE;
}
}
}
return FALSE;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ContentEntityBase:: |
protected | property | Language code identifying the entity active language. | |
ContentEntityBase:: |
protected | property | Local cache for the default language code. | |
ContentEntityBase:: |
protected | property | The default langcode entity key. | |
ContentEntityBase:: |
protected | property | Holds untranslatable entity keys such as the ID, bundle, and revision ID. | |
ContentEntityBase:: |
protected | property | Local cache for field definitions. | |
ContentEntityBase:: |
protected | property | The array of fields, each being an instance of FieldItemListInterface. | |
ContentEntityBase:: |
protected | property | Indicates whether this is the default revision. | |
ContentEntityBase:: |
protected | property | The language entity key. | |
ContentEntityBase:: |
protected | property | Local cache for the available language objects. | |
ContentEntityBase:: |
protected | property | Boolean indicating whether a new revision should be created on save. | |
ContentEntityBase:: |
protected | property | Holds translatable entity keys such as the label. | |
ContentEntityBase:: |
protected | property | A flag indicating whether a translation object is being initialized. | |
ContentEntityBase:: |
protected | property | An array of entity translation metadata. | |
ContentEntityBase:: |
protected | property | Whether entity validation was performed. | |
ContentEntityBase:: |
protected | property | Whether entity validation is required before saving the entity. | |
ContentEntityBase:: |
protected | property | The plain data values of the contained fields. | |
ContentEntityBase:: |
public | function |
Checks data value access. Overrides Entity:: |
1 |
ContentEntityBase:: |
public | function |
Adds a new translation to the translatable object. Overrides TranslatableInterface:: |
|
ContentEntityBase:: |
public | function |
Gets the bundle of the entity. Overrides Entity:: |
|
ContentEntityBase:: |
public static | function |
Provides field definitions for a specific bundle. Overrides FieldableEntityInterface:: |
3 |
ContentEntityBase:: |
protected | function | Clear entity translation object cache to remove stale references. | |
ContentEntityBase:: |
public | function |
Creates a duplicate of the entity. Overrides Entity:: |
1 |
ContentEntityBase:: |
public | function |
Gets a field item list. Overrides FieldableEntityInterface:: |
|
ContentEntityBase:: |
protected | function | Gets the value of the given entity key, if defined. | |
ContentEntityBase:: |
public | function |
Gets the definition of a contained field. Overrides FieldableEntityInterface:: |
|
ContentEntityBase:: |
public | function |
Gets an array of field definitions of all contained fields. Overrides FieldableEntityInterface:: |
|
ContentEntityBase:: |
public | function |
Gets an array of all field item lists. Overrides FieldableEntityInterface:: |
|
ContentEntityBase:: |
public | function | ||
ContentEntityBase:: |
protected | function | ||
ContentEntityBase:: |
public | function |
Gets the revision identifier of the entity. Overrides RevisionableInterface:: |
|
ContentEntityBase:: |
public | function |
Gets an array of field item lists for translatable fields. Overrides FieldableEntityInterface:: |
|
ContentEntityBase:: |
protected | function | Gets a translated field. | |
ContentEntityBase:: |
public | function |
Gets a translation of the data. Overrides TranslatableInterface:: |
|
ContentEntityBase:: |
public | function |
Returns the languages the data is translated to. Overrides TranslatableInterface:: |
|
ContentEntityBase:: |
public | function |
Returns the translatable object referring to the original language. Overrides TranslatableInterface:: |
|
ContentEntityBase:: |
public | function |
Determines whether the entity has a field with the given name. Overrides FieldableEntityInterface:: |
|
ContentEntityBase:: |
public | function |
Returns TRUE there is a translation for the given language code. Overrides TranslatableInterface:: |
|
ContentEntityBase:: |
public | function |
Determines if the current translation of the entity has unsaved changes. Overrides ContentEntityInterface:: |
|
ContentEntityBase:: |
public | function |
Gets the identifier. Overrides Entity:: |
|
ContentEntityBase:: |
protected | function | Instantiates a translation object for an existing translation. | |
ContentEntityBase:: |
public | function |
Checks if this entity is the default revision. Overrides RevisionableInterface:: |
|
ContentEntityBase:: |
public | function |
Checks whether the translation is the default one. Overrides TranslatableInterface:: |
|
ContentEntityBase:: |
public | function |
Determines whether a new revision should be created on save. Overrides RevisionableInterface:: |
|
ContentEntityBase:: |
public | function |
Checks whether the translation is new. Overrides TranslatableInterface:: |
|
ContentEntityBase:: |
public | function |
Checks whether the current translation is affected by the current revision. Overrides ContentEntityInterface:: |
|
ContentEntityBase:: |
public | function |
Returns the translation support status. Overrides TranslatableInterface:: |
|
ContentEntityBase:: |
public | function |
Checks whether entity validation is required before saving the entity. Overrides FieldableEntityInterface:: |
|
ContentEntityBase:: |
public | function |
Gets the label of the entity. Overrides Entity:: |
3 |
ContentEntityBase:: |
public | function |
Gets the language of the entity. Overrides Entity:: |
|
ContentEntityBase:: |
public | function |
Reacts to changes to a field. Overrides FieldableEntityInterface:: |
|
ContentEntityBase:: |
public | function |
Acts on an entity after it is created but before hooks are invoked. Overrides Entity:: |
|
ContentEntityBase:: |
public | function |
Acts on an entity before the presave hook is invoked. Overrides Entity:: |
5 |
ContentEntityBase:: |
public | function |
Acts on a revision before it gets saved. Overrides RevisionableInterface:: |
2 |
ContentEntityBase:: |
public | function |
Gets a list of entities referenced by this entity. Overrides Entity:: |
1 |
ContentEntityBase:: |
public | function |
Removes the translation identified by the given language code. Overrides TranslatableInterface:: |
|
ContentEntityBase:: |
public | function |
Sets a field value. Overrides FieldableEntityInterface:: |
|
ContentEntityBase:: |
protected | function | Populates the local cache for the default language code. | |
ContentEntityBase:: |
public | function |
Enforces an entity to be saved as a new revision. Overrides RevisionableInterface:: |
|
ContentEntityBase:: |
public | function |
Marks the current revision translation as affected. Overrides ContentEntityInterface:: |
|
ContentEntityBase:: |
public | function |
Sets whether entity validation is required before saving the entity. Overrides FieldableEntityInterface:: |
|
ContentEntityBase:: |
public | function |
Gets an array of all property values. Overrides Entity:: |
|
ContentEntityBase:: |
constant | Status code identifying a newly created translation. | ||
ContentEntityBase:: |
constant | Status code identifying an existing translation. | ||
ContentEntityBase:: |
constant | Status code identifying a removed translation. | ||
ContentEntityBase:: |
protected | function | Updates language for already instantiated fields. | |
ContentEntityBase:: |
public | function | Updates the original values with the interim changes. | |
ContentEntityBase:: |
public | function |
Gets the entity UUID (Universally Unique Identifier). Overrides Entity:: |
|
ContentEntityBase:: |
public | function |
Validates the currently set values. Overrides FieldableEntityInterface:: |
|
ContentEntityBase:: |
public | function | Magic method: Implements a deep clone. | |
ContentEntityBase:: |
public | function |
Constructs an Entity object. Overrides Entity:: |
|
ContentEntityBase:: |
public | function | Implements the magic method for getting object properties. | |
ContentEntityBase:: |
public | function | Implements the magic method for isset(). | |
ContentEntityBase:: |
public | function | Implements the magic method for setting object properties. | |
ContentEntityBase:: |
public | function |
Overrides Entity:: |
|
ContentEntityBase:: |
public | function | Implements the magic method for unset(). | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | Aliased as: traitSleep | 1 |
DependencySerializationTrait:: |
public | function | 2 | |
Entity:: |
protected | property | Boolean indicating whether the entity should be forced to be new. | |
Entity:: |
protected | property | The entity type. | |
Entity:: |
protected | property | A typed data object wrapping this entity. | |
Entity:: |
public static | function |
Overrides EntityInterface:: |
|
Entity:: |
public | function |
Deletes an entity permanently. Overrides EntityInterface:: |
2 |
Entity:: |
public | function |
Enforces an entity to be new. Overrides EntityInterface:: |
|
Entity:: |
protected | function | Gets the entity manager. | |
Entity:: |
protected | function | Gets the entity type manager. | |
Entity:: |
public | function |
The cache contexts associated with this object. Overrides RefinableCacheableDependencyTrait:: |
|
Entity:: |
public | function |
The maximum age for which this object may be cached. Overrides RefinableCacheableDependencyTrait:: |
|
Entity:: |
public | function |
The cache tags associated with this object. Overrides RefinableCacheableDependencyTrait:: |
|
Entity:: |
public | function |
Returns the cache tags that should be used to invalidate caches. Overrides EntityInterface:: |
3 |
Entity:: |
public | function |
Gets the key that is used to store configuration dependencies. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Gets the configuration dependency name. Overrides EntityInterface:: |
1 |
Entity:: |
public | function |
Gets the configuration target identifier for the entity. Overrides EntityInterface:: |
1 |
Entity:: |
public | function |
Gets the entity type definition. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Gets the ID of the type of the entity. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Gets the original ID. Overrides EntityInterface:: |
1 |
Entity:: |
public | function |
Gets a typed data object for this entity object. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Indicates if a link template exists for a given key. Overrides EntityInterface:: |
|
Entity:: |
protected static | function | Invalidates an entity's cache tags upon delete. | 1 |
Entity:: |
protected | function | Invalidates an entity's cache tags upon save. | 1 |
Entity:: |
public | function |
Determines whether the entity is new. Overrides EntityInterface:: |
2 |
Entity:: |
protected | function | Gets the language manager. | |
Entity:: |
public | function |
Deprecated way of generating a link to the entity. See toLink(). Overrides EntityInterface:: |
1 |
Entity:: |
protected | function | Gets an array link templates. | 1 |
Entity:: |
public static | function |
Overrides EntityInterface:: |
|
Entity:: |
public static | function |
Overrides EntityInterface:: |
|
Entity:: |
public static | function |
Acts on deleted entities before the delete hook is invoked. Overrides EntityInterface:: |
14 |
Entity:: |
public static | function |
Acts on loaded entities. Overrides EntityInterface:: |
2 |
Entity:: |
public | function |
Acts on a saved entity before the insert or update hook is invoked. Overrides EntityInterface:: |
20 |
Entity:: |
public static | function |
Changes the values of an entity before it is created. Overrides EntityInterface:: |
6 |
Entity:: |
public static | function |
Acts on entities before they are deleted and before hooks are invoked. Overrides EntityInterface:: |
5 |
Entity:: |
public | function |
Saves an entity permanently. Overrides EntityInterface:: |
2 |
Entity:: |
public | function |
Sets the original ID. Overrides EntityInterface:: |
1 |
Entity:: |
public | function |
Generates the HTML for a link to this entity. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Gets the URL object for the entity. Overrides EntityInterface:: |
1 |
Entity:: |
public | function |
Returns a list of URI relationships supported by this entity. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Gets the public URL for this entity. Overrides EntityInterface:: |
2 |
Entity:: |
public | function |
Gets the URL object for the entity. Overrides EntityInterface:: |
1 |
Entity:: |
protected | function | Gets an array of placeholders for this entity. | 1 |
Entity:: |
protected | function | Gets the UUID generator. | |
FieldableEntityInterface:: |
public static | function | Provides base field definitions for an entity type. | 14 |
RefinableCacheableDependencyTrait:: |
protected | property | Cache contexts. | |
RefinableCacheableDependencyTrait:: |
protected | property | Cache max-age. | |
RefinableCacheableDependencyTrait:: |
protected | property | Cache tags. | |
RefinableCacheableDependencyTrait:: |
public | function | 1 | |
RefinableCacheableDependencyTrait:: |
public | function | ||
RefinableCacheableDependencyTrait:: |
public | function | ||
RefinableCacheableDependencyTrait:: |
public | function |