Commit 17df2c5c authored by alexpott's avatar alexpott

Issue #1916790 by plach, YesCT, penyaskito, Gábor Hojtsy, das-peter, herom,...

Issue #1916790 by plach, YesCT, penyaskito, Gábor Hojtsy, das-peter, herom, larowlan: Convert translation metadata into regular entity fields
parent c15cf7d2
......@@ -158,11 +158,9 @@ public function testDisabledBundle() {
$enabled_block_content = $this->createBlockContent();
$disabled_block_content = $this->createBlockContent(FALSE, $bundle->id());
// Make sure that only a single row was inserted into the
// {content_translation} table.
$rows = db_query('SELECT * FROM {content_translation}')->fetchAll();
// Make sure that only a single row was inserted into the block table.
$rows = db_query('SELECT * FROM {block_content_field_data} WHERE id = :id', array(':id' => $enabled_block_content->id()))->fetchAll();
$this->assertEqual(1, count($rows));
$this->assertEqual($enabled_block_content->id(), reset($rows)->entity_id);
}
}
......@@ -9,12 +9,28 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\content_translation\ContentTranslationHandler;
use Drupal\Core\Form\FormStateInterface;
/**
* Defines the translation handler for comments.
*/
class CommentTranslationHandler extends ContentTranslationHandler {
/**
* {@inheritdoc}
*/
public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
parent::entityFormAlter($form, $form_state, $entity);
if (isset($form['content_translation'])) {
// We do not need to show these values on comment forms: they inherit the
// basic comment property values.
$form['content_translation']['status']['#access'] = FALSE;
$form['content_translation']['name']['#access'] = FALSE;
$form['content_translation']['created']['#access'] = FALSE;
}
}
/**
* {@inheritdoc}
*/
......@@ -22,4 +38,17 @@ protected function entityFormTitle(EntityInterface $entity) {
return t('Edit comment @subject', array('@subject' => $entity->label()));
}
/**
* {@inheritdoc}
*/
public function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, FormStateInterface $form_state) {
if ($form_state->hasValue('content_translation')) {
$translation = &$form_state->getValue('content_translation');
/** @var \Drupal\comment\CommentInterface $entity */
$translation['status'] = $entity->isPublished();
$translation['name'] = $entity->getAuthorName();
}
parent::entityFormEntityBuild($entity_type, $entity, $form, $form_state);
}
}
......@@ -9,6 +9,7 @@
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\content_translation\Tests\ContentTranslationUITest;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the Comment Translation UI.
......@@ -108,30 +109,58 @@ protected function getNewEntityValues($langcode) {
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::assertPublishedStatus().
* {@inheritdoc}
*/
protected function assertPublishedStatus() {
parent::assertPublishedStatus();
$entity = entity_load($this->entityTypeId, $this->entityId);
$user = $this->drupalCreateUser(array('access comments'));
$this->drupalLogin($user);
$languages = $this->container->get('language_manager')->getLanguages();
protected function doTestPublishedStatus() {
$entity_manager = \Drupal::entityManager();
$storage = $entity_manager->getStorage($this->entityTypeId);
$storage->resetCache();
$entity = $storage->load($this->entityId);
// Check that simple users cannot see unpublished field translations.
// Unpublish translations.
foreach ($this->langcodes as $index => $langcode) {
$translation = $this->getTranslation($entity, $langcode);
$value = $this->getValue($translation, 'comment_body', $langcode);
$this->drupalGet($entity->urlInfo(), array('language' => $languages[$langcode]));
if ($index > 0) {
$this->assertNoRaw($value, 'Unpublished field translation is not shown.');
}
else {
$this->assertRaw($value, 'Published field translation is shown.');
$edit = array('status' => 0);
$url = $entity->urlInfo('edit-form', array('language' => ConfigurableLanguage::load($langcode)));
$this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $langcode));
$storage->resetCache();
$entity = $storage->load($this->entityId);
$this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($langcode))->isPublished(), 'The translation has been correctly unpublished.');
}
}
}
/**
* {@inheritdoc}
*/
protected function doTestAuthoringInfo() {
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
$path = $entity->getSystemPath('edit-form');
$languages = $this->container->get('language_manager')->getLanguages();
$values = array();
// Post different authoring information for each translation.
foreach ($this->langcodes as $langcode) {
$user = $this->drupalCreateUser();
$values[$langcode] = array(
'uid' => $user->id(),
'created' => REQUEST_TIME - mt_rand(0, 1000),
);
$edit = array(
'name' => $user->getUsername(),
'date[date]' => format_date($values[$langcode]['created'], 'custom', 'Y-m-d'),
'date[time]' => format_date($values[$langcode]['created'], 'custom', 'H:i:s'),
);
$this->drupalPostForm($path, $edit, $this->getFormSubmitAction($entity, $langcode), array('language' => $languages[$langcode]));
}
// Login as translator again to ensure subsequent tests do not break.
$this->drupalLogin($this->translator);
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
foreach ($this->langcodes as $langcode) {
$metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
$this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.');
$this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.');
}
}
/**
......
......@@ -97,7 +97,8 @@ function _content_translation_form_language_content_settings_form_alter(array &$
if ($fields) {
foreach ($fields as $field_name => $definition) {
// Allow to configure only fields supporting multilingual storage.
if (!empty($storage_definitions[$field_name]) && $storage_definitions[$field_name]->isTranslatable()) {
// We skip our own fields as they are always translatable.
if (!empty($storage_definitions[$field_name]) && $storage_definitions[$field_name]->isTranslatable() && $storage_definitions[$field_name]->getProvider() != 'content_translation') {
$form['settings'][$entity_type_id][$bundle]['fields'][$field_name] = array(
'#label' => $definition->getLabel(),
'#type' => 'checkbox',
......@@ -338,4 +339,5 @@ function content_translation_form_language_content_settings_submit(array $form,
// Ensure entity and menu router information are correctly rebuilt.
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder_indicator')->setRebuildNeeded();
\Drupal::service('content_translation.updates_manager')->updateDefinitions($entity_types);
}
......@@ -5,81 +5,9 @@
* Installation functions for Content Translation module.
*/
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
/**
* Implements hook_schema().
*/
function content_translation_schema() {
$schema['content_translation'] = array(
'description' => 'Table to track content translations',
'fields' => array(
'entity_type' => array(
'type' => 'varchar',
'length' => EntityTypeInterface::ID_MAX_LENGTH,
'not null' => TRUE,
'default' => '',
'description' => 'The entity type this translation relates to',
),
'entity_id' => array(
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
'description' => 'The entity id this translation relates to',
),
'langcode' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
'description' => 'The target language for this translation.',
),
'source' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
'description' => 'The source language from which this translation was created.',
),
'outdated' => array(
'description' => 'A boolean indicating whether this translation needs to be updated.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'uid' => array(
'description' => 'The author of this translation.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'status' => array(
'description' => 'Boolean indicating whether the translation is visible to non-translators.',
'type' => 'int',
'not null' => TRUE,
'default' => 1,
),
'created' => array(
'description' => 'The Unix timestamp when the translation was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'changed' => array(
'description' => 'The Unix timestamp when the translation was most recently saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('entity_type', 'entity_id', 'langcode'),
);
return $schema;
}
/**
* Implements hook_install().
*/
......
......@@ -6,9 +6,10 @@
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Routing\RouteMatchInterface;
......@@ -74,8 +75,9 @@ function content_translation_language_types_info_alter(array &$language_types) {
* Implements hook_entity_type_alter().
*
* The content translation UI relies on the entity info to provide its features.
* See the documentation of hook_entity_type_build() in the Entity API documentation
* for more details on all the entity info keys that may be defined.
* See the documentation of hook_entity_type_build() in the Entity API
* documentation for more details on all the entity info keys that may be
* defined.
*
* To make Content Translation automatically support an entity type some keys
* may need to be defined, but none of them is required unless the entity path
......@@ -83,17 +85,27 @@ function content_translation_language_types_info_alter(array &$language_types) {
* "/taxonomy/term/{taxonomy_term}"), in which case at least the 'canonical' key
* in the 'links' entity info property must be defined.
*
* Every entity type needs a translation controller to be translated. This can
* be specified through the 'translation' key in the 'handlers' entity
* annotation property. If an entity type is translatable and no translation
* handler is defined, \Drupal\content_translation\ContentTranslationHandler
* will be assumed. Every translation handler must implement
* Every entity type needs a translation handler to be translated. This can be
* specified through the 'translation' key in the 'handlers' entity annotation
* property. If an entity type is translatable and no translation handler is
* defined, \Drupal\content_translation\ContentTranslationHandler will be
* assumed. Every translation handler must implement
* \Drupal\content_translation\ContentTranslationHandlerInterface.
*
* To implement its business logic the content translation UI relies on various
* metadata items describing the translation state. The default implementation
* is provided by \Drupal\content_translation\ContentTranslationMetadataWrapper,
* which is relying on one field for each metadata item (field definitions are
* provided by the translation handler). Entity types needing to customize this
* behavior can specify an alternative class through the
* 'content_translation_metadata' key in the entity type definition. Every
* content translation metadata wrapper needs to implement
* \Drupal\content_translation\ContentTranslationMetadataWrapperInterface.
*
* If the entity paths match the default pattern above and there is no need for
* an entity-specific translation handler, Content Translation will
* provide built-in support for the entity. However enabling translation for
* each translatable bundle will be required.
* an entity-specific translation handler, Content Translation will provide
* built-in support for the entity. However enabling translation for each
* translatable bundle will be required.
*
* @see \Drupal\Core\Entity\Annotation\EntityType
*/
......@@ -105,6 +117,9 @@ function content_translation_entity_type_alter(array &$entity_types) {
if (!$entity_type->hasHandlerClass('translation')) {
$entity_type->setHandlerClass('translation', 'Drupal\content_translation\ContentTranslationHandler');
}
if (!$entity_type->get('content_translation_metadata')) {
$entity_type->set('content_translation_metadata', 'Drupal\content_translation\ContentTranslationMetadataWrapper');
}
$translation = $entity_type->get('translation');
if (!$translation || !isset($translation['content_translation'])) {
......@@ -138,6 +153,29 @@ function content_translation_entity_bundle_info_alter(&$bundles) {
}
}
/**
* Implements hook_entity_base_field_info().
*/
function content_translation_entity_base_field_info(EntityTypeInterface $entity_type) {
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
$manager = \Drupal::service('content_translation.manager');
$entity_type_id = $entity_type->id();
if ($manager->isSupported($entity_type_id)) {
$definitions = $manager->getTranslationHandler($entity_type_id)->getFieldDefinitions();
$installed_storage_definitions = \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($entity_type_id);
// We return metadata storage fields whenever content translation is enabled
// or it was enabled before, so that we keep translation metadata around
// when translation is disabled.
// @todo Re-evaluate this approach and consider removing field storage
// definitions and the related field data if the entity type has no bundle
// enabled for translation, once base field purging is supported.
// See https://www.drupal.org/node/2282119.
if ($manager->isEnabled($entity_type_id) || array_intersect_key($definitions, $installed_storage_definitions)) {
return $definitions;
}
}
}
/**
* Implements hook_field_info_alter().
*
......@@ -241,133 +279,23 @@ function content_translation_language_fallback_candidates_entity_view_alter(&$ca
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $context['data'];
$entity_type_id = $entity->getEntityTypeId();
$entity_type = $entity->getEntityType();
$permission = $entity_type->getPermissionGranularity() == 'bundle' ? $permission = "translate {$entity->bundle()} $entity_type_id" : "translate $entity_type_id";
$current_user = \Drupal::currentuser();
if (!$current_user->hasPermission('translate any entity') && !$current_user->hasPermission($permission)) {
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
if (empty($entity->translation[$langcode]['status'])) {
unset($candidates[$langcode]);
}
}
}
}
/**
* Implements hook_entity_storage_load().
*/
function content_translation_entity_storage_load(array $entities, $entity_type) {
$enabled_entities = array();
if (\Drupal::service('content_translation.manager')->isEnabled($entity_type)) {
foreach ($entities as $entity) {
if ($entity instanceof ContentEntityInterface && $entity->isTranslatable()) {
$enabled_entities[$entity->id()] = $entity;
}
}
}
if (!empty($enabled_entities)) {
content_translation_load_translation_metadata($enabled_entities, $entity_type);
}
}
/**
* Loads translation data into the given entities.
*
* @param array $entities
* The entities keyed by entity ID.
* @param string $entity_type
* The type of the entities.
*/
function content_translation_load_translation_metadata(array $entities, $entity_type) {
$query = 'SELECT * FROM {content_translation} te WHERE te.entity_type = :entity_type AND te.entity_id IN (:entity_id)';
$result = db_query($query, array(':entity_type' => $entity_type, ':entity_id' => array_keys($entities)));
$exclude = array('entity_type', 'entity_id', 'langcode');
foreach ($result as $record) {
$entity = $entities[$record->entity_id];
// @todo Declare these as entity (translation?) properties.
foreach ($record as $field_name => $value) {
if (!in_array($field_name, $exclude)) {
$langcode = $record->langcode;
$entity->translation[$langcode][$field_name] = $value;
if (!$entity->hasTranslation($langcode)) {
$entity->initTranslation($langcode);
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
$manager = \Drupal::service('content_translation.manager');
if ($manager->isEnabled($entity_type_id, $entity->bundle())) {
$entity_type = $entity->getEntityType();
$permission = $entity_type->getPermissionGranularity() == 'bundle' ? $permission = "translate {$entity->bundle()} $entity_type_id" : "translate $entity_type_id";
$current_user = \Drupal::currentuser();
if (!$current_user->hasPermission('translate any entity') && !$current_user->hasPermission($permission)) {
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
$metadata = $manager->getTranslationMetadata($entity->getTranslation($langcode));
if (!$metadata->isPublished()) {
unset($candidates[$langcode]);
}
}
}
}
}
/**
* Implements hook_entity_insert().
*/
function content_translation_entity_insert(EntityInterface $entity) {
// Only do something if translation support for the given entity is enabled.
if (!($entity instanceof ContentEntityInterface) || !$entity->isTranslatable()) {
return;
}
$fields = array('entity_type', 'entity_id', 'langcode', 'source', 'outdated', 'uid', 'status', 'created', 'changed');
$query = db_insert('content_translation')->fields($fields);
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
$translation = isset($entity->translation[$langcode]) ? $entity->translation[$langcode] : array();
$translation += array(
'source' => '',
'uid' => \Drupal::currentUser()->id(),
'outdated' => FALSE,
'status' => TRUE,
'created' => REQUEST_TIME,
'changed' => REQUEST_TIME,
);
$translation['entity_type'] = $entity->getEntityTypeId();
$translation['entity_id'] = $entity->id();
$translation['langcode'] = $langcode;
// Reorder values to match the schema.
$values = array();
foreach ($fields as $field_name) {
$value = is_bool($translation[$field_name]) ? intval($translation[$field_name]) : $translation[$field_name];
$values[$field_name] = $value;
}
$query->values($values);
}
$query->execute();
}
/**
* Implements hook_entity_delete().
*/
function content_translation_entity_delete(EntityInterface $entity) {
// Only do something if translation support for the given entity is enabled.
if (!($entity instanceof ContentEntityInterface) || !$entity->isTranslatable()) {
return;
}
db_delete('content_translation')
->condition('entity_type', $entity->getEntityTypeId())
->condition('entity_id', $entity->id())
->execute();
}
/**
* Implements hook_entity_update().
*/
function content_translation_entity_update(EntityInterface $entity) {
// Only do something if translation support for the given entity is enabled.
if (!($entity instanceof ContentEntityInterface) || !$entity->isTranslatable()) {
return;
}
// Delete and create to ensure no stale value remains behind.
content_translation_entity_delete($entity);
content_translation_entity_insert($entity);
}
/**
* Implements hook_entity_extra_field_info().
*/
......@@ -429,11 +357,18 @@ function content_translation_form_field_ui_field_edit_form_alter(array &$form, F
* Implements hook_entity_presave().
*/
function content_translation_entity_presave(EntityInterface $entity) {
if ($entity instanceof ContentEntityInterface && $entity->isTranslatable()) {
// @todo Avoid using request attributes once translation metadata become
// regular fields.
$attributes = \Drupal::request()->attributes;
\Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $entity->language()->getId(), $attributes->get('source_langcode'));
if ($entity instanceof ContentEntityInterface && $entity->isTranslatable() && !$entity->isNew()) {
// If we are creating a new translation we need to use the source language
// as original language, since source values are the only ones available to
// compare against.
if (!isset($entity->original)) {
$entity->original = entity_load_unchanged($entity->entityType(), $entity->id());
}
$langcode = $entity->language()->getId();
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
$manager = \Drupal::service('content_translation.manager');
$source_langcode = !$entity->original->hasTranslation($langcode) ? $manager->getTranslationMetadata($entity)->getSource() : NULL;
\Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $langcode, $source_langcode);
}
}
......
......@@ -24,3 +24,9 @@ services:
content_translation.manager:
class: Drupal\content_translation\ContentTranslationManager
arguments: ['@entity.manager']
content_translation.updates_manager:
class: Drupal\content_translation\ContentTranslationUpdatesManager
arguments: ['@entity.manager', '@entity.definition_update_manager']
tags:
- { name: event_subscriber }
......@@ -8,13 +8,16 @@
namespace Drupal\content_translation;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\Element;
use Drupal\user\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -23,6 +26,7 @@
* @ingroup entity_api
*/
class ContentTranslationHandler implements ContentTranslationHandlerInterface, EntityHandlerInterface {
use DependencySerializationTrait;
/**
* The type of the entity being translated.
......@@ -46,14 +50,11 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
protected $languageManager;
/**
* {@inheritdoc}
* The content translation manager.
*
* @var \Drupal\content_translation\ContentTranslationManagerInterface
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('language_manager')
);
}
protected $manager;
/**
* Initializes an instance of the content translation controller.
......@@ -62,11 +63,124 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
* The info array of the given entity type.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\content_translation\ContentTranslationManagerInterface $manager
* The content translation manager service.
*/
public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager) {
public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager) {
$this->entityTypeId = $entity_type->id();
$this->entityType = $entity_type;
$this->languageManager = $language_manager;
$this->manager = $manager;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('language_manager'),
$container->get('content_translation.manager')
);
}
/**
* {@inheritdoc}
*/
public function getFieldDefinitions() {
$definitions = array();
$definitions['content_translation_source'] = BaseFieldDefinition::create('language')
->setLabel(t('Translation source'))
->setDescription(t('The source language from which this translation was created.'))
->setDefaultValue(LanguageInterface::LANGCODE_NOT_SPECIFIED)
->setRevisionable(TRUE)
->setTranslatable(TRUE);
$definitions['content_translation_outdated'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Translation outdated'))
->setDescription(t('A boolean indicating whether this translation needs to be updated.'))
->setDefaultValue(FALSE)
->setRevisionable(TRUE)
->setTranslatable(TRUE);
if (!$this->hasAuthor()) {
$definitions['content_translation_uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Translation author'))
->setDescription(t('The author of this translation.'))
->setSetting('target_type', 'user')
->setSetting('handler', 'default')
->setRevisionable(TRUE)
->setTranslatable(TRUE);
}
if (!$this->hasPublishedStatus()) {
$definitions['content_translation_status'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Translation status'))
->setDescription(t('A boolean indicating whether the translation is visible to non-translators.'))
->setDefaultValue(TRUE)
->setRevisionable(TRUE)
->setTranslatable(TRUE);
}
if (!$this->hasCreatedTime()) {
$definitions['content_translation_created'] = BaseFieldDefinition::create('created')
->setLabel(t('Translation created time'))
->setDescription(t('The Unix timestamp when the translation was created.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE);
}
if (!$this->hasChangedTime()) {
$definitions['content_translation_changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Translation changed time'))
->setDescription(t('The Unix timestamp when the translation was most recently saved.'))
->setPropertyConstraints('value', array('EntityChanged' => array()))
->setRevisionable(TRUE)
->setTranslatable(TRUE);
}
return $definitions;
}
/**
* Checks whether the entity type supports author natively.
*
* @return bool
* TRUE if metadata is natively supported, FALSE otherwise.
*/
protected function hasAuthor() {
return is_subclass_of($this->entityType->getClass(), '\Drupal\user\EntityOwnerInterface');
}
/**
* Checks whether the entity type supports published status natively.
*
* @return bool
* TRUE if metadata is natively supported, FALSE otherwise.
*/
protected function hasPublishedStatus() {
return array_key_exists('status', \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($this->entityType->id()));
}
/**
* Checks whether the entity type supports modification time natively.
*
* @return bool
* TRUE if metadata is natively supported, FALSE otherwise.
*/
protected function hasChangedTime() {
return is_subclass_of($this->entityType->getClass(), '\Drupal\Core\Entity\EntityChangedInterface');
}
/**
* Checks whether the entity type supports creation time natively.
*
* @return bool