Commit b327c94f authored by catch's avatar catch
Browse files

Issue #3181439 by tstoeckler, Gauravvv, Tomefa, Sam152, smustgrave, larowlan:...

Issue #3181439 by tstoeckler, Gauravvv, Tomefa, Sam152, smustgrave, larowlan: Content Moderation fatals when a moderated entity is re-saved on hook_insert()

(cherry picked from commit 775b3cff)
parent f065974b
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -134,12 +134,17 @@ public static function loadFromModeratedEntity(EntityInterface $entity) {
      /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
      $storage = \Drupal::entityTypeManager()->getStorage('content_moderation_state');

      // New entities may not have a loaded revision ID at this point, but the
      // creation of a content moderation state entity may have already been
      // triggered elsewhere. In this case we have to match on the revision ID
      // (instead of the loaded revision ID).
      $revision_id = $entity->getLoadedRevisionId() ?: $entity->getRevisionId();
      $ids = $storage->getQuery()
        ->accessCheck(FALSE)
        ->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())
        ->condition('content_entity_revision_id', $revision_id)
        ->allRevisions()
        ->execute();

+7 −0
Original line number Diff line number Diff line
name: 'Content moderation test re-save'
type: module
description: 'Re-saves moderated entities for testing purposes.'
package: Testing
version: VERSION
dependencies:
  - drupal:content_moderation
+15 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Contains install functions for the Content moderation test re-save module.
 */

/**
 * Implements hook_install().
 */
function content_moderation_test_resave_install() {
  // Make sure that this module's hooks are run before Content Moderation's
  // hooks.
  module_set_weight('content_moderation_test_resave', -10);
}
+30 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Contains hook implementations for the Content moderation test re-save module.
 */

use Drupal\Core\Entity\EntityInterface;

/**
 * Implements hook_entity_insert().
 */
function content_moderation_test_resave_entity_insert(EntityInterface $entity) {
  /** @var \Drupal\content_moderation\ModerationInformationInterface $content_moderation */
  $content_moderation = \Drupal::service('content_moderation.moderation_information');
  if ($content_moderation->isModeratedEntity($entity)) {
    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    // Saving the passed entity object would populate its loaded revision ID,
    // which we want to avoid. Thus, save a clone of the original object.
    $cloned_entity = clone $entity;
    // Set the entity's syncing status, as we do not want Content Moderation to
    // create new revisions for the re-saving. Without this call Content
    // Moderation would end up creating two separate content moderation state
    // entities: one for the re-save revision and one for the initial revision.
    $cloned_entity->setSyncing(TRUE)->save();

    // Record the fact that a re-save happened.
    \Drupal::state()->set('content_moderation_test_resave', TRUE);
  }
}
+107 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\content_moderation\Kernel;

use Drupal\content_moderation\Entity\ContentModerationState;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;

/**
 * Tests Content Moderation with entities that get re-saved automatically.
 *
 * @group content_moderation
 */
class ContentModerationResaveTest extends KernelTestBase {

  use ContentModerationTestTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    // Make sure the test module is listed first as module weights do not apply
    // for kernel tests.
    /* @see \content_moderation_test_resave_install() */
    'content_moderation_test_resave',
    'content_moderation',
    'entity_test',
    'user',
    'workflows',
  ];

  /**
   * The content moderation state entity storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $contentModerationStateStorage;

  /**
   * The entity storage for the entity type used in the test.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $entityStorage;

  /**
   * The state service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $entity_type_id = 'entity_test_rev';

    $this->installEntitySchema('content_moderation_state');
    $this->installEntitySchema($entity_type_id);

    $workflow = $this->createEditorialWorkflow();
    $this->addEntityTypeAndBundleToWorkflow($workflow, $entity_type_id, $entity_type_id);

    /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
    $entity_type_manager = $this->container->get('entity_type.manager');
    $this->contentModerationStateStorage = $entity_type_manager->getStorage('content_moderation_state');
    $this->entityStorage = $entity_type_manager->getStorage($entity_type_id);
    $this->state = $this->container->get('state');
  }

  /**
   * Tests that Content Moderation works with entities being resaved.
   */
  public function testContentModerationResave() {
    $entity = $this->entityStorage->create();
    $this->assertSame('draft', $entity->get('moderation_state')->value);
    $this->assertNull(\Drupal::state()->get('content_moderation_test_resave'));
    $this->assertNull(ContentModerationState::loadFromModeratedEntity($entity));
    $content_moderation_state_query = $this->contentModerationStateStorage
      ->getQuery()
      ->accessCheck(FALSE)
      ->count();
    $this->assertSame(0, (int) $content_moderation_state_query->execute());
    $content_moderation_state_revision_query = $this->contentModerationStateStorage
      ->getQuery()
      ->accessCheck(FALSE)
      ->allRevisions()
      ->count();
    $this->assertSame(0, (int) $content_moderation_state_revision_query->execute());

    // The test module will re-save the entity in its hook_insert()
    // implementation creating the content moderation state entity before
    // Content Moderation's hook_insert() has run for the initial save
    // operation.
    $entity->save();
    $this->assertSame('draft', $entity->get('moderation_state')->value);
    $this->assertTrue(\Drupal::state()->get('content_moderation_test_resave'));
    $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
    $this->assertInstanceOf(ContentModerationState::class, $content_moderation_state);
    $this->assertSame(1, (int) $content_moderation_state_query->execute());
    $this->assertSame(1, (int) $content_moderation_state_revision_query->execute());
  }

}