diff --git a/core/modules/content_moderation/src/ParamConverter/EntityRevisionConverter.php b/core/modules/content_moderation/src/ParamConverter/EntityRevisionConverter.php index 94f10e9..c981013 100644 --- a/core/modules/content_moderation/src/ParamConverter/EntityRevisionConverter.php +++ b/core/modules/content_moderation/src/ParamConverter/EntityRevisionConverter.php @@ -98,9 +98,7 @@ public function convert($value, $definition, $name, array $defaults) { $latest_revision = $this->entityManager->getTranslationFromContext($latest_revision, NULL, ['operation' => 'entity_upcast']); } - if ($latest_revision->isRevisionTranslationAffected()) { - $entity = $latest_revision; - } + $entity = $latest_revision; } return $entity; diff --git a/core/modules/content_moderation/src/StateTransitionValidation.php b/core/modules/content_moderation/src/StateTransitionValidation.php index c4e26c2..e8e2a4c 100644 --- a/core/modules/content_moderation/src/StateTransitionValidation.php +++ b/core/modules/content_moderation/src/StateTransitionValidation.php @@ -42,8 +42,23 @@ public function getValidTransitions(ContentEntityInterface $entity, AccountInter $workflow = $this->moderationInfo->getWorkflowForEntity($entity); $current_state = $entity->moderation_state->value ? $workflow->getState($entity->moderation_state->value) : $workflow->getInitialState(); - return array_filter($current_state->getTransitions(), function(Transition $transition) use ($workflow, $user) { - return $user->hasPermission('use ' . $workflow->id() . ' transition ' . $transition->id()); + return array_filter($current_state->getTransitions(), function(Transition $transition) use ($workflow, $user, $entity) { + // Don't show the transition if the user doesn't have permission to use + // it. + if (!$user->hasPermission('use ' . $workflow->id() . ' transition ' . $transition->id())) { + return FALSE; + } + + // For entities with more than one translation and forward revisions we + // want to only allow specific transitions. + if (count($entity->getTranslationLanguages()) > 1 && $this->moderationInfo->hasForwardRevision($entity)) { + // The entity needs to be the latest and translation affected, or be + // going from and to the same default state. + return ($this->moderationInfo->isLatestRevision($entity) && $entity->isRevisionTranslationAffected()) + || (($entity->isDefaultRevision() && $transition->to()->isDefaultRevisionState()) || (!$entity->isDefaultRevision() && !$transition->to()->isDefaultRevisionState())); + } + + return TRUE; }); } diff --git a/core/modules/content_moderation/src/Tests/ModerationFormTest.php b/core/modules/content_moderation/src/Tests/ModerationFormTest.php index 16da4e4..07d221d 100644 --- a/core/modules/content_moderation/src/Tests/ModerationFormTest.php +++ b/core/modules/content_moderation/src/Tests/ModerationFormTest.php @@ -9,6 +9,19 @@ */ class ModerationFormTest extends ModerationStateTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = [ + 'node', + 'content_moderation', + 'locale', + 'content_translation', + ]; + /** * {@inheritdoc} */ @@ -103,4 +116,98 @@ public function testModerationForm() { $this->assertResponse(403); } + public function testContentTranslationNodeForm() { + $this->drupalLogin($this->rootUser); + + // Add French language. + $edit = [ + 'predefined_langcode' => 'fr', + ]; + $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language')); + + // Enable content translation on articles. + $this->drupalGet('admin/config/regional/content-language'); + $edit = [ + 'entity_types[node]' => TRUE, + 'settings[node][moderated_content][translatable]' => TRUE, + 'settings[node][moderated_content][settings][language][language_alterable]' => TRUE, + ]; + $this->drupalPostForm(NULL, $edit, t('Save configuration')); + + // Adding languages requires a container rebuild in the test running + // environment so that multilingual services are used. + $this->rebuildContainer(); + + // Create new moderated content in draft. + $this->drupalPostForm('node/add/moderated_content', [ + 'title[0][value]' => 'Some moderated content', + 'body[0][value]' => 'First version of the content.', + ], t('Save and Create New Draft')); + + $node = $this->drupalGetNodeByTitle('Some 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 french translation. + $this->drupalGet($translate_path); + $this->assertTrue($this->xpath('//input[@value="Save and Create New Draft (this translation)"]')); + $this->assertTrue($this->xpath('//input[@value="Save and Publish (this translation)"]')); + $this->assertFalse($this->xpath('//input[@value="Save and Archive (this translation)"]')); + $this->drupalPostForm(NULL, [ + 'body[0][value]' => 'Second version of the content.', + ], t('Save and Publish (this translation)')); + + // Add french forward revision. + $this->drupalGet('fr/' . $edit_path); + $this->assertTrue($this->xpath('//input[@value="Save and Create New Draft (this translation)"]')); + $this->assertTrue($this->xpath('//input[@value="Save and Publish (this translation)"]')); + $this->assertTrue($this->xpath('//input[@value="Save and Archive (this translation)"]')); + $this->drupalPostForm(NULL, [ + 'body[0][value]' => 'Third version of the content.', + ], t('Save and Create New Draft (this translation)')); + + // Add another english revision. + $this->drupalGet($edit_path); + $this->assertTrue($this->xpath('//input[@value="Save and Create New Draft (this translation)"]')); + $this->assertFalse($this->xpath('//input[@value="Save and Publish (this translation)"]')); + $this->assertFalse($this->xpath('//input[@value="Save and Archive (this translation)"]')); + $this->drupalPostForm(NULL, [ + 'body[0][value]' => 'Forth version of the content.', + ], t('Save and Create New Draft (this translation)')); + + // Publish the french forward revision + $this->drupalGet('fr/' . $edit_path); + $this->assertTrue($this->xpath('//input[@value="Save and Create New Draft (this translation)"]')); + $this->assertTrue($this->xpath('//input[@value="Save and Publish (this translation)"]')); + $this->assertFalse($this->xpath('//input[@value="Save and Archive (this translation)"]')); + $this->drupalPostForm(NULL, [ + 'body[0][value]' => 'Fifth version of the content.', + ], t('Save and Publish (this translation)')); + + // Now we can publish the english. + $this->drupalGet($edit_path); + $this->assertTrue($this->xpath('//input[@value="Save and Create New Draft (this translation)"]')); + $this->assertTrue($this->xpath('//input[@value="Save and Publish (this translation)"]')); + $this->assertFalse($this->xpath('//input[@value="Save and Archive (this translation)"]')); + $this->drupalPostForm(NULL, [ + 'body[0][value]' => 'Sixth version of the content.', + ], t('Save and Publish (this translation)')); + + // Make sure we're allowed to create a forward french revision. + $this->drupalGet('fr/' . $edit_path); + $this->assertTrue($this->xpath('//input[@value="Save and Create New Draft (this translation)"]')); + $this->assertTrue($this->xpath('//input[@value="Save and Publish (this translation)"]')); + $this->assertTrue($this->xpath('//input[@value="Save and Archive (this translation)"]')); + + // Add a english forward revision. + $this->drupalGet($edit_path); + $this->assertTrue($this->xpath('//input[@value="Save and Create New Draft (this translation)"]')); + $this->assertTrue($this->xpath('//input[@value="Save and Publish (this translation)"]')); + $this->assertTrue($this->xpath('//input[@value="Save and Archive (this translation)"]')); + $this->drupalPostForm(NULL, [ + 'body[0][value]' => 'Seventh version of the content.', + ], t('Save and Create New Draft (this translation)')); + } + } diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationLocaleTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationLocaleTest.php index a9d95f0..7b50613 100644 --- a/core/modules/content_moderation/tests/src/Functional/ModerationLocaleTest.php +++ b/core/modules/content_moderation/tests/src/Functional/ModerationLocaleTest.php @@ -144,17 +144,12 @@ public function testTranslateModeratedContent() { $this->assertTrue($french_node->isPublished()); $this->assertEqual($french_node->getTitle(), 'Translated node', 'The default revision of the published translation remains the same.'); - // Publish the draft. - $edit = [ - 'new_state' => 'published', - ]; - $this->drupalPostForm('fr/node/' . $english_node->id() . '/latest', $edit, t('Apply')); - $this->assertText(t('The moderation state has been updated.')); + // Publish the French article before testing the archive transition. + $this->drupalPostForm('fr/node/' . $english_node->id() . '/edit', [], t('Save and Publish (this translation)')); + $this->assertText(t('Article New draft of translated node has been updated.')); $english_node = $this->drupalGetNodeByTitle('Another node', TRUE); $french_node = $english_node->getTranslation('fr'); $this->assertEqual($french_node->moderation_state->value, 'published'); - $this->assertTrue($french_node->isPublished()); - $this->assertEqual($french_node->getTitle(), 'New draft of translated node', 'The draft has replaced the published revision.'); // Publish the English article before testing the archive transition. $this->drupalPostForm('node/' . $english_node->id() . '/edit', [], t('Save and Publish (this translation)')); @@ -173,43 +168,6 @@ public function testTranslateModeratedContent() { $this->assertFalse($english_node->isPublished()); $this->assertEqual($french_node->moderation_state->value, 'archived'); $this->assertFalse($french_node->isPublished()); - - // Create another article with its translation. This time publishing english - // after creating a forward french revision. - $edit = [ - 'title[0][value]' => 'An english node', - ]; - $this->drupalPostForm('node/add/article', $edit, t('Save and Create New Draft')); - $this->assertText(t('Article An english node has been created.')); - $english_node = $this->drupalGetNodeByTitle('An english node'); - $this->assertFalse($english_node->isPublished()); - - // Add a French translation. - $this->drupalGet('node/' . $english_node->id() . '/translations'); - $this->clickLink(t('Add')); - $edit = [ - 'title[0][value]' => 'A french node', - ]; - $this->drupalPostForm(NULL, $edit, t('Save and Publish (this translation)')); - $english_node = $this->drupalGetNodeByTitle('An english node', TRUE); - $french_node = $english_node->getTranslation('fr'); - $this->assertTrue($french_node->isPublished()); - $this->assertFalse($english_node->isPublished()); - - // Create a forward revision - $this->drupalPostForm('fr/node/' . $english_node->id() . '/edit', [], t('Save and Create New Draft (this translation)')); - $english_node = $this->drupalGetNodeByTitle('An english node', TRUE); - $french_node = $english_node->getTranslation('fr'); - $this->assertTrue($french_node->isPublished()); - $this->assertFalse($english_node->isPublished()); - - // Publish the english node and the default french node not the latest - // french node should be used. - $this->drupalPostForm('/node/' . $english_node->id() . '/edit', [], t('Save and Publish (this translation)')); - $english_node = $this->drupalGetNodeByTitle('An english node', TRUE); - $french_node = $english_node->getTranslation('fr'); - $this->assertTrue($french_node->isPublished()); - $this->assertTrue($english_node->isPublished()); } }