<?php
namespace Drupal\Tests\tmgmt_content\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\node\Entity\Node;
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
use Drupal\Tests\tmgmt\Functional\TmgmtEntityTestTrait;
use Drupal\Tests\tmgmt\Functional\TMGMTTestBase;
use Drupal\tmgmt_composite_test\Entity\EntityTestComposite;
use Drupal\workflows\Entity\Workflow;
class ContentEntitySourceContentModerationTest extends TMGMTTestBase {
use TmgmtEntityTestTrait;
use EntityReferenceTestTrait;
protected static $modules = [
'tmgmt_content',
'content_moderation',
'field',
'tmgmt_composite_test',
];
protected $workflow;
function setUp() : void {
parent::setUp();
$this
->addLanguage('de');
$this
->addLanguage('es');
$this
->addLanguage('fr');
$this
->addLanguage('it');
$this
->createNodeType('page', 'Page', TRUE);
$this
->createNodeType('article', 'Article', TRUE);
$this
->createEditorialWorkflow('article');
$this
->loginAsAdmin([
'create translation jobs',
'submit translation jobs',
'accept translation jobs',
'administer blocks',
'administer content translation',
]);
}
public function testModerationWithUntranslatableCompositeEntityReference() {
$this
->createNodeType('composite', 'composite', TRUE, FALSE);
$this
->createEditorialWorkflow('composite');
\Drupal::service('content_translation.manager')
->setEnabled('entity_test_composite', 'entity_test_composite', TRUE);
$this
->createEntityReferenceField('node', 'composite', 'entity_test_composite', 'entity_test_composite', 'entity_test_composite');
FieldConfig::loadByName('node', 'composite', 'entity_test_composite')
->setTranslatable(FALSE)
->save();
\Drupal::service('entity_display.repository')
->getFormDisplay('node', 'composite')
->setComponent('entity_test_composite', [
'type' => 'entity_reference_autocomplete',
])
->save();
$composite = EntityTestComposite::create([
'name' => 'composite name',
]);
$composite
->save();
$this->default_translator
->setAutoAccept(TRUE)
->save();
$user = $this
->loginAsTranslator([
'administer tmgmt',
'translate any entity',
'create content translations',
'access content',
'view own unpublished content',
'edit own composite content',
'access content overview',
'view all revisions',
'view latest version',
'view test entity',
'use ' . $this->workflow
->id() . ' transition create_new_draft',
'use ' . $this->workflow
->id() . ' transition publish',
]);
$node = $this
->createNode([
'title' => 'node title',
'type' => 'composite',
'entity_test_composite' => $composite,
'moderation_state' => 'published',
'uid' => $user
->id(),
]);
$this
->drupalGet($node
->toUrl('edit-form'));
$edit = [
'title[0][value]' => 'node title (draft)',
'moderation_state[0][state]' => 'draft',
];
$this
->submitForm($edit, 'Save');
$this
->drupalGet('/node/' . $node
->id() . '/translations');
$this
->submitForm([
'languages[de]' => TRUE,
], 'Request translation');
$this
->assertSession()
->pageTextContainsOnce('One job needs to be checked out.');
$this
->submitForm([
'target_language' => 'de',
], 'Submit to provider');
$this
->assertSession()
->pageTextContains('Test translation created.');
$this
->assertSession()
->pageTextContains('This translation cannot be accepted as there is a pending revision in the default translation. You must publish node title (draft) first before saving this translation.');
$this
->clickLink('Review');
$url = $this
->getUrl();
$this
->submitForm([], 'Validate');
$this
->assertSession()
->pageTextContainsOnce('Validation completed successfully.');
$this
->submitForm([], 'Save as completed');
$this
->assertSession()
->pageTextContains('This translation cannot be accepted as there is a pending revision in the default translation. You must publish node title (draft) first before saving this translation.');
$this
->clickLink('node title (draft)');
$this
->submitForm([
'new_state' => 'published',
], 'Apply');
$this
->assertSession()
->pageTextContainsOnce('The moderation state has been updated.');
$this
->drupalGet($url);
$this
->assertTrue($this
->assertSession()
->optionExists('edit-moderation-state-new-state', 'published')
->isSelected());
$this
->submitForm([], 'Save as completed');
$this
->assertSession()
->pageTextContains('The translation for node title (draft) has been accepted as de(de-ch): node title (draft).');
$this
->drupalGet($node
->toUrl('edit-form')
->toString());
$this
->assertSession()
->fieldValueEquals('entity_test_composite[0][target_id]', 'composite name (' . $composite
->id() . ')');
$composite = EntityTestComposite::load($composite
->id());
$this
->assertEquals('de(de-ch): composite name', $composite
->getTranslation('de')
->getName());
$referenced_values = [
'langcode' => 'en',
'name' => 'Referenced entity',
];
$referenced_entity = EntityTestComposite::create($referenced_values);
$referenced_entity
->save();
$node = Node::create([
'type' => 'composite',
'title' => 'Example',
'entity_test_composite' => $referenced_entity,
]);
$node
->save();
$job = tmgmt_job_create('en', 'es');
$job->translator = 'test_translator';
$job
->save();
$job_item = tmgmt_job_item_create('content', 'node', $node
->id(), [
'tjid' => $job
->id(),
]);
$job_item
->save();
$job
->requestTranslation();
$job
->acceptTranslation();
$items = $job
->getItems();
$item = reset($items);
$item
->isAccepted();
$node = Node::load($node
->id());
$this
->assertTrue($node
->hasTranslation('es'));
$job = tmgmt_job_create('en', 'de');
$job
->acceptTranslation();
$job->translator = 'test_translator';
$job
->save();
$job_item = tmgmt_job_item_create('content', 'node', $node
->id(), [
'tjid' => $job
->id(),
]);
$job_item
->save();
$job
->requestTranslation();
$job
->acceptTranslation();
$items = $job
->getItems();
$item = reset($items);
$item
->isAccepted();
$node = Node::load($node
->id());
$this
->assertTrue($node
->hasTranslation('de'));
}
function testModeratedContentTranslations() {
$this
->loginAsTranslator([
'administer tmgmt',
'translate any entity',
'create content translations',
'access content',
'view own unpublished content',
'edit own article content',
'access content overview',
'view all revisions',
'view latest version',
'use ' . $this->workflow
->id() . ' transition create_new_draft',
'use ' . $this->workflow
->id() . ' transition publish',
]);
$title = 'Moderated node';
$node = $this
->createNode([
'title' => $title,
'type' => 'article',
'langcode' => 'en',
'moderation_state' => 'published',
'uid' => $this->translator_user
->id(),
]);
$this
->drupalGet($node
->toUrl('edit-form'));
$draft_title = '[Draft] ' . $title;
$edit = [
'title[0][value]' => $draft_title,
'moderation_state[0][state]' => 'draft',
];
$this
->submitForm($edit, 'Save');
$this
->drupalGet('admin/tmgmt/sources');
$this
->assertSession()
->linkExists($draft_title);
$edit = [
'items[' . $node
->id() . ']' => $node
->id(),
];
$this
->submitForm($edit, 'Request translation');
$this
->assertSession()
->pageTextContains('One job needs to be checked out.');
$this
->assertSession()
->pageTextContains($draft_title . ' (English to ?, Unprocessed)');
$edit = [
'target_language' => 'de',
];
$this
->submitForm($edit, 'Submit to provider');
$this
->assertSession()
->pageTextContains(t('The translation of @title to German is finished and can now be reviewed.', [
'@title' => $draft_title,
]));
$this
->drupalGet('admin/tmgmt/jobs');
$this
->assertSession()
->pageTextContains($draft_title);
$this
->clickLink('Manage');
$this
->assertSession()
->pageTextContains($draft_title . ' (English to German, Active)');
$this
->clickLink('Review');
$this
->assertSession()
->pageTextContains('Job item ' . $draft_title);
$this
->assertSession()
->fieldNotExists('moderation_state|0|value[source]');
$this
->assertSession()
->pageTextContains('Current source state');
$this
->assertSession()
->pageTextContains('Draft');
$this
->assertTrue($this
->assertSession()
->optionExists('edit-moderation-state-new-state', 'draft')
->isSelected());
$translation_title = 'de(de-ch): [Published] ' . $title;
$edit = [
'title|0|value[translation]' => $translation_title,
'moderation_state[new_state]' => 'published',
];
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains(t('The translation for @title has been saved successfully.', [
'@title' => $draft_title,
]));
$this
->clickLink('Review');
$this
->assertTrue($this
->assertSession()
->optionExists('edit-moderation-state-new-state', 'published')
->isSelected());
$review_url = $this
->getUrl();
$this
->clickLink('Preview');
$this
->assertSession()
->pageTextContains(t('Preview of @title for German', [
'@title' => $draft_title,
]));
$this
->assertSession()
->pageTextContains($translation_title);
$this
->drupalGet($review_url);
$this
->submitForm([], 'Save as completed');
$this
->assertSession()
->pageTextContains('Validation completed successfully.');
$this
->assertSession()
->pageTextContains(t('The translation for @title has been accepted as @translation_title.', [
'@title' => $draft_title,
'@translation_title' => $translation_title,
]));
$this
->clickLink($translation_title);
$this
->assertSession()
->addressEquals('de/node/' . $node
->id());
$this
->assertSession()
->pageTextContains($translation_title);
$this
->clickLink('Revisions');
$this
->assertSession()
->pageTextContains("Created by translation job {$draft_title}.");
$this
->assertNodeTranslationsRevisionsCount($node
->id(), 'de', 1);
$this
->drupalGet('node/' . $node
->id());
$this
->assertSession()
->pageTextContains($title);
$this
->drupalGet('node/' . $node
->id() . '/latest');
$this
->assertSession()
->pageTextContains($draft_title);
$this
->drupalGet('admin/tmgmt/sources');
$edit = [
'target_language' => 'es',
'items[' . $node
->id() . ']' => $node
->id(),
];
$this
->submitForm($edit, 'Request translation');
$this
->submitForm([], 'Submit to provider');
$this
->assertSession()
->pageTextContains(t('The translation of @title to Spanish is finished and can now be reviewed.', [
'@title' => $draft_title,
]));
$this
->clickLink('reviewed');
$this
->submitForm([], 'Save as completed');
$this
->assertSession()
->pageTextContains(t('The translation for @title has been accepted.', [
'@title' => $draft_title,
]));
$this
->drupalGet('es/node/' . $node
->id());
$this
->assertSession()
->pageTextContains($title);
$this
->assertSession()
->pageTextNotContains('es: ' . $title);
$this
->clickLink('Latest version');
$this
->assertSession()
->pageTextContains('es: ' . $draft_title);
$this
->assertNodeTranslationsRevisionsCount($node
->id(), 'de', 2);
$this
->assertNodeTranslationsRevisionsCount($node
->id(), 'es', 1);
$this
->drupalGet($node
->toUrl('edit-form'));
$second_draft_title = "{$draft_title} (2)";
$edit = [
'title[0][value]' => $second_draft_title,
'moderation_state[0][state]' => 'draft',
];
$this
->submitForm($edit, 'Save (this translation)');
$this
->assertNodeTranslationsRevisionsCount($node
->id(), 'de', 3);
$this
->drupalGet('it/node/' . $node
->id() . '/translations/add/en/it');
$edit = [
'title[0][value]' => "it: {$second_draft_title}",
'moderation_state[0][state]' => 'draft',
];
$this
->submitForm($edit, 'Save (this translation)');
$this
->assertNodeTranslationsRevisionsCount($node
->id(), 'de', 4);
$this
->drupalGet('admin/tmgmt/sources');
$this
->assertCount(1, $this
->xpath('//tbody/tr'));
$this
->assertTextByXpath('//tbody/tr[1]/td[4]/@class', 'langstatus-en');
$this
->assertTextByXpath('//tbody/tr[1]/td[4]/a/img/@title', 'Original language');
$this
->assertTextByXpath('//tbody/tr[1]/td[5]/@class', 'langstatus-fr');
$this
->assertTextByXpath('//tbody/tr[1]/td[5]/img/@title', 'Not translated');
$this
->assertTextByXpath('//tbody/tr[1]/td[6]/@class', 'langstatus-de');
$this
->assertTextByXpath('//tbody/tr[1]/td[6]/a/img/@title', 'Translation up to date');
$this
->assertTextByXpath('//tbody/tr[1]/td[7]/@class', 'langstatus-it');
$this
->assertTextByXpath('//tbody/tr[1]/td[7]/a/img/@title', 'Translation up to date');
$this
->assertTextByXpath('//tbody/tr[1]/td[8]/@class', 'langstatus-es');
$this
->assertTextByXpath('//tbody/tr[1]/td[8]/a/img/@title', 'Translation up to date');
$this
->drupalGet('admin/tmgmt/sources/content/node');
$edit = [
'search[target_language]' => 'de',
'search[target_status]' => 'untranslated',
];
$this
->submitForm($edit, 'Search');
$this
->assertSession()
->pageTextContains('No source items matching given criteria have been found.');
$this
->assertSession()
->linkNotExists($second_draft_title);
$edit = [
'search[target_language]' => 'it',
'search[target_status]' => 'untranslated',
];
$this
->submitForm($edit, 'Search');
$this
->assertSession()
->pageTextContains('No source items matching given criteria have been found.');
$this
->assertSession()
->linkNotExists($second_draft_title);
$edit = [
'search[target_language]' => 'fr',
'search[target_status]' => 'untranslated',
];
$this
->submitForm($edit, 'Search');
$this
->assertSession()
->linkExists($second_draft_title);
$this
->drupalGet('admin/tmgmt/sources');
$edit = [
'target_language' => 'de',
'items[' . $node
->id() . ']' => $node
->id(),
];
$this
->submitForm($edit, 'Request translation');
$this
->submitForm([], 'Submit to provider');
$this
->assertSession()
->pageTextContains(t('The translation of @title to German is finished and can now be reviewed.', [
'@title' => $second_draft_title,
]));
$this
->clickLink('reviewed');
$this
->assertTrue($this
->assertSession()
->optionExists('edit-moderation-state-new-state', 'draft')
->isSelected());
$this
->submitForm([], 'Save as completed');
$this
->assertNodeTranslationsRevisionsCount($node
->id(), 'de', 5);
$this
->drupalGet('de/node/' . $node
->id());
$this
->assertSession()
->pageTextContains($translation_title);
$this
->drupalGet('de/node/' . $node
->id() . '/latest');
$this
->assertSession()
->pageTextContains('de(de-ch): [Draft] Moderated node (2)');
$this
->clickLink('Revisions');
$this
->assertSession()
->pageTextContains('Created by translation job [Draft] Moderated node (2).');
$this
->assertSession()
->pageTextContains('Created by translation job [Draft] Moderated node.');
$this
->drupalGet('it/node/' . $node
->id());
$this
->assertSession()
->pageTextNotContains('it: ' . $second_draft_title);
$this
->clickLink('Latest version');
$this
->assertSession()
->pageTextContains('it: ' . $second_draft_title);
$this
->drupalGet('es/node/' . $node
->id());
$this
->assertSession()
->pageTextNotContains('es: ' . $draft_title);
$this
->clickLink('Latest version');
$this
->assertSession()
->pageTextContains('es: ' . $draft_title);
$title = 'Published article';
$node = $this
->createNode([
'title' => $title,
'type' => 'article',
'langcode' => 'en',
'moderation_state' => 'published',
'uid' => $this->translator_user
->id(),
]);
$this
->drupalGet($node
->toUrl('edit-form'));
$draft_title = 'Draft article';
$edit = [
'title[0][value]' => $draft_title,
'moderation_state[0][state]' => 'draft',
];
$this
->submitForm($edit, 'Save');
$this
->drupalGet('de/node/' . $node
->id() . '/translations/add/en/de');
$edit = [
'title[0][value]' => "de: {$draft_title}",
'moderation_state[0][state]' => 'published',
];
$this
->submitForm($edit, 'Save (this translation)');
$this
->drupalGet('admin/tmgmt/sources');
$edit = [
'items[' . $node
->id() . ']' => $node
->id(),
'target_language' => 'de',
];
$this
->submitForm($edit, 'Request translation');
$this
->submitForm([], 'Submit to provider');
$this
->assertSession()
->pageTextContains("The translation of {$draft_title} to German is finished and can now be reviewed.");
$this
->clickLink('reviewed');
$this
->assertTrue($this
->assertSession()
->optionExists('edit-moderation-state-new-state', 'draft')
->isSelected());
$edit = [
'moderation_state[new_state]' => 'published',
];
$this
->submitForm($edit, 'Save as completed');
$this
->assertSession()
->pageTextContains("The translation for {$draft_title} has been accepted as de(de-ch): {$draft_title}.");
\Drupal::configFactory()
->getEditable('tmgmt_content.settings')
->set('default_moderation_states', [
$this->workflow
->id() => 'published',
])
->save();
$this
->drupalGet('admin/tmgmt/sources');
$edit = [
'items[' . $node
->id() . ']' => $node
->id(),
'target_language' => 'es',
];
$this
->submitForm($edit, 'Request translation');
$this
->submitForm([], 'Submit to provider');
$this
->assertSession()
->pageTextContains("The translation of {$draft_title} to Spanish is finished and can now be reviewed.");
$this
->clickLink('reviewed');
$this
->assertTrue($this
->assertSession()
->optionExists('edit-moderation-state-new-state', 'published')
->isSelected());
$this
->submitForm([], 'Save as completed');
$this
->assertSession()
->pageTextContains("The translation for {$draft_title} has been accepted as es: {$draft_title}.");
$node = Node::load($node
->id());
$this
->assertEquals([
'en',
'de',
'es',
], array_keys($node
->getTranslationLanguages()));
$node = $this
->createNode([
'title' => 'Moderated node (de)',
'type' => 'article',
'langcode' => 'de',
'moderation_state' => 'published',
'uid' => $this->translator_user
->id(),
]);
$this
->drupalGet($node
->toUrl('edit-form'));
$edit = [
'title[0][value]' => 'Draft node (de)',
'moderation_state[0][state]' => 'draft',
];
$this
->submitForm($edit, 'Save');
$this
->drupalGet('admin/tmgmt/sources');
$this
->assertSession()
->pageTextContains('Draft node (de)');
$this
->assertSession()
->pageTextNotContains('Moderated node (de)');
}
protected function assertNodeTranslationsRevisionsCount($id, $langcode, $expected) {
$translation_revisions_count = \Drupal::entityQuery('node')
->accessCheck(FALSE)
->condition('nid', $id)
->condition('langcode', $langcode)
->allRevisions()
->count()
->execute();
$this
->assertEquals($expected, $translation_revisions_count);
}
function testNonModeratedContentTranslations() {
$this
->loginAsTranslator([
'translate any entity',
'create content translations',
'administer nodes',
'bypass node access',
]);
$title = 'Non-moderated node';
$node = $this
->createNode([
'title' => $title,
'type' => 'page',
'langcode' => 'en',
'status' => FALSE,
'uid' => $this->translator_user
->id(),
]);
$this
->drupalGet('admin/tmgmt/sources');
$this
->assertSession()
->linkExists($title);
$edit = [
'items[' . $node
->id() . ']' => $node
->id(),
];
$this
->submitForm($edit, 'Request translation');
$this
->assertSession()
->pageTextContains('One job needs to be checked out.');
$this
->assertSession()
->pageTextContains($title . ' (English to ?, Unprocessed)');
$edit = [
'target_language' => 'de',
];
$this
->submitForm($edit, 'Submit to provider');
$this
->assertSession()
->pageTextContains(t('The translation of @title to German is finished and can now be reviewed.', [
'@title' => $title,
]));
$this
->clickLink('reviewed');
$this
->assertSession()
->pageTextContains('Job item ' . $title);
$this
->assertSession()
->pageTextNotContains('Current source state');
$this
->assertSession()
->pageTextContains('Translation publish status');
$this
->assertSession()
->checkboxNotChecked('edit-status-published');
$translation_title = 'de(de-ch): [Published] ' . $title;
$edit = [
'title|0|value[translation]' => $translation_title,
'status[published]' => TRUE,
];
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains(t('The translation for @title has been saved successfully.', [
'@title' => $title,
]));
$this
->clickLink('Review');
$this
->assertSession()
->checkboxChecked('edit-status-published');
$this
->submitForm([], 'Save as completed');
$this
->assertSession()
->pageTextContains('Validation completed successfully.');
$this
->assertSession()
->pageTextContains(t('The translation for @title has been accepted as @translation_title.', [
'@title' => $title,
'@translation_title' => $translation_title,
]));
$this
->clickLink($translation_title);
$this
->assertSession()
->addressEquals('de/node/' . $node
->id());
$this
->assertSession()
->pageTextContains($translation_title);
$this
->clickLink('Revisions');
$this
->assertSession()
->pageTextContains("Created by translation job {$title}.");
$this
->assertNodeTranslationsRevisionsCount($node
->id(), 'de', 1);
$this
->drupalGet('admin/tmgmt/sources');
$edit = [
'target_language' => 'es',
'items[' . $node
->id() . ']' => $node
->id(),
];
$this
->submitForm($edit, 'Request translation');
$this
->submitForm([], 'Submit to provider');
$this
->assertSession()
->pageTextContains(t('The translation of @title to Spanish is finished and can now be reviewed.', [
'@title' => $title,
]));
$this
->clickLink('reviewed');
$this
->submitForm([], 'Save as completed');
$this
->assertSession()
->pageTextContains(t('The translation for @title has been accepted as es: @title', [
'@title' => $title,
]));
$this
->drupalLogout();
$this
->drupalGet('es/node/' . $node
->id());
$this
->assertSession()
->statusCodeEquals(403);
$this
->drupalGet('node/' . $node
->id());
$this
->assertSession()
->statusCodeEquals(403);
$this
->drupalGet('de/node/' . $node
->id());
$this
->assertSession()
->statusCodeEquals(200);
}
protected function createEditorialWorkflow($bundle) {
if (!isset($this->workflow)) {
$this->workflow = Workflow::create([
'type' => 'content_moderation',
'id' => $this
->randomMachineName(),
'label' => 'Editorial',
'type_settings' => [
'states' => [
'archived' => [
'label' => 'Archived',
'weight' => 5,
'published' => FALSE,
'default_revision' => TRUE,
],
'draft' => [
'label' => 'Draft',
'published' => FALSE,
'default_revision' => FALSE,
'weight' => -5,
],
'published' => [
'label' => 'Published',
'published' => TRUE,
'default_revision' => TRUE,
'weight' => 0,
],
],
'transitions' => [
'archive' => [
'label' => 'Archive',
'from' => [
'published',
],
'to' => 'archived',
'weight' => 2,
],
'archived_draft' => [
'label' => 'Restore to Draft',
'from' => [
'archived',
],
'to' => 'draft',
'weight' => 3,
],
'archived_published' => [
'label' => 'Restore',
'from' => [
'archived',
],
'to' => 'published',
'weight' => 4,
],
'create_new_draft' => [
'label' => 'Create New Draft',
'to' => 'draft',
'weight' => 0,
'from' => [
'draft',
'published',
],
],
'publish' => [
'label' => 'Publish',
'to' => 'published',
'weight' => 1,
'from' => [
'draft',
'published',
],
],
],
],
]);
}
$this->workflow
->getTypePlugin()
->addEntityTypeAndBundle('node', $bundle);
$this->workflow
->save();
}
}