diff --git a/core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php b/core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php index 542cc845acd6..62e504e4c0e2 100644 --- a/core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php +++ b/core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php @@ -4,6 +4,7 @@ namespace Drupal\content_moderation\Entity\Handler; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityHandlerInterface; +use Drupal\Core\Entity\EntityPublishedInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -32,6 +33,11 @@ class ModerationHandler implements ModerationHandlerInterface, EntityHandlerInte // This is probably not necessary if configuration is setup correctly. $entity->setNewRevision(TRUE); $entity->isDefaultRevision($default_revision); + + // Update publishing status if it can be updated and if it needs updating. + if (($entity instanceof EntityPublishedInterface) && $entity->isPublished() !== $published_state) { + $published_state ? $entity->setPublished() : $entity->setUnpublished(); + } } /** diff --git a/core/modules/content_moderation/src/Entity/Handler/NodeModerationHandler.php b/core/modules/content_moderation/src/Entity/Handler/NodeModerationHandler.php index 247d352a466c..76f00e00afea 100644 --- a/core/modules/content_moderation/src/Entity/Handler/NodeModerationHandler.php +++ b/core/modules/content_moderation/src/Entity/Handler/NodeModerationHandler.php @@ -3,7 +3,6 @@ namespace Drupal\content_moderation\Entity\Handler; use Drupal\content_moderation\ModerationInformationInterface; -use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Form\FormStateInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -39,18 +38,6 @@ class NodeModerationHandler extends ModerationHandler { ); } - /** - * {@inheritdoc} - */ - public function onPresave(ContentEntityInterface $entity, $default_revision, $published_state) { - if ($this->shouldModerate($entity, $published_state)) { - parent::onPresave($entity, $default_revision, $published_state); - // Only nodes have a concept of published. - /** @var \Drupal\node\NodeInterface $entity */ - $entity->setPublished($published_state); - } - } - /** * {@inheritdoc} */ @@ -74,22 +61,4 @@ class NodeModerationHandler extends ModerationHandler { } } - /** - * Check if an entity's default revision and/or state needs adjusting. - * - * @param \Drupal\Core\Entity\ContentEntityInterface $entity - * The entity to check. - * @param bool $published_state - * Whether the state being transitioned to is a published state or not. - * - * @return bool - * TRUE when either the default revision or the state needs to be updated. - */ - protected function shouldModerate(ContentEntityInterface $entity, $published_state) { - // @todo clarify the first condition. - // First condition is needed so you can add a translation. - // Second condition checks to see if the published status has changed. - return $entity->isDefaultTranslation() || $entity->isPublished() !== $published_state; - } - } diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php index d059e7265d53..69bb5804f752 100644 --- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php +++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php @@ -3,14 +3,14 @@ namespace Drupal\Tests\content_moderation\Kernel; use Drupal\content_moderation\Entity\ContentModerationState; +use Drupal\Core\Entity\EntityPublishedInterface; use Drupal\entity_test\Entity\EntityTestBundle; -use Drupal\entity_test\Entity\EntityTestRev; use Drupal\entity_test\Entity\EntityTestWithBundle; +use Drupal\Core\Entity\EntityInterface; use Drupal\KernelTests\KernelTestBase; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; -use Drupal\node\NodeInterface; use Drupal\workflows\Entity\Workflow; /** @@ -26,6 +26,7 @@ class ContentModerationStateTest extends KernelTestBase { public static $modules = [ 'entity_test', 'node', + 'block_content', 'content_moderation', 'user', 'system', @@ -35,6 +36,11 @@ class ContentModerationStateTest extends KernelTestBase { 'workflows', ]; + /** + * @var \Drupal\Core\Entity\EntityTypeManager + */ + protected $entityTypeManager; + /** * {@inheritdoc} */ @@ -46,36 +52,56 @@ class ContentModerationStateTest extends KernelTestBase { $this->installEntitySchema('user'); $this->installEntitySchema('entity_test_with_bundle'); $this->installEntitySchema('entity_test_rev'); + $this->installEntitySchema('entity_test_mulrevpub'); + $this->installEntitySchema('block_content'); $this->installEntitySchema('content_moderation_state'); $this->installConfig('content_moderation'); + + $this->entityTypeManager = $this->container->get('entity_type.manager'); } /** * Tests basic monolingual content moderation through the API. + * + * @dataProvider basicModerationTestCases */ - public function testBasicModeration() { - $node_type = NodeType::create([ - 'type' => 'example', - ]); - $node_type->save(); + public function testBasicModeration($entity_type_id) { + // Make the 'entity_test_with_bundle' entity type revisionable. + if ($entity_type_id == 'entity_test_with_bundle') { + $this->setEntityTestWithBundleKeys(['revision' => 'revision_id']); + } + + $entity_storage = $this->entityTypeManager->getStorage($entity_type_id); + $bundle_id = $entity_type_id; + $bundle_entity_type_id = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType(); + if ($bundle_entity_type_id) { + $bundle_entity_type_definition = $this->entityTypeManager->getDefinition($bundle_entity_type_id); + $entity_type_storage = $this->entityTypeManager->getStorage($bundle_entity_type_id); + + $entity_type = $entity_type_storage->create([ + $bundle_entity_type_definition->getKey('id') => 'example', + ]); + $entity_type->save(); + $bundle_id = $entity_type->id(); + } $workflow = Workflow::load('editorial'); - $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example'); + $workflow->getTypePlugin()->addEntityTypeAndBundle($entity_type_id, $bundle_id); $workflow->save(); - $node = Node::create([ - 'type' => 'example', + $entity = $entity_storage->create([ 'title' => 'Test title', + $this->entityTypeManager->getDefinition($entity_type_id)->getKey('bundle') => $bundle_id, ]); - $node->save(); - $node = $this->reloadNode($node); - $this->assertEquals('draft', $node->moderation_state->value); + $entity->save(); + $entity = $this->reloadEntity($entity); + $this->assertEquals('draft', $entity->moderation_state->value); - $node->moderation_state->value = 'published'; - $node->save(); + $entity->moderation_state->value = 'published'; + $entity->save(); - $node = $this->reloadNode($node); - $this->assertEquals('published', $node->moderation_state->value); + $entity = $this->reloadEntity($entity); + $this->assertEquals('published', $entity->moderation_state->value); // Change the state without saving the node. $content_moderation_state = ContentModerationState::load(1); @@ -83,40 +109,71 @@ class ContentModerationStateTest extends KernelTestBase { $content_moderation_state->setNewRevision(TRUE); $content_moderation_state->save(); - $node = $this->reloadNode($node, 3); - $this->assertEquals('draft', $node->moderation_state->value); - $this->assertFalse($node->isPublished()); + $entity = $this->reloadEntity($entity, 3); + $this->assertEquals('draft', $entity->moderation_state->value); + if ($entity instanceof EntityPublishedInterface) { + $this->assertFalse($entity->isPublished()); + } // Get the default revision. - $node = $this->reloadNode($node); - $this->assertTrue($node->isPublished()); - $this->assertEquals(2, $node->getRevisionId()); + $entity = $this->reloadEntity($entity); + if ($entity instanceof EntityPublishedInterface) { + $this->assertTrue((bool) $entity->isPublished()); + } + $this->assertEquals(2, $entity->getRevisionId()); - $node->moderation_state->value = 'published'; - $node->save(); + $entity->moderation_state->value = 'published'; + $entity->save(); - $node = $this->reloadNode($node, 4); - $this->assertEquals('published', $node->moderation_state->value); + $entity = $this->reloadEntity($entity, 4); + $this->assertEquals('published', $entity->moderation_state->value); // Get the default revision. - $node = $this->reloadNode($node); - $this->assertTrue($node->isPublished()); - $this->assertEquals(4, $node->getRevisionId()); + $entity = $this->reloadEntity($entity); + if ($entity instanceof EntityPublishedInterface) { + $this->assertTrue((bool) $entity->isPublished()); + } + $this->assertEquals(4, $entity->getRevisionId()); // Update the node to archived which will then be the default revision. - $node->moderation_state->value = 'archived'; - $node->save(); + $entity->moderation_state->value = 'archived'; + $entity->save(); // Revert to the previous (published) revision. - $previous_revision = \Drupal::entityTypeManager()->getStorage('node')->loadRevision(4); + $previous_revision = $entity_storage->loadRevision(4); $previous_revision->isDefaultRevision(TRUE); $previous_revision->setNewRevision(TRUE); $previous_revision->save(); // Get the default revision. - $node = $this->reloadNode($node); - $this->assertEquals('published', $node->moderation_state->value); - $this->assertTrue($node->isPublished()); + $entity = $this->reloadEntity($entity); + $this->assertEquals('published', $entity->moderation_state->value); + if ($entity instanceof EntityPublishedInterface) { + $this->assertTrue($entity->isPublished()); + } + } + + /** + * Test cases for basic moderation test. + */ + public function basicModerationTestCases() { + return [ + 'Nodes' => [ + 'node', + ], + 'Block content' => [ + 'block_content', + ], + 'Test Entity with Bundle' => [ + 'entity_test_with_bundle', + ], + 'Test entity - revisions, data table, and published interface' => [ + 'entity_test_mulrevpub', + ], + 'Entity Test with revisions' => [ + 'entity_test_rev', + ] + ]; } /** @@ -140,37 +197,37 @@ class ContentModerationStateTest extends KernelTestBase { ]); // Revision 1 (en). $english_node - ->setPublished(FALSE) + ->setUnpublished() ->save(); $this->assertEquals('draft', $english_node->moderation_state->value); $this->assertFalse($english_node->isPublished()); // Create a French translation. $french_node = $english_node->addTranslation('fr', ['title' => 'French title']); - $french_node->setPublished(FALSE); + $french_node->setUnpublished(); // Revision 1 (fr). $french_node->save(); - $french_node = $this->reloadNode($english_node)->getTranslation('fr'); + $french_node = $this->reloadEntity($english_node)->getTranslation('fr'); $this->assertEquals('draft', $french_node->moderation_state->value); $this->assertFalse($french_node->isPublished()); // Move English node to create another draft. - $english_node = $this->reloadNode($english_node); + $english_node = $this->reloadEntity($english_node); $english_node->moderation_state->value = 'draft'; // Revision 2 (en, fr). $english_node->save(); - $english_node = $this->reloadNode($english_node); + $english_node = $this->reloadEntity($english_node); $this->assertEquals('draft', $english_node->moderation_state->value); // French node should still be in draft. - $french_node = $this->reloadNode($english_node)->getTranslation('fr'); + $french_node = $this->reloadEntity($english_node)->getTranslation('fr'); $this->assertEquals('draft', $french_node->moderation_state->value); // Publish the French node. $french_node->moderation_state->value = 'published'; // Revision 3 (en, fr). $french_node->save(); - $french_node = $this->reloadNode($french_node)->getTranslation('fr'); + $french_node = $this->reloadEntity($french_node)->getTranslation('fr'); $this->assertTrue($french_node->isPublished()); $this->assertEquals('published', $french_node->moderation_state->value); $this->assertTrue($french_node->isPublished()); @@ -181,16 +238,16 @@ class ContentModerationStateTest extends KernelTestBase { $english_node->moderation_state->value = 'published'; // Revision 4 (en, fr). $english_node->save(); - $english_node = $this->reloadNode($english_node); + $english_node = $this->reloadEntity($english_node); $this->assertTrue($english_node->isPublished()); // Move the French node back to draft. - $french_node = $this->reloadNode($english_node)->getTranslation('fr'); + $french_node = $this->reloadEntity($english_node)->getTranslation('fr'); $this->assertTrue($french_node->isPublished()); $french_node->moderation_state->value = 'draft'; // Revision 5 (en, fr). $french_node->save(); - $french_node = $this->reloadNode($english_node, 5)->getTranslation('fr'); + $french_node = $this->reloadEntity($english_node, 5)->getTranslation('fr'); $this->assertFalse($french_node->isPublished()); $this->assertTrue($french_node->getTranslation('en')->isPublished()); @@ -198,7 +255,7 @@ class ContentModerationStateTest extends KernelTestBase { $french_node->moderation_state->value = 'published'; // Revision 6 (en, fr). $french_node->save(); - $french_node = $this->reloadNode($english_node)->getTranslation('fr'); + $french_node = $this->reloadEntity($english_node)->getTranslation('fr'); $this->assertTrue($french_node->isPublished()); // Change the EN state without saving the node. @@ -207,10 +264,10 @@ class ContentModerationStateTest extends KernelTestBase { $content_moderation_state->setNewRevision(TRUE); // Revision 7 (en, fr). $content_moderation_state->save(); - $english_node = $this->reloadNode($french_node, $french_node->getRevisionId() + 1); + $english_node = $this->reloadEntity($french_node, $french_node->getRevisionId() + 1); $this->assertEquals('draft', $english_node->moderation_state->value); - $french_node = $this->reloadNode($english_node)->getTranslation('fr'); + $french_node = $this->reloadEntity($english_node)->getTranslation('fr'); $this->assertEquals('published', $french_node->moderation_state->value); // This should unpublish the French node. @@ -221,16 +278,16 @@ class ContentModerationStateTest extends KernelTestBase { // Revision 8 (en, fr). $content_moderation_state->save(); - $english_node = $this->reloadNode($english_node, $english_node->getRevisionId()); + $english_node = $this->reloadEntity($english_node, $english_node->getRevisionId()); $this->assertEquals('draft', $english_node->moderation_state->value); - $french_node = $this->reloadNode($english_node, '8')->getTranslation('fr'); + $french_node = $this->reloadEntity($english_node, '8')->getTranslation('fr'); $this->assertEquals('draft', $french_node->moderation_state->value); // Switching the moderation state to an unpublished state should update the // entity. $this->assertFalse($french_node->isPublished()); // Get the default english node. - $english_node = $this->reloadNode($english_node); + $english_node = $this->reloadEntity($english_node); $this->assertTrue($english_node->isPublished()); $this->assertEquals(6, $english_node->getRevisionId()); } @@ -240,12 +297,7 @@ class ContentModerationStateTest extends KernelTestBase { */ public function testNonTranslatableEntityTypeModeration() { // Make the 'entity_test_with_bundle' entity type revisionable. - $entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle'); - $keys = $entity_type->getKeys(); - $keys['revision'] = 'revision_id'; - $entity_type->set('entity_keys', $keys); - \Drupal::state()->set('entity_test_with_bundle.entity_type', $entity_type); - \Drupal::entityDefinitionUpdateManager()->applyUpdates(); + $this->setEntityTestWithBundleKeys(['revision' => 'revision_id']); // Create a test bundle. $entity_test_bundle = EntityTestBundle::create([ @@ -281,13 +333,7 @@ class ContentModerationStateTest extends KernelTestBase { public function testNonLangcodeEntityTypeModeration() { // Make the 'entity_test_with_bundle' entity type revisionable and unset // the langcode entity key. - $entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle'); - $keys = $entity_type->getKeys(); - $keys['revision'] = 'revision_id'; - unset($keys['langcode']); - $entity_type->set('entity_keys', $keys); - \Drupal::state()->set('entity_test_with_bundle.entity_type', $entity_type); - \Drupal::entityDefinitionUpdateManager()->applyUpdates(); + $this->setEntityTestWithBundleKeys(['revision' => 'revision_id'], ['langcode']); // Create a test bundle. $entity_test_bundle = EntityTestBundle::create([ @@ -317,50 +363,43 @@ class ContentModerationStateTest extends KernelTestBase { } /** - * Tests that entity types without config bundles can be moderated. + * Set the keys on the test entity type. + * + * @param array $keys + * The entity keys to override + * @param array $remove_keys + * Keys to remove. */ - public function testNonBundleConfigEntityTypeModeration() { - $workflow = Workflow::load('editorial'); - $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev'); - $workflow->save(); - - // Check that the tested entity type does not have bundles managed by a - // config entity type. - $entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_rev'); - $this->assertNull($entity_type->getBundleEntityType(), 'The test entity type does not have config bundles.'); - - // Create a test entity. - $entity_test = EntityTestRev::create([ - 'type' => 'entity_test_rev' - ]); - $entity_test->save(); - $this->assertEquals('draft', $entity_test->moderation_state->value); - - $entity_test->moderation_state->value = 'published'; - $entity_test->save(); - - $this->assertEquals('published', EntityTestRev::load($entity_test->id())->moderation_state->value); + protected function setEntityTestWithBundleKeys($keys, $remove_keys = []) { + $entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle'); + $original_keys = $entity_type->getKeys(); + foreach ($remove_keys as $remove_key) { + unset($original_keys[$remove_key]); + } + $entity_type->set('entity_keys', $keys + $original_keys); + \Drupal::state()->set('entity_test_with_bundle.entity_type', $entity_type); + \Drupal::entityDefinitionUpdateManager()->applyUpdates(); } /** - * Reloads the node after clearing the static cache. + * Reloads the entity after clearing the static cache. * - * @param \Drupal\node\NodeInterface $node - * The node to reload. - * @param int|false $revision_id + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to reload. + * @param int|bool $revision_id * The specific revision ID to load. Defaults FALSE and just loads the * default revision. * - * @return \Drupal\node\NodeInterface - * The reloaded node. + * @return \Drupal\Core\Entity\EntityInterface + * The reloaded entity. */ - protected function reloadNode(NodeInterface $node, $revision_id = FALSE) { - $storage = \Drupal::entityTypeManager()->getStorage('node'); - $storage->resetCache([$node->id()]); + protected function reloadEntity(EntityInterface $entity, $revision_id = FALSE) { + $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId()); + $storage->resetCache([$entity->id()]); if ($revision_id) { return $storage->loadRevision($revision_id); } - return $storage->load($node->id()); + return $storage->load($entity->id()); } } diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php new file mode 100644 index 000000000000..862958e92d0a --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php @@ -0,0 +1,64 @@ +