Unverified Commit 520019bb authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3481238 by phenaproxima, a.dmitriiev, b_sharpe, alexpott: Add a config...

Issue #3481238 by phenaproxima, a.dmitriiev, b_sharpe, alexpott: Add a config action to clone a config entity
parent cf8f4990
Loading
Loading
Loading
Loading
Loading
+61 −0
Changes for core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/EntityClone.php: 61 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Config\Action\Plugin\ConfigAction;

use Drupal\Core\Config\Action\Attribute\ConfigAction;
use Drupal\Core\Config\Action\ConfigActionException;
use Drupal\Core\Config\Action\ConfigActionManager;
use Drupal\Core\Config\Action\ConfigActionPluginInterface;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * @internal
 *   This API is experimental.
 */
#[ConfigAction(
  id: 'cloneAs',
  admin_label: new TranslatableMarkup('Clone entity with a new ID'),
  entity_types: ['*'],
)]
final class EntityClone implements ConfigActionPluginInterface, ContainerFactoryPluginInterface {

  public function __construct(
    private readonly ConfigManagerInterface $configManager,
    private readonly ConfigActionManager $configActionManager,
  ) {}

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
    return new static(
      $container->get(ConfigManagerInterface::class),
      $container->get('plugin.manager.config_action'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function apply(string $configName, mixed $duplicate_id): void {
    assert(is_string($duplicate_id));

    // If the original doesn't exist, there's nothing to clone.
    $original = $this->configManager->loadConfigEntityByName($configName);
    if (empty($original)) {
      throw new ConfigActionException("Cannot clone '$configName' because it does not exist.");
    }
    $clone = $original->createDuplicate();
    $clone->set($original->getEntityType()->getKey('id'), $duplicate_id);

    // Use the config action manager to invoke the `entity_create` action on
    // the clone, so that it will be validated.
    $this->configActionManager->applyAction('entity_create:createIfNotExists', $clone->getConfigDependencyName(), $clone->toArray());
  }

}
+72 −0
Changes for core/tests/Drupal/KernelTests/Core/Recipe/EntityCloneConfigActionTest.php: 72 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\KernelTests\Core\Recipe;

use Drupal\Core\Config\Action\ConfigActionException;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\user\Entity\Role;

/**
 * @covers \Drupal\Core\Config\Action\Plugin\ConfigAction\EntityClone
 * @group Recipe
 */
class EntityCloneConfigActionTest extends KernelTestBase {

  use UserCreationTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = ['system', 'user'];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installConfig('user');

    $this->createRole(['access user profiles'], 'test');
  }

  /**
   * Tests error if original entity does not exist.
   */
  public function testErrorIfOriginalDoesNotExist(): void {
    $this->expectException(ConfigActionException::class);
    $this->expectExceptionMessage("Cannot clone 'user.role.nope' because it does not exist.");
    $this->container->get('plugin.manager.config_action')
      ->applyAction('cloneAs', 'user.role.nope', 'user.role.yep');
  }

  /**
   * Tests successful clone.
   */
  public function testSuccessfulClone(): void {
    $this->container->get('plugin.manager.config_action')
      ->applyAction('cloneAs', 'user.role.test', 'cloned');

    $clone = Role::load('cloned');
    $this->assertInstanceOf(Role::class, $clone);
    $this->assertTrue($clone->hasPermission('access user profiles'));
  }

  /**
   * Tests no error is thrown when an entity with the same ID already exists.
   */
  public function testNoErrorWithExistingEntity(): void {
    $this->createRole(['administer site configuration'], 'cloned');

    $this->container->get('plugin.manager.config_action')
      ->applyAction('cloneAs', 'user.role.test', 'cloned');

    $clone = Role::load('cloned');
    $this->assertInstanceOf(Role::class, $clone);
    $this->assertTrue($clone->hasPermission('administer site configuration'));
    $this->assertFalse($clone->hasPermission('access user profiles'));
  }

}