3 namespace Drupal\Tests\content_moderation\Functional;
5 use Drupal\Core\Entity\Entity\EntityFormDisplay;
9 * Tests the moderation form, specifically on nodes.
11 * @group content_moderation
13 class ModerationFormTest extends ModerationStateTestBase {
20 public static $modules = [
24 'content_translation',
30 protected function setUp() {
32 $this->drupalLogin($this->adminUser);
33 $this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
34 $this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
38 * Tests the moderation form that shows on the latest version page.
40 * The latest version page only shows if there is a pending revision.
42 * @see \Drupal\content_moderation\EntityOperations
43 * @see \Drupal\Tests\content_moderation\Functional\ModerationStateBlockTest::testCustomBlockModeration
45 public function testModerationForm() {
46 // Create new moderated content in draft.
47 $this->drupalPostForm('node/add/moderated_content', [
48 'title[0][value]' => 'Some moderated content',
49 'body[0][value]' => 'First version of the content.',
50 'moderation_state[0][state]' => 'draft',
53 $node = $this->drupalGetNodeByTitle('Some moderated content');
54 $canonical_path = sprintf('node/%d', $node->id());
55 $edit_path = sprintf('node/%d/edit', $node->id());
56 $latest_version_path = sprintf('node/%d/latest', $node->id());
58 $this->assertTrue($this->adminUser->hasPermission('edit any moderated_content content'));
60 // The canonical view should have a moderation form, because it is not the
62 $this->drupalGet($canonical_path);
63 $this->assertResponse(200);
64 $this->assertField('edit-new-state', 'The node view page has a moderation form.');
66 // The latest version page should not show, because there is no pending
68 $this->drupalGet($latest_version_path);
69 $this->assertResponse(403);
72 $this->drupalPostForm($edit_path, [
73 'body[0][value]' => 'Second version of the content.',
74 'moderation_state[0][state]' => 'draft',
77 // The canonical view should have a moderation form, because it is not the
79 $this->drupalGet($canonical_path);
80 $this->assertResponse(200);
81 $this->assertField('edit-new-state', 'The node view page has a moderation form.');
84 $this->drupalPostForm($edit_path, [
85 'body[0][value]' => 'Second version of the content.',
86 'moderation_state[0][state]' => 'draft',
89 // The preview view should not have a moderation form.
90 $preview_url = Url::fromRoute('entity.node.preview', [
91 'node_preview' => $node->uuid(),
92 'view_mode_id' => 'full',
94 $this->assertResponse(200);
95 $this->assertUrl($preview_url);
96 $this->assertNoField('edit-new-state', 'The node preview page has no moderation form.');
98 // The latest version page should not show, because there is still no
100 $this->drupalGet($latest_version_path);
101 $this->assertResponse(403);
103 // Publish the draft.
104 $this->drupalPostForm($edit_path, [
105 'body[0][value]' => 'Third version of the content.',
106 'moderation_state[0][state]' => 'published',
109 // Check widget default value.
110 $this->drupalGet($edit_path);
111 $this->assertFieldByName('moderation_state[0][state]', 'published', 'The moderation default value is set correctly.');
113 // The published view should not have a moderation form, because it is the
115 $this->drupalGet($canonical_path);
116 $this->assertResponse(200);
117 $this->assertNoField('edit-new-state', 'The node view page has no moderation form.');
119 // The latest version page should not show, because there is still no
121 $this->drupalGet($latest_version_path);
122 $this->assertResponse(403);
124 // Make a pending revision.
125 $this->drupalPostForm($edit_path, [
126 'body[0][value]' => 'Fourth version of the content.',
127 'moderation_state[0][state]' => 'draft',
130 // The published view should not have a moderation form, because it is the
132 $this->drupalGet($canonical_path);
133 $this->assertResponse(200);
134 $this->assertNoField('edit-new-state', 'The node view page has no moderation form.');
136 // The latest version page should show the moderation form and have "Draft"
137 // status, because the pending revision is in "Draft".
138 $this->drupalGet($latest_version_path);
139 $this->assertResponse(200);
140 $this->assertField('edit-new-state', 'The latest-version page has a moderation form.');
141 $this->assertText('Draft', 'Correct status found on the latest-version page.');
143 // Submit the moderation form to change status to published.
144 $this->drupalPostForm($latest_version_path, [
145 'new_state' => 'published',
148 // The latest version page should not show, because there is no
150 $this->drupalGet($latest_version_path);
151 $this->assertResponse(403);
155 * Test moderation non-bundle entity type.
157 public function testNonBundleModerationForm() {
158 $this->drupalLogin($this->rootUser);
159 $this->workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_mulrevpub', 'entity_test_mulrevpub');
160 $this->workflow->save();
162 // Create new moderated content in draft.
163 $this->drupalPostForm('entity_test_mulrevpub/add', ['moderation_state[0][state]' => 'draft'], t('Save'));
165 // The latest version page should not show, because there is no pending
167 $this->drupalGet('/entity_test_mulrevpub/manage/1/latest');
168 $this->assertResponse(403);
171 $this->drupalPostForm('entity_test_mulrevpub/manage/1/edit', ['moderation_state[0][state]' => 'draft'], t('Save'));
173 // The latest version page should not show, because there is still no
175 $this->drupalGet('/entity_test_mulrevpub/manage/1/latest');
176 $this->assertResponse(403);
178 // Publish the draft.
179 $this->drupalPostForm('entity_test_mulrevpub/manage/1/edit', ['moderation_state[0][state]' => 'published'], t('Save'));
181 // The published view should not have a moderation form, because it is the
183 $this->drupalGet('entity_test_mulrevpub/manage/1');
184 $this->assertResponse(200);
185 $this->assertNoText('Status', 'The node view page has no moderation form.');
187 // The latest version page should not show, because there is still no
189 $this->drupalGet('entity_test_mulrevpub/manage/1/latest');
190 $this->assertResponse(403);
192 // Make a pending revision.
193 $this->drupalPostForm('entity_test_mulrevpub/manage/1/edit', ['moderation_state[0][state]' => 'draft'], t('Save'));
195 // The published view should not have a moderation form, because it is the
197 $this->drupalGet('entity_test_mulrevpub/manage/1');
198 $this->assertResponse(200);
199 $this->assertNoText('Status', 'The node view page has no moderation form.');
201 // The latest version page should show the moderation form and have "Draft"
202 // status, because the pending revision is in "Draft".
203 $this->drupalGet('entity_test_mulrevpub/manage/1/latest');
204 $this->assertResponse(200);
205 $this->assertText('Moderation state', 'Form text found on the latest-version page.');
206 $this->assertText('Draft', 'Correct status found on the latest-version page.');
208 // Submit the moderation form to change status to published.
209 $this->drupalPostForm('entity_test_mulrevpub/manage/1/latest', [
210 'new_state' => 'published',
213 // The latest version page should not show, because there is no
215 $this->drupalGet('entity_test_mulrevpub/manage/1/latest');
216 $this->assertResponse(403);
220 * Tests the revision author is updated when the moderation form is used.
222 public function testModerationFormSetsRevisionAuthor() {
223 // Create new moderated content in published.
224 $node = $this->createNode(['type' => 'moderated_content', 'moderation_state' => 'published']);
225 // Make a pending revision.
226 $node->title = $this->randomMachineName();
227 $node->moderation_state->value = 'draft';
228 $node->setRevisionCreationTime(12345);
231 $another_user = $this->drupalCreateUser($this->permissions);
232 $this->grantUserPermissionToCreateContentOfType($another_user, 'moderated_content');
233 $this->drupalLogin($another_user);
234 $this->drupalPostForm(sprintf('node/%d/latest', $node->id()), [
235 'new_state' => 'published',
238 $this->drupalGet(sprintf('node/%d/revisions', $node->id()));
239 $this->assertText('by ' . $another_user->getAccountName());
241 // Verify the revision creation time has been updated.
242 $node = $node->load($node->id());
243 $this->assertGreaterThan(12345, $node->getRevisionCreationTime());
247 * Tests translated and moderated nodes.
249 public function testContentTranslationNodeForm() {
250 $this->drupalLogin($this->rootUser);
252 // Add French language.
254 'predefined_langcode' => 'fr',
256 $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
258 // Enable content translation on articles.
259 $this->drupalGet('admin/config/regional/content-language');
261 'entity_types[node]' => TRUE,
262 'settings[node][moderated_content][translatable]' => TRUE,
263 'settings[node][moderated_content][settings][language][language_alterable]' => TRUE,
265 $this->drupalPostForm(NULL, $edit, t('Save configuration'));
267 // Adding languages requires a container rebuild in the test running
268 // environment so that multilingual services are used.
269 $this->rebuildContainer();
271 // Create new moderated content in draft (revision 1).
272 $this->drupalPostForm('node/add/moderated_content', [
273 'title[0][value]' => 'Some moderated content',
274 'body[0][value]' => 'First version of the content.',
275 'moderation_state[0][state]' => 'draft',
277 $this->assertTrue($this->xpath('//ul[@class="entity-moderation-form"]'));
279 $node = $this->drupalGetNodeByTitle('Some moderated content');
280 $this->assertTrue($node->language(), 'en');
281 $edit_path = sprintf('node/%d/edit', $node->id());
282 $translate_path = sprintf('node/%d/translations/add/en/fr', $node->id());
283 $latest_version_path = sprintf('node/%d/latest', $node->id());
284 $french = \Drupal::languageManager()->getLanguage('fr');
286 $this->drupalGet($latest_version_path);
287 $this->assertSession()->statusCodeEquals('403');
288 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
290 // Add french translation (revision 2).
291 $this->drupalGet($translate_path);
292 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
293 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
294 $this->assertSession()->optionNotExists('moderation_state[0][state]', 'archived');
295 $this->drupalPostForm(NULL, [
296 'body[0][value]' => 'Second version of the content.',
297 'moderation_state[0][state]' => 'published',
298 ], t('Save (this translation)'));
300 $this->drupalGet($latest_version_path, ['language' => $french]);
301 $this->assertSession()->statusCodeEquals('403');
302 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
304 // Add french pending revision (revision 3).
305 $this->drupalGet($edit_path, ['language' => $french]);
306 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
307 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
308 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
309 $this->drupalPostForm(NULL, [
310 'body[0][value]' => 'Third version of the content.',
311 'moderation_state[0][state]' => 'draft',
312 ], t('Save (this translation)'));
314 $this->drupalGet($latest_version_path, ['language' => $french]);
315 $this->assertTrue($this->xpath('//ul[@class="entity-moderation-form"]'));
317 $this->drupalGet($edit_path);
318 $this->clickLink('Delete');
319 $this->assertSession()->buttonExists('Delete');
321 $this->drupalGet($latest_version_path);
322 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
324 // Publish the french pending revision (revision 4).
325 $this->drupalGet($edit_path, ['language' => $french]);
326 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
327 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
328 $this->assertSession()->optionNotExists('moderation_state[0][state]', 'archived');
329 $this->drupalPostForm(NULL, [
330 'body[0][value]' => 'Fifth version of the content.',
331 'moderation_state[0][state]' => 'published',
332 ], t('Save (this translation)'));
334 $this->drupalGet($latest_version_path, ['language' => $french]);
335 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
337 // Publish the English pending revision (revision 5).
338 $this->drupalGet($edit_path);
339 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
340 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
341 $this->assertSession()->optionNotExists('moderation_state[0][state]', 'archived');
342 $this->drupalPostForm(NULL, [
343 'body[0][value]' => 'Sixth version of the content.',
344 'moderation_state[0][state]' => 'published',
345 ], t('Save (this translation)'));
347 $this->drupalGet($latest_version_path);
348 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
350 // Make sure we are allowed to create a pending French revision.
351 $this->drupalGet($edit_path, ['language' => $french]);
352 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
353 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
354 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
356 // Add an English pending revision (revision 6).
357 $this->drupalGet($edit_path);
358 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
359 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
360 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
361 $this->drupalPostForm(NULL, [
362 'body[0][value]' => 'Seventh version of the content.',
363 'moderation_state[0][state]' => 'draft',
364 ], t('Save (this translation)'));
366 $this->drupalGet($latest_version_path);
367 $this->assertTrue($this->xpath('//ul[@class="entity-moderation-form"]'));
368 $this->drupalGet($latest_version_path, ['language' => $french]);
369 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
371 // Publish the English pending revision (revision 7)
372 $this->drupalGet($edit_path);
373 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
374 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
375 $this->assertSession()->optionNotExists('moderation_state[0][state]', 'archived');
376 $this->drupalPostForm(NULL, [
377 'body[0][value]' => 'Eighth version of the content.',
378 'moderation_state[0][state]' => 'published',
379 ], t('Save (this translation)'));
381 $this->drupalGet($latest_version_path);
382 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
384 // Make sure we are allowed to create a pending French revision.
385 $this->drupalGet($edit_path, ['language' => $french]);
386 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
387 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
388 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
390 // Make sure we are allowed to create a pending English revision.
391 $this->drupalGet($edit_path);
392 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
393 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
394 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
396 // Create new moderated content (revision 1).
397 $this->drupalPostForm('node/add/moderated_content', [
398 'title[0][value]' => 'Third moderated content',
399 'moderation_state[0][state]' => 'published',
402 $node = $this->drupalGetNodeByTitle('Third moderated content');
403 $this->assertTrue($node->language(), 'en');
404 $edit_path = sprintf('node/%d/edit', $node->id());
405 $translate_path = sprintf('node/%d/translations/add/en/fr', $node->id());
407 // Translate it, without updating data (revision 2).
408 $this->drupalGet($translate_path);
409 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
410 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
411 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
412 $this->drupalPostForm(NULL, [
413 'moderation_state[0][state]' => 'draft',
414 ], t('Save (this translation)'));
416 // Add another draft for the translation (revision 3).
417 $this->drupalGet($edit_path, ['language' => $french]);
418 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
419 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
420 $this->assertSession()->optionNotExists('moderation_state[0][state]', 'archived');
421 $this->drupalPostForm(NULL, [
422 'moderation_state[0][state]' => 'draft',
423 ], t('Save (this translation)'));
425 // Updating and publishing the french translation is still possible.
426 $this->drupalGet($edit_path, ['language' => $french]);
427 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
428 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
429 $this->assertSession()->optionNotExists('moderation_state[0][state]', 'archived');
430 $this->drupalPostForm(NULL, [
431 'moderation_state[0][state]' => 'published',
432 ], t('Save (this translation)'));
434 // Now the french translation is published, an english draft can be added.
435 $this->drupalGet($edit_path);
436 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
437 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
438 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
439 $this->drupalPostForm(NULL, [
440 'moderation_state[0][state]' => 'draft',
441 ], t('Save (this translation)'));
445 * Test the moderation_state field when an alternative widget is set.
447 public function testAlternativeModerationStateWidget() {
448 $entity_form_display = EntityFormDisplay::load('node.moderated_content.default');
449 $entity_form_display->setComponent('moderation_state', [
450 'type' => 'string_textfield',
451 'region' => 'content',
453 $entity_form_display->save();
454 $this->drupalPostForm('node/add/moderated_content', [
455 'title[0][value]' => 'Test content',
456 'moderation_state[0][value]' => 'published',
458 $this->assertSession()->pageTextContains('Moderated content Test content has been created.');
462 * Tests that workflows and states can not be deleted if they are in use.
464 * @covers \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration::workflowHasData
465 * @covers \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration::workflowStateHasData
467 public function testWorkflowInUse() {
468 $user = $this->createUser([
469 'administer workflows',
470 'create moderated_content content',
471 'edit own moderated_content content',
472 'use editorial transition create_new_draft',
473 'use editorial transition publish',
474 'use editorial transition archive',
476 $this->drupalLogin($user);
478 'archived_state' => 'admin/config/workflow/workflows/manage/editorial/state/archived/delete',
479 'editorial_workflow' => 'admin/config/workflow/workflows/manage/editorial/delete',
482 'archived_state' => 'This workflow state is in use. You cannot remove this workflow state until you have removed all content using it.',
483 'editorial_workflow' => 'This workflow is in use. You cannot remove this workflow until you have removed all content using it.',
485 foreach ($paths as $path) {
486 $this->drupalGet($path);
487 $this->assertSession()->buttonExists('Delete');
489 // Create new moderated content in draft.
490 $this->drupalPostForm('node/add/moderated_content', [
491 'title[0][value]' => 'Some moderated content',
492 'body[0][value]' => 'First version of the content.',
493 'moderation_state[0][state]' => 'draft',
496 // The archived state is not used yet, so can still be deleted.
497 $this->drupalGet($paths['archived_state']);
498 $this->assertSession()->buttonExists('Delete');
500 // The workflow is being used, so can't be deleted.
501 $this->drupalGet($paths['editorial_workflow']);
502 $this->assertSession()->buttonNotExists('Delete');
503 $this->assertSession()->statusCodeEquals(200);
504 $this->assertSession()->pageTextContains($messages['editorial_workflow']);
506 $node = $this->drupalGetNodeByTitle('Some moderated content');
507 $this->drupalPostForm('node/' . $node->id() . '/edit', [
508 'moderation_state[0][state]' => 'published',
510 $this->drupalPostForm('node/' . $node->id() . '/edit', [
511 'moderation_state[0][state]' => 'archived',
514 // Now the archived state is being used so it can not be deleted either.
515 foreach ($paths as $type => $path) {
516 $this->drupalGet($path);
517 $this->assertSession()->buttonNotExists('Delete');
518 $this->assertSession()->statusCodeEquals(200);
519 $this->assertSession()->pageTextContains($messages[$type]);