From 5be422b7900a2f9d527976a47f2cd754dc49265b Mon Sep 17 00:00:00 2001
From: Benjamin Kott <benjamin.kott@outlook.com>
Date: Sat, 29 Dec 2018 12:04:22 +0100
Subject: [PATCH] [FEATURE] Introduce events to modify CKEditor configuration

This patch introduces a set of PSR-14 Events to modify
the CKEditor configuration.

- AfterGetExternalPluginsEvent
- BeforeGetExternalPluginsEvent
- AfterPrepareConfigurationForEditorEvent
- BeforePrepareConfigurationForEditorEvent

Resolves: #88818
Releases: master
Change-Id: I1f810e31274a05d52082de5e03fc530b3cee1a44
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/59304
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Benni Mack <benni@typo3.org>
---
 ...uceEventsToModifyCKEditorConfiguration.rst | 74 +++++++++++++++++++
 .../Event/AfterGetExternalPluginsEvent.php    | 54 ++++++++++++++
 ...fterPrepareConfigurationForEditorEvent.php | 54 ++++++++++++++
 .../Event/BeforeGetExternalPluginsEvent.php   | 54 ++++++++++++++
 ...forePrepareConfigurationForEditorEvent.php | 54 ++++++++++++++
 .../Classes/Form/Element/RichTextElement.php  | 66 +++++++++++++----
 6 files changed, 343 insertions(+), 13 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-88818-IntroduceEventsToModifyCKEditorConfiguration.rst
 create mode 100644 typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/AfterGetExternalPluginsEvent.php
 create mode 100644 typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/AfterPrepareConfigurationForEditorEvent.php
 create mode 100644 typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/BeforeGetExternalPluginsEvent.php
 create mode 100644 typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/BeforePrepareConfigurationForEditorEvent.php

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-88818-IntroduceEventsToModifyCKEditorConfiguration.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-88818-IntroduceEventsToModifyCKEditorConfiguration.rst
new file mode 100644
index 000000000000..c4e207373619
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-88818-IntroduceEventsToModifyCKEditorConfiguration.rst
@@ -0,0 +1,74 @@
+.. include:: ../../Includes.txt
+
+===================================================================
+Feature: #88818 - Introduce events to modify CKEditor configuration
+===================================================================
+
+See :issue:`88818`
+
+Description
+===========
+
+The following new PSR-14-based Events are introduced which allow
+to modify CKEditor configuration.
+
+- :php:`TYPO3\CMS\RteCKEditor\Form\Element\Event\AfterGetExternalPluginsEvent`
+- :php:`TYPO3\CMS\RteCKEditor\Form\Element\Event\BeforeGetExternalPluginsEvent`
+- :php:`TYPO3\CMS\RteCKEditor\Form\Element\Event\AfterPrepareConfigurationForEditorEvent`
+- :php:`TYPO3\CMS\RteCKEditor\Form\Element\Event\BeforePrepareConfigurationForEditorEvent`
+
+Example
+=======
+
+An example implementation how you could extend the existing
+configuration to register a new plugin:
+
+:file:`EXT:my_extension/Configuration/Services.yaml`
+
+.. code-block:: yaml
+
+   services:
+     Vendor\MyExtension\EventListener\RteConfigEnhancer:
+       tags:
+         - name: event.listener
+           identifier: 'ext-myextension/rteConfigEnhancer'
+           method: 'beforeGetExternalPlugins'
+           event: TYPO3\CMS\RteCKEditor\Form\Element\Event\BeforeGetExternalPluginsEvent
+         - name: event.listener
+           identifier: 'ext-myextension/rteConfigEnhancer'
+           method: 'beforePrepareConfiguration'
+           event: TYPO3\CMS\RteCKEditor\Form\Element\Event\BeforePrepareConfigurationForEditorEvent
+
+:file:`EXT:my_extension/Classes/EventListener/RteConfigEnhancer.php`
+
+.. code-block:: php
+
+   namespace Vendor\MyExtension\EventListener;
+
+   use TYPO3\CMS\RteCKEditor\Form\Element\Event\BeforeGetExternalPluginsEvent;
+   use TYPO3\CMS\RteCKEditor\Form\Element\Event\BeforePrepareConfigurationForEditorEvent;
+
+   class RteConfigEnhancer
+   {
+      public function beforeGetExternalPlugins(BeforeGetExternalPluginsEvent $event): void
+      {
+         $data = $event->getData();
+         // @todo make useful decisions on fetched data
+         $configuration = $event->getConfiguration();
+         $configuration['example_plugin'] = [
+            'resource' => 'EXT:my_extension/Resources/Public/CKEditor/Plugins/ExamplePlugin/plugin.js'
+         ];
+         $event->setConfiguration($configuration);
+      }
+
+      public function beforePrepareConfiguration(BeforePrepareConfigurationForEditorEvent $event): void
+      {
+         $data = $event->getData();
+         // @todo make useful decisions on fetched data
+         $configuration = $event->getConfiguration();
+         $configuration['extraPlugins'][] = 'example_plugin';
+         $event->setConfiguration($configuration);
+      }
+   }
+
+.. index:: Backend, PHP-API, RTE, ext:rte_ckeditor
diff --git a/typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/AfterGetExternalPluginsEvent.php b/typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/AfterGetExternalPluginsEvent.php
new file mode 100644
index 000000000000..c1f515cf516b
--- /dev/null
+++ b/typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/AfterGetExternalPluginsEvent.php
@@ -0,0 +1,54 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\RteCKEditor\Form\Element\Event;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * This event is fired after processing external plugin configuration.
+ */
+final class AfterGetExternalPluginsEvent
+{
+    /**
+     * @var array
+     */
+    private $configuration;
+
+    /**
+     * @var array
+     */
+    private $data;
+
+    public function __construct(array $configuration, array $data)
+    {
+        $this->configuration = $configuration;
+        $this->data = $data;
+    }
+
+    public function getData(): array
+    {
+        return $this->data;
+    }
+
+    public function getConfiguration(): array
+    {
+        return $this->configuration;
+    }
+
+    public function setConfiguration(array $configuration): void
+    {
+        $this->configuration = $configuration;
+    }
+}
diff --git a/typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/AfterPrepareConfigurationForEditorEvent.php b/typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/AfterPrepareConfigurationForEditorEvent.php
new file mode 100644
index 000000000000..662e4dbc0825
--- /dev/null
+++ b/typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/AfterPrepareConfigurationForEditorEvent.php
@@ -0,0 +1,54 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\RteCKEditor\Form\Element\Event;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * This event is fired after preparing the editor configuration.
+ */
+final class AfterPrepareConfigurationForEditorEvent
+{
+    /**
+     * @var array
+     */
+    private $configuration;
+
+    /**
+     * @var array
+     */
+    private $data;
+
+    public function __construct(array $configuration, array $data)
+    {
+        $this->configuration = $configuration;
+        $this->data = $data;
+    }
+
+    public function getData(): array
+    {
+        return $this->data;
+    }
+
+    public function getConfiguration(): array
+    {
+        return $this->configuration;
+    }
+
+    public function setConfiguration(array $configuration): void
+    {
+        $this->configuration = $configuration;
+    }
+}
diff --git a/typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/BeforeGetExternalPluginsEvent.php b/typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/BeforeGetExternalPluginsEvent.php
new file mode 100644
index 000000000000..6e4132174b59
--- /dev/null
+++ b/typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/BeforeGetExternalPluginsEvent.php
@@ -0,0 +1,54 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\RteCKEditor\Form\Element\Event;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * This event is fired before processing external plugin configuration.
+ */
+final class BeforeGetExternalPluginsEvent
+{
+    /**
+     * @var array
+     */
+    private $configuration;
+
+    /**
+     * @var array
+     */
+    private $data;
+
+    public function __construct(array $configuration, array $data)
+    {
+        $this->configuration = $configuration;
+        $this->data = $data;
+    }
+
+    public function getData(): array
+    {
+        return $this->data;
+    }
+
+    public function getConfiguration(): array
+    {
+        return $this->configuration;
+    }
+
+    public function setConfiguration(array $configuration): void
+    {
+        $this->configuration = $configuration;
+    }
+}
diff --git a/typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/BeforePrepareConfigurationForEditorEvent.php b/typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/BeforePrepareConfigurationForEditorEvent.php
new file mode 100644
index 000000000000..25cdd07ad8db
--- /dev/null
+++ b/typo3/sysext/rte_ckeditor/Classes/Form/Element/Event/BeforePrepareConfigurationForEditorEvent.php
@@ -0,0 +1,54 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\RteCKEditor\Form\Element\Event;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * This event is fired before starting the prepare of the editor configuration.
+ */
+final class BeforePrepareConfigurationForEditorEvent
+{
+    /**
+     * @var array
+     */
+    private $configuration;
+
+    /**
+     * @var array
+     */
+    private $data;
+
+    public function __construct(array $configuration, array $data)
+    {
+        $this->configuration = $configuration;
+        $this->data = $data;
+    }
+
+    public function getData(): array
+    {
+        return $this->data;
+    }
+
+    public function getConfiguration(): array
+    {
+        return $this->configuration;
+    }
+
+    public function setConfiguration(array $configuration): void
+    {
+        $this->configuration = $configuration;
+    }
+}
diff --git a/typo3/sysext/rte_ckeditor/Classes/Form/Element/RichTextElement.php b/typo3/sysext/rte_ckeditor/Classes/Form/Element/RichTextElement.php
index 4ff0a8330d4b..ef13f692a7d9 100644
--- a/typo3/sysext/rte_ckeditor/Classes/Form/Element/RichTextElement.php
+++ b/typo3/sysext/rte_ckeditor/Classes/Form/Element/RichTextElement.php
@@ -15,12 +15,18 @@ namespace TYPO3\CMS\RteCKEditor\Form\Element;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\EventDispatcher\EventDispatcherInterface;
 use TYPO3\CMS\Backend\Form\Element\AbstractFormElement;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Localization\Locales;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
