diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php index 5b1c250..639dde0 100644 --- a/core/modules/content_moderation/src/Entity/ContentModerationState.php +++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php @@ -3,6 +3,8 @@ namespace Drupal\content_moderation\Entity; use Drupal\content_moderation\ContentModerationStateInterface; +use Drupal\content_moderation\Event\ContentModerationEvents; +use Drupal\content_moderation\Event\ContentModerationStateChangedEvent; use Drupal\Core\Entity\ContentEntityBase; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; @@ -150,15 +152,26 @@ public static function getCurrentUserId() { } /** - * {@inheritdoc} + * Load the entity whose state is being tracked by this entity. + * + * @return \Drupal\Core\Entity\ContentEntityInterface + * The entity whose state is being tracked. */ - public function save() { + protected function loadRelatedEntity() { $related_entity = \Drupal::entityTypeManager() ->getStorage($this->content_entity_type_id->value) ->loadRevision($this->content_entity_revision_id->value); if ($related_entity instanceof TranslatableInterface) { $related_entity = $related_entity->getTranslation($this->activeLangcode); } + return $related_entity; + } + + /** + * {@inheritdoc} + */ + public function save() { + $related_entity = $this->loadRelatedEntity(); $related_entity->moderation_state = $this->moderation_state; return $related_entity->save(); } @@ -176,7 +189,25 @@ public function save() { * In case of failures an exception is thrown. */ protected function realSave() { - return parent::save(); + if (!$this->getLoadedRevisionId()) { + $original_state = FALSE; + } + else { + $original_content_moderation_state = \Drupal::entityTypeManager() + ->getStorage($this->getEntityTypeId()) + ->loadRevision($this->getLoadedRevisionId()); + if (!$this->isDefaultTranslation() && $original_content_moderation_state->hasTranslation($this->activeLangcode)) { + $original_content_moderation_state = $original_content_moderation_state->getTranslation($this->activeLangcode); + } + $original_state = $original_content_moderation_state->moderation_state->value; + } + + $result = parent::save(); + + $event = new ContentModerationStateChangedEvent($this->loadRelatedEntity(), $this->moderation_state->value, $original_state, $this->workflow->target_id); + \Drupal::service('event_dispatcher')->dispatch(ContentModerationEvents::STATE_CHANGED, $event); + + return $result; } } diff --git a/core/modules/content_moderation/src/Event/ContentModerationEvents.php b/core/modules/content_moderation/src/Event/ContentModerationEvents.php new file mode 100644 index 0000000..4c0b58e --- /dev/null +++ b/core/modules/content_moderation/src/Event/ContentModerationEvents.php @@ -0,0 +1,20 @@ +moderatedEntity = $moderated_entity; + $this->newState = $new_state; + $this->originalState = $original_state; + $this->workflow = $workflow; + } + + /** + * Get the entity that is being moderated. + * + * @return \Drupal\Core\Entity\ContentEntityInterface + * The entity that is being moderated. + */ + public function getModeratedEntity() { + return $this->moderatedEntity; + } + + /** + * Get the new state of the content. + * + * @return string + * The state the content has been changed to. + */ + public function getNewState() { + return $this->newState; + } + + /** + * Get the original state of the content. + * + * @return string + * The state the content was before. + */ + public function getOriginalState() { + return $this->originalState; + } + + /** + * Get the ID of the workflow which allowed this state change. + * + * @return string + * The ID of the workflow. + */ + public function getWorkflow() { + return $this->workflow; + } + +} diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateChangedEventTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateChangedEventTest.php new file mode 100644 index 0000000..e5bfcbf --- /dev/null +++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateChangedEventTest.php @@ -0,0 +1,157 @@ +installEntitySchema('content_moderation_state'); + $this->installEntitySchema('user'); + $this->installEntitySchema('node'); + $this->installConfig('workflows'); + $this->installSchema('node', ['node_access']); + $this->installConfig('content_moderation'); + + NodeType::create([ + 'title' => 'Test node', + 'type' => 'example', + ])->save(); + + $workflow = Workflow::load('editorial'); + $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example'); + $workflow->save(); + + $this->eventDispatcher = $this->getMock(EventDispatcherInterface::class); + $this->container->set('event_dispatcher', $this->eventDispatcher); + + ConfigurableLanguage::createFromLangcode('fr')->save(); + } + + /** + * Tests events for adding and updating moderation state entities. + */ + public function testCreateUpdateStates() { + + $this->assertEventDispatchedAtIndex(0, function($event_name, ContentModerationStateChangedEvent $event) { + $this->assertEquals('content_moderation.state_changed', $event_name); + $this->assertEquals('editorial', $event->getWorkflow()); + $this->assertEquals(FALSE, $event->getOriginalState()); + $this->assertEquals('draft', $event->getNewState()); + $this->assertEquals('node', $event->getModeratedEntity()->getEntityTypeId()); + $this->assertEquals(1, $event->getModeratedEntity()->getRevisionId()); + }); + + $this->assertEventDispatchedAtIndex(1, function($event_name, ContentModerationStateChangedEvent $event) { + $this->assertEquals('content_moderation.state_changed', $event_name); + $this->assertEquals('editorial', $event->getWorkflow()); + $this->assertEquals('draft', $event->getOriginalState()); + $this->assertEquals('published', $event->getNewState()); + $this->assertEquals('node', $event->getModeratedEntity()->getEntityTypeId()); + $this->assertEquals(2, $event->getModeratedEntity()->getRevisionId()); + }); + + $this->assertEventDispatchedAtIndex(2, function($event_name, ContentModerationStateChangedEvent $event) { + $this->assertEquals('content_moderation.state_changed', $event_name); + $this->assertEquals('editorial', $event->getWorkflow()); + $this->assertEquals('published', $event->getOriginalState()); + $this->assertEquals('archived', $event->getNewState()); + $this->assertEquals('node', $event->getModeratedEntity()->getEntityTypeId()); + $this->assertEquals(3, $event->getModeratedEntity()->getRevisionId()); + }); + + $this->assertEventDispatchedAtIndex(3, function($event_name, ContentModerationStateChangedEvent $event) { + $this->assertEquals('content_moderation.state_changed', $event_name); + $this->assertEquals('editorial', $event->getWorkflow()); + $this->assertEquals('archived', $event->getOriginalState()); + $this->assertEquals('published', $event->getNewState()); + $this->assertEquals('node', $event->getModeratedEntity()->getEntityTypeId()); + $this->assertEquals(4, $event->getModeratedEntity()->getRevisionId()); + $this->assertEquals('fr', $event->getModeratedEntity()->language()->getId()); + }); + + $this->assertEventDispatchedAtIndex(4, function($event_name, ContentModerationStateChangedEvent $event) { + $this->assertEquals('content_moderation.state_changed', $event_name); + $this->assertEquals('editorial', $event->getWorkflow()); + $this->assertEquals('published', $event->getOriginalState()); + $this->assertEquals('draft', $event->getNewState()); + $this->assertEquals('node', $event->getModeratedEntity()->getEntityTypeId()); + $this->assertEquals(5, $event->getModeratedEntity()->getRevisionId()); + $this->assertEquals('fr', $event->getModeratedEntity()->language()->getId()); + }); + + $node = Node::create([ + 'type' => 'example', + 'title' => 'Foo', + 'moderation_state' => 'draft', + ]); + $node->save(); + + $node->moderation_state = 'published'; + $node->save(); + + $node->moderation_state = 'archived'; + $node->save(); + + $french_node = $node->addTranslation('fr'); + $french_node->title = 'French node'; + $french_node->moderation_state = 'published'; + $french_node->save(); + + $french_node->moderation_state = 'draft'; + $french_node->save(); + } + + /** + * Assert the event information dispatched at a particular index. + * + * @param int $index + * The index. + * @param callable $callback + * A callback passed two arguments, the event name and event. + */ + protected function assertEventDispatchedAtIndex($index, $callback) { + $this->eventDispatcher + ->expects($this->at($index)) + ->method('dispatch') + ->willReturnCallback($callback); + } + +}