summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreffulgentsia2018-02-06 03:39:45 (GMT)
committereffulgentsia2018-02-06 03:39:45 (GMT)
commita26167382e52351795ac285f3ee8643a3bbc60d0 (patch)
tree8a635db389f1311ca91a34a4b945dae500a56c28
parentdd993b38f6807a99d61cded55ccca58f94cae8b0 (diff)
Issue #2860097 by plach, timmillwood, Sam152, Wim Leers, hchonov, catch, amateescu, matsbla: Ensure that content translations can be moderated independently
-rw-r--r--core/modules/content_moderation/content_moderation.module18
-rw-r--r--core/modules/content_moderation/src/Entity/ContentModerationState.php12
-rw-r--r--core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php5
-rw-r--r--core/modules/content_moderation/src/EntityOperations.php30
-rw-r--r--core/modules/content_moderation/src/EntityTypeInfo.php99
-rw-r--r--core/modules/content_moderation/src/Form/EntityModerationForm.php3
-rw-r--r--core/modules/content_moderation/src/ModerationInformation.php33
-rw-r--r--core/modules/content_moderation/src/ModerationInformationInterface.php13
-rw-r--r--core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php5
-rw-r--r--core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php10
-rw-r--r--core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php58
-rw-r--r--core/modules/content_moderation/tests/src/Functional/ModerationLocaleTest.php379
-rw-r--r--core/modules/content_moderation/tests/src/Kernel/ViewsModerationStateFilterTest.php6
-rw-r--r--core/modules/content_translation/src/ContentTranslationManager.php17
-rw-r--r--core/modules/content_translation/src/Controller/ContentTranslationController.php28
-rw-r--r--core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php6
16 files changed, 560 insertions, 162 deletions
diff --git a/core/modules/content_moderation/content_moderation.module b/core/modules/content_moderation/content_moderation.module
index 985f91f..df1c222 100644
--- a/core/modules/content_moderation/content_moderation.module
+++ b/core/modules/content_moderation/content_moderation.module
@@ -128,6 +128,15 @@ function content_moderation_entity_translation_delete(EntityInterface $translati
}
/**
+ * Implements hook_entity_prepare_form().
+ */
+function content_moderation_entity_prepare_form(EntityInterface $entity, $operation, FormStateInterface $form_state) {
+ \Drupal::service('class_resolver')
+ ->getInstanceFromDefinition(EntityTypeInfo::class)
+ ->entityPrepareForm($entity, $operation, $form_state);
+}
+
+/**
* Implements hook_form_alter().
*/
function content_moderation_form_alter(&$form, FormStateInterface $form_state, $form_id) {
@@ -252,6 +261,7 @@ function content_moderation_action_info_alter(&$definitions) {
* Implements hook_entity_bundle_info_alter().
*/
function content_moderation_entity_bundle_info_alter(&$bundles) {
+ $translatable = FALSE;
/** @var \Drupal\workflows\WorkflowInterface $workflow */
foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
@@ -260,10 +270,18 @@ function content_moderation_entity_bundle_info_alter(&$bundles) {
foreach ($plugin->getBundlesForEntityType($entity_type_id) as $bundle_id) {
if (isset($bundles[$entity_type_id][$bundle_id])) {
$bundles[$entity_type_id][$bundle_id]['workflow'] = $workflow->id();
+ // If we have even one moderation-enabled translatable bundle, we need
+ // to make the moderation state bundle translatable as well, to enable
+ // the revision translation merge logic also for content moderation
+ // state revisions.
+ if (!empty($bundles[$entity_type_id][$bundle_id]['translatable'])) {
+ $translatable = TRUE;
+ }
}
}
}
}
+ $bundles['content_moderation_state']['content_moderation_state']['translatable'] = $translatable;
}
/**
diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php
index e54fbbe..b56f0d8 100644
--- a/core/modules/content_moderation/src/Entity/ContentModerationState.php
+++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
@@ -221,4 +221,16 @@ class ContentModerationState extends ContentEntityBase implements ContentModerat
return parent::save();
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function getFieldsToSkipFromTranslationChangesCheck() {
+ $field_names = parent::getFieldsToSkipFromTranslationChangesCheck();
+ // We need to skip the parent entity revision ID, since that will always
+ // change on every save, otherwise every translation would be marked as
+ // affected regardless of actual changes.
+ $field_names[] = 'content_entity_revision_id';
+ return $field_names;
+ }
+
}
diff --git a/core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php b/core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php
index f2c6917..c44ab09 100644
--- a/core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php
+++ b/core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php
@@ -35,11 +35,6 @@ class ModerationHandler implements ModerationHandlerInterface, EntityHandlerInte
// This is probably not necessary if configuration is setup correctly.
$entity->setNewRevision(TRUE);
$entity->isDefaultRevision($default_revision);
- if ($entity->hasField('revision_translation_affected')) {
- // @todo remove this when revision and translation issues have been
- // resolved. https://www.drupal.org/node/2860097
- $entity->set('revision_translation_affected', TRUE);
- }
// Update publishing status if it can be updated and if it needs updating.
if (($entity instanceof EntityPublishedInterface) && $entity->isPublished() !== $published_state) {
diff --git a/core/modules/content_moderation/src/EntityOperations.php b/core/modules/content_moderation/src/EntityOperations.php
index 0d25d2a..fa76bf0 100644
--- a/core/modules/content_moderation/src/EntityOperations.php
+++ b/core/modules/content_moderation/src/EntityOperations.php
@@ -98,10 +98,9 @@ class EntityOperations implements ContainerInjectionInterface {
$current_state = $workflow->getTypePlugin()
->getState($entity->moderation_state->value);
- // This entity is default if it is new, a new translation, the default
- // revision, or the default revision is not published.
+ // This entity is default if it is new, the default revision, or the
+ // default revision is not published.
$update_default_revision = $entity->isNew()
- || $entity->isNewTranslation()
|| $current_state->isDefaultRevisionState()
|| !$this->moderationInfo->isDefaultRevisionPublished($entity);
@@ -247,27 +246,28 @@ class EntityOperations implements ContainerInjectionInterface {
* @see EntityFieldManagerInterface::getExtraFields()
*/
public function entityView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
if (!$this->moderationInfo->isModeratedEntity($entity)) {
return;
}
- if (!$this->moderationInfo->isLatestRevision($entity)) {
+ // If the component is not defined for this display, we have nothing to do.
+ if (!$display->getComponent('content_moderation_control')) {
return;
}
- if ($this->moderationInfo->isLiveRevision($entity)) {
+ // The moderation form should be displayed only when viewing the latest
+ // (translation-affecting) revision, unless it was created as published
+ // default revision.
+ if (!$entity->isLatestRevision() && !$entity->isLatestTranslationAffectedRevision()) {
return;
}
- // Don't display the moderation form when when:
- // - The revision is not translation affected.
- // - There are more than one translation languages.
- // - The entity has pending revisions.
- if (!$this->moderationInfo->isPendingRevisionAllowed($entity)) {
- return;
+ if (($entity->isDefaultRevision() || $entity->wasDefaultRevision()) && ($moderation_state = $entity->get('moderation_state')->value)) {
+ $workflow = $this->moderationInfo->getWorkflowForEntity($entity);
+ if ($workflow->getTypePlugin()->getState($moderation_state)->isPublishedState()) {
+ return;
+ }
}
- $component = $display->getComponent('content_moderation_control');
- if ($component) {
- $build['content_moderation_control'] = $this->formBuilder->getForm(EntityModerationForm::class, $entity);
- }
+ $build['content_moderation_control'] = $this->formBuilder->getForm(EntityModerationForm::class, $entity);
}
}
diff --git a/core/modules/content_moderation/src/EntityTypeInfo.php b/core/modules/content_moderation/src/EntityTypeInfo.php
index 5c7cda9..c03cf49 100644
--- a/core/modules/content_moderation/src/EntityTypeInfo.php
+++ b/core/modules/content_moderation/src/EntityTypeInfo.php
@@ -7,10 +7,12 @@ use Drupal\Core\Entity\BundleEntityFormBase;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
+use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -269,6 +271,40 @@ class EntityTypeInfo implements ContainerInjectionInterface {
}
/**
+ * Replaces the entity form entity object with a proper revision object.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * The entity being edited.
+ * @param string $operation
+ * The entity form operation.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The form state.
+ *
+ * @see hook_entity_prepare_form()
+ */
+ public function entityPrepareForm(EntityInterface $entity, $operation, FormStateInterface $form_state) {
+ /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */
+ $form_object = $form_state->getFormObject();
+
+ if ($this->isModeratedEntityEditForm($form_object) && !$entity->isNew()) {
+ // Generate a proper revision object for the current entity. This allows
+ // to correctly handle translatable entities having pending revisions.
+ /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+ $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $new_revision */
+ $new_revision = $storage->createRevision($entity, FALSE);
+
+ // Restore the revision ID as other modules may expect to find it still
+ // populated. This will reset the "new revision" flag, however the entity
+ // object will be marked as a new revision again on submit.
+ // @see \Drupal\Core\Entity\ContentEntityForm::buildEntity()
+ $revision_key = $new_revision->getEntityType()->getKey('revision');
+ $new_revision->set($revision_key, $new_revision->getLoadedRevisionId());
+ $form_object->setEntity($new_revision);
+ }
+ }
+
+ /**
* Alters bundle forms to enforce revision handling.
*
* @param array $form
@@ -291,57 +327,15 @@ class EntityTypeInfo implements ContainerInjectionInterface {
$this->entityTypeManager->getHandler($config_entity_type->getBundleOf(), 'moderation')->enforceRevisionsBundleFormAlter($form, $form_state, $form_id);
}
}
- elseif ($form_object instanceof ContentEntityFormInterface && in_array($form_object->getOperation(), ['edit', 'default'])) {
+ elseif ($this->isModeratedEntityEditForm($form_object)) {
+ /** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $form_object->getEntity();
if ($this->moderationInfo->isModeratedEntity($entity)) {
$this->entityTypeManager
->getHandler($entity->getEntityTypeId(), 'moderation')
->enforceRevisionsEntityFormAlter($form, $form_state, $form_id);
- if (!$this->moderationInfo->isPendingRevisionAllowed($entity)) {
- $latest_revision = $this->moderationInfo->getLatestRevision($entity->getEntityTypeId(), $entity->id());
- if ($entity->bundle()) {
- $bundle_type_id = $entity->getEntityType()->getBundleEntityType();
- $bundle = $this->entityTypeManager->getStorage($bundle_type_id)->load($entity->bundle());
- $type_label = $bundle->label();
- }
- else {
- $type_label = $entity->getEntityType()->getLabel();
- }
-
- $translation = $this->moderationInfo->getAffectedRevisionTranslation($latest_revision);
- $args = [
- '@type_label' => $type_label,
- '@latest_revision_edit_url' => $translation->toUrl('edit-form', ['language' => $translation->language()])->toString(),
- '@latest_revision_delete_url' => $translation->toUrl('delete-form', ['language' => $translation->language()])->toString(),
- ];
- $label = $this->t('Unable to save this @type_label.', $args);
- $message = $this->t('<a href="@latest_revision_edit_url">Publish</a> or <a href="@latest_revision_delete_url">delete</a> the latest revision to allow all workflow transitions.', $args);
- $full_message = $this->t('Unable to save this @type_label. <a href="@latest_revision_edit_url">Publish</a> or <a href="@latest_revision_delete_url">delete</a> the latest revision to allow all workflow transitions.', $args);
- drupal_set_message($full_message, 'error');
-
- $form['moderation_state']['#access'] = FALSE;
- $form['actions']['#access'] = FALSE;
- $form['invalid_transitions'] = [
- 'label' => [
- '#type' => 'item',
- '#prefix' => '<strong class="label">',
- '#markup' => $label,
- '#suffix' => '</strong>',
- ],
- 'message' => [
- '#type' => 'item',
- '#markup' => $message,
- ],
- '#weight' => 999,
- '#no_valid_transitions' => TRUE,
- ];
-
- if ($form['footer']) {
- $form['invalid_transitions']['#group'] = 'footer';
- }
- }
-
// Submit handler to redirect to the latest version, if available.
$form['actions']['submit']['#submit'][] = [EntityTypeInfo::class, 'bundleFormRedirect'];
@@ -361,6 +355,21 @@ class EntityTypeInfo implements ContainerInjectionInterface {
}
/**
+ * Checks whether the specified form allows to edit a moderated entity.
+ *
+ * @param \Drupal\Core\Form\FormInterface $form_object
+ * The form object.
+ *
+ * @return bool
+ * TRUE if the form should get form moderation, FALSE otherwise.
+ */
+ protected function isModeratedEntityEditForm(FormInterface $form_object) {
+ return $form_object instanceof ContentEntityFormInterface &&
+ in_array($form_object->getOperation(), ['edit', 'default'], TRUE) &&
+ $this->moderationInfo->isModeratedEntity($form_object->getEntity());
+ }
+
+ /**
* Redirect content entity edit forms on save, if there is a pending revision.
*
* When saving their changes, editors should see those changes displayed on
diff --git a/core/modules/content_moderation/src/Form/EntityModerationForm.php b/core/modules/content_moderation/src/Form/EntityModerationForm.php
index 507ef54..98f6fdf 100644
--- a/core/modules/content_moderation/src/Form/EntityModerationForm.php
+++ b/core/modules/content_moderation/src/Form/EntityModerationForm.php
@@ -138,6 +138,9 @@ class EntityModerationForm extends FormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $form_state->get('entity');
+ /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+ $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
+ $entity = $storage->createRevision($entity, $entity->isDefaultRevision());
$new_state = $form_state->getValue('new_state');
diff --git a/core/modules/content_moderation/src/ModerationInformation.php b/core/modules/content_moderation/src/ModerationInformation.php
index 7e3e513..f42ec33 100644
--- a/core/modules/content_moderation/src/ModerationInformation.php
+++ b/core/modules/content_moderation/src/ModerationInformation.php
@@ -130,13 +130,6 @@ class ModerationInformation implements ModerationInformationInterface {
/**
* {@inheritdoc}
*/
- public function isPendingRevisionAllowed(ContentEntityInterface $entity) {
- return !(!$entity->isRevisionTranslationAffected() && count($entity->getTranslationLanguages()) > 1 && $this->hasPendingRevision($entity));
- }
-
- /**
- * {@inheritdoc}
- */
public function isLatestRevision(ContentEntityInterface $entity) {
return $entity->getRevisionId() == $this->getLatestRevisionId($entity->getEntityTypeId(), $entity->id());
}
@@ -145,8 +138,20 @@ class ModerationInformation implements ModerationInformationInterface {
* {@inheritdoc}
*/
public function hasPendingRevision(ContentEntityInterface $entity) {
- return $this->isModeratedEntity($entity)
- && !($this->getLatestRevisionId($entity->getEntityTypeId(), $entity->id()) == $this->getDefaultRevisionId($entity->getEntityTypeId(), $entity->id()));
+ $result = FALSE;
+ if ($this->isModeratedEntity($entity)) {
+ /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+ $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
+ $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $entity->language()->getId());
+ $default_revision_id = $entity->isDefaultRevision() && !$entity->isNewRevision() && ($revision_id = $entity->getRevisionId()) ?
+ $revision_id : $this->getDefaultRevisionId($entity->getEntityTypeId(), $entity->id());
+ if ($latest_revision_id != $default_revision_id) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $latest_revision */
+ $latest_revision = $storage->loadRevision($latest_revision_id);
+ $result = !$latest_revision->wasDefaultRevision();
+ }
+ }
+ return $result;
}
/**
@@ -172,9 +177,15 @@ class ModerationInformation implements ModerationInformationInterface {
// Loop through each language that has a translation.
foreach ($default_revision->getTranslationLanguages() as $language) {
// Load the translated revision.
- $language_revision = $default_revision->getTranslation($language->getId());
+ $translation = $default_revision->getTranslation($language->getId());
+ // If the moderation state is empty, it was not stored yet so no point
+ // in doing further work.
+ $moderation_state = $translation->moderation_state->value;
+ if (!$moderation_state) {
+ continue;
+ }
// Return TRUE if a translation with a published state is found.
- if ($workflow->getTypePlugin()->getState($language_revision->moderation_state->value)->isPublishedState()) {
+ if ($workflow->getTypePlugin()->getState($moderation_state)->isPublishedState()) {
return TRUE;
}
}
diff --git a/core/modules/content_moderation/src/ModerationInformationInterface.php b/core/modules/content_moderation/src/ModerationInformationInterface.php
index 1dafb3f..739c16b 100644
--- a/core/modules/content_moderation/src/ModerationInformationInterface.php
+++ b/core/modules/content_moderation/src/ModerationInformationInterface.php
@@ -101,19 +101,6 @@ interface ModerationInformationInterface {
public function getAffectedRevisionTranslation(ContentEntityInterface $entity);
/**
- * Determines if pending revisions are allowed.
- *
- * @internal
- *
- * @param \Drupal\Core\Entity\ContentEntityInterface $entity
- * The content entity.
- *
- * @return bool
- * If pending revisions are allowed.
- */
- public function isPendingRevisionAllowed(ContentEntityInterface $entity);
-
- /**
* Determines if an entity is a latest revision.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
diff --git a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
index 4c72e9a..1256bf2 100644
--- a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
+++ b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
@@ -166,10 +166,9 @@ class ModerationStateFieldItemList extends FieldItemList {
/** @var \Drupal\content_moderation\ContentModerationState $current_state */
$current_state = $workflow->getTypePlugin()->getState($moderation_state_id);
- // This entity is default if it is new, a new translation, the default
- // revision state, or the default revision is not published.
+ // This entity is default if it is new, the default revision state, or the
+ // default revision is not published.
$update_default_revision = $entity->isNew()
- || $entity->isNewTranslation()
|| $current_state->isDefaultRevisionState()
|| !$content_moderation_info->isDefaultRevisionPublished($entity);
diff --git a/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php b/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php
index 035ad3e..c658ef8 100644
--- a/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php
+++ b/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php
@@ -176,8 +176,8 @@ class ModerationStateFilter extends InOperator implements DependentWithRemovalPl
$entity_base_table_alias = $this->table;
// The bundle field of an entity type is not revisionable so we need to
- // join the data table.
- $entity_base_table = $entity_type->isTranslatable() ? $entity_type->getDataTable() : $entity_type->getBaseTable();
+ // join the base table.
+ $entity_base_table = $entity_type->getBaseTable();
$entity_revision_base_table = $entity_type->isTranslatable() ? $entity_type->getRevisionDataTable() : $entity_type->getRevisionTable();
if ($this->table === $entity_revision_base_table) {
$configuration = [
@@ -187,12 +187,6 @@ class ModerationStateFilter extends InOperator implements DependentWithRemovalPl
'left_field' => $entity_type->getKey('id'),
'type' => 'INNER',
];
- if ($entity_type->isTranslatable()) {
- $configuration['extra'][] = [
- 'field' => $entity_type->getKey('langcode'),
- 'left_field' => $entity_type->getKey('langcode'),
- ];
- }
$join = Views::pluginManager('join')->createInstance('standard', $configuration);
$entity_base_table_alias = $this->query->addRelationship($entity_base_table, $join, $entity_revision_base_table);
diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php
index b474762..4b48358 100644
--- a/core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php
+++ b/core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php
@@ -296,14 +296,6 @@ class ModerationFormTest extends ModerationStateTestBase {
$this->drupalGet($latest_version_path, ['language' => $french]);
$this->assertTrue($this->xpath('//ul[@class="entity-moderation-form"]'));
- // It should not be possible to add a new english revision.
- $this->drupalGet($edit_path);
- $this->assertSession()->fieldNotExists('moderation_state[0][state]');
- $this->assertSession()->pageTextContains('Unable to save this Moderated content.');
-
- $this->clickLink('Publish');
- $this->assertSession()->fieldValueEquals('body[0][value]', 'Third version of the content.');
-
$this->drupalGet($edit_path);
$this->clickLink('Delete');
$this->assertSession()->buttonExists('Delete');
@@ -324,7 +316,7 @@ class ModerationFormTest extends ModerationStateTestBase {
$this->drupalGet($latest_version_path, ['language' => $french]);
$this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
- // Now we can publish the english (revision 5).
+ // Publish the English pending revision (revision 5).
$this->drupalGet($edit_path);
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
@@ -337,13 +329,13 @@ class ModerationFormTest extends ModerationStateTestBase {
$this->drupalGet($latest_version_path);
$this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
- // Make sure we're allowed to create a pending french revision.
+ // Make sure we are allowed to create a pending French revision.
$this->drupalGet($edit_path, ['language' => $french]);
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
$this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
- // Add a english pending revision (revision 6).
+ // Add an English pending revision (revision 6).
$this->drupalGet($edit_path);
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
@@ -355,16 +347,10 @@ class ModerationFormTest extends ModerationStateTestBase {
$this->drupalGet($latest_version_path);
$this->assertTrue($this->xpath('//ul[@class="entity-moderation-form"]'));
-
- // Make sure we're not allowed to create a pending french revision.
- $this->drupalGet($edit_path, ['language' => $french]);
- $this->assertSession()->fieldNotExists('moderation_state[0][state]');
- $this->assertSession()->pageTextContains('Unable to save this Moderated content.');
-
$this->drupalGet($latest_version_path, ['language' => $french]);
$this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
- // We should be able to publish the english pending revision (revision 7)
+ // Publish the English pending revision (revision 7)
$this->drupalGet($edit_path);
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
@@ -377,44 +363,17 @@ class ModerationFormTest extends ModerationStateTestBase {
$this->drupalGet($latest_version_path);
$this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
- // Make sure we're allowed to create a pending french revision.
+ // Make sure we are allowed to create a pending French revision.
$this->drupalGet($edit_path, ['language' => $french]);
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
$this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
- // Make sure we're allowed to create a pending english revision.
- $this->drupalGet($edit_path);
- $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
- $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
- $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
-
- // Create new moderated content. (revision 1).
- $this->drupalPostForm('node/add/moderated_content', [
- 'title[0][value]' => 'Second moderated content',
- 'body[0][value]' => 'First version of the content.',
- 'moderation_state[0][state]' => 'published',
- ], t('Save'));
-
- $node = $this->drupalGetNodeByTitle('Second moderated content');
- $this->assertTrue($node->language(), 'en');
- $edit_path = sprintf('node/%d/edit', $node->id());
- $translate_path = sprintf('node/%d/translations/add/en/fr', $node->id());
-
- // Add a pending revision (revision 2).
+ // Make sure we are allowed to create a pending English revision.
$this->drupalGet($edit_path);
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
$this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
- $this->drupalPostForm(NULL, [
- 'body[0][value]' => 'Second version of the content.',
- 'moderation_state[0][state]' => 'draft',
- ], t('Save'));
-
- // It shouldn't be possible to translate as we have a pending revision.
- $this->drupalGet($translate_path);
- $this->assertSession()->fieldNotExists('moderation_state[0][state]');
- $this->assertSession()->pageTextContains('Unable to save this Moderated content.');
// Create new moderated content (revision 1).
$this->drupalPostForm('node/add/moderated_content', [
@@ -445,11 +404,6 @@ class ModerationFormTest extends ModerationStateTestBase {
'moderation_state[0][state]' => 'draft',
], t('Save (this translation)'));
- // Editing the original translation should not be possible.
- $this->drupalGet($edit_path);
- $this->assertSession()->fieldNotExists('moderation_state[0][state]');
- $this->assertSession()->pageTextContains('Unable to save this Moderated content.');
-
// Updating and publishing the french translation is still possible.
$this->drupalGet($edit_path, ['language' => $french]);
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationLocaleTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationLocaleTest.php
index 3104450..28de50c 100644
--- a/core/modules/content_moderation/tests/src/Functional/ModerationLocaleTest.php
+++ b/core/modules/content_moderation/tests/src/Functional/ModerationLocaleTest.php
@@ -2,6 +2,8 @@
namespace Drupal\Tests\content_moderation\Functional;
+use Drupal\node\NodeInterface;
+
/**
* Test content_moderation functionality with localization and translation.
*
@@ -22,19 +24,23 @@ class ModerationLocaleTest extends ModerationStateTestBase {
];
/**
- * Tests article translations can be moderated separately.
+ * {@inheritdoc}
*/
- public function testTranslateModeratedContent() {
+ protected function setUp() {
+ parent::setUp();
+
$this->drupalLogin($this->rootUser);
// Enable moderation on Article node type.
$this->createContentTypeFromUi('Article', 'article', TRUE);
- // Add French language.
- $edit = [
- 'predefined_langcode' => 'fr',
- ];
- $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
+ // Add French and Italian languages.
+ foreach (['fr', 'it'] as $langcode) {
+ $edit = [
+ 'predefined_langcode' => $langcode,
+ ];
+ $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
+ }
// Enable content translation on articles.
$this->drupalGet('admin/config/regional/content-language');
@@ -48,7 +54,12 @@ class ModerationLocaleTest extends ModerationStateTestBase {
// Adding languages requires a container rebuild in the test running
// environment so that multilingual services are used.
$this->rebuildContainer();
+ }
+ /**
+ * Tests article translations can be moderated separately.
+ */
+ public function testTranslateModeratedContent() {
// Create a published article in English.
$edit = [
'title[0][value]' => 'Published English node',
@@ -191,4 +202,358 @@ class ModerationLocaleTest extends ModerationStateTestBase {
$this->assertFalse($french_node->isPublished());
}
+ /**
+ * Tests that individual translations can be moderated independently.
+ */
+ public function testLanguageIndependentContentModeration() {
+ // Create a published article in English (revision 1).
+ $this->drupalGet('node/add/article');
+ $node = $this->submitNodeForm('Test 1.1 EN', 'published');
+ $this->assertNotLatestVersionPage($node);
+
+ $edit_path = $node->toUrl('edit-form');
+ $translate_path = $node->toUrl('drupal:content-translation-overview');
+
+ // Create a new English draft (revision 2).
+ $this->drupalGet($edit_path);
+ $this->submitNodeForm('Test 1.2 EN', 'draft', TRUE);
+ $this->assertLatestVersionPage($node);
+
+ // Add a French translation draft (revision 3).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Add'));
+ $this->submitNodeForm('Test 1.3 FR', 'draft');
+ $fr_node = $this->loadTranslation($node, 'fr');
+ $this->assertLatestVersionPage($fr_node);
+ $this->assertModerationForm($node);
+
+ // Add an Italian translation draft (revision 4).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Add'));
+ $this->submitNodeForm('Test 1.4 IT', 'draft');
+ $it_node = $this->loadTranslation($node, 'it');
+ $this->assertLatestVersionPage($it_node);
+ $this->assertModerationForm($node);
+ $this->assertModerationForm($fr_node);
+
+ // Publish the English draft (revision 5).
+ $this->drupalGet($edit_path);
+ $this->submitNodeForm('Test 1.5 EN', 'published', TRUE);
+ $this->assertNotLatestVersionPage($node);
+ $this->assertModerationForm($fr_node);
+ $this->assertModerationForm($it_node);
+
+ // Publish the Italian draft (revision 6).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Edit'), 2);
+ $this->submitNodeForm('Test 1.6 IT', 'published');
+ $this->assertNotLatestVersionPage($it_node);
+ $this->assertNoModerationForm($node);
+ $this->assertModerationForm($fr_node);
+
+ // Publish the French draft (revision 7).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Edit'), 1);
+ $this->submitNodeForm('Test 1.7 FR', 'published');
+ $this->assertNotLatestVersionPage($fr_node);
+ $this->assertNoModerationForm($node);
+ $this->assertNoModerationForm($it_node);
+
+ // Create an Italian draft (revision 8).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Edit'), 2);
+ $this->submitNodeForm('Test 1.8 IT', 'draft');
+ $this->assertLatestVersionPage($it_node);
+ $this->assertNoModerationForm($node);
+ $this->assertNoModerationForm($fr_node);
+
+ // Create a French draft (revision 9).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Edit'), 1);
+ $this->submitNodeForm('Test 1.9 FR', 'draft');
+ $this->assertLatestVersionPage($fr_node);
+ $this->assertNoModerationForm($node);
+ $this->assertModerationForm($it_node);
+
+ // Create an English draft (revision 10).
+ $this->drupalGet($edit_path);
+ $this->submitNodeForm('Test 1.10 EN', 'draft');
+ $this->assertLatestVersionPage($node);
+ $this->assertModerationForm($fr_node);
+ $this->assertModerationForm($it_node);
+
+ // Now start from a draft article in English (revision 1).
+ $this->drupalGet('node/add/article');
+ $node2 = $this->submitNodeForm('Test 2.1 EN', 'draft', TRUE);
+ $this->assertNotLatestVersionPage($node2, TRUE);
+
+ $edit_path = $node2->toUrl('edit-form');
+ $translate_path = $node2->toUrl('drupal:content-translation-overview');
+
+ // Add a French translation (revision 2).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Add'));
+ $this->submitNodeForm('Test 2.2 FR', 'draft');
+ $fr_node2 = $this->loadTranslation($node2, 'fr');
+ $this->assertNotLatestVersionPage($fr_node2, TRUE);
+ $this->assertModerationForm($node2, FALSE);
+
+ // Add an Italian translation (revision 3).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Add'));
+ $this->submitNodeForm('Test 2.3 IT', 'draft');
+ $it_node2 = $this->loadTranslation($node2, 'it');
+ $this->assertNotLatestVersionPage($it_node2, TRUE);
+ $this->assertModerationForm($node2, FALSE);
+ $this->assertModerationForm($fr_node2, FALSE);
+
+ // Publish the English draft (revision 4).
+ $this->drupalGet($edit_path);
+ $this->submitNodeForm('Test 2.4 EN', 'published', TRUE);
+ $this->assertNotLatestVersionPage($node2);
+ $this->assertModerationForm($fr_node2, FALSE);
+ $this->assertModerationForm($it_node2, FALSE);
+
+ // Publish the Italian draft (revision 5).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Edit'), 2);
+ $this->submitNodeForm('Test 2.5 IT', 'published');
+ $this->assertNotLatestVersionPage($it_node2);
+ $this->assertNoModerationForm($node2);
+ $this->assertModerationForm($fr_node2, FALSE);
+
+ // Publish the French draft (revision 6).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Edit'), 1);
+ $this->submitNodeForm('Test 2.6 FR', 'published');
+ $this->assertNotLatestVersionPage($fr_node2);
+ $this->assertNoModerationForm($node2);
+ $this->assertNoModerationForm($it_node2);
+
+ // Now that all revision translations are published, verify that the
+ // moderation form is never displayed on revision pages.
+ /** @var \Drupal\node\NodeStorageInterface $storage */
+ $storage = $this->container->get('entity_type.manager')->getStorage('node');
+ foreach (range(11, 16) as $revision_id) {
+ /** @var \Drupal\node\NodeInterface $revision */
+ $revision = $storage->loadRevision($revision_id);
+ foreach ($revision->getTranslationLanguages() as $langcode => $language) {
+ if ($revision->isRevisionTranslationAffected()) {
+ $this->drupalGet($revision->toUrl('revision'));
+ $this->assertFalse($this->hasModerationForm(), 'Moderation form is not displayed correctly for revision ' . $revision_id);
+ break;
+ }
+ }
+ }
+
+ // Create an Italian draft (revision 7).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Edit'), 2);
+ $this->submitNodeForm('Test 2.7 IT', 'draft');
+ $this->assertLatestVersionPage($it_node2);
+ $this->assertNoModerationForm($node2);
+ $this->assertNoModerationForm($fr_node2);
+
+ // Create a French draft (revision 8).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Edit'), 1);
+ $this->submitNodeForm('Test 2.8 FR', 'draft');
+ $this->assertLatestVersionPage($fr_node2);
+ $this->assertNoModerationForm($node2);
+ $this->assertModerationForm($it_node2);
+
+ // Create an English draft (revision 9).
+ $this->drupalGet($edit_path);
+ $this->submitNodeForm('Test 2.9 EN', 'draft', TRUE);
+ $this->assertLatestVersionPage($node2);
+ $this->assertModerationForm($fr_node2);
+ $this->assertModerationForm($it_node2);
+
+ // Now publish a draft in another language first and verify that the
+ // moderation form is not displayed on the English node view page.
+ $this->drupalGet('node/add/article');
+ $node3 = $this->submitNodeForm('Test 3.1 EN', 'published');
+ $this->assertNotLatestVersionPage($node3);
+
+ $edit_path = $node3->toUrl('edit-form');
+ $translate_path = $node3->toUrl('drupal:content-translation-overview');
+
+ // Create an English draft (revision 2).
+ $this->drupalGet($edit_path);
+ $this->submitNodeForm('Test 3.2 EN', 'draft', TRUE);
+ $this->assertLatestVersionPage($node3);
+
+ // Add a French translation (revision 3).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Add'));
+ $this->submitNodeForm('Test 3.3 FR', 'draft');
+ $fr_node3 = $this->loadTranslation($node3, 'fr');
+ $this->assertLatestVersionPage($fr_node3);
+ $this->assertModerationForm($node3);
+
+ // Publish the French draft (revision 4).
+ $this->drupalGet($translate_path);
+ $this->clickLink(t('Edit'), 1);
+ $this->submitNodeForm('Test 3.4 FR', 'published');
+ $this->assertNotLatestVersionPage($fr_node3);
+ $this->assertModerationForm($node3);
+ }
+
+ /**
+ * Checks that new translation values are populated properly.
+ */
+ public function testNewTranslationSourceValues() {
+ // Create a published article in Italian (revision 1).
+ $this->drupalGet('node/add/article');
+ $node = $this->submitNodeForm('Test 1.1 IT', 'published', TRUE, 'it');
+ $this->assertNotLatestVersionPage($node);
+
+ // Create a new draft (revision 2).
+ $this->drupalGet($node->toUrl('edit-form'));
+ $this->submitNodeForm('Test 1.2 IT', 'draft', TRUE);
+ $this->assertLatestVersionPage($node);
+
+ // Create an English draft (revision 3) and verify that the Italian draft
+ // values are used as source values.
+ $url = $node->toUrl('drupal:content-translation-add');
+ $url->setRouteParameter('source', 'it');
+ $url->setRouteParameter('target', 'en');
+ $this->drupalGet($url);
+ $this->assertSession()->pageTextContains('Test 1.2 IT');
+ $this->submitNodeForm('Test 1.3 EN', 'draft');
+ $this->assertLatestVersionPage($node);
+
+ // Create a French draft (without saving) and verify that the Italian draft
+ // values are used as source values.
+ $url->setRouteParameter('target', 'fr');
+ $this->drupalGet($url);
+ $this->assertSession()->pageTextContains('Test 1.2 IT');
+
+ // Now switch source language and verify that the English draft values are
+ // used as source values.
+ $url->setRouteParameter('source', 'en');
+ $this->drupalGet($url);
+ $this->assertSession()->pageTextContains('Test 1.3 EN');
+ }
+
+ /**
+ * Submits the node form at the current URL with the specified values.
+ *
+ * @param string $title
+ * The node title.
+ * @param string $moderation_state
+ * The moderation state.
+ * @param bool $default_translation
+ * (optional) Whether we are editing the default translation.
+ * @param string|null $langcode
+ * (optional) The node language. Defaults to English.
+ *
+ * @return \Drupal\node\NodeInterface|null
+ * A node object if a new one is being created, NULL otherwise.
+ */
+ protected function submitNodeForm($title, $moderation_state, $default_translation = FALSE, $langcode = 'en') {
+ $is_new = strpos($this->getSession()->getCurrentUrl(), '/node/add/') !== FALSE;
+ $edit = [
+ 'title[0][value]' => $title,
+ 'moderation_state[0][state]' => $moderation_state,
+ ];
+ if ($is_new) {
+ $default_translation = TRUE;
+ $edit['langcode[0][value]'] = $langcode;
+ }
+ $submit = $default_translation ? t('Save') : t('Save (this translation)');
+ $this->drupalPostForm(NULL, $edit, $submit);
+ $message = $is_new ? "Article $title has been created." : "Article $title has been updated.";
+ $this->assertSession()->pageTextContains($message);
+ return $is_new ? $this->drupalGetNodeByTitle($title) : NULL;
+ }
+
+ /**
+ * Loads the node translation for the specified language.
+ *
+ * @param \Drupal\node\NodeInterface $node
+ * A node object.
+ * @param string $langcode
+ * The translation language code.
+ *
+ * @return \Drupal\node\NodeInterface
+ * The node translation object.
+ */
+ protected function loadTranslation(NodeInterface $node, $langcode) {
+ /** @var \Drupal\node\NodeStorageInterface $storage */
+ $storage = $this->container->get('entity_type.manager')->getStorage('node');
+ /** @var \Drupal\node\NodeInterface $node */
+ $node = $storage->loadRevision($storage->getLatestRevisionId($node->id()));
+ return $node->getTranslation($langcode);
+ }
+
+ /**
+ * Asserts that this is the "latest version" page for the specified node.
+ *
+ * @param \Drupal\node\NodeInterface $node
+ * A node object.
+ */
+ public function assertLatestVersionPage(NodeInterface $node) {
+ $this->assertEquals($node->toUrl('latest-version')->setAbsolute()->toString(), $this->getSession()->getCurrentUrl());
+ $this->assertModerationForm($node);
+ }
+
+ /**
+ * Asserts that this is not the "latest version" page for the specified node.
+ *
+ * @param \Drupal\node\NodeInterface $node
+ * A node object.
+ * @param bool $moderation_form
+ * (optional) Whether the page should contain the moderation form. Defaults
+ * to FALSE.
+ */
+ public function assertNotLatestVersionPage(NodeInterface $node, $moderation_form = FALSE) {
+ $this->assertNotEquals($node->toUrl('latest-version')->setAbsolute()->toString(), $this->getSession()->getCurrentUrl());
+ if ($moderation_form) {
+ $this->assertModerationForm($node, FALSE);
+ }
+ else {
+ $this->assertNoModerationForm($node);
+ }
+ }
+
+ /**
+ * Asserts that the moderation form is displayed for the specified node.
+ *
+ * @param \Drupal\node\NodeInterface $node
+ * A node object.
+ * @param bool $latest_tab
+ * (optional) Whether the node form is expected to be displayed on the
+ * latest version page or on the node view page. Defaults to the former.
+ */
+ public function assertModerationForm(NodeInterface $node, $latest_tab = TRUE) {
+ $this->drupalGet($node->toUrl());
+ $this->assertEquals(!$latest_tab, $this->hasModerationForm());
+ $this->drupalGet($node->toUrl('latest-version'));
+ $this->assertEquals($latest_tab, $this->hasModerationForm());
+ }
+
+ /**
+ * Asserts that the moderation form is not displayed for the specified node.
+ *
+ * @param \Drupal\node\NodeInterface $node
+ * A node object.
+ */
+ public function assertNoModerationForm(NodeInterface $node) {
+ $this->drupalGet($node->toUrl());
+ $this->assertFalse($this->hasModerationForm());
+ $this->drupalGet($node->toUrl('latest-version'));
+ $this->assertEquals(403, $this->getSession()->getStatusCode());
+ }
+
+ /**
+ * Checks whether the page contains the moderation form.
+ *
+ * @return bool
+ * TRUE if the moderation form could be find in the page, FALSE otherwise.
+ */
+ public function hasModerationForm() {
+ return (bool) $this->xpath('//ul[@class="entity-moderation-form"]');
+ }
+
}
diff --git a/core/modules/content_moderation/tests/src/Kernel/ViewsModerationStateFilterTest.php b/core/modules/content_moderation/tests/src/Kernel/ViewsModerationStateFilterTest.php
index 637e70a..da7ac92 100644
--- a/core/modules/content_moderation/tests/src/Kernel/ViewsModerationStateFilterTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/ViewsModerationStateFilterTest.php
@@ -121,10 +121,10 @@ class ViewsModerationStateFilterTest extends ViewsKernelTestBase {
$translated_forward_revision->moderation_state = 'translated_draft';
$translated_forward_revision->save();
- // Four revisions for the nodes when no filter.
- $this->assertNodesWithFilters([$node, $second_node, $third_node, $third_node], []);
+ // The three default revisions are listed when no filter is specified.
+ $this->assertNodesWithFilters([$node, $second_node, $third_node], []);
- // The default revision of node one and three is published.
+ // The default revision of node one and three are published.
$this->assertNodesWithFilters([$node, $third_node], [
'default_revision_state' => 'editorial-published',
]);
diff --git a/core/modules/content_translation/src/ContentTranslationManager.php b/core/modules/content_translation/src/ContentTranslationManager.php
index 8b3831a..3a21f94 100644
--- a/core/modules/content_translation/src/ContentTranslationManager.php
+++ b/core/modules/content_translation/src/ContentTranslationManager.php
@@ -145,4 +145,21 @@ class ContentTranslationManager implements ContentTranslationManagerInterface, B
return $config;
}
+ /**
+ * Checks whether support for pending revisions should be enabled.
+ *
+ * @return bool
+ * TRUE if pending revisions should be enabled, FALSE otherwise.
+ *
+ * @internal
+ * There is ongoing discussion about how pending revisions should behave.
+ * The logic enabling pending revision support is likely to change once a
+ * decision is made.
+ *
+ * @see https://www.drupal.org/node/2940575
+ */
+ public static function isPendingRevisionSupportEnabled() {
+ return \Drupal::moduleHandler()->moduleExists('content_moderation');
+ }
+
}
diff --git a/core/modules/content_translation/src/Controller/ContentTranslationController.php b/core/modules/content_translation/src/Controller/ContentTranslationController.php
index 190778d..e556154 100644
--- a/core/modules/content_translation/src/Controller/ContentTranslationController.php
+++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php
@@ -2,6 +2,7 @@
namespace Drupal\content_translation\Controller;
+use Drupal\content_translation\ContentTranslationManager;
use Drupal\content_translation\ContentTranslationManagerInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Controller\ControllerBase;
@@ -87,6 +88,7 @@ class ContentTranslationController extends ControllerBase {
$handler = $this->entityManager()->getHandler($entity_type_id, 'translation');
$manager = $this->manager;
$entity_type = $entity->getEntityType();
+ $use_latest_revisions = $entity_type->isRevisionable() && ContentTranslationManager::isPendingRevisionSupportEnabled();
// Start collecting the cacheability metadata, starting with the entity and
// later merge in the access result cacheability metadata.
@@ -99,6 +101,9 @@ class ContentTranslationController extends ControllerBase {
$rows = [];
$show_source_column = FALSE;
+ $default_revision = $entity;
+ /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+ $storage = $this->entityTypeManager()->getStorage($entity_type_id);
if ($this->languageManager()->isMultilingual()) {
// Determine whether the current entity is translatable.
@@ -121,6 +126,16 @@ class ContentTranslationController extends ControllerBase {
$language_name = $language->getName();
$langcode = $language->getId();
+ // If the entity type is revisionable, we may have pending revisions
+ // with translations not available yet in the default revision. Thus we
+ // need to load the latest translation-affecting revision for each
+ // language to be sure we are listing all available translations.
+ if ($use_latest_revisions) {
+ $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $langcode);
+ $entity = $latest_revision_id ? $storage->loadRevision($latest_revision_id) : $default_revision;
+ $translations = $entity->getTranslationLanguages();
+ }
+
$add_url = new Url(
"entity.$entity_type_id.content_translation_add",
[
@@ -330,8 +345,21 @@ class ContentTranslationController extends ControllerBase {
* A processed form array ready to be rendered.
*/
public function add(LanguageInterface $source, LanguageInterface $target, RouteMatchInterface $route_match, $entity_type_id = NULL) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $route_match->getParameter($entity_type_id);
+ // In case of a pending revision, make sure we load the latest
+ // translation-affecting revision for the source language, otherwise the
+ // initial form values may not be up-to-date.
+ if (!$entity->isDefaultRevision() && ContentTranslationManager::isPendingRevisionSupportEnabled()) {
+ /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+ $storage = $this->entityTypeManager()->getStorage($entity->getEntityTypeId());
+ $revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $source->getId());
+ if ($revision_id != $entity->getRevisionId()) {
+ $entity = $storage->loadRevision($revision_id);
+ }
+ }
+
// @todo Exploit the upcoming hook_entity_prepare() when available.
// See https://www.drupal.org/node/1810394.
$this->prepareTranslation($entity, $source, $target);
diff --git a/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php b/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php
index 6aae426..ac979d3 100644
--- a/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php
+++ b/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php
@@ -2,6 +2,7 @@
namespace Drupal\content_translation\Routing;
+use Drupal\content_translation\ContentTranslationManager;
use Drupal\content_translation\ContentTranslationManagerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
@@ -55,6 +56,7 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
}
$path = $base_path . '/translations';
+ $load_latest_revision = ContentTranslationManager::isPendingRevisionSupportEnabled();
$route = new Route(
$path,
@@ -70,6 +72,7 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
'parameters' => [
$entity_type_id => [
'type' => 'entity:' . $entity_type_id,
+ 'load_latest_revision' => $load_latest_revision,
],
],
'_admin_route' => $is_admin,
@@ -102,6 +105,7 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
],
$entity_type_id => [
'type' => 'entity:' . $entity_type_id,
+ 'load_latest_revision' => $load_latest_revision,
],
],
'_admin_route' => $is_admin,
@@ -127,6 +131,7 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
],
$entity_type_id => [
'type' => 'entity:' . $entity_type_id,
+ 'load_latest_revision' => $load_latest_revision,
],
],
'_admin_route' => $is_admin,
@@ -152,6 +157,7 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
],
$entity_type_id => [
'type' => 'entity:' . $entity_type_id,
+ 'load_latest_revision' => $load_latest_revision,
],
],
'_admin_route' => $is_admin,