+use TYPO3\CMS\RteCKEditor\Form\Element\Event\AfterGetExternalPluginsEvent;
+use TYPO3\CMS\RteCKEditor\Form\Element\Event\AfterPrepareConfigurationForEditorEvent;
+use TYPO3\CMS\RteCKEditor\Form\Element\Event\BeforeGetExternalPluginsEvent;
+use TYPO3\CMS\RteCKEditor\Form\Element\Event\BeforePrepareConfigurationForEditorEvent;
 
 /**
  * Render rich text editor in FormEngine
@@ -70,6 +76,24 @@ class RichTextElement extends AbstractFormElement
      */
     protected $rteConfiguration = [];
 
+    /**
+     * @var EventDispatcherInterface
+     */
+    protected $eventDispatcher;
+
+    /**
+     * Container objects give $nodeFactory down to other containers.
+     *
+     * @param NodeFactory $nodeFactory
+     * @param array $data
+     * @param EventDispatcherInterface|null $eventDispatcher
+     */
+    public function __construct(NodeFactory $nodeFactory, array $data, EventDispatcherInterface $eventDispatcher = null)
+    {
+        parent::__construct($nodeFactory, $data);
+        $this->eventDispatcher = $eventDispatcher ?? GeneralUtility::getContainer()->get(EventDispatcherInterface::class);
+    }
+
     /**
      * Renders the ckeditor element
      *
@@ -232,6 +256,11 @@ class RichTextElement extends AbstractFormElement
      */
     protected function getExtraPlugins(): array
     {
+        $externalPlugins = $this->rteConfiguration['externalPlugins'] ?? [];
+        $externalPlugins = $this->eventDispatcher
+            ->dispatch(new BeforeGetExternalPluginsEvent($externalPlugins, $this->data))
+            ->getConfiguration();
+
         $urlParameters = [
             'P' => [
                 'table'      => $this->data['tableName'],
@@ -244,21 +273,23 @@ class RichTextElement extends AbstractFormElement
         ];
 
         $pluginConfiguration = [];
-        if (isset($this->rteConfiguration['externalPlugins']) && is_array($this->rteConfiguration['externalPlugins'])) {
-            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-            foreach ($this->rteConfiguration['externalPlugins'] as $pluginName => $configuration) {
-                $pluginConfiguration[$pluginName] = [
-                    'resource' => $this->resolveUrlPath($configuration['resource'])
-                ];
-                unset($configuration['resource']);
-
-                if ($configuration['route']) {
-                    $configuration['routeUrl'] = (string)$uriBuilder->buildUriFromRoute($configuration['route'], $urlParameters);
-                }
-
-                $pluginConfiguration[$pluginName]['config'] = $configuration;
+        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+        foreach ($externalPlugins as $pluginName => $configuration) {
+            $pluginConfiguration[$pluginName] = [
+                'resource' => $this->resolveUrlPath($configuration['resource'])
+            ];
+            unset($configuration['resource']);
+
+            if ($configuration['route']) {
+                $configuration['routeUrl'] = (string)$uriBuilder->buildUriFromRoute($configuration['route'], $urlParameters);
             }
+
+            $pluginConfiguration[$pluginName]['config'] = $configuration;
         }
+
+        $pluginConfiguration = $this->eventDispatcher
+            ->dispatch(new AfterGetExternalPluginsEvent($pluginConfiguration, $this->data))
+            ->getConfiguration();
         return $pluginConfiguration;
     }
 
@@ -327,6 +358,11 @@ class RichTextElement extends AbstractFormElement
         if (is_array($this->rteConfiguration['config'])) {
             $configuration = array_replace_recursive($configuration, $this->rteConfiguration['config']);
         }
+
+        $configuration = $this->eventDispatcher
+            ->dispatch(new BeforePrepareConfigurationForEditorEvent($configuration, $this->data))
+            ->getConfiguration();
+
         // Set the UI language of the editor if not hard-coded by the existing configuration
         if (empty($configuration['language'])) {
             $configuration['language'] = $this->getBackendUser()->uc['lang'] ?: ($this->getBackendUser()->user['lang'] ?: 'en');
@@ -349,6 +385,10 @@ class RichTextElement extends AbstractFormElement
             $configuration['removeButtons'] = implode(',', $configuration['removeButtons']);
         }
 
+        $configuration = $this->eventDispatcher
+            ->dispatch(new AfterPrepareConfigurationForEditorEvent($configuration, $this->data))
+            ->getConfiguration();
+
         return $configuration;
     }
 
-- 
GitLab