Version 1
[yaffs-website] / web / core / modules / content_moderation / tests / src / Kernel / ContentModerationStateTest.php
1 <?php
2
3 namespace Drupal\Tests\content_moderation\Kernel;
4
5 use Drupal\content_moderation\Entity\ContentModerationState;
6 use Drupal\Core\Entity\EntityPublishedInterface;
7 use Drupal\Core\Entity\EntityStorageException;
8 use Drupal\entity_test\Entity\EntityTestBundle;
9 use Drupal\entity_test\Entity\EntityTestWithBundle;
10 use Drupal\Core\Entity\EntityInterface;
11 use Drupal\KernelTests\KernelTestBase;
12 use Drupal\language\Entity\ConfigurableLanguage;
13 use Drupal\node\Entity\Node;
14 use Drupal\node\Entity\NodeType;
15 use Drupal\workflows\Entity\Workflow;
16
17 /**
18  * Tests links between a content entity and a content_moderation_state entity.
19  *
20  * @group content_moderation
21  */
22 class ContentModerationStateTest extends KernelTestBase {
23
24   /**
25    * {@inheritdoc}
26    */
27   public static $modules = [
28     'entity_test',
29     'node',
30     'block_content',
31     'content_moderation',
32     'user',
33     'system',
34     'language',
35     'content_translation',
36     'text',
37     'workflows',
38   ];
39
40   /**
41    * @var \Drupal\Core\Entity\EntityTypeManager
42    */
43   protected $entityTypeManager;
44
45   /**
46    * {@inheritdoc}
47    */
48   protected function setUp() {
49     parent::setUp();
50
51     $this->installSchema('node', 'node_access');
52     $this->installEntitySchema('node');
53     $this->installEntitySchema('user');
54     $this->installEntitySchema('entity_test_with_bundle');
55     $this->installEntitySchema('entity_test_rev');
56     $this->installEntitySchema('entity_test_no_bundle');
57     $this->installEntitySchema('entity_test_mulrevpub');
58     $this->installEntitySchema('block_content');
59     $this->installEntitySchema('content_moderation_state');
60     $this->installConfig('content_moderation');
61
62     $this->entityTypeManager = $this->container->get('entity_type.manager');
63   }
64
65   /**
66    * Tests basic monolingual content moderation through the API.
67    *
68    * @dataProvider basicModerationTestCases
69    */
70   public function testBasicModeration($entity_type_id) {
71     // Make the 'entity_test_with_bundle' entity type revisionable.
72     if ($entity_type_id == 'entity_test_with_bundle') {
73       $this->setEntityTestWithBundleKeys(['revision' => 'revision_id']);
74     }
75
76     $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
77     $bundle_id = $entity_type_id;
78     $bundle_entity_type_id = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType();
79     if ($bundle_entity_type_id) {
80       $bundle_entity_type_definition = $this->entityTypeManager->getDefinition($bundle_entity_type_id);
81       $entity_type_storage = $this->entityTypeManager->getStorage($bundle_entity_type_id);
82
83       $entity_type = $entity_type_storage->create([
84         $bundle_entity_type_definition->getKey('id') => 'example',
85       ]);
86       $entity_type->save();
87       $bundle_id = $entity_type->id();
88     }
89
90     $workflow = Workflow::load('editorial');
91     $workflow->getTypePlugin()->addEntityTypeAndBundle($entity_type_id, $bundle_id);
92     $workflow->save();
93
94     $entity = $entity_storage->create([
95       'title' => 'Test title',
96       $this->entityTypeManager->getDefinition($entity_type_id)->getKey('bundle') => $bundle_id,
97     ]);
98     if ($entity instanceof EntityPublishedInterface) {
99       $entity->setUnpublished();
100     }
101     $entity->save();
102     $entity = $this->reloadEntity($entity);
103     $this->assertEquals('draft', $entity->moderation_state->value);
104
105     $entity->moderation_state->value = 'published';
106     $entity->save();
107
108     $entity = $this->reloadEntity($entity);
109     $this->assertEquals('published', $entity->moderation_state->value);
110
111     // Change the state without saving the node.
112     $content_moderation_state = ContentModerationState::load(1);
113     $content_moderation_state->set('moderation_state', 'draft');
114     $content_moderation_state->setNewRevision(TRUE);
115     $content_moderation_state->save();
116
117     $entity = $this->reloadEntity($entity, 3);
118     $this->assertEquals('draft', $entity->moderation_state->value);
119     if ($entity instanceof EntityPublishedInterface) {
120       $this->assertFalse($entity->isPublished());
121     }
122
123     // Get the default revision.
124     $entity = $this->reloadEntity($entity);
125     if ($entity instanceof EntityPublishedInterface) {
126       $this->assertTrue((bool) $entity->isPublished());
127     }
128     $this->assertEquals(2, $entity->getRevisionId());
129
130     $entity->moderation_state->value = 'published';
131     $entity->save();
132
133     $entity = $this->reloadEntity($entity, 4);
134     $this->assertEquals('published', $entity->moderation_state->value);
135
136     // Get the default revision.
137     $entity = $this->reloadEntity($entity);
138     if ($entity instanceof EntityPublishedInterface) {
139       $this->assertTrue((bool) $entity->isPublished());
140     }
141     $this->assertEquals(4, $entity->getRevisionId());
142
143     // Update the node to archived which will then be the default revision.
144     $entity->moderation_state->value = 'archived';
145     $entity->save();
146
147     // Revert to the previous (published) revision.
148     $previous_revision = $entity_storage->loadRevision(4);
149     $previous_revision->isDefaultRevision(TRUE);
150     $previous_revision->setNewRevision(TRUE);
151     $previous_revision->save();
152
153     // Get the default revision.
154     $entity = $this->reloadEntity($entity);
155     $this->assertEquals('published', $entity->moderation_state->value);
156     if ($entity instanceof EntityPublishedInterface) {
157       $this->assertTrue($entity->isPublished());
158     }
159
160     // Set an invalid moderation state.
161     $this->setExpectedException(EntityStorageException::class);
162     $entity->moderation_state->value = 'foobar';
163     $entity->save();
164   }
165
166   /**
167    * Test cases for basic moderation test.
168    */
169   public function basicModerationTestCases() {
170     return [
171       'Nodes' => [
172         'node',
173       ],
174       'Block content' => [
175         'block_content',
176       ],
177       'Test Entity with Bundle' => [
178         'entity_test_with_bundle',
179       ],
180       'Test entity - revisions, data table, and published interface' => [
181         'entity_test_mulrevpub',
182       ],
183       'Entity Test with revisions' => [
184         'entity_test_rev',
185       ],
186       'Entity without bundle' => [
187         'entity_test_no_bundle',
188       ],
189     ];
190   }
191
192   /**
193    * Tests basic multilingual content moderation through the API.
194    */
195   public function testMultilingualModeration() {
196     // Enable French.
197     ConfigurableLanguage::createFromLangcode('fr')->save();
198     $node_type = NodeType::create([
199       'type' => 'example',
200     ]);
201     $node_type->save();
202
203     $workflow = Workflow::load('editorial');
204     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
205     $workflow->save();
206
207     $english_node = Node::create([
208       'type' => 'example',
209       'title' => 'Test title',
210     ]);
211     // Revision 1 (en).
212     $english_node
213       ->setUnpublished()
214       ->save();
215     $this->assertEquals('draft', $english_node->moderation_state->value);
216     $this->assertFalse($english_node->isPublished());
217
218     // Create a French translation.
219     $french_node = $english_node->addTranslation('fr', ['title' => 'French title']);
220     $french_node->setUnpublished();
221     // Revision 1 (fr).
222     $french_node->save();
223     $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
224     $this->assertEquals('draft', $french_node->moderation_state->value);
225     $this->assertFalse($french_node->isPublished());
226
227     // Move English node to create another draft.
228     $english_node = $this->reloadEntity($english_node);
229     $english_node->moderation_state->value = 'draft';
230     // Revision 2 (en, fr).
231     $english_node->save();
232     $english_node = $this->reloadEntity($english_node);
233     $this->assertEquals('draft', $english_node->moderation_state->value);
234
235     // French node should still be in draft.
236     $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
237     $this->assertEquals('draft', $french_node->moderation_state->value);
238
239     // Publish the French node.
240     $french_node->moderation_state->value = 'published';
241     // Revision 3 (en, fr).
242     $french_node->save();
243     $french_node = $this->reloadEntity($french_node)->getTranslation('fr');
244     $this->assertTrue($french_node->isPublished());
245     $this->assertEquals('published', $french_node->moderation_state->value);
246     $this->assertTrue($french_node->isPublished());
247     $english_node = $french_node->getTranslation('en');
248     $this->assertEquals('draft', $english_node->moderation_state->value);
249
250     // Publish the English node.
251     $english_node->moderation_state->value = 'published';
252     // Revision 4 (en, fr).
253     $english_node->save();
254     $english_node = $this->reloadEntity($english_node);
255     $this->assertTrue($english_node->isPublished());
256
257     // Move the French node back to draft.
258     $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
259     $this->assertTrue($french_node->isPublished());
260     $french_node->moderation_state->value = 'draft';
261     // Revision 5 (en, fr).
262     $french_node->save();
263     $french_node = $this->reloadEntity($english_node, 5)->getTranslation('fr');
264     $this->assertFalse($french_node->isPublished());
265     $this->assertTrue($french_node->getTranslation('en')->isPublished());
266
267     // Republish the French node.
268     $french_node->moderation_state->value = 'published';
269     // Revision 6 (en, fr).
270     $french_node->save();
271     $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
272     $this->assertTrue($french_node->isPublished());
273
274     // Change the EN state without saving the node.
275     $content_moderation_state = ContentModerationState::load(1);
276     $content_moderation_state->set('moderation_state', 'draft');
277     $content_moderation_state->setNewRevision(TRUE);
278     // Revision 7 (en, fr).
279     $content_moderation_state->save();
280     $english_node = $this->reloadEntity($french_node, $french_node->getRevisionId() + 1);
281
282     $this->assertEquals('draft', $english_node->moderation_state->value);
283     $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
284     $this->assertEquals('published', $french_node->moderation_state->value);
285
286     // This should unpublish the French node.
287     $content_moderation_state = ContentModerationState::load(1);
288     $content_moderation_state = $content_moderation_state->getTranslation('fr');
289     $content_moderation_state->set('moderation_state', 'draft');
290     $content_moderation_state->setNewRevision(TRUE);
291     // Revision 8 (en, fr).
292     $content_moderation_state->save();
293
294     $english_node = $this->reloadEntity($english_node, $english_node->getRevisionId());
295     $this->assertEquals('draft', $english_node->moderation_state->value);
296     $french_node = $this->reloadEntity($english_node, '8')->getTranslation('fr');
297     $this->assertEquals('draft', $french_node->moderation_state->value);
298     // Switching the moderation state to an unpublished state should update the
299     // entity.
300     $this->assertFalse($french_node->isPublished());
301
302     // Get the default english node.
303     $english_node = $this->reloadEntity($english_node);
304     $this->assertTrue($english_node->isPublished());
305     $this->assertEquals(6, $english_node->getRevisionId());
306   }
307
308   /**
309    * Tests that a non-translatable entity type with a langcode can be moderated.
310    */
311   public function testNonTranslatableEntityTypeModeration() {
312     // Make the 'entity_test_with_bundle' entity type revisionable.
313     $this->setEntityTestWithBundleKeys(['revision' => 'revision_id']);
314
315     // Create a test bundle.
316     $entity_test_bundle = EntityTestBundle::create([
317       'id' => 'example',
318     ]);
319     $entity_test_bundle->save();
320
321     $workflow = Workflow::load('editorial');
322     $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_with_bundle', 'example');
323     $workflow->save();
324
325     // Check that the tested entity type is not translatable.
326     $entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
327     $this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
328
329     // Create a test entity.
330     $entity_test_with_bundle = EntityTestWithBundle::create([
331       'type' => 'example'
332     ]);
333     $entity_test_with_bundle->save();
334     $this->assertEquals('draft', $entity_test_with_bundle->moderation_state->value);
335
336     $entity_test_with_bundle->moderation_state->value = 'published';
337     $entity_test_with_bundle->save();
338
339     $this->assertEquals('published', EntityTestWithBundle::load($entity_test_with_bundle->id())->moderation_state->value);
340   }
341
342   /**
343    * Tests that a non-translatable entity type without a langcode can be
344    * moderated.
345    */
346   public function testNonLangcodeEntityTypeModeration() {
347     // Make the 'entity_test_with_bundle' entity type revisionable and unset
348     // the langcode entity key.
349     $this->setEntityTestWithBundleKeys(['revision' => 'revision_id'], ['langcode']);
350
351     // Create a test bundle.
352     $entity_test_bundle = EntityTestBundle::create([
353       'id' => 'example',
354     ]);
355     $entity_test_bundle->save();
356
357     $workflow = Workflow::load('editorial');
358     $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_with_bundle', 'example');
359     $workflow->save();
360
361     // Check that the tested entity type is not translatable.
362     $entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
363     $this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
364
365     // Create a test entity.
366     $entity_test_with_bundle = EntityTestWithBundle::create([
367       'type' => 'example'
368     ]);
369     $entity_test_with_bundle->save();
370     $this->assertEquals('draft', $entity_test_with_bundle->moderation_state->value);
371
372     $entity_test_with_bundle->moderation_state->value = 'published';
373     $entity_test_with_bundle->save();
374
375     $this->assertEquals('published', EntityTestWithBundle::load($entity_test_with_bundle->id())->moderation_state->value);
376   }
377
378   /**
379    * Set the keys on the test entity type.
380    *
381    * @param array $keys
382    *   The entity keys to override
383    * @param array $remove_keys
384    *   Keys to remove.
385    */
386   protected function setEntityTestWithBundleKeys($keys, $remove_keys = []) {
387     $entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
388     $original_keys = $entity_type->getKeys();
389     foreach ($remove_keys as $remove_key) {
390       unset($original_keys[$remove_key]);
391     }
392     $entity_type->set('entity_keys', $keys + $original_keys);
393     \Drupal::state()->set('entity_test_with_bundle.entity_type', $entity_type);
394     \Drupal::entityDefinitionUpdateManager()->applyUpdates();
395   }
396
397   /**
398    * Tests the dependencies of the workflow when using content moderation.
399    */
400   public function testWorkflowDependencies() {
401     $node_type = NodeType::create([
402       'type' => 'example',
403     ]);
404     $node_type->save();
405
406     $workflow = Workflow::load('editorial');
407     // Test both a config and non-config based bundle and entity type.
408     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
409     $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
410     $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_no_bundle', 'entity_test_no_bundle');
411     $workflow->save();
412
413     $this->assertEquals([
414       'module' => [
415         'content_moderation',
416         'entity_test',
417       ],
418       'config' => [
419         'node.type.example',
420       ],
421     ], $workflow->getDependencies());
422
423     $this->assertEquals([
424       'entity_test_no_bundle',
425       'entity_test_rev',
426       'node'
427     ], $workflow->getTypePlugin()->getEntityTypes());
428
429     // Delete the node type and ensure it is removed from the workflow.
430     $node_type->delete();
431     $workflow = Workflow::load('editorial');
432     $entity_types = $workflow->getTypePlugin()->getEntityTypes();
433     $this->assertFalse(in_array('node', $entity_types));
434
435     // Uninstall entity test and ensure it's removed from the workflow.
436     $this->container->get('config.manager')->uninstall('module', 'entity_test');
437     $workflow = Workflow::load('editorial');
438     $entity_types = $workflow->getTypePlugin()->getEntityTypes();
439     $this->assertEquals([], $entity_types);
440   }
441
442   /**
443    * Reloads the entity after clearing the static cache.
444    *
445    * @param \Drupal\Core\Entity\EntityInterface $entity
446    *   The entity to reload.
447    * @param int|bool $revision_id
448    *   The specific revision ID to load. Defaults FALSE and just loads the
449    *   default revision.
450    *
451    * @return \Drupal\Core\Entity\EntityInterface
452    *   The reloaded entity.
453    */
454   protected function reloadEntity(EntityInterface $entity, $revision_id = FALSE) {
455     $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
456     $storage->resetCache([$entity->id()]);
457     if ($revision_id) {
458       return $storage->loadRevision($revision_id);
459     }
460     return $storage->load($entity->id());
461   }
462
463 }