Skip to content
Snippets Groups Projects
Unverified Commit b7eee72c authored by Alex Pott's avatar Alex Pott
Browse files

Revert "Issue #3464550 by phenaproxima, a.dmitriiev, b_sharpe, alexpott:...

Revert "Issue #3464550 by phenaproxima, a.dmitriiev, b_sharpe, alexpott: Create config action which can create an entity for every bundle of another entity type"

This reverts commit fcbad7f5.
parent 36a124b2
No related branches found
No related tags found
2 merge requests!11185Issue #3477324 by andypost, alexpott: Fix usage of str_getcsv() and fgetcsv() for PHP 8.4,!9944Issue #3483353: Consider making the createCopy config action optionally fail...
Pipeline #313481 passed with warnings
Pipeline: drupal

#313514

    Pipeline: drupal

    #313505

      Pipeline: drupal

      #313491

        +1
        <?php
        declare(strict_types=1);
        namespace Drupal\Core\Config\Action\Plugin\ConfigAction;
        use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
        use Drupal\Core\Config\Action\Attribute\ConfigAction;
        use Drupal\Core\Config\Action\ConfigActionManager;
        use Drupal\Core\Config\Action\ConfigActionPluginInterface;
        use Drupal\Core\Config\Action\Plugin\ConfigAction\Deriver\CreateForEachBundleDeriver;
        use Drupal\Core\Config\ConfigManagerInterface;
        use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
        use Drupal\Core\StringTranslation\TranslatableMarkup;
        use Symfony\Component\DependencyInjection\ContainerInterface;
        /**
        * Creates config entities for each bundle of a particular entity type.
        *
        * An example of using this in a recipe's config actions would be:
        * @code
        * node.type.*:
        * createForEach:
        * language.content_settings.node.%bundle:
        * target_entity_type_id: node
        * target_bundle: %bundle
        * image.style.node_%bundle_big:
        * label: 'Big images for %label content'
        * @endcode
        * This will create two entities for each existing content type: a content
        * language settings entity, and an image style. For example, for a content type
        * called `blog`, this will create `language.content_settings.node.blog` and
        * `image.style.node_blog_big`, with the given values. The `%bundle` and
        * `%label` placeholders will be replaced with the ID and label of the content
        * type, respectively.
        *
        * @internal
        * This API is experimental.
        */
        #[ConfigAction(
        id: 'create_for_each_bundle',
        admin_label: new TranslatableMarkup('Create entities for each bundle of an entity type'),
        deriver: CreateForEachBundleDeriver::class,
        )]
        final class CreateForEachBundle implements ConfigActionPluginInterface, ContainerFactoryPluginInterface {
        /**
        * The placeholder which is replaced with the ID of the current bundle.
        *
        * @var string
        */
        private const BUNDLE_PLACEHOLDER = '%bundle';
        /**
        * The placeholder which is replaced with the label of the current bundle.
        *
        * @var string
        */
        private const LABEL_PLACEHOLDER = '%label';
        public function __construct(
        private readonly ConfigManagerInterface $configManager,
        private readonly string $createAction,
        private readonly ConfigActionManager $configActionManager,
        ) {}
        /**
        * {@inheritdoc}
        */
        public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
        // If there are no bundle entity types, this plugin should not be usable.
        if (empty($plugin_definition['entity_types'])) {
        throw new InvalidPluginDefinitionException($plugin_id, "The $plugin_id config action must be restricted to entity types that are bundles of another entity type.");
        }
        return new static(
        $container->get(ConfigManagerInterface::class),
        $plugin_definition['create_action'],
        $container->get('plugin.manager.config_action'),
        );
        }
        /**
        * {@inheritdoc}
        */
        public function apply(string $configName, mixed $value): void {
        assert(is_array($value));
        $bundle = $this->configManager->loadConfigEntityByName($configName);
        assert(is_object($bundle));
        $value = static::replacePlaceholders($value, [
        static::BUNDLE_PLACEHOLDER => $bundle->id(),
        static::LABEL_PLACEHOLDER => $bundle->label(),
        ]);
        foreach ($value as $name => $values) {
        // Invoke the actual create action via the config action manager, so that
        // the created entity will be validated.
        $this->configActionManager->applyAction('entity_create:' . $this->createAction, $name, $values);
        }
        }
        /**
        * Replaces placeholders recursively.
        *
        * @param mixed $data
        * The data to process. If this is an array, it'll be processed recursively.
        * @param array $replacements
        * An array whose keys are the placeholders to replace in the data, and
        * whose values are the the replacements. Normally this will only mention
        * the `%bundle` and `%label` placeholders. If $data is an array, the only
        * placeholder that is replaced in the array's keys is `%bundle`.
        *
        * @return mixed
        * The given $data, with the `%bundle` and `%label` placeholders replaced.
        */
        private static function replacePlaceholders(mixed $data, array $replacements): mixed {
        assert(array_key_exists(static::BUNDLE_PLACEHOLDER, $replacements));
        if (is_string($data)) {
        $data = str_replace(array_keys($replacements), $replacements, $data);
        }
        elseif (is_array($data)) {
        foreach ($data as $old_key => $value) {
        $value = static::replacePlaceholders($value, $replacements);
        // Only replace the `%bundle` placeholder in array keys.
        $new_key = str_replace(static::BUNDLE_PLACEHOLDER, $replacements[static::BUNDLE_PLACEHOLDER], $old_key);
        if ($old_key !== $new_key) {
        unset($data[$old_key]);
        }
        $data[$new_key] = $value;
        }
        }
        return $data;
        }
        }
        <?php
        declare(strict_types=1);
        namespace Drupal\Core\Config\Action\Plugin\ConfigAction\Deriver;
        use Drupal\Component\Plugin\Derivative\DeriverBase;
        use Drupal\Core\Entity\EntityTypeInterface;
        use Drupal\Core\Entity\EntityTypeManagerInterface;
        use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
        use Symfony\Component\DependencyInjection\ContainerInterface;
        /**
        * Generates derivatives for the create_for_each_bundle config action.
        *
        * @internal
        * This API is experimental.
        */
        final class CreateForEachBundleDeriver extends DeriverBase implements ContainerDeriverInterface {
        public function __construct(
        private readonly EntityTypeManagerInterface $entityTypeManager,
        ) {}
        /**
        * {@inheritdoc}
        */
        public static function create(ContainerInterface $container, $base_plugin_id): static {
        return new static(
        $container->get(EntityTypeManagerInterface::class),
        );
        }
        /**
        * {@inheritdoc}
        */
        public function getDerivativeDefinitions($base_plugin_definition): array {
        // The action should only be available for entity types that are bundles of
        // another entity type, such as node types, media types, taxonomy
        // vocabularies, and so forth.
        $bundle_entity_types = array_filter(
        $this->entityTypeManager->getDefinitions(),
        fn (EntityTypeInterface $entity_type) => is_string($entity_type->getBundleOf()),
        );
        $base_plugin_definition['entity_types'] = array_keys($bundle_entity_types);
        $this->derivatives['createForEachIfNotExists'] = $base_plugin_definition + [
        'create_action' => 'createIfNotExists',
        ];
        $this->derivatives['createForEach'] = $base_plugin_definition + [
        'create_action' => 'create',
        ];
        return $this->derivatives;
        }
        }
        ...@@ -70,9 +70,7 @@ public function apply(string $configName, mixed $value): void { ...@@ -70,9 +70,7 @@ public function apply(string $configName, mixed $value): void {
        $id = substr($configName, strlen($entity_type->getConfigPrefix()) + 1); $id = substr($configName, strlen($entity_type->getConfigPrefix()) + 1);
        $entity_type_manager $entity_type_manager
        ->getStorage($entity_type->id()) ->getStorage($entity_type->id())
        ->create($value + [ ->create($value + ['id' => $id])
        $entity_type->getKey('id') => $id,
        ])
        ->save(); ->save();
        } }
        ......
        ...@@ -4,27 +4,19 @@ ...@@ -4,27 +4,19 @@
        namespace Drupal\KernelTests\Core\Recipe; namespace Drupal\KernelTests\Core\Recipe;
        use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
        use Drupal\Component\Plugin\Exception\PluginNotFoundException;
        use Drupal\Core\Config\Action\ConfigActionException; use Drupal\Core\Config\Action\ConfigActionException;
        use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface;
        use Drupal\Core\Recipe\InvalidConfigException;
        use Drupal\Core\Recipe\RecipeRunner; use Drupal\Core\Recipe\RecipeRunner;
        use Drupal\entity_test\Entity\EntityTestBundle; use Drupal\entity_test\Entity\EntityTestBundle;
        use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldConfig;
        use Drupal\field\Entity\FieldStorageConfig; use Drupal\field\Entity\FieldStorageConfig;
        use Drupal\FunctionalTests\Core\Recipe\RecipeTestTrait; use Drupal\FunctionalTests\Core\Recipe\RecipeTestTrait;
        use Drupal\image\Entity\ImageStyle;
        use Drupal\KernelTests\KernelTestBase; use Drupal\KernelTests\KernelTestBase;
        use Drupal\language\Entity\ContentLanguageSettings;
        use Drupal\Tests\node\Traits\ContentTypeCreationTrait; use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
        use Symfony\Component\Validator\Constraints\NotNull;
        use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
        /** /**
        * Tests config actions targeting multiple entities using wildcards. * Tests config actions targeting multiple entities using wildcards.
        * *
        * @covers \Drupal\Core\Config\Action\Plugin\ConfigAction\CreateForEachBundle
        * @group Recipe * @group Recipe
        */ */
        class WildcardConfigActionsTest extends KernelTestBase { class WildcardConfigActionsTest extends KernelTestBase {
        ...@@ -51,8 +43,8 @@ protected function setUp(): void { ...@@ -51,8 +43,8 @@ protected function setUp(): void {
        parent::setUp(); parent::setUp();
        $this->installConfig('node'); $this->installConfig('node');
        $this->createContentType(['type' => 'one', 'name' => 'Type A']); $this->createContentType(['type' => 'one']);
        $this->createContentType(['type' => 'two', 'name' => 'Type B']); $this->createContentType(['type' => 'two']);
        EntityTestBundle::create(['id' => 'one'])->save(); EntityTestBundle::create(['id' => 'one'])->save();
        EntityTestBundle::create(['id' => 'two'])->save(); EntityTestBundle::create(['id' => 'two'])->save();
        ...@@ -140,138 +132,4 @@ public function testInvalidExpression(string $expression, string $expected_excep ...@@ -140,138 +132,4 @@ public function testInvalidExpression(string $expression, string $expected_excep
        RecipeRunner::processRecipe($recipe); RecipeRunner::processRecipe($recipe);
        } }
        /**
        * Tests that the createForEach action works as expected in normal conditions.
        */
        public function testCreateForEach(): void {
        $this->enableModules(['image', 'language']);
        /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
        $manager = $this->container->get('plugin.manager.config_action');
        $manager->applyAction('createForEach', 'node.type.*', [
        'language.content_settings.node.%bundle' => [
        'target_entity_type_id' => 'node',
        'target_bundle' => '%bundle',
        ],
        ]);
        $this->assertIsObject(ContentLanguageSettings::load('node.one'));
        $this->assertIsObject(ContentLanguageSettings::load('node.two'));
        }
        /**
        * Tests that the createForEach action validates the config it creates.
        */
        public function testCreateForEachValidatesCreatedEntities(): void {
        $this->enableModules(['image']);
        // To prove that the validation runs, we need to disable strict schema
        // checking in this test. We need to explicitly unsubscribe it from events
        // because by this point in the test it has been fully wired up into the
        // container and can't be changed.
        $schema_checker = $this->container->get('testing.config_schema_checker');
        $this->container->get(EventDispatcherInterface::class)
        ->removeSubscriber($schema_checker);
        try {
        $this->container->get('plugin.manager.config_action')
        ->applyAction('createForEach', 'node.type.*', [
        'image.style.node__%bundle' => [],
        ]);
        $this->fail('Expected an exception to be thrown but it was not.');
        }
        catch (InvalidConfigException $e) {
        $this->assertSame('image.style.node__one', $e->data->getName());
        $this->assertCount(1, $e->violations);
        $this->assertSame('label', $e->violations[0]->getPropertyPath());
        $this->assertSame(NotNull::IS_NULL_ERROR, $e->violations[0]->getCode());
        }
        }
        /**
        * Tests using the `%label` placeholder with the createForEach action.
        */
        public function testCreateForEachWithLabel(): void {
        $this->enableModules(['image']);
        // We should be able to use the `%label` placeholder.
        $this->container->get('plugin.manager.config_action')
        ->applyAction('createForEach', 'node.type.*', [
        'image.style.node_%bundle_big' => [
        'label' => 'Big image for %label content',
        ],
        ]);
        $this->assertSame('Big image for Type A content', ImageStyle::load('node_one_big')?->label());
        $this->assertSame('Big image for Type B content', ImageStyle::load('node_two_big')?->label());
        }
        /**
        * Tests that the createForEachIfNotExists action ignores existing config.
        */
        public function testCreateForEachIfNotExists(): void {
        $this->enableModules(['language']);
        ContentLanguageSettings::create([
        'target_entity_type_id' => 'node',
        'target_bundle' => 'one',
        ])->save();
        $this->container->get('plugin.manager.config_action')
        ->applyAction('createForEachIfNotExists', 'node.type.*', [
        'language.content_settings.node.%bundle' => [
        'target_entity_type_id' => 'node',
        'target_bundle' => '%bundle',
        ],
        ]);
        $this->assertIsObject(ContentLanguageSettings::loadByEntityTypeBundle('node', 'two'));
        }
        /**
        * Tests that the createForEach action errs on conflict with existing config.
        */
        public function testCreateForEachErrorsIfAlreadyExists(): void {
        $this->enableModules(['language']);
        ContentLanguageSettings::create([
        'target_entity_type_id' => 'node',
        'target_bundle' => 'one',
        ])->save();
        $this->expectExceptionMessage(ConfigActionException::class);
        $this->expectExceptionMessage('Entity language.content_settings.node.one exists');
        $this->container->get('plugin.manager.config_action')
        ->applyAction('createForEach', 'node.type.*', [
        'language.content_settings.node.%bundle' => [
        'target_entity_type_id' => 'node',
        'target_bundle' => '%bundle',
        ],
        ]);
        }
        /**
        * Tests that the createForEach action only works on bundle entities.
        */
        public function testCreateForEachNotAvailableOnNonBundleEntities(): void {
        $this->enableModules(['language']);
        // We should not be able to use this action on entities that aren't
        // themselves bundles of another entity type.
        $this->expectException(PluginNotFoundException::class);
        $this->expectExceptionMessage('The "language_content_settings" entity does not support the "createForEach" config action.');
        $this->container->get('plugin.manager.config_action')
        ->applyAction('createForEach', 'language.content_settings.node.*', []);
        }
        /**
        * Tests that the createForEach action requires bundle entity types to exist.
        */
        public function testCreateForEachErrorsIfNoBundleEntityTypesExist(): void {
        $this->disableModules(['node', 'entity_test']);
        $manager = $this->container->get('plugin.manager.config_action');
        $manager->clearCachedDefinitions();
        $this->expectException(InvalidPluginDefinitionException::class);
        $this->expectExceptionMessage('The create_for_each_bundle:createForEach config action must be restricted to entity types that are bundles of another entity type.');
        $manager->applyAction('create_for_each_bundle:createForEach', 'node.type.*', []);
        }
        } }
        0% Loading or .
        You are about to add 0 people to the discussion. Proceed with caution.
        Finish editing this message first!
        Please register or to comment