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() { ...@@ -158,11 +158,9 @@ public function testDisabledBundle() {
$enabled_block_content = $this->createBlockContent(); $enabled_block_content = $this->createBlockContent();
$disabled_block_content = $this->createBlockContent(FALSE, $bundle->id()); $disabled_block_content = $this->createBlockContent(FALSE, $bundle->id());
// Make sure that only a single row was inserted into the // Make sure that only a single row was inserted into the block table.
// {content_translation} table. $rows = db_query('SELECT * FROM {block_content_field_data} WHERE id = :id', array(':id' => $enabled_block_content->id()))->fetchAll();
$rows = db_query('SELECT * FROM {content_translation}')->fetchAll();
$this->assertEqual(1, count($rows)); $this->assertEqual(1, count($rows));
$this->assertEqual($enabled_block_content->id(), reset($rows)->entity_id);
} }
} }
...@@ -9,12 +9,28 @@ ...@@ -9,12 +9,28 @@
use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
use Drupal\content_translation\ContentTranslationHandler; use Drupal\content_translation\ContentTranslationHandler;
use Drupal\Core\Form\FormStateInterface;
/** /**
* Defines the translation handler for comments. * Defines the translation handler for comments.
*/ */
class CommentTranslationHandler extends ContentTranslationHandler { 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} * {@inheritdoc}
*/ */
...@@ -22,4 +38,17 @@ protected function entityFormTitle(EntityInterface $entity) { ...@@ -22,4 +38,17 @@ protected function entityFormTitle(EntityInterface $entity) {
return t('Edit comment @subject', array('@subject' => $entity->label())); 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 @@ ...@@ -9,6 +9,7 @@
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface; use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\content_translation\Tests\ContentTranslationUITest; use Drupal\content_translation\Tests\ContentTranslationUITest;
use Drupal\language\Entity\ConfigurableLanguage;
/** /**
* Tests the Comment Translation UI. * Tests the Comment Translation UI.
...@@ -108,30 +109,58 @@ protected function getNewEntityValues($langcode) { ...@@ -108,30 +109,58 @@ protected function getNewEntityValues($langcode) {
} }
/** /**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::assertPublishedStatus(). * {@inheritdoc}
*/ */
protected function assertPublishedStatus() { protected function doTestPublishedStatus() {
parent::assertPublishedStatus(); $entity_manager = \Drupal::entityManager();
$entity = entity_load($this->entityTypeId, $this->entityId); $storage = $entity_manager->getStorage($this->entityTypeId);
$user = $this->drupalCreateUser(array('access comments'));
$this->drupalLogin($user); $storage->resetCache();
$languages = $this->container->get('language_manager')->getLanguages(); $entity = $storage->load($this->entityId);
// Check that simple users cannot see unpublished field translations. // Unpublish translations.
foreach ($this->langcodes as $index => $langcode) { 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) { if ($index > 0) {
$this->assertNoRaw($value, 'Unpublished field translation is not shown.'); $edit = array('status' => 0);
} $url = $entity->urlInfo('edit-form', array('language' => ConfigurableLanguage::load($langcode)));
else { $this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $langcode));
$this->assertRaw($value, 'Published field translation is shown.'); $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. $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
$this->drupalLogin($this->translator); 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 &$ ...@@ -97,7 +97,8 @@ function _content_translation_form_language_content_settings_form_alter(array &$
if ($fields) { if ($fields) {
foreach ($fields as $field_name => $definition) { foreach ($fields as $field_name => $definition) {
// Allow to configure only fields supporting multilingual storage. // 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( $form['settings'][$entity_type_id][$bundle]['fields'][$field_name] = array(
'#label' => $definition->getLabel(), '#label' => $definition->getLabel(),
'#type' => 'checkbox', '#type' => 'checkbox',
...@@ -338,4 +339,5 @@ function content_translation_form_language_content_settings_submit(array $form, ...@@ -338,4 +339,5 @@ function content_translation_form_language_content_settings_submit(array $form,
// Ensure entity and menu router information are correctly rebuilt. // Ensure entity and menu router information are correctly rebuilt.
\Drupal::entityManager()->clearCachedDefinitions(); \Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder_indicator')->setRebuildNeeded(); \Drupal::service('router.builder_indicator')->setRebuildNeeded();
\Drupal::service('content_translation.updates_manager')->updateDefinitions($entity_types);
} }
...@@ -5,81 +5,9 @@ ...@@ -5,81 +5,9 @@
* Installation functions for Content Translation module. * Installation functions for Content Translation module.
*/ */
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl; 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(). * Implements hook_install().
*/ */
......
...@@ -24,3 +24,9 @@ services: ...@@ -24,3 +24,9 @@ services:
content_translation.manager: content_translation.manager:
class: Drupal\content_translation\ContentTranslationManager class: Drupal\content_translation\ContentTranslationManager
arguments: ['@entity.manager'] arguments: ['@entity.manager']
content_translation.updates_manager:
class: Drupal\content_translation\ContentTranslationUpdatesManager
arguments: ['@entity.manager', '@entity.definition_update_manager']
tags:
- { name: event_subscriber }
...@@ -18,6 +18,13 @@ ...@@ -18,6 +18,13 @@
*/ */
interface ContentTranslationHandlerInterface { interface ContentTranslationHandlerInterface {
/**
* Returns a set of field definitions to be used to store metadata items.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
*/
public function getFieldDefinitions();
/** /**
* Checks if the user can perform the given operation on translations of the * Checks if the user can perform the given operation on translations of the
* wrapped entity. * wrapped entity.
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
namespace Drupal\content_translation; namespace Drupal\content_translation;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\language\Entity\ContentLanguageSettings; use Drupal\language\Entity\ContentLanguageSettings;
/** /**
...@@ -32,6 +34,23 @@ public function __construct(EntityManagerInterface $manager) { ...@@ -32,6 +34,23 @@ public function __construct(EntityManagerInterface $manager) {
$this->entityManager = $manager; $this->entityManager = $manager;
} }
/**
* {@inheritdoc}
*/
function getTranslationHandler($entity_type_id) {
return $this->entityManager->getHandler($entity_type_id, 'translation');
}
/**
* {@inheritdoc}
*/
public function getTranslationMetadata(EntityInterface $translation) {
// We need a new instance of the metadata handler wrapping each translation.
$entity_type = $translation->getEntityType();
$class = $entity_type->get('content_translation_metadata');
return new $class($translation, $this->getTranslationHandler($entity_type->id()));
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
namespace Drupal\content_translation; namespace Drupal\content_translation;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
/** /**
* Provides an interface for common functionality for content translation. * Provides an interface for common functionality for content translation.
*/ */
...@@ -31,6 +34,28 @@ public function getSupportedEntityTypes(); ...@@ -31,6 +34,28 @@ public function getSupportedEntityTypes();
*/ */
public function isSupported($entity_type_id); public function isSupported($entity_type_id);
/**
* Returns an instance of the Content translation handler.
*
* @param string $entity_type_id
* The type of the entity being translated.
*
* @return \Drupal\content_translation\ContentTranslationHandlerInterface
* An instance of the content translation handler.
*/
public function getTranslationHandler($entity_type_id);
/**
* Returns an instance of the Content translation metadata.
*
* @param \Drupal\Core\Entity\EntityInterface $translation
* The entity translation whose metadata needs to be retrieved.
*
* @return \Drupal\content_translation\ContentTranslationMetadataWrapperInterface
* An instance of the content translation metadata.
*/
public function getTranslationMetadata(EntityInterface $translation);
/** /**
* Sets the value for translatability of the given entity type bundle. * Sets the value for translatability of the given entity type bundle.
* *
......
<?php
/**
* @file
* Contains \Drupal\content_translation\ContentTranslationMetadata.
*/
namespace Drupal\content_translation;
use Drupal\Core\Entity\EntityInterface;
use Drupal\user\UserInterface;
/**
* Base class for content translation metadata wrappers.
*/
class ContentTranslationMetadataWrapper implements ContentTranslationMetadataWrapperInterface {
/**
* The wrapped entity translation.
*
* @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\FieldableEntityInterface|\Drupal\Core\TypedData\TranslatableInterface
*/
protected $translation;
/**
* The content translation handler.
*
* @var \Drupal\content_translation\ContentTranslationHandlerInterface
*/
protected $handler;
/**
* Initializes an instance of the content translation metadata handler.
*
* @param EntityInterface $translation
* The entity translation to be wrapped.
* @param ContentTranslationHandlerInterface $handler
* The content translation handler.
*/
public function __construct(EntityInterface $translation, ContentTranslationHandlerInterface $handler) {
$this->translation = $translation;
$this->handler = $handler;
}
/**
* {@inheritdoc}
*/
public function getSource() {
return $this->translation->get('content_translation_source')->value;
}
/**
* {@inheritdoc}
*/
public function setSource($source) {
$this->translation->set('content_translation_source', $source);
return $this;
}
/**
* {@inheritdoc}
*/
public function isOutdated() {
return (bool) $this->translation->get('content_translation_outdated')->value;
}
/**
* {@inheritdoc}
*/
public function setOutdated($outdated) {
$this->translation->set('content_translation_outdated', $outdated);
return $this;
}
/**
* {@inheritdoc}
*/
public function getAuthor() {
return $this->translation->hasField('content_translation_uid') ? $this->translation->get('content_translation_uid')->entity : $this->translation->getOwner();
}
/**
* {@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);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function isPublished() {
$field_name = $this->translation->hasField('content_translation_status') ? 'content_translation_status' : 'status';
return (bool) $this->translation->get($field_name)->value;
}
/**
* {@inheritdoc}
*/
public function setPublished($published) {
$field_name = $this->translation->hasField('content_translation_status') ? 'content_translation_status' : 'status';
$this->translation->set($field_name, $published);
return $this;
}
/**
* {@inheritdoc}
*/
public function getCreatedTime() {
$field_name = $this->translation->hasField('content_translation_created') ? 'content_translation_created' : 'created';
return $this->translation->get($field_name)->value;
}
/**
* {@inheritdoc}
*/
public function setCreatedTime($timestamp) {
$field_name = $this->translation->hasField('content_translation_created') ? 'content_translation_created' : 'created';
$this->translation->set($field_name, $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getChangedTime() {
return $this->translation->hasField('content_translation_changed') ? $this->translation->get('content_translation_changed')->value : $this->translation->getChangedTime();
}
/**
* {@inheritdoc}
*/
public function setChangedTime($timestamp) {
$field_name = $this->translation->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed';
$this->translation->set($field_name, $timestamp);
return $this;
}
}
<?php
/**
* @file
* Contains \Drupal\content_translation\ContentTranslationMetadataInterface.
*/
namespace Drupal\content_translation;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\user\UserInterface;
/**
* Common interface for content translation metadata wrappers.
*
* This acts as a wrapper for an entity translation object, encapsulating the
* logic needed to retrieve translation metadata.
*/
interface ContentTranslationMetadataWrapperInterface extends EntityChangedInterface {
/**
* Retrieves the source language for this translation.
*
* @return string
* The source language code.
*/
public function getSource();
/**
* Sets the source language for this translation.
*
* @param string $source
* The source language code.
*
* @return $this
*/
public function setSource($source);
/**
* Returns the translation outdated status.
*
* @return bool
* TRUE if the translation is outdated, FALSE otherwise.
*/
public function isOutdated();
/**
* Sets the translation outdated status.
*
* @param bool $outdated
* TRUE if the translation is outdated, FALSE otherwise.
*
* @return $this
*/
public function setOutdated($outdated);
/**
* Returns the translation author.
*
* @return \Drupal\user\UserInterface
* The user entity for the translation author.
*/
public function getAuthor();
/**
* Sets the translation author.
*
* @param \Drupal\user\UserInterface $account
* The translation author user entity.
*
* @return $this
*/
public function setAuthor(UserInterface $account);
/**
* Returns the translation published status.
*
* @return bool
* TRUE if the translation is published, FALSE otherwise.
*/
public function isPublished();
/**
* Sets the translation published status.
*
* @param bool $published
* TRUE if the translation is published, FALSE otherwise.
*
* @return $this
*/
public function setPublished($published);
/**
* Returns the translation creation timestamp.
*
* @return int
* The UNIX timestamp of when the translation was created.
*/
public function getCreatedTime();
/**
* Sets the translation creation timestamp.
*
* @param int $timestamp
* The UNIX timestamp of when the translation was created.
*
* @return $this
*/
public function setCreatedTime($timestamp);
/**
* Sets the translation modification timestamp.
*
* @param int $timestamp
* The UNIX timestamp of when the translation was last modified.
*
* @return $this
*/
public function setChangedTime($timestamp);
}
<?php
/**
* Contains \Drupal\content_translation\ContentTranslationUpdatesManager.
*/
namespace Drupal\content_translation;