3 namespace Drupal\Tests\content_moderation\Functional;
5 use Drupal\node\NodeInterface;
8 * Test content_moderation functionality with localization and translation.
10 * @group content_moderation
12 class ModerationLocaleTest extends ModerationStateTestBase {
19 public static $modules = [
23 'content_translation',
29 protected function setUp() {
32 $this->drupalLogin($this->rootUser);
34 // Enable moderation on Article node type.
35 $this->createContentTypeFromUi('Article', 'article', TRUE);
37 // Add French and Italian languages.
38 foreach (['fr', 'it'] as $langcode) {
40 'predefined_langcode' => $langcode,
42 $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
45 // Enable content translation on articles.
46 $this->drupalGet('admin/config/regional/content-language');
48 'entity_types[node]' => TRUE,
49 'settings[node][article][translatable]' => TRUE,
50 'settings[node][article][settings][language][language_alterable]' => TRUE,
52 $this->drupalPostForm(NULL, $edit, t('Save configuration'));
54 // Adding languages requires a container rebuild in the test running
55 // environment so that multilingual services are used.
56 $this->rebuildContainer();
60 * Tests article translations can be moderated separately.
62 public function testTranslateModeratedContent() {
63 // Create a published article in English.
65 'title[0][value]' => 'Published English node',
66 'langcode[0][value]' => 'en',
67 'moderation_state[0][state]' => 'published',
69 $this->drupalPostForm('node/add/article', $edit, t('Save'));
70 $this->assertText(t('Article Published English node has been created.'));
71 $english_node = $this->drupalGetNodeByTitle('Published English node');
73 // Add a French translation.
74 $this->drupalGet('node/' . $english_node->id() . '/translations');
75 $this->clickLink(t('Add'));
77 'title[0][value]' => 'French node Draft',
78 'moderation_state[0][state]' => 'draft',
80 $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
81 // Here the error has occurred "The website encountered an unexpected error.
82 // Please try again later."
83 // If the translation has got lost.
84 $this->assertText(t('Article French node Draft has been updated.'));
86 // Create an article in English.
88 'title[0][value]' => 'English node',
89 'langcode[0][value]' => 'en',
90 'moderation_state[0][state]' => 'draft',
92 $this->drupalPostForm('node/add/article', $edit, t('Save'));
93 $this->assertText(t('Article English node has been created.'));
94 $english_node = $this->drupalGetNodeByTitle('English node');
96 // Add a French translation.
97 $this->drupalGet('node/' . $english_node->id() . '/translations');
98 $this->clickLink(t('Add'));
100 'title[0][value]' => 'French node',
101 'moderation_state[0][state]' => 'draft',
103 $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
104 $this->assertText(t('Article French node has been updated.'));
105 $english_node = $this->drupalGetNodeByTitle('English node', TRUE);
107 // Publish the English article and check that the translation stays
109 $this->drupalPostForm('node/' . $english_node->id() . '/edit', [
110 'moderation_state[0][state]' => 'published',
111 ], t('Save (this translation)'));
112 $this->assertText(t('Article English node has been updated.'));
113 $english_node = $this->drupalGetNodeByTitle('English node', TRUE);
114 $french_node = $english_node->getTranslation('fr');
115 $this->assertEqual('French node', $french_node->label());
117 $this->assertEqual($english_node->moderation_state->value, 'published');
118 $this->assertTrue($english_node->isPublished());
119 $this->assertEqual($french_node->moderation_state->value, 'draft');
120 $this->assertFalse($french_node->isPublished());
122 // Create another article with its translation. This time we will publish
123 // the translation first.
125 'title[0][value]' => 'Another node',
126 'moderation_state[0][state]' => 'draft',
128 $this->drupalPostForm('node/add/article', $edit, t('Save'));
129 $this->assertText(t('Article Another node has been created.'));
130 $english_node = $this->drupalGetNodeByTitle('Another node');
132 // Add a French translation.
133 $this->drupalGet('node/' . $english_node->id() . '/translations');
134 $this->clickLink(t('Add'));
136 'title[0][value]' => 'Translated node',
137 'moderation_state[0][state]' => 'draft',
139 $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
140 $this->assertText(t('Article Translated node has been updated.'));
141 $english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
143 // Publish the translation and check that the source language version stays
145 $this->drupalPostForm('fr/node/' . $english_node->id() . '/edit', [
146 'moderation_state[0][state]' => 'published',
147 ], t('Save (this translation)'));
148 $this->assertText(t('Article Translated node has been updated.'));
149 $english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
150 $french_node = $english_node->getTranslation('fr');
151 $this->assertEqual($french_node->moderation_state->value, 'published');
152 $this->assertTrue($french_node->isPublished());
153 $this->assertEqual($english_node->moderation_state->value, 'draft');
154 $this->assertFalse($english_node->isPublished());
156 // Now check that we can create a new draft of the translation.
158 'title[0][value]' => 'New draft of translated node',
159 'moderation_state[0][state]' => 'draft',
161 $this->drupalPostForm('fr/node/' . $english_node->id() . '/edit', $edit, t('Save (this translation)'));
162 $this->assertText(t('Article New draft of translated node has been updated.'));
163 $english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
164 $french_node = $english_node->getTranslation('fr');
165 $this->assertEqual($french_node->moderation_state->value, 'published');
166 $this->assertTrue($french_node->isPublished());
167 $this->assertEqual($french_node->getTitle(), 'Translated node', 'The default revision of the published translation remains the same.');
169 // Publish the French article before testing the archive transition.
170 $this->drupalPostForm('fr/node/' . $english_node->id() . '/edit', [
171 'moderation_state[0][state]' => 'published',
172 ], t('Save (this translation)'));
173 $this->assertText(t('Article New draft of translated node has been updated.'));
174 $english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
175 $french_node = $english_node->getTranslation('fr');
176 $this->assertEqual($french_node->moderation_state->value, 'published');
177 $this->assertTrue($french_node->isPublished());
178 $this->assertEqual($french_node->getTitle(), 'New draft of translated node', 'The draft has replaced the published revision.');
180 // Publish the English article before testing the archive transition.
181 $this->drupalPostForm('node/' . $english_node->id() . '/edit', [
182 'moderation_state[0][state]' => 'published',
183 ], t('Save (this translation)'));
184 $this->assertText(t('Article Another node has been updated.'));
185 $english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
186 $this->assertEqual($english_node->moderation_state->value, 'published');
188 // Archive the node and its translation.
189 $this->drupalPostForm('node/' . $english_node->id() . '/edit', [
190 'moderation_state[0][state]' => 'archived',
191 ], t('Save (this translation)'));
192 $this->assertText(t('Article Another node has been updated.'));
193 $this->drupalPostForm('fr/node/' . $english_node->id() . '/edit', [
194 'moderation_state[0][state]' => 'archived',
195 ], t('Save (this translation)'));
196 $this->assertText(t('Article New draft of translated node has been updated.'));
197 $english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
198 $french_node = $english_node->getTranslation('fr');
199 $this->assertEqual($english_node->moderation_state->value, 'archived');
200 $this->assertFalse($english_node->isPublished());
201 $this->assertEqual($french_node->moderation_state->value, 'archived');
202 $this->assertFalse($french_node->isPublished());
206 * Tests that individual translations can be moderated independently.
208 public function testLanguageIndependentContentModeration() {
209 // Create a published article in English (revision 1).
210 $this->drupalGet('node/add/article');
211 $node = $this->submitNodeForm('Test 1.1 EN', 'published');
212 $this->assertNotLatestVersionPage($node);
214 $edit_path = $node->toUrl('edit-form');
215 $translate_path = $node->toUrl('drupal:content-translation-overview');
217 // Create a new English draft (revision 2).
218 $this->drupalGet($edit_path);
219 $this->submitNodeForm('Test 1.2 EN', 'draft', TRUE);
220 $this->assertLatestVersionPage($node);
222 // Add a French translation draft (revision 3).
223 $this->drupalGet($translate_path);
224 $this->clickLink(t('Add'));
225 $this->submitNodeForm('Test 1.3 FR', 'draft');
226 $fr_node = $this->loadTranslation($node, 'fr');
227 $this->assertLatestVersionPage($fr_node);
228 $this->assertModerationForm($node);
230 // Add an Italian translation draft (revision 4).
231 $this->drupalGet($translate_path);
232 $this->clickLink(t('Add'));
233 $this->submitNodeForm('Test 1.4 IT', 'draft');
234 $it_node = $this->loadTranslation($node, 'it');
235 $this->assertLatestVersionPage($it_node);
236 $this->assertModerationForm($node);
237 $this->assertModerationForm($fr_node);
239 // Publish the English draft (revision 5).
240 $this->drupalGet($edit_path);
241 $this->submitNodeForm('Test 1.5 EN', 'published', TRUE);
242 $this->assertNotLatestVersionPage($node);
243 $this->assertModerationForm($fr_node);
244 $this->assertModerationForm($it_node);
246 // Publish the Italian draft (revision 6).
247 $this->drupalGet($translate_path);
248 $this->clickLink(t('Edit'), 2);
249 $this->submitNodeForm('Test 1.6 IT', 'published');
250 $this->assertNotLatestVersionPage($it_node);
251 $this->assertNoModerationForm($node);
252 $this->assertModerationForm($fr_node);
254 // Publish the French draft (revision 7).
255 $this->drupalGet($translate_path);
256 $this->clickLink(t('Edit'), 1);
257 $this->submitNodeForm('Test 1.7 FR', 'published');
258 $this->assertNotLatestVersionPage($fr_node);
259 $this->assertNoModerationForm($node);
260 $this->assertNoModerationForm($it_node);
262 // Create an Italian draft (revision 8).
263 $this->drupalGet($translate_path);
264 $this->clickLink(t('Edit'), 2);
265 $this->submitNodeForm('Test 1.8 IT', 'draft');
266 $this->assertLatestVersionPage($it_node);
267 $this->assertNoModerationForm($node);
268 $this->assertNoModerationForm($fr_node);
270 // Create a French draft (revision 9).
271 $this->drupalGet($translate_path);
272 $this->clickLink(t('Edit'), 1);
273 $this->submitNodeForm('Test 1.9 FR', 'draft');
274 $this->assertLatestVersionPage($fr_node);
275 $this->assertNoModerationForm($node);
276 $this->assertModerationForm($it_node);
278 // Create an English draft (revision 10).
279 $this->drupalGet($edit_path);
280 $this->submitNodeForm('Test 1.10 EN', 'draft');
281 $this->assertLatestVersionPage($node);
282 $this->assertModerationForm($fr_node);
283 $this->assertModerationForm($it_node);
285 // Now start from a draft article in English (revision 1).
286 $this->drupalGet('node/add/article');
287 $node2 = $this->submitNodeForm('Test 2.1 EN', 'draft', TRUE);
288 $this->assertNotLatestVersionPage($node2, TRUE);
290 $edit_path = $node2->toUrl('edit-form');
291 $translate_path = $node2->toUrl('drupal:content-translation-overview');
293 // Add a French translation (revision 2).
294 $this->drupalGet($translate_path);
295 $this->clickLink(t('Add'));
296 $this->submitNodeForm('Test 2.2 FR', 'draft');
297 $fr_node2 = $this->loadTranslation($node2, 'fr');
298 $this->assertNotLatestVersionPage($fr_node2, TRUE);
299 $this->assertModerationForm($node2, FALSE);
301 // Add an Italian translation (revision 3).
302 $this->drupalGet($translate_path);
303 $this->clickLink(t('Add'));
304 $this->submitNodeForm('Test 2.3 IT', 'draft');
305 $it_node2 = $this->loadTranslation($node2, 'it');
306 $this->assertNotLatestVersionPage($it_node2, TRUE);
307 $this->assertModerationForm($node2, FALSE);
308 $this->assertModerationForm($fr_node2, FALSE);
310 // Publish the English draft (revision 4).
311 $this->drupalGet($edit_path);
312 $this->submitNodeForm('Test 2.4 EN', 'published', TRUE);
313 $this->assertNotLatestVersionPage($node2);
314 $this->assertModerationForm($fr_node2, FALSE);
315 $this->assertModerationForm($it_node2, FALSE);
317 // Publish the Italian draft (revision 5).
318 $this->drupalGet($translate_path);
319 $this->clickLink(t('Edit'), 2);
320 $this->submitNodeForm('Test 2.5 IT', 'published');
321 $this->assertNotLatestVersionPage($it_node2);
322 $this->assertNoModerationForm($node2);
323 $this->assertModerationForm($fr_node2, FALSE);
325 // Publish the French draft (revision 6).
326 $this->drupalGet($translate_path);
327 $this->clickLink(t('Edit'), 1);
328 $this->submitNodeForm('Test 2.6 FR', 'published');
329 $this->assertNotLatestVersionPage($fr_node2);
330 $this->assertNoModerationForm($node2);
331 $this->assertNoModerationForm($it_node2);
333 // Now that all revision translations are published, verify that the
334 // moderation form is never displayed on revision pages.
335 /** @var \Drupal\node\NodeStorageInterface $storage */
336 $storage = $this->container->get('entity_type.manager')->getStorage('node');
337 foreach (range(11, 16) as $revision_id) {
338 /** @var \Drupal\node\NodeInterface $revision */
339 $revision = $storage->loadRevision($revision_id);
340 foreach ($revision->getTranslationLanguages() as $langcode => $language) {
341 if ($revision->isRevisionTranslationAffected()) {
342 $this->drupalGet($revision->toUrl('revision'));
343 $this->assertFalse($this->hasModerationForm(), 'Moderation form is not displayed correctly for revision ' . $revision_id);
349 // Create an Italian draft (revision 7).
350 $this->drupalGet($translate_path);
351 $this->clickLink(t('Edit'), 2);
352 $this->submitNodeForm('Test 2.7 IT', 'draft');
353 $this->assertLatestVersionPage($it_node2);
354 $this->assertNoModerationForm($node2);
355 $this->assertNoModerationForm($fr_node2);
357 // Create a French draft (revision 8).
358 $this->drupalGet($translate_path);
359 $this->clickLink(t('Edit'), 1);
360 $this->submitNodeForm('Test 2.8 FR', 'draft');
361 $this->assertLatestVersionPage($fr_node2);
362 $this->assertNoModerationForm($node2);
363 $this->assertModerationForm($it_node2);
365 // Create an English draft (revision 9).
366 $this->drupalGet($edit_path);
367 $this->submitNodeForm('Test 2.9 EN', 'draft', TRUE);
368 $this->assertLatestVersionPage($node2);
369 $this->assertModerationForm($fr_node2);
370 $this->assertModerationForm($it_node2);
372 // Now publish a draft in another language first and verify that the
373 // moderation form is not displayed on the English node view page.
374 $this->drupalGet('node/add/article');
375 $node3 = $this->submitNodeForm('Test 3.1 EN', 'published');
376 $this->assertNotLatestVersionPage($node3);
378 $edit_path = $node3->toUrl('edit-form');
379 $translate_path = $node3->toUrl('drupal:content-translation-overview');
381 // Create an English draft (revision 2).
382 $this->drupalGet($edit_path);
383 $this->submitNodeForm('Test 3.2 EN', 'draft', TRUE);
384 $this->assertLatestVersionPage($node3);
386 // Add a French translation (revision 3).
387 $this->drupalGet($translate_path);
388 $this->clickLink(t('Add'));
389 $this->submitNodeForm('Test 3.3 FR', 'draft');
390 $fr_node3 = $this->loadTranslation($node3, 'fr');
391 $this->assertLatestVersionPage($fr_node3);
392 $this->assertModerationForm($node3);
394 // Publish the French draft (revision 4).
395 $this->drupalGet($translate_path);
396 $this->clickLink(t('Edit'), 1);
397 $this->submitNodeForm('Test 3.4 FR', 'published');
398 $this->assertNotLatestVersionPage($fr_node3);
399 $this->assertModerationForm($node3);
403 * Checks that new translation values are populated properly.
405 public function testNewTranslationSourceValues() {
406 // Create a published article in Italian (revision 1).
407 $this->drupalGet('node/add/article');
408 $node = $this->submitNodeForm('Test 1.1 IT', 'published', TRUE, 'it');
409 $this->assertNotLatestVersionPage($node);
411 // Create a new draft (revision 2).
412 $this->drupalGet($node->toUrl('edit-form'));
413 $this->submitNodeForm('Test 1.2 IT', 'draft', TRUE);
414 $this->assertLatestVersionPage($node);
416 // Create an English draft (revision 3) and verify that the Italian draft
417 // values are used as source values.
418 $url = $node->toUrl('drupal:content-translation-add');
419 $url->setRouteParameter('source', 'it');
420 $url->setRouteParameter('target', 'en');
421 $this->drupalGet($url);
422 $this->assertSession()->pageTextContains('Test 1.2 IT');
423 $this->submitNodeForm('Test 1.3 EN', 'draft');
424 $this->assertLatestVersionPage($node);
426 // Create a French draft (without saving) and verify that the Italian draft
427 // values are used as source values.
428 $url->setRouteParameter('target', 'fr');
429 $this->drupalGet($url);
430 $this->assertSession()->pageTextContains('Test 1.2 IT');
432 // Now switch source language and verify that the English draft values are
433 // used as source values.
434 $url->setRouteParameter('source', 'en');
435 $this->drupalGet($url);
436 $this->assertSession()->pageTextContains('Test 1.3 EN');
440 * Submits the node form at the current URL with the specified values.
442 * @param string $title
444 * @param string $moderation_state
445 * The moderation state.
446 * @param bool $default_translation
447 * (optional) Whether we are editing the default translation.
448 * @param string|null $langcode
449 * (optional) The node language. Defaults to English.
451 * @return \Drupal\node\NodeInterface|null
452 * A node object if a new one is being created, NULL otherwise.
454 protected function submitNodeForm($title, $moderation_state, $default_translation = FALSE, $langcode = 'en') {
455 $is_new = strpos($this->getSession()->getCurrentUrl(), '/node/add/') !== FALSE;
457 'title[0][value]' => $title,
458 'moderation_state[0][state]' => $moderation_state,
461 $default_translation = TRUE;
462 $edit['langcode[0][value]'] = $langcode;
464 $submit = $default_translation ? t('Save') : t('Save (this translation)');
465 $this->drupalPostForm(NULL, $edit, $submit);
466 $message = $is_new ? "Article $title has been created." : "Article $title has been updated.";
467 $this->assertSession()->pageTextContains($message);
468 return $is_new ? $this->drupalGetNodeByTitle($title) : NULL;
472 * Loads the node translation for the specified language.
474 * @param \Drupal\node\NodeInterface $node
476 * @param string $langcode
477 * The translation language code.
479 * @return \Drupal\node\NodeInterface
480 * The node translation object.
482 protected function loadTranslation(NodeInterface $node, $langcode) {
483 /** @var \Drupal\node\NodeStorageInterface $storage */
484 $storage = $this->container->get('entity_type.manager')->getStorage('node');
485 /** @var \Drupal\node\NodeInterface $node */
486 $node = $storage->loadRevision($storage->getLatestRevisionId($node->id()));
487 return $node->getTranslation($langcode);
491 * Asserts that this is the "latest version" page for the specified node.
493 * @param \Drupal\node\NodeInterface $node
496 public function assertLatestVersionPage(NodeInterface $node) {
497 $this->assertEquals($node->toUrl('latest-version')->setAbsolute()->toString(), $this->getSession()->getCurrentUrl());
498 $this->assertModerationForm($node);
502 * Asserts that this is not the "latest version" page for the specified node.
504 * @param \Drupal\node\NodeInterface $node
506 * @param bool $moderation_form
507 * (optional) Whether the page should contain the moderation form. Defaults
510 public function assertNotLatestVersionPage(NodeInterface $node, $moderation_form = FALSE) {
511 $this->assertNotEquals($node->toUrl('latest-version')->setAbsolute()->toString(), $this->getSession()->getCurrentUrl());
512 if ($moderation_form) {
513 $this->assertModerationForm($node, FALSE);
516 $this->assertNoModerationForm($node);
521 * Asserts that the moderation form is displayed for the specified node.
523 * @param \Drupal\node\NodeInterface $node
525 * @param bool $latest_tab
526 * (optional) Whether the node form is expected to be displayed on the
527 * latest version page or on the node view page. Defaults to the former.
529 public function assertModerationForm(NodeInterface $node, $latest_tab = TRUE) {
530 $this->drupalGet($node->toUrl());
531 $this->assertEquals(!$latest_tab, $this->hasModerationForm());
532 $this->drupalGet($node->toUrl('latest-version'));
533 $this->assertEquals($latest_tab, $this->hasModerationForm());
537 * Asserts that the moderation form is not displayed for the specified node.
539 * @param \Drupal\node\NodeInterface $node
542 public function assertNoModerationForm(NodeInterface $node) {
543 $this->drupalGet($node->toUrl());
544 $this->assertFalse($this->hasModerationForm());
545 $this->drupalGet($node->toUrl('latest-version'));
546 $this->assertEquals(403, $this->getSession()->getStatusCode());
550 * Checks whether the page contains the moderation form.
553 * TRUE if the moderation form could be find in the page, FALSE otherwise.
555 public function hasModerationForm() {
556 return (bool) $this->xpath('//ul[@class="entity-moderation-form"]');