diff --git a/core/modules/ckeditor5/src/Annotation/DrupalAspectsOfCKEditor5Plugin.php b/core/modules/ckeditor5/src/Annotation/DrupalAspectsOfCKEditor5Plugin.php index a8feeb33a22058729055450df8f780f79df0a7ed..fff4a59aa89fec303464b536fddae3e6acdf86a2 100644 --- a/core/modules/ckeditor5/src/Annotation/DrupalAspectsOfCKEditor5Plugin.php +++ b/core/modules/ckeditor5/src/Annotation/DrupalAspectsOfCKEditor5Plugin.php @@ -42,6 +42,15 @@ class DrupalAspectsOfCKEditor5Plugin extends Plugin { */ public $class = CKEditor5PluginDefault::class; + /** + * The CKEditor 5 plugin deriver class. + * + * This property is optional and it does not need to be declared. + * + * @var string|null + */ + public $deriver = NULL; + /** * The library this plugin requires. * diff --git a/core/modules/ckeditor5/src/Plugin/CKEditor5PluginDefinition.php b/core/modules/ckeditor5/src/Plugin/CKEditor5PluginDefinition.php index 6fcae7bdd8645d90fdeaee142b8a9c5e7eed02ff..371a53aeaaf8de85766c1671e7dc7f4318326d90 100644 --- a/core/modules/ckeditor5/src/Plugin/CKEditor5PluginDefinition.php +++ b/core/modules/ckeditor5/src/Plugin/CKEditor5PluginDefinition.php @@ -6,6 +6,7 @@ use Drupal\ckeditor5\HTMLRestrictions; use Drupal\Component\Assertion\Inspector; +use Drupal\Component\Plugin\Definition\DerivablePluginDefinitionInterface; use Drupal\Component\Plugin\Definition\PluginDefinition; use Drupal\Component\Plugin\Definition\PluginDefinitionInterface; use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; @@ -16,7 +17,7 @@ /** * Provides an implementation of a CKEditor 5 plugin definition. */ -final class CKEditor5PluginDefinition extends PluginDefinition implements PluginDefinitionInterface { +final class CKEditor5PluginDefinition extends PluginDefinition implements PluginDefinitionInterface, DerivablePluginDefinitionInterface { use SchemaCheckTrait; @@ -40,22 +41,17 @@ final class CKEditor5PluginDefinition extends PluginDefinition implements Plugin * @param array $definition * An array of values from the annotation/YAML. * - * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \InvalidArgumentException */ public function __construct(array $definition) { - $this->id = $id = $definition['id']; - - $expected_prefix = sprintf("%s_", $definition['provider']); - if (strpos($id, $expected_prefix) !== 0) { - throw new InvalidPluginDefinitionException($id, sprintf('The "%s" CKEditor 5 plugin definition must have a plugin ID that starts with "%s".', $id, $expected_prefix)); + foreach ($definition as $property => $value) { + if (property_exists($this, $property)) { + $this->{$property} = $value; + } + else { + throw new \InvalidArgumentException(sprintf('Property %s with value %s does not exist on %s.', $property, $value, __CLASS__)); + } } - $this->provider = $definition['provider']; - - static::validateCKEditor5Aspects($id, $definition); - $this->ckeditor5 = $definition['ckeditor5']; - - $this->validateDrupalAspects($id, $definition); - $this->drupal = $definition['drupal']; } /** @@ -81,8 +77,11 @@ public function toArray(): array { * The plugin definition to validate. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * + * @internal + * @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::processDefinition() */ - private static function validateCKEditor5Aspects(string $id, array $definition): void { + public static function validateCKEditor5Aspects(string $id, array $definition): void { if (!isset($definition['ckeditor5'])) { throw new InvalidPluginDefinitionException($id, sprintf('The "%s" CKEditor 5 plugin definition must contain a "ckeditor5" key.', $id)); } @@ -122,8 +121,11 @@ private static function validateCKEditor5Aspects(string $id, array $definition): * The plugin definition to validate. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * + * @internal + * @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::processDefinition() */ - private function validateDrupalAspects(string $id, array $definition): void { + public function validateDrupalAspects(string $id, array $definition): void { if (!isset($definition['drupal'])) { throw new InvalidPluginDefinitionException($id, sprintf('The "%s" CKEditor 5 plugin definition must contain a "drupal" key.', $id)); } @@ -304,6 +306,31 @@ public function setClass($class) { return $this; } + /** + * {@inheritdoc} + * + * @see \Drupal\ckeditor5\Annotation\DrupalAspectsOfCKEditor5Plugin::$deriver + */ + public function getDeriver() { + // TRICKY: this is the only key that is allowed to not be set, because it is + // possible that this plugin definition is a partial/incomplete one, and the + // default from the annotation is only applied automatically for class + // annotation CKEditor 5 plugin definitions (because they create an instance + // of the DrupalAspectsOfCKEditor5Plugin annotation level), not for CKEditor + // 5 plugin definitions in YAML. + // @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::processDefinition() + // @see \Drupal\ckeditor5\Annotation\CKEditor5Plugin::__construct() + return $this->drupal['deriver'] ?? NULL; + } + + /** + * {@inheritdoc} + */ + public function setDeriver($deriver) { + $this->drupal['deriver'] = $deriver; + return $this; + } + /** * Whether this plugin is configurable by the user. * diff --git a/core/modules/ckeditor5/src/Plugin/CKEditor5PluginManager.php b/core/modules/ckeditor5/src/Plugin/CKEditor5PluginManager.php index 69e8eb51be0a232aea026a0bcacaf42ed3517be3..3558ab407bc0afc7f8c3cf063fe0a0bde340d838 100644 --- a/core/modules/ckeditor5/src/Plugin/CKEditor5PluginManager.php +++ b/core/modules/ckeditor5/src/Plugin/CKEditor5PluginManager.php @@ -8,11 +8,13 @@ use Drupal\ckeditor5\HTMLRestrictions; use Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator; use Drupal\Component\Assertion\Inspector; +use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery; +use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator; use Drupal\Core\Plugin\Discovery\YamlDiscoveryDecorator; use Drupal\editor\EditorInterface; use Drupal\filter\FilterPluginCollection; @@ -61,11 +63,55 @@ protected function getDiscovery() { // supports top-level properties. // @see \Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition::label() $discovery = new AnnotationBridgeDecorator($discovery, $this->pluginDefinitionAnnotationName); + $discovery = new ContainerDerivativeDiscoveryDecorator($discovery); $this->discovery = $discovery; } return $this->discovery; } + /** + * {@inheritdoc} + */ + public function processDefinition(&$definition, $plugin_id) { + if (!$definition instanceof CKEditor5PluginDefinition) { + throw new InvalidPluginDefinitionException($plugin_id, sprintf('The "%s" CKEditor 5 plugin definition must extend %s', $plugin_id, CKEditor5PluginDefinition::class)); + } + + // A derived plugin will still have the ID of the derivative, rather than + // that of the derived plugin ID (`<base plugin ID>:<derivative ID>`). + // Generate an updated CKEditor5PluginDefinition. + // @see \Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator::encodePluginId() + // @todo Remove this in https://www.drupal.org/project/drupal/issues/2458769. + $is_derived = $definition->id() !== $plugin_id; + if ($is_derived) { + $definition = new CKEditor5PluginDefinition(['id' => $plugin_id] + $definition->toArray()); + } + + $expected_prefix = sprintf("%s_", $definition->getProvider()); + $id = $definition->id(); + if (strpos($id, $expected_prefix) !== 0) { + throw new InvalidPluginDefinitionException($id, sprintf('The "%s" CKEditor 5 plugin definition must have a plugin ID that starts with "%s".', $id, $expected_prefix)); + } + + try { + $definition->validateCKEditor5Aspects($id, $definition->toArray()); + $definition->validateDrupalAspects($id, $definition->toArray()); + } + catch (InvalidPluginDefinitionException $e) { + // If this exception is thrown for a derived CKEditor 5 plugin definition, + // it means the deriver did not generate a valid plugin definition. + // Re-throw the exception, but tweak the language for DX: clarify it is + // for a derived plugin definition. + if ($is_derived) { + throw new InvalidPluginDefinitionException($e->getPluginId(), str_replace('plugin definition', 'derived plugin definition', $e->getMessage())); + } + // Otherwise, the exception was appropriate: re-throw it. + throw $e; + } + + parent::processDefinition($definition, $plugin_id); + } + /** * {@inheritdoc} */ diff --git a/core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php b/core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php index aa5d2661668a1ca03d75be766a3cd37e86c5f18f..64e18af00e5f1091612a34b833a1f2a7e328726c 100644 --- a/core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php +++ b/core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php @@ -3,8 +3,11 @@ namespace Drupal\Tests\ckeditor5\Kernel; use Composer\Autoload\ClassLoader; +use Drupal\ckeditor5\Annotation\CKEditor5AspectsOfCKEditor5Plugin; +use Drupal\ckeditor5\Annotation\DrupalAspectsOfCKEditor5Plugin; use Drupal\ckeditor5\HTMLRestrictions; use Drupal\ckeditor5\Plugin\CKEditor5Plugin\Heading; +use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition; use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\editor\Entity\Editor; @@ -12,6 +15,7 @@ use Drupal\filter\Entity\FilterFormat; use Drupal\Tests\SchemaCheckTestTrait; use org\bovigo\vfs\vfsStream; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; use Symfony\Component\Yaml\Yaml; @@ -78,38 +82,44 @@ protected function setUp(): void { } /** - * @covers \Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition::__construct() - * @dataProvider providerTestInvalidPluginDefinitions + * Mocks a module providing a CKEditor 5 plugin in VFS. + * + * @param string $module_name + * The name of the module. + * @param string $yaml + * The YAML to be stored in the *.ckeditor5.yml file. + * @param array $additional_files + * The additional files to create. + * + * @return \Symfony\Component\DependencyInjection\ContainerInterface + * The container that has the VFS-mocked CKEditor 5 plugin-providing module + * installed in it; this container must be used to simulate this module + * being installed. */ - public function testInvalidPluginDefinitions(string $yaml, ?string $expected_message, array $additional_files = []): void { - if ($expected_message) { - $this->expectException(InvalidPluginDefinitionException::class); - $this->expectExceptionMessage($expected_message); - } - + private function mockModuleInVfs(string $module_name, string $yaml, array $additional_files = []): ContainerInterface { $site_directory = ltrim(parse_url($this->siteDirectory)['path'], '/'); vfsStream::create([ 'modules' => [ - 'ckeditor5_invalid_plugin' => [ - 'ckeditor5_invalid_plugin.info.yml' => <<<YAML -name: CKEditor 5 Invalid Plugin Definition Test + $module_name => [ + "$module_name.info.yml" => <<<YAML +name: CKEditor 5 Test $module_name type: module core_version_requirement: ^9 YAML, - 'ckeditor5_invalid_plugin.ckeditor5.yml' => $yaml, + "$module_name.ckeditor5.yml" => $yaml, ] + $additional_files, ], ], $this->vfsRoot->getChild($site_directory)); if (!empty($additional_files)) { $additional_class_loader = new ClassLoader(); - $additional_class_loader->addPsr4("Drupal\\ckeditor5_invalid_plugin\\Plugin\\CKEditor5Plugin\\", vfsStream::url("root/$site_directory/modules/ckeditor5_invalid_plugin/src/Plugin/CKEditor5Plugin")); + $additional_class_loader->addPsr4("Drupal\\$module_name\\Plugin\\CKEditor5Plugin\\", vfsStream::url("root/$site_directory/modules/$module_name/src/Plugin/CKEditor5Plugin")); $additional_class_loader->register(TRUE); } $config_sync = \Drupal::service('config.storage'); $config_data = $this->config('core.extension')->get(); - $config_data['module']['ckeditor5_invalid_plugin'] = 1; + $config_data['module'][$module_name] = 1; $config_sync->write('core.extension', $config_data); // Construct a new container for testing a plugin definition in isolation, @@ -123,12 +133,15 @@ public function testInvalidPluginDefinitions(string $yaml, ?string $expected_mes $container = new ContainerBuilder(new FrozenParameterBag([ 'app.root' => $root, 'container.modules' => [ - 'ckeditor5_invalid_plugin' => [ + $module_name => [ 'type' => 'module', - 'pathname' => 'modules/ckeditor5_invalid_plugin/ckeditor5_invalid_plugin.info.yml', + 'pathname' => "modules/$module_name/$module_name.info.yml", 'filename' => NULL, ] + $this->container->getParameter('container.modules'), ], + 'container.namespaces' => [ + "Drupal\\$module_name" => vfsStream::url("root/$site_directory/modules/$module_name/src"), + ] + $this->container->getParameter('container.namespaces'), ] + $this->container->getParameterBag()->all())); $container->setDefinitions($this->container->getDefinitions()); @@ -146,14 +159,28 @@ public function testInvalidPluginDefinitions(string $yaml, ?string $expected_mes // only work-around possible is to manipulate the config schema definition // cache. // @todo Remove this in https://www.drupal.org/project/drupal/issues/2961541. - if (isset($additional_files['config']['schema']['ckeditor5_invalid_plugin.schema.yml'])) { - $cache = \Drupal::service('cache.discovery')->get('typed_config_definitions'); + if (isset($additional_files['config']['schema']["$module_name.schema.yml"])) { + $cache = \Drupal::service('cache.discovery') + ->get('typed_config_definitions'); $typed_config_definitions = $cache->data; - $typed_config_definitions += Yaml::parse($additional_files['config']['schema']['ckeditor5_invalid_plugin.schema.yml']); + $typed_config_definitions += Yaml::parse($additional_files['config']['schema']["$module_name.schema.yml"]); \Drupal::service('config.typed')->clearCachedDefinitions(); \Drupal::service('cache.discovery')->set('typed_config_definitions', $typed_config_definitions, $cache->expire, $cache->tags); } + return $container; + } + + /** + * @covers \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::processDefinition() + * @dataProvider providerTestInvalidPluginDefinitions + */ + public function testInvalidPluginDefinitions(string $yaml, ?string $expected_message, array $additional_files = []): void { + if ($expected_message) { + $this->expectException(InvalidPluginDefinitionException::class); + $this->expectExceptionMessage($expected_message); + } + $container = $this->mockModuleInVfs('ckeditor5_invalid_plugin', $yaml, $additional_files); $container->get('plugin.manager.ckeditor5.plugin')->getDefinitions(); } @@ -1519,4 +1546,361 @@ public function testExternalLinkAutomaticLinkDecoratorDisallowed(): void { $this->manager->getDefinitions(); } + /** + * @covers ::getDiscovery + * @dataProvider providerTestDerivedPluginDefinitions + */ + public function testDerivedPluginDefinitions(string $yaml, ?string $expected_message, array $additional_files = [], ?array $expected_derived_plugin_definitions = NULL): void { + if ($expected_message) { + $this->expectException(InvalidPluginDefinitionException::class); + $this->expectExceptionMessage($expected_message); + } + $container = $this->mockModuleInVfs('ckeditor5_derived_plugin', $yaml, $additional_files); + + $actual_definitions = $container->get('plugin.manager.ckeditor5.plugin')->getDefinitions(); + $this->assertEquals($expected_derived_plugin_definitions, $actual_definitions); + } + + /** + * Data provider. + * + * @return \Generator + * Test scenarios. + */ + public function providerTestDerivedPluginDefinitions(): \Generator { + // Defaults inherited from CKEditor5AspectsOfCKEditor5Plugin. + $ckeditor5_aspects_defaults = get_class_vars(CKEditor5AspectsOfCKEditor5Plugin::class); + // Defaults inherited from DrupalAspectsOfCKEditor5Plugin. + $drupal_aspects_defaults = get_class_vars(DrupalAspectsOfCKEditor5Plugin::class); + + $simple_deriver_additional_files = [ + 'src' => [ + 'Plugin' => [ + 'CKEditor5Plugin' => [ + 'SimpleDeriver.php' => <<<'PHP' +<?php +namespace Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin; +use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition; +use Drupal\Component\Plugin\Derivative\DeriverBase; +class SimpleDeriver extends DeriverBase { + public function getDerivativeDefinitions($base_plugin_definition) { + assert($base_plugin_definition instanceof CKEditor5PluginDefinition); + foreach (['bar', 'baz'] as $id) { + $definition = $base_plugin_definition->toArray(); + $definition['id'] = $id; + $definition['drupal']['label'] = sprintf("Foo %s", $id); + $this->derivatives[$id] = new CKEditor5PluginDefinition($definition); + } + return $this->derivatives; + } +} +PHP, + ], + ], + ], + ]; + + yield 'INVALID: simple deriver but without `drupal.elements` in the base definition and it not getting set by the deriver' => [ + <<<YAML +ckeditor5_derived_plugin_foo: + ckeditor5: + plugins: {} + drupal: + deriver: Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\SimpleDeriver +YAML, + 'The "ckeditor5_derived_plugin_foo:bar" CKEditor 5 derived plugin definition must contain a "drupal.elements" key.', + $simple_deriver_additional_files, + ]; + + yield 'INVALID: simple deriver but without `ckeditor5.plugins` in the base definition and it not getting set by the deriver' => [ + <<<YAML +ckeditor5_derived_plugin_foo: + ckeditor5: {} + drupal: + elements: false + deriver: Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\SimpleDeriver +YAML, + 'The "ckeditor5_derived_plugin_foo:bar" CKEditor 5 derived plugin definition must contain a "ckeditor5.plugins" key.', + $simple_deriver_additional_files, + ]; + + yield 'INVALID: simple deriver but without `ckeditor5` in the base definition and it not getting set by the deriver' => [ + <<<YAML +ckeditor5_derived_plugin_foo: + drupal: + elements: false + deriver: Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\SimpleDeriver +YAML, + 'The "ckeditor5_derived_plugin_foo:bar" CKEditor 5 derived plugin definition must contain a "ckeditor5" key.', + $simple_deriver_additional_files, + ]; + + yield 'INVALID: simple deriver which returns arrays instead of CKEditor5PluginDefinition instances' => [ + <<<YAML +ckeditor5_derived_plugin_foo: + ckeditor5: + plugins: {} + drupal: + elements: false + deriver: Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\SimpleDeriver +YAML, + 'The "ckeditor5_derived_plugin_foo:bar" CKEditor 5 plugin definition must extend Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition', + [ + 'src' => [ + 'Plugin' => [ + 'CKEditor5Plugin' => [ + 'SimpleDeriver.php' => <<<'PHP' +<?php +namespace Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin; +use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition; +use Drupal\Component\Plugin\Derivative\DeriverBase; +class SimpleDeriver extends DeriverBase { + public function getDerivativeDefinitions($base_plugin_definition) { + assert($base_plugin_definition instanceof CKEditor5PluginDefinition); + foreach (['bar', 'baz'] as $id) { + $definition = $base_plugin_definition->toArray(); + $definition['id'] = $id; + $definition['drupal']['label'] = sprintf("Foo %s", $id); + $this->derivatives[$id] = $definition; + } + return $this->derivatives; + } +} +PHP, + ], + ], + ], + ], + ]; + + yield 'VALID: simple deriver, base definition in YAML' => [ + <<<YAML +ckeditor5_derived_plugin_foo: + ckeditor5: + plugins: {} + drupal: + elements: false + deriver: Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\SimpleDeriver +YAML, + NULL, + $simple_deriver_additional_files, + [ + 'ckeditor5_derived_plugin_foo:bar' => new CKEditor5PluginDefinition([ + 'provider' => 'ckeditor5_derived_plugin', + 'id' => 'ckeditor5_derived_plugin_foo:bar', + 'ckeditor5' => ['plugins' => []] + $ckeditor5_aspects_defaults, + 'drupal' => [ + 'label' => 'Foo bar', + 'elements' => FALSE, + 'deriver' => 'Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\SimpleDeriver', + ] + $drupal_aspects_defaults, + ]), + 'ckeditor5_derived_plugin_foo:baz' => new CKEditor5PluginDefinition([ + 'provider' => 'ckeditor5_derived_plugin', + 'id' => 'ckeditor5_derived_plugin_foo:baz', + 'ckeditor5' => ['plugins' => []] + $ckeditor5_aspects_defaults, + 'drupal' => [ + 'label' => 'Foo baz', + 'elements' => FALSE, + 'deriver' => 'Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\SimpleDeriver', + ] + $drupal_aspects_defaults, + ]), + ], + ]; + + yield 'VALID: simple deriver, base definition in PHP' => [ + '', + NULL, + [ + 'src' => [ + 'Plugin' => [ + 'CKEditor5Plugin' => [ + 'Foo.php' => <<<'PHP' +<?php +declare(strict_types = 1); +namespace Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin; +use Drupal\ckeditor5\Plugin\CKEditor5PluginDefault; +/** + * @CKEditor5Plugin( + * id = "ckeditor5_derived_plugin_foo", + * ckeditor5 = @CKEditor5AspectsOfCKEditor5Plugin( + * plugins = {}, + * ), + * drupal = @DrupalAspectsOfCKEditor5Plugin( + * elements = false, + * deriver = "Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\SimpleDeriver", + * ) + * ) + */ +class Foo extends CKEditor5PluginDefault { +} +PHP, + 'SimpleDeriver.php' => $simple_deriver_additional_files['src']['Plugin']['CKEditor5Plugin']['SimpleDeriver.php'], + ], + ], + ], + ], + [ + 'ckeditor5_derived_plugin_foo:bar' => new CKEditor5PluginDefinition([ + 'provider' => 'ckeditor5_derived_plugin', + 'id' => 'ckeditor5_derived_plugin_foo:bar', + 'ckeditor5' => ['plugins' => []] + $ckeditor5_aspects_defaults, + 'drupal' => [ + 'class' => 'Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\Foo', + 'label' => 'Foo bar', + 'elements' => FALSE, + 'deriver' => 'Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\SimpleDeriver', + ] + $drupal_aspects_defaults, + ]), + 'ckeditor5_derived_plugin_foo:baz' => new CKEditor5PluginDefinition([ + 'provider' => 'ckeditor5_derived_plugin', + 'id' => 'ckeditor5_derived_plugin_foo:baz', + 'ckeditor5' => ['plugins' => []] + $ckeditor5_aspects_defaults, + 'drupal' => [ + 'class' => 'Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\Foo', + 'label' => 'Foo baz', + 'elements' => FALSE, + 'deriver' => 'Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\SimpleDeriver', + ] + $drupal_aspects_defaults, + ]), + ], + ]; + + yield 'VALID: minimal base plugin definition, maximal deriver' => [ + <<<YAML +# Minimal annotation key-value pairs set in the YAML, most set in the deriver. +ckeditor5_derived_plugin_foo: + drupal: + deriver: Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\MaximalDeriver +YAML, + NULL, + [ + 'src' => [ + 'Plugin' => [ + 'CKEditor5Plugin' => [ + 'MaximalDeriver.php' => <<<'PHP' +<?php +namespace Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin; +use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition; +use Drupal\Component\Plugin\Derivative\DeriverBase; +class MaximalDeriver extends DeriverBase { + public function getDerivativeDefinitions($base_plugin_definition) { + assert($base_plugin_definition instanceof CKEditor5PluginDefinition); + foreach (['A', 'B'] as $id) { + $definition = $base_plugin_definition->toArray(); + $definition['id'] = $id; + $definition['drupal']['label'] = sprintf("Foo %s", $id); + $definition['drupal']['elements'] = FALSE; + $definition['ckeditor5']['plugins'] = []; + $this->derivatives[$id] = new CKEditor5PluginDefinition($definition); + } + return $this->derivatives; + } +} +PHP, + ], + ], + ], + ], + [ + 'ckeditor5_derived_plugin_foo:A' => new CKEditor5PluginDefinition([ + 'provider' => 'ckeditor5_derived_plugin', + 'id' => 'ckeditor5_derived_plugin_foo:A', + 'ckeditor5' => ['plugins' => []], + 'drupal' => [ + 'label' => 'Foo A', + 'elements' => FALSE, + 'deriver' => 'Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\MaximalDeriver', + ] + $drupal_aspects_defaults, + ]), + 'ckeditor5_derived_plugin_foo:B' => new CKEditor5PluginDefinition([ + 'provider' => 'ckeditor5_derived_plugin', + 'id' => 'ckeditor5_derived_plugin_foo:B', + 'ckeditor5' => ['plugins' => []], + 'drupal' => [ + 'label' => 'Foo B', + 'elements' => FALSE, + 'deriver' => 'Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\MaximalDeriver', + ] + $drupal_aspects_defaults, + ]), + ], + ]; + + yield 'VALID: container-dependent deriver' => [ + <<<YAML +ckeditor5_derived_plugin_foo: + ckeditor5: + plugins: {} + drupal: + elements: false + deriver: Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\ContainerDependentDeriver +YAML, + NULL, + [ + 'config' => [ + 'schema' => [ + 'ckeditor5_derived_plugin.schema.yml' => <<<YAML +ckeditor5.plugin.ckeditor5_derived_plugin: + type: mapping + label: 'Foo' + mapping: + foo: + type: boolean + label: 'Foo' +YAML, + ], + ], + 'src' => [ + 'Plugin' => [ + 'CKEditor5Plugin' => [ + 'ContainerDependentDeriver.php' => <<<'PHP' +<?php +namespace Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin; +use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition; +use Drupal\Component\Plugin\Derivative\DeriverBase; +use Drupal\Core\Authentication\AuthenticationCollectorInterface; +use Drupal\Core\Entity\EntityTypeRepositoryInterface; +use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +class ContainerDependentDeriver extends DeriverBase implements ContainerDeriverInterface { + protected $authenticationCollector; + public function __construct(AuthenticationCollectorInterface $authentication_collector) { + $this->authenticationCollector = $authentication_collector; + } + public static function create(ContainerInterface $container, $base_plugin_id) { + assert($base_plugin_id === 'ckeditor5_derived_plugin_foo'); + return new static($container->get('authentication_collector')); + } + public function getDerivativeDefinitions($base_plugin_definition) { + assert($base_plugin_definition instanceof CKEditor5PluginDefinition); + $authentication_providers = array_keys($this->authenticationCollector->getSortedProviders()); + foreach ($authentication_providers as $id) { + $definition = $base_plugin_definition->toArray(); + $definition['id'] = $id; + $definition['drupal']['label'] = sprintf("Foo %s", $id); + $this->derivatives[$definition['id']] = new CKEditor5PluginDefinition($definition); + } + return $this->derivatives; + } +} +PHP, + ], + ], + ], + ], + [ + 'ckeditor5_derived_plugin_foo:cookie' => new CKEditor5PluginDefinition([ + 'provider' => 'ckeditor5_derived_plugin', + 'id' => 'ckeditor5_derived_plugin_foo:cookie', + 'ckeditor5' => ['plugins' => []] + $ckeditor5_aspects_defaults, + 'drupal' => [ + 'label' => 'Foo cookie', + 'elements' => FALSE, + 'deriver' => 'Drupal\ckeditor5_derived_plugin\Plugin\CKEditor5Plugin\ContainerDependentDeriver', + ] + $drupal_aspects_defaults, + ]), + ], + ]; + } + }