3 namespace Drupal\Tests\content_moderation\Kernel;
5 use Drupal\content_moderation\Entity\ContentModerationState;
6 use Drupal\Core\Entity\EntityInterface;
7 use Drupal\Core\Entity\EntityPublishedInterface;
8 use Drupal\Core\Entity\EntityStorageException;
9 use Drupal\Core\Language\LanguageInterface;
10 use Drupal\entity_test\Entity\EntityTestRev;
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\Tests\content_moderation\Traits\ContentModerationTestTrait;
16 use Drupal\workflows\Entity\Workflow;
19 * Tests links between a content entity and a content_moderation_state entity.
21 * @group content_moderation
23 class ContentModerationStateTest extends KernelTestBase {
25 use ContentModerationTestTrait;
30 public static $modules = [
44 'content_translation',
50 * @var \Drupal\Core\Entity\EntityTypeManager
52 protected $entityTypeManager;
57 protected function setUp() {
60 $this->installSchema('node', 'node_access');
61 $this->installEntitySchema('node');
62 $this->installEntitySchema('user');
63 $this->installEntitySchema('entity_test_rev');
64 $this->installEntitySchema('entity_test_no_bundle');
65 $this->installEntitySchema('entity_test_mulrevpub');
66 $this->installEntitySchema('block_content');
67 $this->installEntitySchema('media');
68 $this->installEntitySchema('file');
69 $this->installEntitySchema('content_moderation_state');
70 $this->installConfig('content_moderation');
71 $this->installSchema('file', 'file_usage');
72 $this->installConfig(['field', 'system', 'image', 'file', 'media']);
74 $this->entityTypeManager = $this->container->get('entity_type.manager');
78 * Tests basic monolingual content moderation through the API.
80 * @dataProvider basicModerationTestCases
82 public function testBasicModeration($entity_type_id) {
83 $entity = $this->createEntity($entity_type_id);
84 if ($entity instanceof EntityPublishedInterface) {
85 $entity->setUnpublished();
88 $entity = $this->reloadEntity($entity);
89 $this->assertEquals('draft', $entity->moderation_state->value);
91 $entity->moderation_state->value = 'published';
94 $entity = $this->reloadEntity($entity);
95 $this->assertEquals('published', $entity->moderation_state->value);
97 // Change the state without saving the node.
98 $content_moderation_state = ContentModerationState::load(1);
99 $content_moderation_state->set('moderation_state', 'draft');
100 $content_moderation_state->setNewRevision(TRUE);
101 $content_moderation_state->save();
103 $entity = $this->reloadEntity($entity, 3);
104 $this->assertEquals('draft', $entity->moderation_state->value);
105 if ($entity instanceof EntityPublishedInterface) {
106 $this->assertFalse($entity->isPublished());
109 // Get the default revision.
110 $entity = $this->reloadEntity($entity);
111 if ($entity instanceof EntityPublishedInterface) {
112 $this->assertTrue((bool) $entity->isPublished());
114 $this->assertEquals(2, $entity->getRevisionId());
116 $entity->moderation_state->value = 'published';
119 $entity = $this->reloadEntity($entity, 4);
120 $this->assertEquals('published', $entity->moderation_state->value);
122 // Get the default revision.
123 $entity = $this->reloadEntity($entity);
124 if ($entity instanceof EntityPublishedInterface) {
125 $this->assertTrue((bool) $entity->isPublished());
127 $this->assertEquals(4, $entity->getRevisionId());
129 // Update the node to archived which will then be the default revision.
130 $entity->moderation_state->value = 'archived';
133 // Revert to the previous (published) revision.
134 $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
135 $previous_revision = $entity_storage->loadRevision(4);
136 $previous_revision->isDefaultRevision(TRUE);
137 $previous_revision->setNewRevision(TRUE);
138 $previous_revision->save();
140 // Get the default revision.
141 $entity = $this->reloadEntity($entity);
142 $this->assertEquals('published', $entity->moderation_state->value);
143 if ($entity instanceof EntityPublishedInterface) {
144 $this->assertTrue($entity->isPublished());
147 // Set an invalid moderation state.
148 $this->setExpectedException(EntityStorageException::class);
149 $entity->moderation_state->value = 'foobar';
154 * Test cases for basic moderation test.
156 public function basicModerationTestCases() {
167 'Test entity - revisions, data table, and published interface' => [
168 'entity_test_mulrevpub',
170 'Entity Test with revisions' => [
173 'Entity without bundle' => [
174 'entity_test_no_bundle',
180 * Tests removal of content moderation state entity.
182 * @dataProvider basicModerationTestCases
184 public function testContentModerationStateDataRemoval($entity_type_id) {
185 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
186 $entity = $this->createEntity($entity_type_id);
188 $entity = $this->reloadEntity($entity);
190 $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
191 $this->assertFalse($content_moderation_state);
195 * Tests removal of content moderation state entity revisions.
197 * @dataProvider basicModerationTestCases
199 public function testContentModerationStateRevisionDataRemoval($entity_type_id) {
200 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
201 $entity = $this->createEntity($entity_type_id);
203 $revision = clone $entity;
204 $revision->isDefaultRevision(FALSE);
205 $content_moderation_state = ContentModerationState::loadFromModeratedEntity($revision);
206 $this->assertTrue($content_moderation_state);
207 $entity = $this->reloadEntity($entity);
208 $entity->setNewRevision(TRUE);
210 $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
211 $entity_storage->deleteRevision($revision->getRevisionId());
212 $content_moderation_state = ContentModerationState::loadFromModeratedEntity($revision);
213 $this->assertFalse($content_moderation_state);
214 $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
215 $this->assertTrue($content_moderation_state);
219 * Tests removal of content moderation state pending entity revisions.
221 * @dataProvider basicModerationTestCases
223 public function testContentModerationStatePendingRevisionDataRemoval($entity_type_id) {
224 $entity = $this->createEntity($entity_type_id);
225 $entity->moderation_state = 'published';
227 $entity->setNewRevision(TRUE);
228 $entity->moderation_state = 'draft';
231 $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
232 $this->assertTrue($content_moderation_state);
234 $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
235 $entity_storage->deleteRevision($entity->getRevisionId());
237 $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
238 $this->assertFalse($content_moderation_state);
242 * Tests removal of content moderation state translations.
244 * @dataProvider basicModerationTestCases
246 public function testContentModerationStateTranslationDataRemoval($entity_type_id) {
247 // Test content moderation state translation deletion.
248 if ($this->entityTypeManager->getDefinition($entity_type_id)->isTranslatable()) {
249 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
250 $entity = $this->createEntity($entity_type_id);
252 ConfigurableLanguage::createFromLangcode($langcode)
255 $translation = $entity->addTranslation($langcode, ['title' => 'Titolo test']);
256 // Make sure we add values for all of the required fields.
257 if ($entity_type_id == 'block_content') {
258 $translation->info = $this->randomString();
260 $translation->save();
261 $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
262 $this->assertTrue($content_moderation_state->hasTranslation($langcode));
263 $entity->removeTranslation($langcode);
265 $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
266 $this->assertFalse($content_moderation_state->hasTranslation($langcode));
271 * Tests basic multilingual content moderation through the API.
273 public function testMultilingualModeration() {
275 ConfigurableLanguage::createFromLangcode('fr')->save();
276 $node_type = NodeType::create([
281 $workflow = $this->createEditorialWorkflow();
282 $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
285 $english_node = Node::create([
287 'title' => 'Test title',
293 $this->assertEquals('draft', $english_node->moderation_state->value);
294 $this->assertFalse($english_node->isPublished());
296 // Create a French translation.
297 $french_node = $english_node->addTranslation('fr', ['title' => 'French title']);
298 $french_node->setUnpublished();
300 $french_node->save();
301 $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
302 $this->assertEquals('draft', $french_node->moderation_state->value);
303 $this->assertFalse($french_node->isPublished());
305 // Move English node to create another draft.
306 $english_node = $this->reloadEntity($english_node);
307 $english_node->moderation_state->value = 'draft';
308 // Revision 3 (en, fr).
309 $english_node->save();
310 $english_node = $this->reloadEntity($english_node);
311 $this->assertEquals('draft', $english_node->moderation_state->value);
313 // French node should still be in draft.
314 $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
315 $this->assertEquals('draft', $french_node->moderation_state->value);
317 // Publish the French node.
318 $french_node->moderation_state->value = 'published';
319 // Revision 4 (en, fr).
320 $french_node->save();
321 $french_node = $this->reloadEntity($french_node)->getTranslation('fr');
322 $this->assertTrue($french_node->isPublished());
323 $this->assertEquals('published', $french_node->moderation_state->value);
324 $this->assertTrue($french_node->isPublished());
325 $english_node = $french_node->getTranslation('en');
326 $this->assertEquals('draft', $english_node->moderation_state->value);
328 // Publish the English node.
329 $english_node->moderation_state->value = 'published';
330 // Revision 5 (en, fr).
331 $english_node->save();
332 $english_node = $this->reloadEntity($english_node);
333 $this->assertTrue($english_node->isPublished());
335 // Move the French node back to draft.
336 $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
337 $this->assertTrue($french_node->isPublished());
338 $french_node->moderation_state->value = 'draft';
339 // Revision 6 (en, fr).
340 $french_node->save();
341 $french_node = $this->reloadEntity($english_node, 6)->getTranslation('fr');
342 $this->assertFalse($french_node->isPublished());
343 $this->assertTrue($french_node->getTranslation('en')->isPublished());
345 // Republish the French node.
346 $french_node->moderation_state->value = 'published';
347 // Revision 7 (en, fr).
348 $french_node->save();
349 $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
350 $this->assertTrue($french_node->isPublished());
352 // Change the EN state without saving the node.
353 $content_moderation_state = ContentModerationState::load(1);
354 $content_moderation_state->set('moderation_state', 'draft');
355 $content_moderation_state->setNewRevision(TRUE);
356 // Revision 8 (en, fr).
357 $content_moderation_state->save();
358 $english_node = $this->reloadEntity($french_node, $french_node->getRevisionId() + 1);
360 $this->assertEquals('draft', $english_node->moderation_state->value);
361 $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
362 $this->assertEquals('published', $french_node->moderation_state->value);
364 // This should unpublish the French node.
365 $content_moderation_state = ContentModerationState::load(1);
366 $content_moderation_state = $content_moderation_state->getTranslation('fr');
367 $content_moderation_state->set('moderation_state', 'draft');
368 $content_moderation_state->setNewRevision(TRUE);
369 // Revision 9 (en, fr).
370 $content_moderation_state->save();
372 $english_node = $this->reloadEntity($english_node, $english_node->getRevisionId());
373 $this->assertEquals('draft', $english_node->moderation_state->value);
374 $french_node = $this->reloadEntity($english_node, '9')->getTranslation('fr');
375 $this->assertEquals('draft', $french_node->moderation_state->value);
376 // Switching the moderation state to an unpublished state should update the
378 $this->assertFalse($french_node->isPublished());
380 // Get the default english node.
381 $english_node = $this->reloadEntity($english_node);
382 $this->assertTrue($english_node->isPublished());
383 $this->assertEquals(7, $english_node->getRevisionId());
387 * Tests moderation when the moderation_state field has a config override.
389 public function testModerationWithFieldConfigOverride() {
391 'type' => 'test_type',
394 $workflow = $this->createEditorialWorkflow();
395 $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'test_type');
398 $fields = $this->container->get('entity_field.manager')->getFieldDefinitions('node', 'test_type');
399 $field_config = $fields['moderation_state']->getConfig('test_type');
400 $field_config->setLabel('Field Override!');
401 $field_config->save();
403 $node = Node::create([
404 'title' => 'Test node',
405 'type' => 'test_type',
408 $this->assertFalse($node->isPublished());
409 $this->assertEquals('draft', $node->moderation_state->value);
411 $node->moderation_state = 'published';
413 $this->assertTrue($node->isPublished());
414 $this->assertEquals('published', $node->moderation_state->value);
418 * Tests that entities with special languages can be moderated.
420 * @dataProvider moderationWithSpecialLanguagesTestCases
422 public function testModerationWithSpecialLanguages($original_language, $updated_language) {
423 $workflow = $this->createEditorialWorkflow();
424 $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
427 // Create a test entity.
428 $entity = EntityTestRev::create([
429 'langcode' => $original_language,
432 $this->assertEquals('draft', $entity->moderation_state->value);
434 $entity->moderation_state->value = 'published';
435 $entity->langcode = $updated_language;
438 $this->assertEquals('published', EntityTestRev::load($entity->id())->moderation_state->value);
442 * Test cases for ::testModerationWithSpecialLanguages().
444 public function moderationWithSpecialLanguagesTestCases() {
446 'Not specified to not specified' => [
447 LanguageInterface::LANGCODE_NOT_SPECIFIED,
448 LanguageInterface::LANGCODE_NOT_SPECIFIED,
450 'English to not specified' => [
452 LanguageInterface::LANGCODE_NOT_SPECIFIED,
454 'Not specified to english' => [
455 LanguageInterface::LANGCODE_NOT_SPECIFIED,
462 * Test changing the language of content without adding a translation.
464 public function testChangingContentLangcode() {
465 ConfigurableLanguage::createFromLangcode('fr')->save();
467 'type' => 'test_type',
469 $workflow = $this->createEditorialWorkflow();
470 $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'test_type');
473 $entity = Node::create([
474 'title' => 'Test node',
476 'type' => 'test_type',
480 $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
481 $this->assertCount(1, $entity->getTranslationLanguages());
482 $this->assertCount(1, $content_moderation_state->getTranslationLanguages());
483 $this->assertEquals('en', $entity->langcode->value);
484 $this->assertEquals('en', $content_moderation_state->langcode->value);
486 $entity->langcode = 'fr';
489 $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
490 $this->assertCount(1, $entity->getTranslationLanguages());
491 $this->assertCount(1, $content_moderation_state->getTranslationLanguages());
492 $this->assertEquals('fr', $entity->langcode->value);
493 $this->assertEquals('fr', $content_moderation_state->langcode->value);
497 * Tests that a non-translatable entity type with a langcode can be moderated.
499 public function testNonTranslatableEntityTypeModeration() {
500 $workflow = $this->createEditorialWorkflow();
501 $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
504 // Check that the tested entity type is not translatable.
505 $entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_rev');
506 $this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
508 // Create a test entity.
509 $entity = EntityTestRev::create();
511 $this->assertEquals('draft', $entity->moderation_state->value);
513 $entity->moderation_state->value = 'published';
516 $this->assertEquals('published', EntityTestRev::load($entity->id())->moderation_state->value);
520 * Tests that a non-translatable entity type without a langcode can be
523 public function testNonLangcodeEntityTypeModeration() {
524 // Unset the langcode entity key for 'entity_test_rev'.
525 $entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_rev');
526 $keys = $entity_type->getKeys();
527 unset($keys['langcode']);
528 $entity_type->set('entity_keys', $keys);
529 \Drupal::state()->set('entity_test_rev.entity_type', $entity_type);
531 // Update the entity type in order to remove the 'langcode' field.
532 \Drupal::entityDefinitionUpdateManager()->applyUpdates();
534 $workflow = $this->createEditorialWorkflow();
535 $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
538 // Check that the tested entity type is not translatable and does not have a
539 // 'langcode' entity key.
540 $entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_rev');
541 $this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
542 $this->assertFalse($entity_type->getKey('langcode'), "The test entity type does not have a 'langcode' entity key.");
544 // Create a test entity.
545 $entity = EntityTestRev::create();
547 $this->assertEquals('draft', $entity->moderation_state->value);
549 $entity->moderation_state->value = 'published';
552 $this->assertEquals('published', EntityTestRev::load($entity->id())->moderation_state->value);
556 * Tests the dependencies of the workflow when using content moderation.
558 public function testWorkflowDependencies() {
559 $node_type = NodeType::create([
564 $workflow = $this->createEditorialWorkflow();
565 // Test both a config and non-config based bundle and entity type.
566 $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
567 $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
568 $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_no_bundle', 'entity_test_no_bundle');
571 $this->assertEquals([
573 'content_moderation',
579 ], $workflow->getDependencies());
581 $this->assertEquals([
582 'entity_test_no_bundle',
585 ], $workflow->getTypePlugin()->getEntityTypes());
587 // Delete the node type and ensure it is removed from the workflow.
588 $node_type->delete();
589 $workflow = Workflow::load('editorial');
590 $entity_types = $workflow->getTypePlugin()->getEntityTypes();
591 $this->assertFalse(in_array('node', $entity_types));
593 // Uninstall entity test and ensure it's removed from the workflow.
594 $this->container->get('config.manager')->uninstall('module', 'entity_test');
595 $workflow = Workflow::load('editorial');
596 $entity_types = $workflow->getTypePlugin()->getEntityTypes();
597 $this->assertEquals([], $entity_types);
601 * Test the content moderation workflow dependencies for non-config bundles.
603 public function testWorkflowNonConfigBundleDependencies() {
604 // Create a bundle not based on any particular configuration.
605 entity_test_create_bundle('test_bundle');
607 $workflow = $this->createEditorialWorkflow();
608 $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test', 'test_bundle');
611 // Ensure the bundle is correctly added to the workflow.
612 $this->assertEquals([
614 'content_moderation',
617 ], $workflow->getDependencies());
618 $this->assertEquals([
620 ], $workflow->getTypePlugin()->getBundlesForEntityType('entity_test'));
622 // Delete the test bundle to ensure the workflow entity responds
624 entity_test_delete_bundle('test_bundle');
626 $workflow = Workflow::load('editorial');
627 $this->assertEquals([], $workflow->getTypePlugin()->getBundlesForEntityType('entity_test'));
628 $this->assertEquals([
630 'content_moderation',
632 ], $workflow->getDependencies());
636 * Test the revision default state of the moderation state entity revisions.
638 * @param string $entity_type_id
639 * The ID of entity type to be tested.
641 * @dataProvider basicModerationTestCases
643 public function testRevisionDefaultState($entity_type_id) {
644 // Check that the revision default state of the moderated entity and the
645 // content moderation state entity always match.
646 /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
647 $storage = $this->entityTypeManager->getStorage($entity_type_id);
648 /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $cms_storage */
649 $cms_storage = $this->entityTypeManager->getStorage('content_moderation_state');
651 $entity = $this->createEntity($entity_type_id);
652 $entity->get('moderation_state')->value = 'published';
653 $storage->save($entity);
654 /** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
655 $cms_entity = $cms_storage->loadUnchanged(1);
656 $this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
658 $entity->get('moderation_state')->value = 'published';
659 $storage->save($entity);
660 /** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
661 $cms_entity = $cms_storage->loadUnchanged(1);
662 $this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
664 $entity->get('moderation_state')->value = 'draft';
665 $storage->save($entity);
666 /** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
667 $cms_entity = $cms_storage->loadUnchanged(1);
668 $this->assertEquals($entity->getLoadedRevisionId() - 1, $cms_entity->get('content_entity_revision_id')->value);
670 $entity->get('moderation_state')->value = 'published';
671 $storage->save($entity);
672 /** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
673 $cms_entity = $cms_storage->loadUnchanged(1);
674 $this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
680 * The entity will have required fields populated and the corresponding bundle
681 * will be enabled for content moderation.
683 * @param string $entity_type_id
684 * The entity type ID.
686 * @return \Drupal\Core\Entity\ContentEntityInterface
687 * The created entity.
689 protected function createEntity($entity_type_id) {
690 $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
692 $bundle_id = $entity_type_id;
693 // Set up a bundle entity type for the specified entity type, if needed.
694 if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) {
695 $bundle_entity_type = $this->entityTypeManager->getDefinition($bundle_entity_type_id);
696 $bundle_entity_storage = $this->entityTypeManager->getStorage($bundle_entity_type_id);
698 $bundle_id = 'example';
699 if (!$bundle_entity_storage->load($bundle_id)) {
700 $bundle_entity = $bundle_entity_storage->create([
701 $bundle_entity_type->getKey('id') => 'example',
703 if ($entity_type_id == 'media') {
704 $bundle_entity->set('source', 'test');
705 $bundle_entity->save();
706 $source_field = $bundle_entity->getSource()->createSourceField($bundle_entity);
707 $source_field->getFieldStorageDefinition()->save();
708 $source_field->save();
709 $bundle_entity->set('source_configuration', [
710 'source_field' => $source_field->getName(),
713 $bundle_entity->save();
717 $workflow = $this->createEditorialWorkflow();
718 $workflow->getTypePlugin()->addEntityTypeAndBundle($entity_type_id, $bundle_id);
721 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
722 $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
723 $entity = $entity_storage->create([
724 $entity_type->getKey('label') => 'Test title',
725 $entity_type->getKey('bundle') => $bundle_id,
727 // Make sure we add values for all of the required fields.
728 if ($entity_type_id == 'block_content') {
729 $entity->info = $this->randomString();
735 * Reloads the entity after clearing the static cache.
737 * @param \Drupal\Core\Entity\EntityInterface $entity
738 * The entity to reload.
739 * @param int|bool $revision_id
740 * The specific revision ID to load. Defaults FALSE and just loads the
743 * @return \Drupal\Core\Entity\EntityInterface
744 * The reloaded entity.
746 protected function reloadEntity(EntityInterface $entity, $revision_id = FALSE) {
747 $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
748 $storage->resetCache([$entity->id()]);
750 return $storage->loadRevision($revision_id);
752 return $storage->load($entity->id());