Commit 6588593a authored by alexpott's avatar alexpott

Issue #2472621 by hchonov, plach: Translatable entity 'created' and 'uid'...

Issue #2472621 by hchonov, plach: Translatable entity 'created' and 'uid' fields not initialized properly during content translation 'Add'
parent aff100b6
......@@ -11,12 +11,14 @@
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
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\Core\Session\AccountInterface;
use Drupal\user\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -56,6 +58,21 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
*/
protected $manager;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The array of installed field storage definitions for the entity type, keyed
* by field name.
*
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
*/
protected $fieldStorageDefinitions;
/**
* Initializes an instance of the content translation controller.
*
......@@ -65,12 +82,18 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
* The language manager.
* @param \Drupal\content_translation\ContentTranslationManagerInterface $manager
* The content translation manager service.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager) {
public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager, EntityManagerInterface $entity_manager, AccountInterface $current_user) {
$this->entityTypeId = $entity_type->id();
$this->entityType = $entity_type;
$this->languageManager = $language_manager;
$this->manager = $manager;
$this->currentUser = $current_user;
$this->fieldStorageDefinitions = $entity_manager->getLastInstalledFieldStorageDefinitions($this->entityTypeId);
}
/**
......@@ -80,7 +103,9 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
return new static(
$entity_type,
$container->get('language_manager'),
$container->get('content_translation.manager')
$container->get('content_translation.manager'),
$container->get('entity.manager'),
$container->get('current_user')
);
}
......@@ -111,6 +136,7 @@ public function getFieldDefinitions() {
->setSetting('target_type', 'user')
->setSetting('handler', 'default')
->setRevisionable(TRUE)
->setDefaultValueCallback(get_class($this) . '::getDefaultOwnerId')
->setTranslatable(TRUE);
}
......@@ -149,7 +175,11 @@ public function getFieldDefinitions() {
* TRUE if metadata is natively supported, FALSE otherwise.
*/
protected function hasAuthor() {
return is_subclass_of($this->entityType->getClass(), '\Drupal\user\EntityOwnerInterface');
// Check for field named uid, but only in case the entity implements the
// EntityOwnerInterface. This helps to exclude cases, where the uid is
// defined as field name, but is not meant to be an owner field e.g. the
// User entity.
return $this->entityType->isSubclassOf('\Drupal\user\EntityOwnerInterface') && $this->checkFieldStorageDefinitionTranslatability('uid');
}
/**
......@@ -159,7 +189,7 @@ protected function hasAuthor() {
* TRUE if metadata is natively supported, FALSE otherwise.
*/
protected function hasPublishedStatus() {
return array_key_exists('status', \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($this->entityType->id()));
return $this->checkFieldStorageDefinitionTranslatability('status');
}
/**
......@@ -169,7 +199,7 @@ protected function hasPublishedStatus() {
* TRUE if metadata is natively supported, FALSE otherwise.
*/
protected function hasChangedTime() {
return is_subclass_of($this->entityType->getClass(), '\Drupal\Core\Entity\EntityChangedInterface');
return $this->entityType->isSubclassOf('Drupal\Core\Entity\EntityChangedInterface') && $this->checkFieldStorageDefinitionTranslatability('changed');
}
/**
......@@ -179,7 +209,23 @@ protected function hasChangedTime() {
* TRUE if metadata is natively supported, FALSE otherwise.
*/
protected function hasCreatedTime() {
return array_key_exists('created', \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($this->entityType->id()));
return $this->checkFieldStorageDefinitionTranslatability('created');
}
/**
* Checks the field storage definition for translatability support.
*
* Checks whether the given field is defined in the field storage definitions
* and if its definition specifies it as translatable.
*
* @param string $field_name
* The name of the field.
*
* @return bool
* TRUE if translatable field storage definition exists, FALSE otherwise.
*/
protected function checkFieldStorageDefinitionTranslatability($field_name) {
return array_key_exists($field_name, $this->fieldStorageDefinitions) && $this->fieldStorageDefinitions[$field_name]->isTranslatable();
}
/**
......@@ -203,11 +249,10 @@ public function getTranslationAccess(EntityInterface $entity, $op) {
$translate_permission = TRUE;
// If no permission granularity is defined this entity type does not need an
// explicit translate permission.
$current_user = \Drupal::currentUser();
if (!$current_user->hasPermission('translate any entity') && $permission_granularity = $entity_type->getPermissionGranularity()) {
$translate_permission = $current_user->hasPermission($permission_granularity == 'bundle' ? "translate {$entity->bundle()} {$entity->getEntityTypeId()}" : "translate {$entity->getEntityTypeId()}");
if (!$this->currentUser->hasPermission('translate any entity') && $permission_granularity = $entity_type->getPermissionGranularity()) {
$translate_permission = $this->currentUser->hasPermission($permission_granularity == 'bundle' ? "translate {$entity->bundle()} {$entity->getEntityTypeId()}" : "translate {$entity->getEntityTypeId()}");
}
return AccessResult::allowedIf($translate_permission && $current_user->hasPermission("$op content translations"))->cachePerPermissions();
return AccessResult::allowedIf($translate_permission && $this->currentUser->hasPermission("$op content translations"))->cachePerPermissions();
}
/**
......@@ -392,7 +437,7 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
// Default to the anonymous user.
$uid = 0;
if ($new_translation) {
$uid = \Drupal::currentUser()->getAccount()->id();
$uid = $this->currentUser->id();
}
elseif (($account = $metadata->getAuthor()) && $account->id()) {
$uid = $account->id();
......@@ -633,4 +678,13 @@ protected function entityFormTitle(EntityInterface $entity) {
return $entity->label();
}
/**
* Default value callback for the owner base field definition.
*
* @return int
* The user ID.
*/
public static function getDefaultOwnerId() {
return \Drupal::currentUser()->id();
}
}
......@@ -83,12 +83,8 @@ public function getAuthor() {
* {@inheritdoc}
*/
public function setAuthor(UserInterface $account) {
if ($this->translation->hasField('content_translation_uid')) {
$this->translation->set('content_translation_uid', $account->id());
}
else {
$this->translation->setOwner($account);
}
$field_name = $this->translation->hasField('content_translation_uid') ? 'content_translation_uid' : 'uid';
$this->setFieldOnlyIfTranslatable($field_name, $account->id());
return $this;
}
......@@ -105,7 +101,7 @@ public function isPublished() {
*/
public function setPublished($published) {
$field_name = $this->translation->hasField('content_translation_status') ? 'content_translation_status' : 'status';
$this->translation->set($field_name, $published);
$this->setFieldOnlyIfTranslatable($field_name, $published);
return $this;
}
......@@ -122,7 +118,7 @@ public function getCreatedTime() {
*/
public function setCreatedTime($timestamp) {
$field_name = $this->translation->hasField('content_translation_created') ? 'content_translation_created' : 'created';
$this->translation->set($field_name, $timestamp);
$this->setFieldOnlyIfTranslatable($field_name, $timestamp);
return $this;
}
......@@ -138,8 +134,21 @@ public function getChangedTime() {
*/
public function setChangedTime($timestamp) {
$field_name = $this->translation->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed';
$this->translation->set($field_name, $timestamp);
$this->setFieldOnlyIfTranslatable($field_name, $timestamp);
return $this;
}
/**
* Updates a field value, only if the field is translatable.
*
* @param string $field_name
* The name of the field.
* @param mixed $value
* The field value to be set.
*/
protected function setFieldOnlyIfTranslatable($field_name, $value) {
if ($this->translation->getFieldDefinition($field_name)->isTranslatable()) {
$this->translation->set($field_name, $value);
}
}
}
......@@ -64,6 +64,8 @@ public function getAuthor();
/**
* Sets the translation author.
*
* The metadata field will be updated, only if it's translatable.
*
* @param \Drupal\user\UserInterface $account
* The translation author user entity.
*
......@@ -82,6 +84,8 @@ public function isPublished();
/**
* Sets the translation published status.
*
* The metadata field will be updated, only if it's translatable.
*
* @param bool $published
* TRUE if the translation is published, FALSE otherwise.
*
......@@ -100,6 +104,8 @@ public function getCreatedTime();
/**
* Sets the translation creation timestamp.
*
* The metadata field will be updated, only if it's translatable.
*
* @param int $timestamp
* The UNIX timestamp of when the translation was created.
*
......@@ -118,6 +124,8 @@ public function getChangedTime();
/**
* Sets the translation modification timestamp.
*
* The metadata field will be updated, only if it's translatable.
*
* @param int $timestamp
* The UNIX timestamp of when the translation was last modified.
*
......
......@@ -57,7 +57,16 @@ public static function create(ContainerInterface $container) {
public function prepareTranslation(ContentEntityInterface $entity, LanguageInterface $source, LanguageInterface $target) {
/* @var \Drupal\Core\Entity\ContentEntityInterface $source_translation */
$source_translation = $entity->getTranslation($source->getId());
$entity->addTranslation($target->getId(), $source_translation->toArray());
$target_translation = $entity->addTranslation($target->getId(), $source_translation->toArray());
/** @var \Drupal\user\UserInterface $user */
$user = $this->entityManager()->getStorage('user')->load($this->currentUser()->id());
$metadata = $this->manager->getTranslationMetadata($target_translation);
// Update the translation author to current user, as well the translation
// creation time.
$metadata->setAuthor($user);
$metadata->setCreatedTime(REQUEST_TIME);
}
/**
......
<?php
/**
* @file
* Contains \Drupal\content_translation\Tests\ContentTranslationMetadataFieldsTest.
*/
namespace Drupal\content_translation\Tests;
/**
* Tests the Content Translation metadata fields handling.
*
* @group content_translation
*/
class ContentTranslationMetadataFieldsTest extends ContentTranslationTestBase {
/**
* The entity type being tested.
*
* @var string
*/
protected $entityTypeId = 'node';
/**
* The bundle being tested.
*
* @var string
*/
protected $bundle = 'article';
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('language', 'content_translation', 'node');
/**
* The profile to install as a basis for testing.
*
* @var string
*/
protected $profile = 'standard';
/**
* Tests skipping setting non translatable metadata fields.
*/
public function testSkipUntranslatable() {
$this->drupalLogin($this->translator);
$entity_manager = \Drupal::entityManager();
$fields = $entity_manager->getFieldDefinitions($this->entityTypeId, $this->bundle);
// Turn off translatability for the metadata fields on the current bundle.
$metadata_fields = ['created', 'changed', 'uid', 'status'];
foreach ($metadata_fields as $field_name) {
$fields[$field_name]
->getConfig($this->bundle)
->setTranslatable(FALSE)
->save();
}
// Create a new test entity with original values in the default language.
$default_langcode = $this->langcodes[0];
$entity_id = $this->createEntity([], $default_langcode);
$storage = $entity_manager->getStorage($this->entityTypeId);
$storage->resetCache();
$entity = $storage->load($entity_id);
// Add a content translation.
$langcode = 'it';
$values = $entity->toArray();
// Apply a default value for the metadata fields.
foreach ($metadata_fields as $field_name) {
unset($values[$field_name]);
}
$entity->addTranslation($langcode, $values);
$metadata_source_translation = $this->manager->getTranslationMetadata($entity->getTranslation($default_langcode));
$metadata_target_translation = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
$created_time = $metadata_source_translation->getCreatedTime();
$changed_time = $metadata_source_translation->getChangedTime();
$published = $metadata_source_translation->isPublished();
$author = $metadata_source_translation->getAuthor();
$this->assertEqual($created_time, $metadata_target_translation->getCreatedTime(), 'Metadata created field has the same value for both translations.');
$this->assertEqual($changed_time, $metadata_target_translation->getChangedTime(), 'Metadata changed field has the same value for both translations.');
$this->assertEqual($published, $metadata_target_translation->isPublished(), 'Metadata published field has the same value for both translations.');
$this->assertEqual($author->id(), $metadata_target_translation->getAuthor()->id(), 'Metadata author field has the same value for both translations.');
$metadata_target_translation->setCreatedTime(time() + 50);
$metadata_target_translation->setChangedTime(time() + 50);
$metadata_target_translation->setPublished(TRUE);
$metadata_target_translation->setAuthor($this->editor);
$this->assertEqual($created_time, $metadata_target_translation->getCreatedTime(), 'Metadata created field correctly not updated');
$this->assertEqual($changed_time, $metadata_target_translation->getChangedTime(), 'Metadata changed field correctly not updated');
$this->assertEqual($published, $metadata_target_translation->isPublished(), 'Metadata published field correctly not updated');
$this->assertEqual($author->id(), $metadata_target_translation->getAuthor()->id(), 'Metadata author field correctly not updated');
}
/**
* Tests setting translatable metadata fields.
*/
public function testSetTranslatable() {
$this->drupalLogin($this->translator);
$entity_manager = \Drupal::entityManager();
$fields = $entity_manager->getFieldDefinitions($this->entityTypeId, $this->bundle);
// Turn off translatability for the metadata fields on the current bundle.
$metadata_fields = ['created', 'changed', 'uid', 'status'];
foreach ($metadata_fields as $field_name) {
$fields[$field_name]
->getConfig($this->bundle)
->setTranslatable(TRUE)
->save();
}
// Create a new test entity with original values in the default language.
$default_langcode = $this->langcodes[0];
$entity_id = $this->createEntity(['status' => FALSE], $default_langcode);
$storage = $entity_manager->getStorage($this->entityTypeId);
$storage->resetCache();
$entity = $storage->load($entity_id);
// Add a content translation.
$langcode = 'it';
$values = $entity->toArray();
// Apply a default value for the metadata fields.
foreach ($metadata_fields as $field_name) {
unset($values[$field_name]);
}
$entity->addTranslation($langcode, $values);
$metadata_source_translation = $this->manager->getTranslationMetadata($entity->getTranslation($default_langcode));
$metadata_target_translation = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
$metadata_target_translation->setCreatedTime(time() + 50);
$metadata_target_translation->setChangedTime(time() + 50);
$metadata_target_translation->setPublished(TRUE);
$metadata_target_translation->setAuthor($this->editor);
$this->assertNotEqual($metadata_source_translation->getCreatedTime(), $metadata_target_translation->getCreatedTime(), 'Metadata created field correctly different on both translations.');
$this->assertNotEqual($metadata_source_translation->getChangedTime(), $metadata_target_translation->getChangedTime(), 'Metadata changed field correctly different on both translations.');
$this->assertNotEqual($metadata_source_translation->isPublished(), $metadata_target_translation->isPublished(), 'Metadata published field correctly different on both translations.');
$this->assertNotEqual($metadata_source_translation->getAuthor()->id(), $metadata_target_translation->getAuthor()->id(), 'Metadata author field correctly different on both translations.');
}
}
......@@ -12,6 +12,7 @@
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Component\Utility\SafeMarkup;
/**
* Tests the Content Translation UI.
......@@ -53,7 +54,12 @@ protected function doTestBasicTranslation() {
// Create a new test entity with original values in the default language.
$default_langcode = $this->langcodes[0];
$values[$default_langcode] = $this->getNewEntityValues($default_langcode);
// Create the entity with the editor as owner, so that afterwards a new
// translation is created by the translator and the translation author is
// tested.
$this->drupalLogin($this->editor);
$this->entityId = $this->createEntity($values[$default_langcode], $default_langcode);
$this->drupalLogin($this->translator);
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
$this->assertTrue($entity, 'Entity found in the database.');
$this->drupalGet($entity->urlInfo());
......@@ -80,6 +86,36 @@ protected function doTestBasicTranslation() {
'target' => $langcode
], array('language' => $language));
$this->drupalPostForm($add_url, $this->getEditValues($values, $langcode), $this->getFormSubmitActionForNewTranslation($entity, $langcode));
// Get the entity and reset its cache, so that the new translation gets the
// updated values.
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
$metadata_source_translation = $this->manager->getTranslationMetadata($entity->getTranslation($default_langcode));
$metadata_target_translation = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
$author_field_name = $entity->hasField('content_translation_uid') ? 'content_translation_uid' : 'uid';
if ($entity->getFieldDefinition($author_field_name)->isTranslatable()) {
$this->assertEqual($metadata_target_translation->getAuthor()->id(), $this->translator->id(),
SafeMarkup::format('Author of the target translation @langcode correctly stored for translatable owner field.', array('@langcode' => $langcode)));
$this->assertNotEqual($metadata_target_translation->getAuthor()->id(), $metadata_source_translation->getAuthor()->id(),
SafeMarkup::format('Author of the target translation @target different from the author of the source translation @source for translatable owner field.',
array('@target' => $langcode, '@source' => $default_langcode)));
}
else {
$this->assertEqual($metadata_target_translation->getAuthor()->id(), $this->editor->id(), 'Author of the entity remained untouched after translation for non translatable owner field.');
}
$created_field_name = $entity->hasField('content_translation_created') ? 'content_translation_created' : 'created';
if ($entity->getFieldDefinition($created_field_name)->isTranslatable()) {
$this->assertTrue($metadata_target_translation->getCreatedTime() > $metadata_source_translation->getCreatedTime(),
SafeMarkup::format('Translation creation timestamp of the target translation @target is newer than the creation timestamp of the source translation @source for translatable created field.',
array('@target' => $langcode, '@source' => $default_langcode)));
}
else {
$this->assertEqual($metadata_target_translation->getCreatedTime(), $metadata_source_translation->getCreatedTime(), 'Creation timestamp of the entity remained untouched after translation for non translatable created field.');
}
if ($this->testLanguageSelector) {
$this->assertNoFieldByXPath('//select[@id="edit-langcode-0-value"]', NULL, 'Language selector correctly disabled on translations.');
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment