Loading core/core.api.php +14 −0 Changes for core/core.api.php: 14 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -2453,6 +2453,20 @@ function hook_config_schema_info_alter(&$definitions) { $definitions['date_format']['form_element_class'] = '\Drupal\config_translation\FormElement\DateFormat'; } /** * Alter config action plugin definitions. * * @param array[] $definitions * The array of config action plugin definitions, keyed by plugin ID. * * @see \Drupal\Core\Config\Action\ConfigActionManager * @see \Drupal\Core\Config\Action\Attribute\ConfigAction */ function hook_config_action_alter(array &$definitions): void { // Create an alias for an existing config action. $definitions['editor:addItemsToToolbar'] = $definitions['editor:addItemToToolbar']; } /** * Alter validation constraint plugin definitions. * Loading core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php +11 −0 Changes for core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php: 11 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Url; use Drupal\editor\EditorInterface; Loading Loading @@ -381,6 +382,16 @@ public function configSchemaInfoAlter(&$definitions): void { $definitions['ckeditor5_valid_pair__format_and_editor']['mapping']['image_upload'] = $definitions['editor.editor.*']['mapping']['image_upload']; } /** * Implements hook_config_action_alter(). */ #[Hook('config_action_alter')] public function configActionAlter(array &$definitions): void { // Create a pluralized alias for the `addItemToToolbar` action. $definitions['editor:addItemsToToolbar'] = $definitions['editor:addItemToToolbar']; $definitions['editor:addItemsToToolbar']['admin_label'] = new TranslatableMarkup('Add multiple items to a CKEditor 5 toolbar'); } /** * Implements hook_field_widget_single_element_form_alter(). */ Loading core/modules/ckeditor5/src/Plugin/ConfigAction/AddItemToToolbar.php +47 −13 Changes for core/modules/ckeditor5/src/Plugin/ConfigAction/AddItemToToolbar.php: 47 added lines, 13 removed lines. Original line number Diff line number Diff line Loading @@ -46,6 +46,18 @@ public static function create(ContainerInterface $container, array $configuratio * {@inheritdoc} */ public function apply(string $configName, mixed $value): void { // Normalize $value, which could be one of three things: // - A string (add one item to the toolbar, no additional options) // - An associative array (add one item to the toolbar, with options) // - An indexed array (add multiple items to the toolbar, each of which // could be one of the previous two forms) if (is_string($value) || (is_array($value) && !array_is_list($value))) { $value = [$value]; } if (!is_array($value) || $value === []) { throw new ConfigActionException(sprintf('The %s config action requires one or more toolbar items.', $this->pluginId)); } $editor = $this->configManager->loadConfigEntityByName($configName); assert($editor instanceof EditorInterface); Loading @@ -53,13 +65,37 @@ public function apply(string $configName, mixed $value): void { throw new ConfigActionException(sprintf('The %s config action only works with editors that use CKEditor 5.', $this->pluginId)); } $changed = FALSE; foreach ($value as $item) { if (!is_string($item) && !is_array($item)) { throw new ConfigActionException(sprintf('The %s config action requires each toolbar item to be a string or an array of options.', $this->pluginId)); } $changed = $this->applySingle($editor, $item) || $changed; } if ($changed) { $editor->save(); } } /** * Adds an item to the toolbar. * * @param \Drupal\editor\EditorInterface $editor * The editor to which the item should be added. * @param string|array $value * The name of the item, or an array of options for the item. * * @return bool * TRUE if the editor settings were changed, FALSE otherwise. */ private function applySingle(EditorInterface $editor, string|array $value): bool { if (is_string($value)) { $value = ['item_name' => $value]; } assert(is_array($value)); $item_name = $value['item_name']; assert(is_string($item_name)); $item_name = $value['item_name'] ?? NULL; if (!is_string($item_name)) { throw new ConfigActionException(sprintf("The %s config action requires each toolbar item to define an 'item_name' string.", $this->pluginId)); } $replace = $value['replace'] ?? FALSE; assert(is_bool($replace)); Loading @@ -74,7 +110,7 @@ public function apply(string $configName, mixed $value): void { // If the item is already in the toolbar and we're not allowing duplicate // items, we're done. if (in_array($item_name, $editor_settings['toolbar']['items'], TRUE) && $allow_duplicate === FALSE && $item_name !== '|') { return; return FALSE; } if (is_int($position)) { Loading @@ -87,14 +123,10 @@ public function apply(string $configName, mixed $value): void { $editor_settings['toolbar']['items'][] = $item_name; } // If we're just adding a vertical separator, there's nothing else we need // to do at this point. if ($item_name === '|') { return; } // If this item is associated with a plugin, ensure that it's configured // at the editor level, if necessary. // at the editor level, if necessary. A vertical separator is not // associated with any plugin. if ($item_name !== '|') { /** @var \Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition $definition */ foreach ($this->pluginManager->getDefinitions() as $id => $definition) { if (array_key_exists($item_name, $definition->getToolbarItems())) { Loading @@ -111,8 +143,10 @@ public function apply(string $configName, mixed $value): void { break; } } } $editor->setSettings($editor_settings)->save(); $editor->setSettings($editor_settings); return TRUE; } } core/modules/ckeditor5/tests/src/Kernel/ConfigAction/AddItemToToolbarConfigActionTest.php +62 −0 Changes for core/modules/ckeditor5/tests/src/Kernel/ConfigAction/AddItemToToolbarConfigActionTest.php: 62 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -106,6 +106,68 @@ public function testAddItemToToolbar(string|array $action, array $expected_toolb } } /** * Tests adding multiple items to an editor toolbar. */ public function testAddMultipleItemsToToolbar(): void { $recipe = $this->createRecipe([ 'name' => 'CKEditor 5 multiple toolbar items test', 'config' => [ 'actions' => [ 'editor.editor.filter_test' => [ 'addItemsToToolbar' => [ 'sourceEditing', [ 'item_name' => 'italic', 'allow_duplicate' => TRUE, 'position' => 0, ], ], ], ], ], ]); RecipeRunner::processRecipe($recipe); /** @var array{toolbar: array{items: string[]}, plugins: array<string, array<mixed>>} $settings */ $settings = Editor::load('filter_test')?->getSettings(); // The `sourceEditing` item should have been appended to the initial // toolbar, and a duplicate `italic` item inserted at the beginning. $this->assertSame(['italic', 'heading', 'bold', 'italic', 'sourceEditing'], $settings['toolbar']['items']); // The plugin's default settings should have been added. $this->assertSame([], $settings['plugins']['ckeditor5_sourceEditing']['allowed_tags']); } /** * Tests that an empty list of toolbar items is rejected. */ public function testEmptyItemListIsRejected(): void { $this->expectException(ConfigActionException::class); $this->expectExceptionMessageIs('The editor:addItemsToToolbar config action requires one or more toolbar items.'); $this->container->get('plugin.manager.config_action') ->applyAction('editor:addItemsToToolbar', 'editor.editor.filter_test', []); } /** * Tests that a toolbar item which is not a string or array is rejected. */ public function testInvalidItemTypeIsRejected(): void { $this->expectException(ConfigActionException::class); $this->expectExceptionMessageIs('The editor:addItemsToToolbar config action requires each toolbar item to be a string or an array of options.'); $this->container->get('plugin.manager.config_action') ->applyAction('editor:addItemsToToolbar', 'editor.editor.filter_test', [123]); } /** * Tests that a toolbar item without an item name is rejected. */ public function testMissingItemNameIsRejected(): void { $this->expectException(ConfigActionException::class); $this->expectExceptionMessageIs("The editor:addItemsToToolbar config action requires each toolbar item to define an 'item_name' string."); $this->container->get('plugin.manager.config_action') ->applyAction('editor:addItemsToToolbar', 'editor.editor.filter_test', [['position' => 0]]); } /** * Tests that adding non-existent toolbar item to CKEditor triggers an error. */ Loading Loading
core/core.api.php +14 −0 Changes for core/core.api.php: 14 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -2453,6 +2453,20 @@ function hook_config_schema_info_alter(&$definitions) { $definitions['date_format']['form_element_class'] = '\Drupal\config_translation\FormElement\DateFormat'; } /** * Alter config action plugin definitions. * * @param array[] $definitions * The array of config action plugin definitions, keyed by plugin ID. * * @see \Drupal\Core\Config\Action\ConfigActionManager * @see \Drupal\Core\Config\Action\Attribute\ConfigAction */ function hook_config_action_alter(array &$definitions): void { // Create an alias for an existing config action. $definitions['editor:addItemsToToolbar'] = $definitions['editor:addItemToToolbar']; } /** * Alter validation constraint plugin definitions. * Loading
core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php +11 −0 Changes for core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php: 11 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Url; use Drupal\editor\EditorInterface; Loading Loading @@ -381,6 +382,16 @@ public function configSchemaInfoAlter(&$definitions): void { $definitions['ckeditor5_valid_pair__format_and_editor']['mapping']['image_upload'] = $definitions['editor.editor.*']['mapping']['image_upload']; } /** * Implements hook_config_action_alter(). */ #[Hook('config_action_alter')] public function configActionAlter(array &$definitions): void { // Create a pluralized alias for the `addItemToToolbar` action. $definitions['editor:addItemsToToolbar'] = $definitions['editor:addItemToToolbar']; $definitions['editor:addItemsToToolbar']['admin_label'] = new TranslatableMarkup('Add multiple items to a CKEditor 5 toolbar'); } /** * Implements hook_field_widget_single_element_form_alter(). */ Loading
core/modules/ckeditor5/src/Plugin/ConfigAction/AddItemToToolbar.php +47 −13 Changes for core/modules/ckeditor5/src/Plugin/ConfigAction/AddItemToToolbar.php: 47 added lines, 13 removed lines. Original line number Diff line number Diff line Loading @@ -46,6 +46,18 @@ public static function create(ContainerInterface $container, array $configuratio * {@inheritdoc} */ public function apply(string $configName, mixed $value): void { // Normalize $value, which could be one of three things: // - A string (add one item to the toolbar, no additional options) // - An associative array (add one item to the toolbar, with options) // - An indexed array (add multiple items to the toolbar, each of which // could be one of the previous two forms) if (is_string($value) || (is_array($value) && !array_is_list($value))) { $value = [$value]; } if (!is_array($value) || $value === []) { throw new ConfigActionException(sprintf('The %s config action requires one or more toolbar items.', $this->pluginId)); } $editor = $this->configManager->loadConfigEntityByName($configName); assert($editor instanceof EditorInterface); Loading @@ -53,13 +65,37 @@ public function apply(string $configName, mixed $value): void { throw new ConfigActionException(sprintf('The %s config action only works with editors that use CKEditor 5.', $this->pluginId)); } $changed = FALSE; foreach ($value as $item) { if (!is_string($item) && !is_array($item)) { throw new ConfigActionException(sprintf('The %s config action requires each toolbar item to be a string or an array of options.', $this->pluginId)); } $changed = $this->applySingle($editor, $item) || $changed; } if ($changed) { $editor->save(); } } /** * Adds an item to the toolbar. * * @param \Drupal\editor\EditorInterface $editor * The editor to which the item should be added. * @param string|array $value * The name of the item, or an array of options for the item. * * @return bool * TRUE if the editor settings were changed, FALSE otherwise. */ private function applySingle(EditorInterface $editor, string|array $value): bool { if (is_string($value)) { $value = ['item_name' => $value]; } assert(is_array($value)); $item_name = $value['item_name']; assert(is_string($item_name)); $item_name = $value['item_name'] ?? NULL; if (!is_string($item_name)) { throw new ConfigActionException(sprintf("The %s config action requires each toolbar item to define an 'item_name' string.", $this->pluginId)); } $replace = $value['replace'] ?? FALSE; assert(is_bool($replace)); Loading @@ -74,7 +110,7 @@ public function apply(string $configName, mixed $value): void { // If the item is already in the toolbar and we're not allowing duplicate // items, we're done. if (in_array($item_name, $editor_settings['toolbar']['items'], TRUE) && $allow_duplicate === FALSE && $item_name !== '|') { return; return FALSE; } if (is_int($position)) { Loading @@ -87,14 +123,10 @@ public function apply(string $configName, mixed $value): void { $editor_settings['toolbar']['items'][] = $item_name; } // If we're just adding a vertical separator, there's nothing else we need // to do at this point. if ($item_name === '|') { return; } // If this item is associated with a plugin, ensure that it's configured // at the editor level, if necessary. // at the editor level, if necessary. A vertical separator is not // associated with any plugin. if ($item_name !== '|') { /** @var \Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition $definition */ foreach ($this->pluginManager->getDefinitions() as $id => $definition) { if (array_key_exists($item_name, $definition->getToolbarItems())) { Loading @@ -111,8 +143,10 @@ public function apply(string $configName, mixed $value): void { break; } } } $editor->setSettings($editor_settings)->save(); $editor->setSettings($editor_settings); return TRUE; } }
core/modules/ckeditor5/tests/src/Kernel/ConfigAction/AddItemToToolbarConfigActionTest.php +62 −0 Changes for core/modules/ckeditor5/tests/src/Kernel/ConfigAction/AddItemToToolbarConfigActionTest.php: 62 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -106,6 +106,68 @@ public function testAddItemToToolbar(string|array $action, array $expected_toolb } } /** * Tests adding multiple items to an editor toolbar. */ public function testAddMultipleItemsToToolbar(): void { $recipe = $this->createRecipe([ 'name' => 'CKEditor 5 multiple toolbar items test', 'config' => [ 'actions' => [ 'editor.editor.filter_test' => [ 'addItemsToToolbar' => [ 'sourceEditing', [ 'item_name' => 'italic', 'allow_duplicate' => TRUE, 'position' => 0, ], ], ], ], ], ]); RecipeRunner::processRecipe($recipe); /** @var array{toolbar: array{items: string[]}, plugins: array<string, array<mixed>>} $settings */ $settings = Editor::load('filter_test')?->getSettings(); // The `sourceEditing` item should have been appended to the initial // toolbar, and a duplicate `italic` item inserted at the beginning. $this->assertSame(['italic', 'heading', 'bold', 'italic', 'sourceEditing'], $settings['toolbar']['items']); // The plugin's default settings should have been added. $this->assertSame([], $settings['plugins']['ckeditor5_sourceEditing']['allowed_tags']); } /** * Tests that an empty list of toolbar items is rejected. */ public function testEmptyItemListIsRejected(): void { $this->expectException(ConfigActionException::class); $this->expectExceptionMessageIs('The editor:addItemsToToolbar config action requires one or more toolbar items.'); $this->container->get('plugin.manager.config_action') ->applyAction('editor:addItemsToToolbar', 'editor.editor.filter_test', []); } /** * Tests that a toolbar item which is not a string or array is rejected. */ public function testInvalidItemTypeIsRejected(): void { $this->expectException(ConfigActionException::class); $this->expectExceptionMessageIs('The editor:addItemsToToolbar config action requires each toolbar item to be a string or an array of options.'); $this->container->get('plugin.manager.config_action') ->applyAction('editor:addItemsToToolbar', 'editor.editor.filter_test', [123]); } /** * Tests that a toolbar item without an item name is rejected. */ public function testMissingItemNameIsRejected(): void { $this->expectException(ConfigActionException::class); $this->expectExceptionMessageIs("The editor:addItemsToToolbar config action requires each toolbar item to define an 'item_name' string."); $this->container->get('plugin.manager.config_action') ->applyAction('editor:addItemsToToolbar', 'editor.editor.filter_test', [['position' => 0]]); } /** * Tests that adding non-existent toolbar item to CKEditor triggers an error. */ Loading