diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 2c62589fa2..914db25130 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -1361,4 +1361,30 @@ public function hasTranslationChanges() { return FALSE; } + /** + * {@inheritdoc} + */ + public function getLatestRevisionId() { + if (!$this->getEntityType()->isRevisionable() || $this->isNew()) { + return NULL; + } + $revision_ids = $this->entityTypeManager() + ->getStorage($this->entityTypeId) + ->getQuery() + ->allRevisions() + ->condition($this->getEntityType()->getKey('id'), $this->id()) + ->sort($this->getEntityType()->getKey('revision'), 'DESC') + ->range(0, 1) + ->execute(); + $keys = array_keys($revision_ids); + return array_shift($keys); + } + + /** + * {@inheritdoc} + */ + public function isLatestRevision() { + return $this->getLoadedRevisionId() == $this->getLatestRevisionId(); + } + } diff --git a/core/lib/Drupal/Core/Entity/RevisionableInterface.php b/core/lib/Drupal/Core/Entity/RevisionableInterface.php index 14690e3c54..3a38af7f82 100644 --- a/core/lib/Drupal/Core/Entity/RevisionableInterface.php +++ b/core/lib/Drupal/Core/Entity/RevisionableInterface.php @@ -61,4 +61,20 @@ public function isDefaultRevision($new_value = NULL); */ public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record); + /** + * Get the latest revision ID of the entity. + * + * @return int|null + * The latest revision ID or NULL if the entity does not have a revision. + */ + public function getLatestRevisionId(); + + /** + * Check if the loaded revision is the latest. + * + * @return bool + * TRUE if the loaded revision is the latest revision, FALSE otherwise. + */ + public function isLatestRevision(); + } diff --git a/core/modules/content_moderation/content_moderation.services.yml b/core/modules/content_moderation/content_moderation.services.yml index 5035b673c5..aea2c6286f 100644 --- a/core/modules/content_moderation/content_moderation.services.yml +++ b/core/modules/content_moderation/content_moderation.services.yml @@ -3,7 +3,7 @@ services: class: Drupal\content_moderation\ParamConverter\EntityRevisionConverter arguments: ['@entity.manager', '@content_moderation.moderation_information'] tags: - - { name: paramconverter, priority: 5 } + - { name: paramconverter, priority: 6 } content_moderation.state_transition_validation: class: \Drupal\content_moderation\StateTransitionValidation arguments: ['@content_moderation.moderation_information'] diff --git a/core/modules/editor/editor.routing.yml b/core/modules/editor/editor.routing.yml index 0c5b2249bb..b44e3ffedc 100644 --- a/core/modules/editor/editor.routing.yml +++ b/core/modules/editor/editor.routing.yml @@ -13,6 +13,7 @@ editor.field_untransformed_text: parameters: entity: type: entity:{entity_type} + load_pending_revision: true requirements: _permission: 'access in-place editing' _access_quickedit_entity_field: 'TRUE' diff --git a/core/modules/image/image.routing.yml b/core/modules/image/image.routing.yml index 4a0cb88a83..ad0e785252 100644 --- a/core/modules/image/image.routing.yml +++ b/core/modules/image/image.routing.yml @@ -93,6 +93,7 @@ image.info: parameters: entity: type: entity:{entity_type} + load_pending_revision: true requirements: _permission: 'access in-place editing' _access_quickedit_entity_field: 'TRUE' diff --git a/core/modules/node/src/NodeViewBuilder.php b/core/modules/node/src/NodeViewBuilder.php index f0971fb3bb..ef7ac94fed 100644 --- a/core/modules/node/src/NodeViewBuilder.php +++ b/core/modules/node/src/NodeViewBuilder.php @@ -148,7 +148,7 @@ protected function alterBuild(array &$build, EntityInterface $entity, EntityView /** @var \Drupal\node\NodeInterface $entity */ parent::alterBuild($build, $entity, $display, $view_mode); if ($entity->id()) { - if ($entity->isDefaultRevision()) { + if ($entity->isDefaultRevision() || $entity->isLatestRevision()) { $build['#contextual_links']['node'] = [ 'route_parameters' => ['node' => $entity->id()], 'metadata' => ['changed' => $entity->getChangedTime()], diff --git a/core/modules/quickedit/quickedit.routing.yml b/core/modules/quickedit/quickedit.routing.yml index af22056cb7..5db28a8cd8 100644 --- a/core/modules/quickedit/quickedit.routing.yml +++ b/core/modules/quickedit/quickedit.routing.yml @@ -20,6 +20,7 @@ quickedit.field_form: parameters: entity: type: entity:{entity_type} + load_pending_revision: true requirements: _permission: 'access in-place editing' _access_quickedit_entity_field: 'TRUE' @@ -35,3 +36,4 @@ quickedit.entity_save: parameters: entity: type: entity:{entity_type} + load_pending_revision: true diff --git a/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditContentModerationTest.php b/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditContentModerationTest.php new file mode 100644 index 0000000000..6252376391 --- /dev/null +++ b/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditContentModerationTest.php @@ -0,0 +1,104 @@ +drupalCreateContentType(['type' => 'article', 'name' => 'Article']); + /* @var \Drupal\workflows\WorkflowInterface $workflow */ + $workflow = Workflow::load('editorial'); + $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'article'); + $workflow->save(); + + // Create a privileged user. + $user = $this->drupalCreateUser([ + 'access contextual links', + 'access toolbar', + 'access in-place editing', + 'access content', + 'create article content', + 'edit any article content', + 'view any unpublished content', + 'view latest version', + 'use editorial transition create_new_draft', + 'use editorial transition publish', + ]); + $this->setCurrentUser($user); + $this->drupalLogin($user); + } + + /** + * Tests Quick-Editing a Moderated Node's draft. + */ + public function testModeratedQuickEdit() { + $node = $this->createNode([ + 'type' => 'article', + 'title' => 'Change me', + 'moderation_state' => 'published', + ]); + $this->drupalGet('node/' . $node->id()); + + $entity_selector = '[data-quickedit-entity-id="node/' . $node->id() . '"]'; + $field_selector = '[data-quickedit-field-id="node/' . $node->id() . '/title/' . $node->language()->getId() . '/full"]'; + + // Create a forward revision. + $node->moderation_state->setValue('draft'); + $node->save(); + $this->drupalGet('node/' . $node->id() . '/latest'); + + // Wait until Quick Edit loads. + $condition = "jQuery('" . $entity_selector . " .quickedit').length > 0"; + $this->assertJsCondition($condition, 10000); + + // Initiate Quick Editing. + $this->click('.contextual-toolbar-tab button'); + $this->click($entity_selector . ' [data-contextual-id] > button'); + $this->click($entity_selector . ' [data-contextual-id] .quickedit > a'); + $this->click($field_selector); + $this->assertSession()->assertWaitOnAjaxRequest(); + + // Trigger an edit with Javascript (this is a "contenteditable" element). + $this->getSession()->executeScript("jQuery('" . $field_selector . "').text('Hello world').trigger('keyup');"); + + // To prevent 403s on save, we re-set our request (cookie) state. This is + // done when explicitly making requests with drupalGet() and submitForm(), + // but not when triggering AJAX requests within a functional test. + $this->prepareRequest(); + + // Save the change. + $this->click('.quickedit-button.action-save'); + $this->assertSession()->assertWaitOnAjaxRequest(); + + // Verify that the default revision does not include this change. + $this->drupalGet('node/' . $node->id()); + $this->assertSession()->pageTextNotContains('Hello world'); + $this->assertSession()->pageTextContains('Change me'); + } + +}