Unverified Commit 51ae0389 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3491782 by phenaproxima, penyaskito, thejimbirch, gambry, amateescu,...

Issue #3491782 by phenaproxima, penyaskito, thejimbirch, gambry, amateescu, alexpott: When applying a recipe, we need to trigger an event pre importing content
parent ac347da7
Loading
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
use Drupal\user\EntityOwnerInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

/**
 * A service for handling import of content.
@@ -45,6 +46,7 @@ public function __construct(
    private readonly FileSystemInterface $fileSystem,
    private readonly LanguageManagerInterface $languageManager,
    private readonly EntityRepositoryInterface $entityRepository,
    private readonly EventDispatcherInterface $eventDispatcher,
  ) {}

  /**
@@ -70,6 +72,9 @@ public function importContent(Finder $content, Existing $existing = Existing::Er
      return;
    }

    $event = new PreImportEvent($content, $existing);
    $skip = $this->eventDispatcher->dispatch($event)->getSkipList();

    $account = $this->accountSwitcher->switchToAdministrator();

    try {
@@ -80,6 +85,19 @@ public function importContent(Finder $content, Existing $existing = Existing::Er
        assert(is_string($entity_type_id));
        assert(is_string($path));

        // The event subscribers asked to skip importing this entity. If they
        // explained why, log that.
        if (array_key_exists($uuid, $skip)) {
          if ($skip[$uuid]) {
            $this->logger?->info('Skipped importing @entity_type @uuid because: %reason', [
              '@entity_type' => $entity_type_id,
              '@uuid' => $uuid,
              '%reason' => $skip[$uuid],
            ]);
          }
          continue;
        }

        $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
        /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
        if (!$entity_type->entityClassImplements(ContentEntityInterface::class)) {
+72 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\DefaultContent;

use Symfony\Contracts\EventDispatcher\Event;

/**
 * Event dispatched before default content is imported.
 *
 * Subscribers to this event should avoid modifying content, because it is
 * probably about to change again. This event is best used for tasks like
 * notifications, logging, or updating a value in state. It can also be used
 * to skip importing certain entities, identified by their UUID.
 */
final class PreImportEvent extends Event {

  /**
   * Entity UUIDs that should not be imported.
   *
   * @var string[]
   */
  private array $skip = [];

  /**
   * Constructs a PreImportEvent object.
   *
   * @param \Drupal\Core\DefaultContent\Finder $finder
   *   The content finder, which has information on the entities to create
   *   in the necessary dependency order.
   * @param \Drupal\Core\DefaultContent\Existing $existing
   *   What the importer will do when importing an entity that already exists.
   */
  public function __construct(
    public readonly Finder $finder,
    public readonly Existing $existing,
  ) {}

  /**
   * Adds an entity UUID to the skip list.
   *
   * @param string $uuid
   *   The UUID of an entity that should not be imported.
   * @param string|\Stringable|null $reason
   *   (optional) A reason why the entity is being skipped. Defaults to NULL.
   *
   * @throws \InvalidArgumentException
   *   If the given UUID is not one of the ones being imported.
   */
  public function skip(string $uuid, string|\Stringable|null $reason = NULL): void {
    if (array_key_exists($uuid, $this->finder->data)) {
      $this->skip[$uuid] = $reason;
    }
    else {
      throw new \InvalidArgumentException("Content entity '$uuid' cannot be skipped, because it is not one of the entities being imported.");
    }
  }

  /**
   * Returns the list of entity UUIDs that should not be imported.
   *
   * @return string|\Stringable|null[]
   *   An array whose keys are the UUIDs of the entities that should not be
   *   imported, and the values are either a short explanation of why that
   *   entity was skipped, or NULL if no explanation was given.
   */
  public function getSkipList(): array {
    return $this->skip;
  }

}
+45 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
use Drupal\block_content\BlockContentInterface;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\DefaultContent\PreImportEvent;
use Drupal\Core\DefaultContent\Existing;
use Drupal\Core\DefaultContent\Finder;
use Drupal\Core\DefaultContent\Importer;
@@ -33,6 +34,7 @@
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
use Psr\Log\LogLevel;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

/**
 * @covers \Drupal\Core\DefaultContent\Importer
@@ -277,7 +279,50 @@ private function assertContentWasImported(): void {
    $this->assertInstanceOf(Section::class, $section);
    $this->assertCount(2, $section->getComponents());
    $this->assertSame('system_powered_by_block', $section->getComponent('03b45f14-cf74-469a-8398-edf3383ce7fa')->getPluginId());
  }

  /**
   * Tests that the pre-import event allows skipping certain entities.
   */
  public function testPreImportEvent(): void {
    $invalid_uuid_detected = FALSE;

    $listener = function (PreImportEvent $event) use (&$invalid_uuid_detected): void {
      $event->skip('3434bd5a-d2cd-4f26-bf79-a7f6b951a21b', 'Decided not to!');
      try {
        $event->skip('not-a-thing');
      }
      catch (\InvalidArgumentException) {
        $invalid_uuid_detected = TRUE;
      }
    };
    \Drupal::service(EventDispatcherInterface::class)
      ->addListener(PreImportEvent::class, $listener);

    $finder = new Finder($this->contentDir);
    $this->assertSame('menu_link_content', $finder->data['3434bd5a-d2cd-4f26-bf79-a7f6b951a21b']['_meta']['entity_type']);

    /** @var \Drupal\Core\DefaultContent\Importer $importer */
    $importer = \Drupal::service(Importer::class);
    $logger = new TestLogger();
    $importer->setLogger($logger);
    $importer->importContent($finder, Existing::Error);

    // The entity we skipped should not be here, and the reason why should have
    // been logged.
    $menu_link = \Drupal::service(EntityRepositoryInterface::class)
      ->loadEntityByUuid('menu_link_content', '3434bd5a-d2cd-4f26-bf79-a7f6b951a21b');
    $this->assertNull($menu_link);
    $this->assertTrue($logger->hasInfo([
      'message' => 'Skipped importing @entity_type @uuid because: %reason',
      'context' => [
        '@entity_type' => 'menu_link_content',
        '@uuid' => '3434bd5a-d2cd-4f26-bf79-a7f6b951a21b',
        '%reason' => 'Decided not to!',
      ],
    ]));
    // We should have caught an exception for trying to skip an invalid UUID.
    $this->assertTrue($invalid_uuid_detected);
  }

}