ContentModerationStateTest.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. <?php
  2. namespace Drupal\Tests\content_moderation\Kernel;
  3. use Drupal\content_moderation\Entity\ContentModerationState;
  4. use Drupal\Core\Entity\EntityInterface;
  5. use Drupal\Core\Entity\EntityPublishedInterface;
  6. use Drupal\Core\Entity\EntityStorageException;
  7. use Drupal\Core\Language\LanguageInterface;
  8. use Drupal\entity_test\Entity\EntityTestRev;
  9. use Drupal\KernelTests\KernelTestBase;
  10. use Drupal\language\Entity\ConfigurableLanguage;
  11. use Drupal\node\Entity\Node;
  12. use Drupal\node\Entity\NodeType;
  13. use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
  14. use Drupal\workflows\Entity\Workflow;
  15. /**
  16. * Tests links between a content entity and a content_moderation_state entity.
  17. *
  18. * @group content_moderation
  19. */
  20. class ContentModerationStateTest extends KernelTestBase {
  21. use ContentModerationTestTrait;
  22. /**
  23. * {@inheritdoc}
  24. */
  25. public static $modules = [
  26. 'entity_test',
  27. 'node',
  28. 'block',
  29. 'block_content',
  30. 'media',
  31. 'media_test_source',
  32. 'image',
  33. 'file',
  34. 'field',
  35. 'content_moderation',
  36. 'user',
  37. 'system',
  38. 'language',
  39. 'content_translation',
  40. 'text',
  41. 'workflows',
  42. ];
  43. /**
  44. * @var \Drupal\Core\Entity\EntityTypeManager
  45. */
  46. protected $entityTypeManager;
  47. /**
  48. * {@inheritdoc}
  49. */
  50. protected function setUp() {
  51. parent::setUp();
  52. $this->installSchema('node', 'node_access');
  53. $this->installEntitySchema('node');
  54. $this->installEntitySchema('user');
  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('media');
  60. $this->installEntitySchema('file');
  61. $this->installEntitySchema('content_moderation_state');
  62. $this->installConfig('content_moderation');
  63. $this->installSchema('file', 'file_usage');
  64. $this->installConfig(['field', 'system', 'image', 'file', 'media']);
  65. $this->entityTypeManager = $this->container->get('entity_type.manager');
  66. }
  67. /**
  68. * Tests basic monolingual content moderation through the API.
  69. *
  70. * @dataProvider basicModerationTestCases
  71. */
  72. public function testBasicModeration($entity_type_id) {
  73. $entity = $this->createEntity($entity_type_id);
  74. if ($entity instanceof EntityPublishedInterface) {
  75. $entity->setUnpublished();
  76. }
  77. $entity->save();
  78. $entity = $this->reloadEntity($entity);
  79. $this->assertEquals('draft', $entity->moderation_state->value);
  80. $entity->moderation_state->value = 'published';
  81. $entity->save();
  82. $entity = $this->reloadEntity($entity);
  83. $this->assertEquals('published', $entity->moderation_state->value);
  84. // Change the state without saving the node.
  85. $content_moderation_state = ContentModerationState::load(1);
  86. $content_moderation_state->set('moderation_state', 'draft');
  87. $content_moderation_state->setNewRevision(TRUE);
  88. $content_moderation_state->save();
  89. $entity = $this->reloadEntity($entity, 3);
  90. $this->assertEquals('draft', $entity->moderation_state->value);
  91. if ($entity instanceof EntityPublishedInterface) {
  92. $this->assertFalse($entity->isPublished());
  93. }
  94. // Get the default revision.
  95. $entity = $this->reloadEntity($entity);
  96. if ($entity instanceof EntityPublishedInterface) {
  97. $this->assertTrue((bool) $entity->isPublished());
  98. }
  99. $this->assertEquals(2, $entity->getRevisionId());
  100. $entity->moderation_state->value = 'published';
  101. $entity->save();
  102. $entity = $this->reloadEntity($entity, 4);
  103. $this->assertEquals('published', $entity->moderation_state->value);
  104. // Get the default revision.
  105. $entity = $this->reloadEntity($entity);
  106. if ($entity instanceof EntityPublishedInterface) {
  107. $this->assertTrue((bool) $entity->isPublished());
  108. }
  109. $this->assertEquals(4, $entity->getRevisionId());
  110. // Update the node to archived which will then be the default revision.
  111. $entity->moderation_state->value = 'archived';
  112. $entity->save();
  113. // Revert to the previous (published) revision.
  114. $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
  115. $previous_revision = $entity_storage->loadRevision(4);
  116. $previous_revision->isDefaultRevision(TRUE);
  117. $previous_revision->setNewRevision(TRUE);
  118. $previous_revision->save();
  119. // Get the default revision.
  120. $entity = $this->reloadEntity($entity);
  121. $this->assertEquals('published', $entity->moderation_state->value);
  122. if ($entity instanceof EntityPublishedInterface) {
  123. $this->assertTrue($entity->isPublished());
  124. }
  125. // Set an invalid moderation state.
  126. $this->setExpectedException(EntityStorageException::class);
  127. $entity->moderation_state->value = 'foobar';
  128. $entity->save();
  129. }
  130. /**
  131. * Test cases for basic moderation test.
  132. */
  133. public function basicModerationTestCases() {
  134. return [
  135. 'Nodes' => [
  136. 'node',
  137. ],
  138. 'Block content' => [
  139. 'block_content',
  140. ],
  141. 'Media' => [
  142. 'media',
  143. ],
  144. 'Test entity - revisions, data table, and published interface' => [
  145. 'entity_test_mulrevpub',
  146. ],
  147. 'Entity Test with revisions' => [
  148. 'entity_test_rev',
  149. ],
  150. 'Entity without bundle' => [
  151. 'entity_test_no_bundle',
  152. ],
  153. ];
  154. }
  155. /**
  156. * Tests removal of content moderation state entity.
  157. *
  158. * @dataProvider basicModerationTestCases
  159. */
  160. public function testContentModerationStateDataRemoval($entity_type_id) {
  161. /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
  162. $entity = $this->createEntity($entity_type_id);
  163. $entity->save();
  164. $entity = $this->reloadEntity($entity);
  165. $entity->delete();
  166. $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
  167. $this->assertFalse($content_moderation_state);
  168. }
  169. /**
  170. * Tests removal of content moderation state entity revisions.
  171. *
  172. * @dataProvider basicModerationTestCases
  173. */
  174. public function testContentModerationStateRevisionDataRemoval($entity_type_id) {
  175. /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
  176. $entity = $this->createEntity($entity_type_id);
  177. $entity->save();
  178. $revision = clone $entity;
  179. $revision->isDefaultRevision(FALSE);
  180. $content_moderation_state = ContentModerationState::loadFromModeratedEntity($revision);
  181. $this->assertTrue($content_moderation_state);
  182. $entity = $this->reloadEntity($entity);
  183. $entity->setNewRevision(TRUE);
  184. $entity->save();
  185. $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
  186. $entity_storage->deleteRevision($revision->getRevisionId());
  187. $content_moderation_state = ContentModerationState::loadFromModeratedEntity($revision);
  188. $this->assertFalse($content_moderation_state);
  189. $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
  190. $this->assertTrue($content_moderation_state);
  191. }
  192. /**
  193. * Tests removal of content moderation state pending entity revisions.
  194. *
  195. * @dataProvider basicModerationTestCases
  196. */
  197. public function testContentModerationStatePendingRevisionDataRemoval($entity_type_id) {
  198. $entity = $this->createEntity($entity_type_id);
  199. $entity->moderation_state = 'published';
  200. $entity->save();
  201. $entity->setNewRevision(TRUE);
  202. $entity->moderation_state = 'draft';
  203. $entity->save();
  204. $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
  205. $this->assertTrue($content_moderation_state);
  206. $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
  207. $entity_storage->deleteRevision($entity->getRevisionId());
  208. $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
  209. $this->assertFalse($content_moderation_state);
  210. }
  211. /**
  212. * Tests removal of content moderation state translations.
  213. *
  214. * @dataProvider basicModerationTestCases
  215. */
  216. public function testContentModerationStateTranslationDataRemoval($entity_type_id) {
  217. // Test content moderation state translation deletion.
  218. if ($this->entityTypeManager->getDefinition($entity_type_id)->isTranslatable()) {
  219. /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
  220. $entity = $this->createEntity($entity_type_id);
  221. $langcode = 'it';
  222. ConfigurableLanguage::createFromLangcode($langcode)
  223. ->save();
  224. $entity->save();
  225. $translation = $entity->addTranslation($langcode, ['title' => 'Titolo test']);
  226. // Make sure we add values for all of the required fields.
  227. if ($entity_type_id == 'block_content') {
  228. $translation->info = $this->randomString();
  229. }
  230. $translation->save();
  231. $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
  232. $this->assertTrue($content_moderation_state->hasTranslation($langcode));
  233. $entity->removeTranslation($langcode);
  234. $entity->save();
  235. $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
  236. $this->assertFalse($content_moderation_state->hasTranslation($langcode));
  237. }
  238. }
  239. /**
  240. * Tests basic multilingual content moderation through the API.
  241. */
  242. public function testMultilingualModeration() {
  243. // Enable French.
  244. ConfigurableLanguage::createFromLangcode('fr')->save();
  245. $node_type = NodeType::create([
  246. 'type' => 'example',
  247. ]);
  248. $node_type->save();
  249. $workflow = $this->createEditorialWorkflow();
  250. $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
  251. $workflow->save();
  252. $english_node = Node::create([
  253. 'type' => 'example',
  254. 'title' => 'Test title',
  255. ]);
  256. // Revision 1 (en).
  257. $english_node
  258. ->setUnpublished()
  259. ->save();
  260. $this->assertEquals('draft', $english_node->moderation_state->value);
  261. $this->assertFalse($english_node->isPublished());
  262. // Create a French translation.
  263. $french_node = $english_node->addTranslation('fr', ['title' => 'French title']);
  264. $french_node->setUnpublished();
  265. // Revision 1 (fr).
  266. $french_node->save();
  267. $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
  268. $this->assertEquals('draft', $french_node->moderation_state->value);
  269. $this->assertFalse($french_node->isPublished());
  270. // Move English node to create another draft.
  271. $english_node = $this->reloadEntity($english_node);
  272. $english_node->moderation_state->value = 'draft';
  273. // Revision 2 (en, fr).
  274. $english_node->save();
  275. $english_node = $this->reloadEntity($english_node);
  276. $this->assertEquals('draft', $english_node->moderation_state->value);
  277. // French node should still be in draft.
  278. $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
  279. $this->assertEquals('draft', $french_node->moderation_state->value);
  280. // Publish the French node.
  281. $french_node->moderation_state->value = 'published';
  282. // Revision 3 (en, fr).
  283. $french_node->save();
  284. $french_node = $this->reloadEntity($french_node)->getTranslation('fr');
  285. $this->assertTrue($french_node->isPublished());
  286. $this->assertEquals('published', $french_node->moderation_state->value);
  287. $this->assertTrue($french_node->isPublished());
  288. $english_node = $french_node->getTranslation('en');
  289. $this->assertEquals('draft', $english_node->moderation_state->value);
  290. // Publish the English node.
  291. $english_node->moderation_state->value = 'published';
  292. // Revision 4 (en, fr).
  293. $english_node->save();
  294. $english_node = $this->reloadEntity($english_node);
  295. $this->assertTrue($english_node->isPublished());
  296. // Move the French node back to draft.
  297. $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
  298. $this->assertTrue($french_node->isPublished());
  299. $french_node->moderation_state->value = 'draft';
  300. // Revision 5 (en, fr).
  301. $french_node->save();
  302. $french_node = $this->reloadEntity($english_node, 5)->getTranslation('fr');
  303. $this->assertFalse($french_node->isPublished());
  304. $this->assertTrue($french_node->getTranslation('en')->isPublished());
  305. // Republish the French node.
  306. $french_node->moderation_state->value = 'published';
  307. // Revision 6 (en, fr).
  308. $french_node->save();
  309. $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
  310. $this->assertTrue($french_node->isPublished());
  311. // Change the EN state without saving the node.
  312. $content_moderation_state = ContentModerationState::load(1);
  313. $content_moderation_state->set('moderation_state', 'draft');
  314. $content_moderation_state->setNewRevision(TRUE);
  315. // Revision 7 (en, fr).
  316. $content_moderation_state->save();
  317. $english_node = $this->reloadEntity($french_node, $french_node->getRevisionId() + 1);
  318. $this->assertEquals('draft', $english_node->moderation_state->value);
  319. $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
  320. $this->assertEquals('published', $french_node->moderation_state->value);
  321. // This should unpublish the French node.
  322. $content_moderation_state = ContentModerationState::load(1);
  323. $content_moderation_state = $content_moderation_state->getTranslation('fr');
  324. $content_moderation_state->set('moderation_state', 'draft');
  325. $content_moderation_state->setNewRevision(TRUE);
  326. // Revision 8 (en, fr).
  327. $content_moderation_state->save();
  328. $english_node = $this->reloadEntity($english_node, $english_node->getRevisionId());
  329. $this->assertEquals('draft', $english_node->moderation_state->value);
  330. $french_node = $this->reloadEntity($english_node, '8')->getTranslation('fr');
  331. $this->assertEquals('draft', $french_node->moderation_state->value);
  332. // Switching the moderation state to an unpublished state should update the
  333. // entity.
  334. $this->assertFalse($french_node->isPublished());
  335. // Get the default english node.
  336. $english_node = $this->reloadEntity($english_node);
  337. $this->assertTrue($english_node->isPublished());
  338. $this->assertEquals(6, $english_node->getRevisionId());
  339. }
  340. /**
  341. * Tests moderation when the moderation_state field has a config override.
  342. */
  343. public function testModerationWithFieldConfigOverride() {
  344. NodeType::create([
  345. 'type' => 'test_type',
  346. ])->save();
  347. $workflow = $this->createEditorialWorkflow();
  348. $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'test_type');
  349. $workflow->save();
  350. $fields = $this->container->get('entity_field.manager')->getFieldDefinitions('node', 'test_type');
  351. $field_config = $fields['moderation_state']->getConfig('test_type');
  352. $field_config->setLabel('Field Override!');
  353. $field_config->save();
  354. $node = Node::create([
  355. 'title' => 'Test node',
  356. 'type' => 'test_type',
  357. ]);
  358. $node->save();
  359. $this->assertFalse($node->isPublished());
  360. $this->assertEquals('draft', $node->moderation_state->value);
  361. $node->moderation_state = 'published';
  362. $node->save();
  363. $this->assertTrue($node->isPublished());
  364. $this->assertEquals('published', $node->moderation_state->value);
  365. }
  366. /**
  367. * Tests that entities with special languages can be moderated.
  368. */
  369. public function testModerationWithSpecialLanguages() {
  370. $workflow = $this->createEditorialWorkflow();
  371. $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
  372. $workflow->save();
  373. // Create a test entity.
  374. $entity = EntityTestRev::create([
  375. 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
  376. ]);
  377. $entity->save();
  378. $this->assertEquals('draft', $entity->moderation_state->value);
  379. $entity->moderation_state->value = 'published';
  380. $entity->save();
  381. $this->assertEquals('published', EntityTestRev::load($entity->id())->moderation_state->value);
  382. }
  383. /**
  384. * Tests that a non-translatable entity type with a langcode can be moderated.
  385. */
  386. public function testNonTranslatableEntityTypeModeration() {
  387. $workflow = $this->createEditorialWorkflow();
  388. $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
  389. $workflow->save();
  390. // Check that the tested entity type is not translatable.
  391. $entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_rev');
  392. $this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
  393. // Create a test entity.
  394. $entity = EntityTestRev::create();
  395. $entity->save();
  396. $this->assertEquals('draft', $entity->moderation_state->value);
  397. $entity->moderation_state->value = 'published';
  398. $entity->save();
  399. $this->assertEquals('published', EntityTestRev::load($entity->id())->moderation_state->value);
  400. }
  401. /**
  402. * Tests that a non-translatable entity type without a langcode can be
  403. * moderated.
  404. */
  405. public function testNonLangcodeEntityTypeModeration() {
  406. // Unset the langcode entity key for 'entity_test_rev'.
  407. $entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_rev');
  408. $keys = $entity_type->getKeys();
  409. unset($keys['langcode']);
  410. $entity_type->set('entity_keys', $keys);
  411. \Drupal::state()->set('entity_test_rev.entity_type', $entity_type);
  412. // Update the entity type in order to remove the 'langcode' field.
  413. \Drupal::entityDefinitionUpdateManager()->applyUpdates();
  414. $workflow = $this->createEditorialWorkflow();
  415. $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
  416. $workflow->save();
  417. // Check that the tested entity type is not translatable and does not have a
  418. // 'langcode' entity key.
  419. $entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_rev');
  420. $this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
  421. $this->assertFalse($entity_type->getKey('langcode'), "The test entity type does not have a 'langcode' entity key.");
  422. // Create a test entity.
  423. $entity = EntityTestRev::create();
  424. $entity->save();
  425. $this->assertEquals('draft', $entity->moderation_state->value);
  426. $entity->moderation_state->value = 'published';
  427. $entity->save();
  428. $this->assertEquals('published', EntityTestRev::load($entity->id())->moderation_state->value);
  429. }
  430. /**
  431. * Tests the dependencies of the workflow when using content moderation.
  432. */
  433. public function testWorkflowDependencies() {
  434. $node_type = NodeType::create([
  435. 'type' => 'example',
  436. ]);
  437. $node_type->save();
  438. $workflow = $this->createEditorialWorkflow();
  439. // Test both a config and non-config based bundle and entity type.
  440. $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
  441. $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
  442. $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_no_bundle', 'entity_test_no_bundle');
  443. $workflow->save();
  444. $this->assertEquals([
  445. 'module' => [
  446. 'content_moderation',
  447. 'entity_test',
  448. ],
  449. 'config' => [
  450. 'node.type.example',
  451. ],
  452. ], $workflow->getDependencies());
  453. $this->assertEquals([
  454. 'entity_test_no_bundle',
  455. 'entity_test_rev',
  456. 'node',
  457. ], $workflow->getTypePlugin()->getEntityTypes());
  458. // Delete the node type and ensure it is removed from the workflow.
  459. $node_type->delete();
  460. $workflow = Workflow::load('editorial');
  461. $entity_types = $workflow->getTypePlugin()->getEntityTypes();
  462. $this->assertFalse(in_array('node', $entity_types));
  463. // Uninstall entity test and ensure it's removed from the workflow.
  464. $this->container->get('config.manager')->uninstall('module', 'entity_test');
  465. $workflow = Workflow::load('editorial');
  466. $entity_types = $workflow->getTypePlugin()->getEntityTypes();
  467. $this->assertEquals([], $entity_types);
  468. }
  469. /**
  470. * Test the content moderation workflow dependencies for non-config bundles.
  471. */
  472. public function testWorkflowNonConfigBundleDependencies() {
  473. // Create a bundle not based on any particular configuration.
  474. entity_test_create_bundle('test_bundle');
  475. $workflow = $this->createEditorialWorkflow();
  476. $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test', 'test_bundle');
  477. $workflow->save();
  478. // Ensure the bundle is correctly added to the workflow.
  479. $this->assertEquals([
  480. 'module' => [
  481. 'content_moderation',
  482. 'entity_test',
  483. ],
  484. ], $workflow->getDependencies());
  485. $this->assertEquals([
  486. 'test_bundle',
  487. ], $workflow->getTypePlugin()->getBundlesForEntityType('entity_test'));
  488. // Delete the test bundle to ensure the workflow entity responds
  489. // appropriately.
  490. entity_test_delete_bundle('test_bundle');
  491. $workflow = Workflow::load('editorial');
  492. $this->assertEquals([], $workflow->getTypePlugin()->getBundlesForEntityType('entity_test'));
  493. $this->assertEquals([
  494. 'module' => [
  495. 'content_moderation',
  496. ],
  497. ], $workflow->getDependencies());
  498. }
  499. /**
  500. * Test the revision default state of the moderation state entity revisions.
  501. *
  502. * @param string $entity_type_id
  503. * The ID of entity type to be tested.
  504. *
  505. * @dataProvider basicModerationTestCases
  506. */
  507. public function testRevisionDefaultState($entity_type_id) {
  508. // Check that the revision default state of the moderated entity and the
  509. // content moderation state entity always match.
  510. /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
  511. $storage = $this->entityTypeManager->getStorage($entity_type_id);
  512. /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $cms_storage */
  513. $cms_storage = $this->entityTypeManager->getStorage('content_moderation_state');
  514. $entity = $this->createEntity($entity_type_id);
  515. $entity->get('moderation_state')->value = 'published';
  516. $storage->save($entity);
  517. /** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
  518. $cms_entity = $cms_storage->loadUnchanged(1);
  519. $this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
  520. $entity->get('moderation_state')->value = 'published';
  521. $storage->save($entity);
  522. /** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
  523. $cms_entity = $cms_storage->loadUnchanged(1);
  524. $this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
  525. $entity->get('moderation_state')->value = 'draft';
  526. $storage->save($entity);
  527. /** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
  528. $cms_entity = $cms_storage->loadUnchanged(1);
  529. $this->assertEquals($entity->getLoadedRevisionId() - 1, $cms_entity->get('content_entity_revision_id')->value);
  530. $entity->get('moderation_state')->value = 'published';
  531. $storage->save($entity);
  532. /** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
  533. $cms_entity = $cms_storage->loadUnchanged(1);
  534. $this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
  535. }
  536. /**
  537. * Creates an entity.
  538. *
  539. * The entity will have required fields populated and the corresponding bundle
  540. * will be enabled for content moderation.
  541. *
  542. * @param string $entity_type_id
  543. * The entity type ID.
  544. *
  545. * @return \Drupal\Core\Entity\ContentEntityInterface
  546. * The created entity.
  547. */
  548. protected function createEntity($entity_type_id) {
  549. $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
  550. $bundle_id = $entity_type_id;
  551. // Set up a bundle entity type for the specified entity type, if needed.
  552. if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) {
  553. $bundle_entity_type = $this->entityTypeManager->getDefinition($bundle_entity_type_id);
  554. $bundle_entity_storage = $this->entityTypeManager->getStorage($bundle_entity_type_id);
  555. $bundle_id = 'example';
  556. if (!$bundle_entity_storage->load($bundle_id)) {
  557. $bundle_entity = $bundle_entity_storage->create([
  558. $bundle_entity_type->getKey('id') => 'example',
  559. ]);
  560. if ($entity_type_id == 'media') {
  561. $bundle_entity->set('source', 'test');
  562. $bundle_entity->save();
  563. $source_field = $bundle_entity->getSource()->createSourceField($bundle_entity);
  564. $source_field->getFieldStorageDefinition()->save();
  565. $source_field->save();
  566. $bundle_entity->set('source_configuration', [
  567. 'source_field' => $source_field->getName(),
  568. ]);
  569. }
  570. $bundle_entity->save();
  571. }
  572. }
  573. $workflow = $this->createEditorialWorkflow();
  574. $workflow->getTypePlugin()->addEntityTypeAndBundle($entity_type_id, $bundle_id);
  575. $workflow->save();
  576. /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
  577. $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
  578. $entity = $entity_storage->create([
  579. $entity_type->getKey('label') => 'Test title',
  580. $entity_type->getKey('bundle') => $bundle_id,
  581. ]);
  582. // Make sure we add values for all of the required fields.
  583. if ($entity_type_id == 'block_content') {
  584. $entity->info = $this->randomString();
  585. }
  586. return $entity;
  587. }
  588. /**
  589. * Reloads the entity after clearing the static cache.
  590. *
  591. * @param \Drupal\Core\Entity\EntityInterface $entity
  592. * The entity to reload.
  593. * @param int|bool $revision_id
  594. * The specific revision ID to load. Defaults FALSE and just loads the
  595. * default revision.
  596. *
  597. * @return \Drupal\Core\Entity\EntityInterface
  598. * The reloaded entity.
  599. */
  600. protected function reloadEntity(EntityInterface $entity, $revision_id = FALSE) {
  601. $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
  602. $storage->resetCache([$entity->id()]);
  603. if ($revision_id) {
  604. return $storage->loadRevision($revision_id);
  605. }
  606. return $storage->load($entity->id());
  607. }
  608. }