diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php index eab7924..639dde0 100644 --- a/core/modules/content_moderation/src/Entity/ContentModerationState.php +++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php @@ -3,7 +3,8 @@ namespace Drupal\content_moderation\Entity; use Drupal\content_moderation\ContentModerationStateInterface; -use Drupal\content_moderation\Event\ContentModerationState as ContentModerationStateEvent; +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; @@ -151,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(); } @@ -177,14 +189,24 @@ public function save() { * In case of failures an exception is thrown. */ protected function realSave() { - $result = parent::save(); - $event = new ContentModerationStateEvent($this); - if ($result === SAVED_NEW) { - \Drupal::service('event_dispatcher')->dispatch(ContentModerationStateEvent::CREATED, $event); + if (!$this->getLoadedRevisionId()) { + $original_state = FALSE; } else { - \Drupal::service('event_dispatcher')->dispatch(ContentModerationStateEvent::UPDATED, $event); + $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 @@ +moderationState = $moderation_state; - } - - /** - * Retrieve the content moderation state entity related to this event. - * - * @return \Drupal\content_moderation\ContentModerationStateInterface - * The content moderation state entity. - */ - public function getModerationState() { - return $this->moderationState; - } - -} diff --git a/core/modules/content_moderation/src/Event/ContentModerationStateChangedEvent.php b/core/modules/content_moderation/src/Event/ContentModerationStateChangedEvent.php new file mode 100644 index 0000000..312681d --- /dev/null +++ b/core/modules/content_moderation/src/Event/ContentModerationStateChangedEvent.php @@ -0,0 +1,103 @@ +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/modules/content_moderation_test_event/content_moderation_test_event.info.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_event/content_moderation_test_event.info.yml deleted file mode 100644 index 36b8d17..0000000 --- a/core/modules/content_moderation/tests/modules/content_moderation_test_event/content_moderation_test_event.info.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: 'Content moderation test event' -type: module -description: 'Provides an event subscriber to content moderation entity updates.' -package: Testing -version: VERSION -core: 8.x -dependencies: - - content_moderation - - node diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_event/content_moderation_test_event.services.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_event/content_moderation_test_event.services.yml deleted file mode 100644 index 8b46008..0000000 --- a/core/modules/content_moderation/tests/modules/content_moderation_test_event/content_moderation_test_event.services.yml +++ /dev/null @@ -1,6 +0,0 @@ -services: - content_moderation_test_event.state_subscriber: - class: \Drupal\content_moderation_test_event\EventSubscriber\Test - arguments: ['@state'] - tags: - - { name: event_subscriber } diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_event/src/EventSubscriber/Test.php b/core/modules/content_moderation/tests/modules/content_moderation_test_event/src/EventSubscriber/Test.php deleted file mode 100644 index 2c7ac38..0000000 --- a/core/modules/content_moderation/tests/modules/content_moderation_test_event/src/EventSubscriber/Test.php +++ /dev/null @@ -1,63 +0,0 @@ -state = $state; - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() { - $events[ContentModerationState::CREATED] = ['onCreate', 50]; - $events[ContentModerationState::UPDATED] = ['onUpdate', 50]; - - return $events; - } - - /** - * React to newly created content moderation entities. - * - * @param \Drupal\content_moderation\Event\ContentModerationState $event - * The fired event. - */ - public function onCreate(ContentModerationState $event) { - $moderation_state = $event->getModerationState(); - $this->state->set('content_moderation_test_event.created', $moderation_state->id()); - } - - /** - * React to updated content moderation entities. - * - * @param \Drupal\content_moderation\Event\ContentModerationState $event - * The fired event. - */ - public function onUpdate(ContentModerationState $event) { - $moderation_state = $event->getModerationState(); - $this->state->set('content_moderation_test_event.updated', $moderation_state->id()); - } - -} 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); + } + +} diff --git a/core/modules/content_moderation/tests/src/Kernel/Event/ContentModerationStateTest.php b/core/modules/content_moderation/tests/src/Kernel/Event/ContentModerationStateTest.php deleted file mode 100644 index 333125a..0000000 --- a/core/modules/content_moderation/tests/src/Kernel/Event/ContentModerationStateTest.php +++ /dev/null @@ -1,53 +0,0 @@ -installEntitySchema('content_moderation_state'); - $this->installEntitySchema('user'); - $this->installConfig('workflows'); - } - - /** - * Tests events for adding and updating moderation state entities. - */ - public function testCreateUpdateStates() { - // Utilize the state system to track if the event was fired. - $this->assertFalse(\Drupal::state()->get('content_moderation_test_event.created', FALSE)); - $state = ContentModerationState::create(['workflow' => 'editorial']); - ContentModerationState::updateOrCreateFromEntity($state); - // @see \Drupal\content_moderation_test_event\EventSubscriber\Test::onCreate - $this->assertEquals($state->id(), \Drupal::state()->get('content_moderation_test_event.created', FALSE)); - - $this->assertFalse(\Drupal::state()->get('content_moderation_test_event.updated', FALSE)); - ContentModerationState::updateOrCreateFromEntity($state); - // @see \Drupal\content_moderation_test_event\EventSubscriber\Test::onUpdate - $this->assertEquals($state->id(), \Drupal::state()->get('content_moderation_test_event.updated', FALSE)); - } - -}