3 namespace Drupal\Tests\content_moderation\Functional;
5 use Drupal\Core\Entity\Entity\EntityFormDisplay;
6 use Drupal\workflows\Entity\Workflow;
10 * Tests the moderation form, specifically on nodes.
12 * @group content_moderation
14 class ModerationFormTest extends ModerationStateTestBase {
21 public static $modules = [
25 'content_translation',
31 protected function setUp() {
33 $this->drupalLogin($this->adminUser);
34 $this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
35 $this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
39 * Tests the moderation form that shows on the latest version page.
41 * The latest version page only shows if there is a pending revision.
43 * @see \Drupal\content_moderation\EntityOperations
44 * @see \Drupal\Tests\content_moderation\Functional\ModerationStateBlockTest::testCustomBlockModeration
46 public function testModerationForm() {
47 // Create new moderated content in draft.
48 $this->drupalPostForm('node/add/moderated_content', [
49 'title[0][value]' => 'Some moderated content',
50 'body[0][value]' => 'First version of the content.',
51 'moderation_state[0][state]' => 'draft',
54 $node = $this->drupalGetNodeByTitle('Some moderated content');
55 $canonical_path = sprintf('node/%d', $node->id());
56 $edit_path = sprintf('node/%d/edit', $node->id());
57 $latest_version_path = sprintf('node/%d/latest', $node->id());
59 $this->assertTrue($this->adminUser->hasPermission('edit any moderated_content content'));
61 // The canonical view should have a moderation form, because it is not the
63 $this->drupalGet($canonical_path);
64 $this->assertResponse(200);
65 $this->assertField('edit-new-state', 'The node view page has a moderation form.');
67 // The latest version page should not show, because there is no pending
69 $this->drupalGet($latest_version_path);
70 $this->assertResponse(403);
73 $this->drupalPostForm($edit_path, [
74 'body[0][value]' => 'Second version of the content.',
75 'moderation_state[0][state]' => 'draft',
78 // The canonical view should have a moderation form, because it is not the
80 $this->drupalGet($canonical_path);
81 $this->assertResponse(200);
82 $this->assertField('edit-new-state', 'The node view page has a moderation form.');
85 $this->drupalPostForm($edit_path, [
86 'body[0][value]' => 'Second version of the content.',
87 'moderation_state[0][state]' => 'draft',
90 // The preview view should not have a moderation form.
91 $preview_url = Url::fromRoute('entity.node.preview', [
92 'node_preview' => $node->uuid(),
93 'view_mode_id' => 'full',
95 $this->assertResponse(200);
96 $this->assertUrl($preview_url);
97 $this->assertNoField('edit-new-state', 'The node preview page has no moderation form.');
99 // The latest version page should not show, because there is still no
101 $this->drupalGet($latest_version_path);
102 $this->assertResponse(403);
104 // Publish the draft.
105 $this->drupalPostForm($edit_path, [
106 'body[0][value]' => 'Third version of the content.',
107 'moderation_state[0][state]' => 'published',
110 // The published view should not have a moderation form, because it is the
112 $this->drupalGet($canonical_path);
113 $this->assertResponse(200);
114 $this->assertNoField('edit-new-state', 'The node view page has no moderation form.');
116 // The latest version page should not show, because there is still no
118 $this->drupalGet($latest_version_path);
119 $this->assertResponse(403);
121 // Make a pending revision.
122 $this->drupalPostForm($edit_path, [
123 'body[0][value]' => 'Fourth version of the content.',
124 'moderation_state[0][state]' => 'draft',
127 // The published view should not have a moderation form, because it is the
129 $this->drupalGet($canonical_path);
130 $this->assertResponse(200);
131 $this->assertNoField('edit-new-state', 'The node view page has no moderation form.');
133 // The latest version page should show the moderation form and have "Draft"
134 // status, because the pending revision is in "Draft".
135 $this->drupalGet($latest_version_path);
136 $this->assertResponse(200);
137 $this->assertField('edit-new-state', 'The latest-version page has a moderation form.');
138 $this->assertText('Draft', 'Correct status found on the latest-version page.');
140 // Submit the moderation form to change status to published.
141 $this->drupalPostForm($latest_version_path, [
142 'new_state' => 'published',
145 // The latest version page should not show, because there is no
147 $this->drupalGet($latest_version_path);
148 $this->assertResponse(403);
152 * Test moderation non-bundle entity type.
154 public function testNonBundleModerationForm() {
155 $this->drupalLogin($this->rootUser);
156 $workflow = Workflow::load('editorial');
157 $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_mulrevpub', 'entity_test_mulrevpub');
160 // Create new moderated content in draft.
161 $this->drupalPostForm('entity_test_mulrevpub/add', ['moderation_state[0][state]' => 'draft'], t('Save'));
163 // The latest version page should not show, because there is no pending
165 $this->drupalGet('/entity_test_mulrevpub/manage/1/latest');
166 $this->assertResponse(403);
169 $this->drupalPostForm('entity_test_mulrevpub/manage/1/edit', ['moderation_state[0][state]' => 'draft'], t('Save'));
171 // The latest version page should not show, because there is still no
173 $this->drupalGet('/entity_test_mulrevpub/manage/1/latest');
174 $this->assertResponse(403);
176 // Publish the draft.
177 $this->drupalPostForm('entity_test_mulrevpub/manage/1/edit', ['moderation_state[0][state]' => 'published'], t('Save'));
179 // The published view should not have a moderation form, because it is the
181 $this->drupalGet('entity_test_mulrevpub/manage/1');
182 $this->assertResponse(200);
183 $this->assertNoText('Status', 'The node view page has no moderation form.');
185 // The latest version page should not show, because there is still no
187 $this->drupalGet('entity_test_mulrevpub/manage/1/latest');
188 $this->assertResponse(403);
190 // Make a pending revision.
191 $this->drupalPostForm('entity_test_mulrevpub/manage/1/edit', ['moderation_state[0][state]' => 'draft'], t('Save'));
193 // The published view should not have a moderation form, because it is the
195 $this->drupalGet('entity_test_mulrevpub/manage/1');
196 $this->assertResponse(200);
197 $this->assertNoText('Status', 'The node view page has no moderation form.');
199 // The latest version page should show the moderation form and have "Draft"
200 // status, because the pending revision is in "Draft".
201 $this->drupalGet('entity_test_mulrevpub/manage/1/latest');
202 $this->assertResponse(200);
203 $this->assertText('Moderation state', 'Form text found on the latest-version page.');
204 $this->assertText('Draft', 'Correct status found on the latest-version page.');
206 // Submit the moderation form to change status to published.
207 $this->drupalPostForm('entity_test_mulrevpub/manage/1/latest', [
208 'new_state' => 'published',
211 // The latest version page should not show, because there is no
213 $this->drupalGet('entity_test_mulrevpub/manage/1/latest');
214 $this->assertResponse(403);
218 * Tests the revision author is updated when the moderation form is used.
220 public function testModerationFormSetsRevisionAuthor() {
221 // Create new moderated content in published.
222 $node = $this->createNode(['type' => 'moderated_content', 'moderation_state' => 'published']);
223 // Make a pending revision.
224 $node->title = $this->randomMachineName();
225 $node->moderation_state->value = 'draft';
226 $node->setRevisionCreationTime(12345);
229 $another_user = $this->drupalCreateUser($this->permissions);
230 $this->grantUserPermissionToCreateContentOfType($another_user, 'moderated_content');
231 $this->drupalLogin($another_user);
232 $this->drupalPostForm(sprintf('node/%d/latest', $node->id()), [
233 'new_state' => 'published',
236 $this->drupalGet(sprintf('node/%d/revisions', $node->id()));
237 $this->assertText('by ' . $another_user->getAccountName());
239 // Verify the revision creation time has been updated.
240 $node = $node->load($node->id());
241 $this->assertGreaterThan(12345, $node->getRevisionCreationTime());
245 * Tests translated and moderated nodes.
247 public function testContentTranslationNodeForm() {
248 $this->drupalLogin($this->rootUser);
250 // Add French language.
252 'predefined_langcode' => 'fr',
254 $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
256 // Enable content translation on articles.
257 $this->drupalGet('admin/config/regional/content-language');
259 'entity_types[node]' => TRUE,
260 'settings[node][moderated_content][translatable]' => TRUE,
261 'settings[node][moderated_content][settings][language][language_alterable]' => TRUE,
263 $this->drupalPostForm(NULL, $edit, t('Save configuration'));
265 // Adding languages requires a container rebuild in the test running
266 // environment so that multilingual services are used.
267 $this->rebuildContainer();
269 // Create new moderated content in draft (revision 1).
270 $this->drupalPostForm('node/add/moderated_content', [
271 'title[0][value]' => 'Some moderated content',
272 'body[0][value]' => 'First version of the content.',
273 'moderation_state[0][state]' => 'draft',
275 $this->assertTrue($this->xpath('//ul[@class="entity-moderation-form"]'));
277 $node = $this->drupalGetNodeByTitle('Some moderated content');
278 $this->assertTrue($node->language(), 'en');
279 $edit_path = sprintf('node/%d/edit', $node->id());
280 $translate_path = sprintf('node/%d/translations/add/en/fr', $node->id());
281 $latest_version_path = sprintf('node/%d/latest', $node->id());
282 $french = \Drupal::languageManager()->getLanguage('fr');
284 $this->drupalGet($latest_version_path);
285 $this->assertSession()->statusCodeEquals('403');
286 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
288 // Add french translation (revision 2).
289 $this->drupalGet($translate_path);
290 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
291 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
292 $this->assertSession()->optionNotExists('moderation_state[0][state]', 'archived');
293 $this->drupalPostForm(NULL, [
294 'body[0][value]' => 'Second version of the content.',
295 'moderation_state[0][state]' => 'published',
296 ], t('Save (this translation)'));
298 $this->drupalGet($latest_version_path, ['language' => $french]);
299 $this->assertSession()->statusCodeEquals('403');
300 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
302 // Add french pending revision (revision 3).
303 $this->drupalGet($edit_path, ['language' => $french]);
304 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
305 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
306 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
307 $this->drupalPostForm(NULL, [
308 'body[0][value]' => 'Third version of the content.',
309 'moderation_state[0][state]' => 'draft',
310 ], t('Save (this translation)'));
312 $this->drupalGet($latest_version_path, ['language' => $french]);
313 $this->assertTrue($this->xpath('//ul[@class="entity-moderation-form"]'));
315 $this->drupalGet($edit_path);
316 $this->clickLink('Delete');
317 $this->assertSession()->buttonExists('Delete');
319 $this->drupalGet($latest_version_path);
320 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
322 // Publish the french pending revision (revision 4).
323 $this->drupalGet($edit_path, ['language' => $french]);
324 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
325 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
326 $this->assertSession()->optionNotExists('moderation_state[0][state]', 'archived');
327 $this->drupalPostForm(NULL, [
328 'body[0][value]' => 'Fifth version of the content.',
329 'moderation_state[0][state]' => 'published',
330 ], t('Save (this translation)'));
332 $this->drupalGet($latest_version_path, ['language' => $french]);
333 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
335 // Publish the English pending revision (revision 5).
336 $this->drupalGet($edit_path);
337 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
338 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
339 $this->assertSession()->optionNotExists('moderation_state[0][state]', 'archived');
340 $this->drupalPostForm(NULL, [
341 'body[0][value]' => 'Sixth version of the content.',
342 'moderation_state[0][state]' => 'published',
343 ], t('Save (this translation)'));
345 $this->drupalGet($latest_version_path);
346 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
348 // Make sure we are allowed to create a pending French revision.
349 $this->drupalGet($edit_path, ['language' => $french]);
350 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
351 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
352 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
354 // Add an English pending revision (revision 6).
355 $this->drupalGet($edit_path);
356 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
357 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
358 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
359 $this->drupalPostForm(NULL, [
360 'body[0][value]' => 'Seventh version of the content.',
361 'moderation_state[0][state]' => 'draft',
362 ], t('Save (this translation)'));
364 $this->drupalGet($latest_version_path);
365 $this->assertTrue($this->xpath('//ul[@class="entity-moderation-form"]'));
366 $this->drupalGet($latest_version_path, ['language' => $french]);
367 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
369 // Publish the English pending revision (revision 7)
370 $this->drupalGet($edit_path);
371 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
372 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
373 $this->assertSession()->optionNotExists('moderation_state[0][state]', 'archived');
374 $this->drupalPostForm(NULL, [
375 'body[0][value]' => 'Eighth version of the content.',
376 'moderation_state[0][state]' => 'published',
377 ], t('Save (this translation)'));
379 $this->drupalGet($latest_version_path);
380 $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
382 // Make sure we are allowed to create a pending French revision.
383 $this->drupalGet($edit_path, ['language' => $french]);
384 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
385 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
386 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
388 // Make sure we are allowed to create a pending English revision.
389 $this->drupalGet($edit_path);
390 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
391 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
392 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
394 // Create new moderated content (revision 1).
395 $this->drupalPostForm('node/add/moderated_content', [
396 'title[0][value]' => 'Third moderated content',
397 'moderation_state[0][state]' => 'published',
400 $node = $this->drupalGetNodeByTitle('Third moderated content');
401 $this->assertTrue($node->language(), 'en');
402 $edit_path = sprintf('node/%d/edit', $node->id());
403 $translate_path = sprintf('node/%d/translations/add/en/fr', $node->id());
405 // Translate it, without updating data (revision 2).
406 $this->drupalGet($translate_path);
407 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
408 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
409 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
410 $this->drupalPostForm(NULL, [
411 'moderation_state[0][state]' => 'draft',
412 ], t('Save (this translation)'));
414 // Add another draft for the translation (revision 3).
415 $this->drupalGet($edit_path, ['language' => $french]);
416 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
417 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
418 $this->assertSession()->optionNotExists('moderation_state[0][state]', 'archived');
419 $this->drupalPostForm(NULL, [
420 'moderation_state[0][state]' => 'draft',
421 ], t('Save (this translation)'));
423 // Updating and publishing the french translation is still possible.
424 $this->drupalGet($edit_path, ['language' => $french]);
425 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
426 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
427 $this->assertSession()->optionNotExists('moderation_state[0][state]', 'archived');
428 $this->drupalPostForm(NULL, [
429 'moderation_state[0][state]' => 'published',
430 ], t('Save (this translation)'));
432 // Now the french translation is published, an english draft can be added.
433 $this->drupalGet($edit_path);
434 $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
435 $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
436 $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
437 $this->drupalPostForm(NULL, [
438 'moderation_state[0][state]' => 'draft',
439 ], t('Save (this translation)'));
443 * Test the moderation_state field when an alternative widget is set.
445 public function testAlternativeModerationStateWidget() {
446 $entity_form_display = EntityFormDisplay::load('node.moderated_content.default');
447 $entity_form_display->setComponent('moderation_state', [
448 'type' => 'string_textfield',
449 'region' => 'content',
451 $entity_form_display->save();
452 $this->drupalPostForm('node/add/moderated_content', [
453 'title[0][value]' => 'Test content',
454 'moderation_state[0][value]' => 'published',
456 $this->assertSession()->pageTextContains('Moderated content Test content has been created.');
460 * Tests that workflows and states can not be deleted if they are in use.
462 * @covers \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration::workflowHasData
463 * @covers \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration::workflowStateHasData
465 public function testWorkflowInUse() {
466 $user = $this->createUser([
467 'administer workflows',
468 'create moderated_content content',
469 'edit own moderated_content content',
470 'use editorial transition create_new_draft',
471 'use editorial transition publish',
472 'use editorial transition archive'
474 $this->drupalLogin($user);
476 'archived_state' => 'admin/config/workflow/workflows/manage/editorial/state/archived/delete',
477 'editorial_workflow' => 'admin/config/workflow/workflows/manage/editorial/delete',
480 'archived_state' => 'This workflow state is in use. You cannot remove this workflow state until you have removed all content using it.',
481 'editorial_workflow' => 'This workflow is in use. You cannot remove this workflow until you have removed all content using it.',
483 foreach ($paths as $path) {
484 $this->drupalGet($path);
485 $this->assertSession()->buttonExists('Delete');
487 // Create new moderated content in draft.
488 $this->drupalPostForm('node/add/moderated_content', [
489 'title[0][value]' => 'Some moderated content',
490 'body[0][value]' => 'First version of the content.',
491 'moderation_state[0][state]' => 'draft',
494 // The archived state is not used yet, so can still be deleted.
495 $this->drupalGet($paths['archived_state']);
496 $this->assertSession()->buttonExists('Delete');
498 // The workflow is being used, so can't be deleted.
499 $this->drupalGet($paths['editorial_workflow']);
500 $this->assertSession()->buttonNotExists('Delete');
501 $this->assertSession()->statusCodeEquals(200);
502 $this->assertSession()->pageTextContains($messages['editorial_workflow']);
504 $node = $this->drupalGetNodeByTitle('Some moderated content');
505 $this->drupalPostForm('node/' . $node->id() . '/edit', [
506 'moderation_state[0][state]' => 'published',
508 $this->drupalPostForm('node/' . $node->id() . '/edit', [
509 'moderation_state[0][state]' => 'archived',
512 // Now the archived state is being used so it can not be deleted either.
513 foreach ($paths as $type => $path) {
514 $this->drupalGet($path);
515 $this->assertSession()->buttonNotExists('Delete');
516 $this->assertSession()->statusCodeEquals(200);
517 $this->assertSession()->pageTextContains($messages[$type]);