diff --git a/core/modules/content_moderation/content_moderation.module b/core/modules/content_moderation/content_moderation.module index 201fc4566949ae04e35483fbf222b68a13805162..09ade0d56583e9057cf78b03af4fb7f4e9f4daea 100644 --- a/core/modules/content_moderation/content_moderation.module +++ b/core/modules/content_moderation/content_moderation.module @@ -91,6 +91,33 @@ function content_moderation_entity_update(EntityInterface $entity) { ->entityUpdate($entity); } +/** + * Implements hook_entity_delete(). + */ +function content_moderation_entity_delete(EntityInterface $entity) { + return \Drupal::service('class_resolver') + ->getInstanceFromDefinition(EntityOperations::class) + ->entityDelete($entity); +} + +/** + * Implements hook_entity_revision_delete(). + */ +function content_moderation_entity_revision_delete(EntityInterface $entity) { + return \Drupal::service('class_resolver') + ->getInstanceFromDefinition(EntityOperations::class) + ->entityRevisionDelete($entity); +} + +/** + * Implements hook_entity_translation_delete(). + */ +function content_moderation_entity_translation_delete(EntityInterface $translation) { + return \Drupal::service('class_resolver') + ->getInstanceFromDefinition(EntityOperations::class) + ->entityTranslationDelete($translation); +} + /** * Implements hook_form_alter(). */ diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php index 39b0ff4e2fc5a7a7202a6d1ae92979b239dac20d..aedcb5fadeea9b04b864c6e10d2493eaa1fdce70 100644 --- a/core/modules/content_moderation/src/Entity/ContentModerationState.php +++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php @@ -4,6 +4,7 @@ use Drupal\content_moderation\ContentModerationStateInterface; use Drupal\Core\Entity\ContentEntityBase; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\TypedData\TranslatableInterface; @@ -142,6 +143,44 @@ public static function updateOrCreateFromEntity(ContentModerationState $content_ $content_moderation_state->realSave(); } + /** + * Loads a content moderation state entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * A moderated entity object. + * + * @return \Drupal\content_moderation\ContentModerationStateInterface|null + * The related content moderation state or NULL if none could be found. + * + * @internal + * This method should only be called by code directly handling the + * ContentModerationState entity objects. + */ + public static function loadFromModeratedEntity(EntityInterface $entity) { + $content_moderation_state = NULL; + $moderation_info = \Drupal::service('content_moderation.moderation_information'); + + if ($moderation_info->isModeratedEntity($entity)) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $storage = \Drupal::entityTypeManager()->getStorage('content_moderation_state'); + + $ids = $storage->getQuery() + ->condition('content_entity_type_id', $entity->getEntityTypeId()) + ->condition('content_entity_id', $entity->id()) + ->condition('workflow', $moderation_info->getWorkflowForEntity($entity)->id()) + ->condition('content_entity_revision_id', $entity->getLoadedRevisionId()) + ->allRevisions() + ->execute(); + + if ($ids) { + /** @var \Drupal\content_moderation\ContentModerationStateInterface $content_moderation_state */ + $content_moderation_state = $storage->loadRevision(key($ids)); + } + } + + return $content_moderation_state; + } + /** * Default value callback for the 'uid' base field definition. * diff --git a/core/modules/content_moderation/src/EntityOperations.php b/core/modules/content_moderation/src/EntityOperations.php index f726582635038e13d8ed9af0efe127fd9b5ec2b6..22e901b03ea58ef65ce64050d95516db7dde8320 100644 --- a/core/modules/content_moderation/src/EntityOperations.php +++ b/core/modules/content_moderation/src/EntityOperations.php @@ -154,31 +154,16 @@ public function entityUpdate(EntityInterface $entity) { * The entity to update or create a moderation state for. */ protected function updateOrCreateFromEntity(EntityInterface $entity) { - $moderation_state = $entity->moderation_state->value; - $workflow = $this->moderationInfo->getWorkflowForEntity($entity); /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ - if (!$moderation_state) { - $moderation_state = $workflow->getTypePlugin()->getInitialState($workflow, $entity)->id(); - } - - // @todo what if $entity->moderation_state is null at this point? - $entity_type_id = $entity->getEntityTypeId(); - $entity_id = $entity->id(); $entity_revision_id = $entity->getRevisionId(); + $workflow = $this->moderationInfo->getWorkflowForEntity($entity); + $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity); - $storage = $this->entityTypeManager->getStorage('content_moderation_state'); - $entities = $storage->loadByProperties([ - 'content_entity_type_id' => $entity_type_id, - 'content_entity_id' => $entity_id, - 'workflow' => $workflow->id(), - ]); - - /** @var \Drupal\content_moderation\ContentModerationStateInterface $content_moderation_state */ - $content_moderation_state = reset($entities); if (!($content_moderation_state instanceof ContentModerationStateInterface)) { + $storage = $this->entityTypeManager->getStorage('content_moderation_state'); $content_moderation_state = $storage->create([ - 'content_entity_type_id' => $entity_type_id, - 'content_entity_id' => $entity_id, + 'content_entity_type_id' => $entity->getEntityTypeId(), + 'content_entity_id' => $entity->id(), // Make sure that the moderation state entity has the same language code // as the moderated entity. 'langcode' => $entity->language()->getId(), @@ -203,6 +188,13 @@ protected function updateOrCreateFromEntity(EntityInterface $entity) { } // Create the ContentModerationState entity for the inserted entity. + $moderation_state = $entity->moderation_state->value; + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + if (!$moderation_state) { + $moderation_state = $workflow->getTypePlugin()->getInitialState($workflow, $entity)->id(); + } + + // @todo what if $entity->moderation_state is null at this point? $content_moderation_state->set('content_entity_revision_id', $entity_revision_id); $content_moderation_state->set('moderation_state', $moderation_state); ContentModerationStateEntity::updateOrCreateFromEntity($content_moderation_state); @@ -224,6 +216,61 @@ protected function setLatestRevision(EntityInterface $entity) { ); } + /** + * Hook bridge. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity being deleted. + * + * @see hook_entity_delete() + */ + public function entityDelete(EntityInterface $entity) { + $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity); + if ($content_moderation_state) { + $content_moderation_state->delete(); + } + } + + /** + * Hook bridge. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity revision being deleted. + * + * @see hook_entity_revision_delete() + */ + public function entityRevisionDelete(EntityInterface $entity) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + if (!$entity->isDefaultRevision()) { + $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity); + if ($content_moderation_state) { + $this->entityTypeManager + ->getStorage('content_moderation_state') + ->deleteRevision($content_moderation_state->getRevisionId()); + } + } + } + + /** + * Hook bridge. + * + * @param \Drupal\Core\Entity\EntityInterface $translation + * The entity translation being deleted. + * + * @see hook_entity_translation_delete() + */ + public function entityTranslationDelete(EntityInterface $translation) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $translation */ + if (!$translation->isDefaultTranslation()) { + $langcode = $translation->language()->getId(); + $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($translation); + if ($content_moderation_state && $content_moderation_state->hasTranslation($langcode)) { + $content_moderation_state->removeTranslation($langcode); + ContentModerationStateEntity::updateOrCreateFromEntity($content_moderation_state); + } + } + } + /** * Act on entities being assembled before rendering. * diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php index 713e5b51684e6afc60261a9aae573ea4fe74bccb..5e237f39fb6e9b09ea35be448bcf32e4196c97b4 100644 --- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php +++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php @@ -3,13 +3,13 @@ namespace Drupal\Tests\content_moderation\Kernel; use Drupal\content_moderation\Entity\ContentModerationState; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityPublishedInterface; use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Language\LanguageInterface; 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; @@ -29,6 +29,7 @@ class ContentModerationStateTest extends KernelTestBase { public static $modules = [ 'entity_test', 'node', + 'block', 'block_content', 'media', 'media_test_source', @@ -74,17 +75,20 @@ protected function setUp() { } /** - * Tests basic monolingual content moderation through the API. + * Sets up a bundle entity type for the specified entity type, if needed. * - * @dataProvider basicModerationTestCases + * @param string $entity_type_id + * The entity type identifier. + * + * @return string + * The bundle identifier. */ - public function testBasicModeration($entity_type_id) { + protected function setupBundleEntityType($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) { @@ -112,6 +116,19 @@ public function testBasicModeration($entity_type_id) { $workflow->getTypePlugin()->addEntityTypeAndBundle($entity_type_id, $bundle_id); $workflow->save(); + return $bundle_id; + } + + /** + * Tests basic monolingual content moderation through the API. + * + * @dataProvider basicModerationTestCases + */ + public function testBasicModeration($entity_type_id) { + $bundle_id = $this->setupBundleEntityType($entity_type_id); + + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity_storage = $this->entityTypeManager->getStorage($entity_type_id); $entity = $entity_storage->create([ 'title' => 'Test title', $this->entityTypeManager->getDefinition($entity_type_id)->getKey('bundle') => $bundle_id, @@ -213,6 +230,69 @@ public function basicModerationTestCases() { ]; } + /** + * Tests removal of content moderation state entity field data. + * + * @dataProvider basicModerationTestCases + */ + public function testContentModerationStateDataRemoval($entity_type_id) { + $bundle_id = $this->setupBundleEntityType($entity_type_id); + + // Test content moderation state deletion. + $entity_storage = $this->entityTypeManager->getStorage($entity_type_id); + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $entity_storage->create([ + 'title' => 'Test title', + $this->entityTypeManager->getDefinition($entity_type_id)->getKey('bundle') => $bundle_id, + ]); + $entity->save(); + $entity = $this->reloadEntity($entity); + $entity->delete(); + $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity); + $this->assertFalse($content_moderation_state); + + // Test content moderation state revision deletion. + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity2 */ + $entity2 = $entity_storage->create([ + 'title' => 'Test title', + $this->entityTypeManager->getDefinition($entity_type_id)->getKey('bundle') => $bundle_id, + ]); + $entity2->save(); + $revision = clone $entity2; + $revision->isDefaultRevision(FALSE); + $content_moderation_state = ContentModerationState::loadFromModeratedEntity($revision); + $this->assertTrue($content_moderation_state); + $entity2 = $this->reloadEntity($entity2); + $entity2->setNewRevision(TRUE); + $entity2->save(); + $entity_storage->deleteRevision($revision->getRevisionId()); + $content_moderation_state = ContentModerationState::loadFromModeratedEntity($revision); + $this->assertFalse($content_moderation_state); + $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity2); + $this->assertTrue($content_moderation_state); + + // Test content moderation state translation deletion. + if ($this->entityTypeManager->getDefinition($entity_type_id)->isTranslatable()) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity3 */ + $entity3 = $entity_storage->create([ + 'title' => 'Test title', + $this->entityTypeManager->getDefinition($entity_type_id)->getKey('bundle') => $bundle_id, + ]); + $langcode = 'it'; + ConfigurableLanguage::createFromLangcode($langcode) + ->save(); + $entity3->save(); + $translation = $entity3->addTranslation($langcode, ['title' => 'Titolo test']); + $translation->save(); + $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity3); + $this->assertTrue($content_moderation_state->hasTranslation($langcode)); + $entity3->removeTranslation($langcode); + $entity3->save(); + $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity3); + $this->assertFalse($content_moderation_state->hasTranslation($langcode)); + } + } + /** * Tests basic multilingual content moderation through the API. */