[FEATURE] Implement configuration for CKEditor 19/50919/20
authorBenni Mack <benni@typo3.org>
Tue, 17 Jan 2017 20:38:07 +0000 (21:38 +0100)
committerBenni Mack <benni@typo3.org>
Wed, 1 Feb 2017 08:09:23 +0000 (09:09 +0100)
The main problem with the current RTE configuration relies in the fact
that it is *only* configurable via pageTS.

CKEditor can now be configured via YAML, but the configuration preset
can be overriden via pageTS.

Additionally, the CKEditor Form Element is cleaned up.

A different preset can be used via PageTS config:
- RTE.default.preset = minimal
- RTE.config.tt_content.bodytext.preset = full

Once included, the next features will be
- a configuration backend module for ckeditor
- Make sure to re-add the feature for RTE.anchorClasses
- Check the languages of CKeditor that they are in line with the TYPO3
languages (possibility to add others)
- image handling
- table handling

Resolves: #78917
Releases: master
Change-Id: Ia222b73072b9e3fdb3a120d0be0a24a913552126
Reviewed-on: https://review.typo3.org/50919
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Daniel Gorges <daniel.gorges@b13.de>
Tested-by: Daniel Gorges <daniel.gorges@b13.de>
Reviewed-by: Frans Saris <franssaris@gmail.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
18 files changed:
.editorconfig
typo3/sysext/core/Classes/Configuration/Loader/YamlFileLoader.php [new file with mode: 0644]
typo3/sysext/core/Classes/Configuration/Richtext.php
typo3/sysext/core/Classes/Html/RteHtmlParser.php
typo3/sysext/core/Documentation/Changelog/master/Feature-79216-AddYAMLConfigurationForCKEditorRTE.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Configuration/Loader/YamlFileLoaderTest.php [new file with mode: 0644]
typo3/sysext/rte_ckeditor/Classes/Controller/BrowseLinksController.php
typo3/sysext/rte_ckeditor/Classes/Form/Element/RichTextElement.php
typo3/sysext/rte_ckeditor/Configuration/RTE/Default.yaml [new file with mode: 0644]
typo3/sysext/rte_ckeditor/Configuration/RTE/Editor/Base.yaml [new file with mode: 0644]
typo3/sysext/rte_ckeditor/Configuration/RTE/Editor/Plugins.yaml [new file with mode: 0644]
typo3/sysext/rte_ckeditor/Configuration/RTE/Full.yaml [new file with mode: 0644]
typo3/sysext/rte_ckeditor/Configuration/RTE/Minimal.yaml [new file with mode: 0644]
typo3/sysext/rte_ckeditor/Configuration/RTE/Processing.yaml [new file with mode: 0644]
typo3/sysext/rte_ckeditor/Resources/Public/Css/contents.css
typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/Plugins/typo3link.js
typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/defaultconfig.js [deleted file]
typo3/sysext/rte_ckeditor/ext_localconf.php

index 3d8633f..4d8f6e9 100644 (file)
@@ -57,6 +57,11 @@ indent_size = 3
 indent_style = space
 indent_size = 4
 
+# YAML-Files
+[{*.yaml,*.yml}]
+indent_style = space
+indent_size = 2
+
 # package.json or .travis.yml
 [{package.json,.travis.yml}]
 indent_style = space
diff --git a/typo3/sysext/core/Classes/Configuration/Loader/YamlFileLoader.php b/typo3/sysext/core/Classes/Configuration/Loader/YamlFileLoader.php
new file mode 100644 (file)
index 0000000..d0c5251
--- /dev/null
@@ -0,0 +1,192 @@
+<?php
+namespace TYPO3\CMS\Core\Configuration\Loader;
+
+/*
+ * 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!
+ */
+
+use Symfony\Component\Yaml\Yaml;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * A Yaml file loader that allows to load YML files, based on the Symfony/Yaml component
+ *
+ * In addition to just load a yaml file, it adds some special functionality.
+ *
+ * - A special "imports" key in the yaml file allows to include other yaml files recursively
+ *   where the actual yaml file gets loaded after the import statements, which are interpreted at the very beginning
+ *
+ * - Merging configuration options of import files when having simple "lists" will add items to the list instead
+ *   of overwriting them.
+ *
+ * - Special placeholder values set via %optionA.suboptionB% replace the value with the named path of the configuration
+ *   The placeholders will act as a full replacement of this value.
+ */
+class YamlFileLoader
+{
+
+    /**
+     * Loads and parses a YAML file, and returns an array with the found data
+     *
+     * @param string $fileName either relative to PATH_site or prefixed with EXT:...
+     * @return array the configuration as array
+     */
+    public function load(string $fileName): array
+    {
+        $content = $this->getFileContents($fileName);
+        $content = Yaml::parse($content);
+
+        $content = $this->processImports($content);
+
+        // Check for "%" placeholders
+        $content = $this->processPlaceholders($content, $content);
+
+        return $content;
+    }
+
+    /**
+     * Put into a separate method to ease the pains with unit tests
+     *
+     * @param string $fileName either relative to PATH_site or prefixed with EXT:...
+     *
+     * @return string the contents of the file
+     * @throws \RuntimeException when the file was not accessible
+     */
+    protected function getFileContents(string $fileName): string
+    {
+        $fileName = GeneralUtility::getFileAbsFileName($fileName);
+        if (!$fileName) {
+            throw new \RuntimeException('YAML File "' . $fileName . '" could not be loaded', 1485784246);
+        }
+        return file_get_contents($fileName);
+    }
+
+    /**
+     * Checks for the special "imports" key on the main level of a file,
+     * which calls "load" recursively.
+     * @param array $content
+     *
+     * @return array
+     */
+    protected function processImports(array $content): array
+    {
+        if (isset($content['imports']) && is_array($content['imports'])) {
+            foreach ($content['imports'] as $import) {
+                $importedContent = $this->load($import['resource']);
+                // override the imported content with the one from the current file
+                $content = $this->merge($importedContent, $content);
+            }
+            unset($content['imports']);
+        }
+        return $content;
+    }
+
+    /**
+     * Main function that gets called recursively to check for %...% placeholders
+     * inside the array
+     *
+     * @param array $content the current sub-level content array
+     * @param array $referenceArray the global configuration array
+     *
+     * @return array the modified sub-level content array
+     */
+    protected function processPlaceholders(array $content, array $referenceArray): array
+    {
+        foreach ($content as $k => $v) {
+            if ($this->isPlaceholder($v)) {
+                $content[$k] = $this->getValueFromReferenceArray($v, $referenceArray);
+            } elseif (is_array($v)) {
+                $content[$k] = $this->processPlaceholders($v, $referenceArray);
+            }
+        }
+        return $content;
+    }
+
+    /**
+     * Returns the value for a placeholder as fetched from the referenceArray
+     *
+     * @param string $placeholder the string to search for
+     * @param array $referenceArray the main configuration array where to look up the data
+     *
+     * @return array|mixed|string
+     */
+    protected function getValueFromReferenceArray(string $placeholder, array $referenceArray)
+    {
+        $pointer = trim($placeholder, '%');
+        $parts = explode('.', $pointer);
+        $referenceData = $referenceArray;
+        foreach ($parts as $part) {
+            if (isset($referenceData[$part])) {
+                $referenceData = $referenceData[$part];
+            } else {
+                // return unsubstituted placeholder
+                return $placeholder;
+            }
+        }
+        if ($this->isPlaceholder($referenceData)) {
+            $referenceData = $this->getValueFromReferenceArray($referenceData, $referenceArray);
+        }
+        return $referenceData;
+    }
+
+    /**
+     * Checks if a value is a string and begins and ends with %...%
+     *
+     * @param mixed $value the probe to check for
+     * @return bool
+     */
+    protected function isPlaceholder($value): bool
+    {
+        return is_string($value) && substr($value, 0, 1) === '%' && substr($value, -1) === '%';
+    }
+
+    /**
+     * Same as array_replace_recursive except that when in simple arrays (= yaml lists), the entries are
+     * appended (array_merge)
+     *
+     * @param array $val1
+     * @param array $val2
+     *
+     * @return array
+     */
+    protected function merge(array $val1, array $val2): array
+    {
+        // Simple lists get merged / added up
+        if (count(array_filter(array_keys($val1), 'is_int')) === count($val1)) {
+            return array_merge($val1, $val2);
+        } else {
+            foreach ($val1 as $k => $v) {
+                // The key also exists in second array, if it is a simple value
+                // then $val2 will override the value, where an array is calling merge() recursively.
+                if (isset($val2[$k])) {
+                    if (is_array($v) && isset($val2[$k])) {
+                        if (is_array($val2[$k])) {
+                            $val1[$k] = $this->merge($v, $val2[$k]);
+                        } else {
+                            $val1[$k] = $val2[$k];
+                        }
+                    } else {
+                        $val1[$k] = $val2[$k];
+                    }
+                    unset($val2[$k]);
+                }
+            }
+            // If there are properties in the second array left, they are added up
+            if (!empty($val2)) {
+                foreach ($val2 as $k => $v) {
+                    $val1[$k] = $v;
+                }
+            }
+        }
+        return $val1;
+    }
+}
index a120b24..433acdd 100644 (file)
@@ -17,7 +17,9 @@ namespace TYPO3\CMS\Core\Configuration;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Prepare richtext configuration. Used in DataHandler and FormEngine
@@ -40,58 +42,89 @@ class Richtext
      */
     public function getConfiguration(string $table, string $field, int $pid, string $recordType, array $tcaFieldConf): array
     {
-        // if (isset($tcaFieldConf['richtextConfiguration'])) {
-            // @todo with yaml parser
-            // create instance of NodeFactory, ask for "text" element
-            //
-            // If text element is instanceof "old" rtehtmlarea, do nothing, or if rtehtml should support ,yml, too
-            // unpack extConf settings, see if "demo", "normal" or whatever is configured, let rtehtmlarea register
-            // these three as possible configuration options in typo3_conf_vars, then yaml parse config. if "richtextConfiguration"
-            // is already set for rtehtmlarea and is "default" then fetch the config that is selected in extConf, else pick
-            // configured one.
-            //
-            // If text element is instanceof "new" ckeditor, and richtextConfiguration is not set, the "default", else
-            // look up in typo3_conf_vars.
-            //
-            // As soon an the Data handler starts using FormDataProviders, this class can vanish again, and the hack to
-            // test for specific rich text instances can be dropped: Split the "TcaText" data provider into multiple parts, each
-            // RTE should register and own data provider that does the transformation / configuration providing. This way,
-            // the explicit check for different RTE classes is removed from core and "hooked in" by the RTE's.
-        // }
+        // create instance of NodeFactory, ask for "text" element
+        //
+        // As soon an the Data handler starts using FormDataProviders, this class can vanish again, and the hack to
+        // test for specific rich text instances can be dropped: Split the "TcaText" data provider into multiple parts, each
+        // RTE should register and own data provider that does the transformation / configuration providing. This way,
+        // the explicit check for different RTE classes is removed from core and "hooked in" by the RTE's.
 
-        $rtePageTs = $this->getRtePageTsConfigOfPid($pid);
-        $configuration = $rtePageTs['properties'];
-        unset($configuration['default.']);
-        unset($configuration['config.']);
-        if (is_array($rtePageTs['properties']['default.'])) {
-            ArrayUtility::mergeRecursiveWithOverrule($configuration, $rtePageTs['properties']['default.']);
+        // The main problem here is that all parameters that the processing needs is handed over to as TSconfig
+        // "dotted array" syntax. We convert at least the processing information available under "processing"
+        // together with pageTS, this way it can be overridden and understood in RteHtmlParser.
+        // However, all other parts of the core will depend on the non-dotted syntax (coming from Yaml directly)
+
+        $usePreset = '';
+        $configuration = [];
+        // Check if a configuration preset file is added
+        if (isset($tcaFieldConf['richtextConfiguration'])) {
+            $usePreset = $tcaFieldConf['richtextConfiguration'];
+            $configuration = $this->loadConfigurationFromPreset($tcaFieldConf['richtextConfiguration']);
+        }
+
+        // Overload with PageTSconfig configuration
+        // First use RTE.default
+        // Then overload with RTE.config.tt_content.bodytext
+        // Then overload with RTE.config.tt_content.bodytext.types.textmedia
+        $fullPageTsConfig = $this->getRtePageTsConfigOfPid($pid);
+
+        if (is_array($fullPageTsConfig['properties']['default.'])) {
+            ArrayUtility::mergeRecursiveWithOverrule($configuration, $fullPageTsConfig['properties']['default.']);
         }
-        $rtePageTsField = $rtePageTs['properties']['config.'][$table . '.'][$field . '.'];
+        $rtePageTsField = $fullPageTsConfig['properties']['config.'][$table . '.'][$field . '.'];
         if (is_array($rtePageTsField)) {
             unset($rtePageTsField['types.']);
             ArrayUtility::mergeRecursiveWithOverrule($configuration, $rtePageTsField);
         }
-        if ($recordType && is_array($rtePageTs['properties']['config.'][$table . '.'][$field . '.']['types.'][$recordType . '.'])) {
+        if ($recordType && is_array($fullPageTsConfig['properties']['config.'][$table . '.'][$field . '.']['types.'][$recordType . '.'])) {
             ArrayUtility::mergeRecursiveWithOverrule(
                 $configuration,
-                $rtePageTs['properties']['config.'][$table . '.'][$field . '.']['types.'][$recordType . '.']
+                $fullPageTsConfig['properties']['config.'][$table . '.'][$field . '.']['types.'][$recordType . '.']
             );
         }
 
-        // Handle "mode" / "transformation" config for RteHtmlParser
-        if (!isset($configuration['proc.']['overruleMode'])) {
-            // Fall back to 'default' transformations
-            $configuration['proc.']['overruleMode'] = 'default';
+        // Reload the base configuration, if overridden via PageTS "RTE.default.preset = Minimal" for instance
+        if (isset($configuration['preset']) && $usePreset !== $configuration['preset']) {
+            $configuration = $this->loadConfigurationFromPreset($configuration['preset']);
         }
-        if ($configuration['proc.']['overruleMode'] === 'ts_css') {
+
+        // Handle "mode" / "transformation" config when overridden
+        if (isset($configuration['proc.']['overruleMode']) && $configuration['proc.']['overruleMode'] === 'ts_css') {
             // Change legacy 'ts_css' to 'default'
             $configuration['proc.']['overruleMode'] = 'default';
+        } elseif (!isset($configuration['proc.']['mode']) && !isset($configuration['proc.']['overruleMode'])) {
+            $configuration['proc.']['overruleMode'] = 'default';
         }
 
         return $configuration;
     }
 
     /**
+     * Load a configuration preset from an external resource (currently only YAML is supported).
+     * This is the default behaviour and can be overridden by pageTSconfig.
+     *
+     * @param string $presetName
+     * @return array the parsed configuration
+     */
+    protected function loadConfigurationFromPreset(string $presetName = ''): array
+    {
+        $configuration = [
+            'processing' => [
+                'mode' => 'default'
+            ]
+        ];
+        if (!empty($presetName) && isset($GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets'][$presetName])) {
+            $fileLoader = GeneralUtility::makeInstance(YamlFileLoader::class);
+            $configuration = $fileLoader->load($GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets'][$presetName]);
+        }
+        // For future versions, you should however rely on the "processing" key and not the "proc" key.
+        if (is_array($configuration['processing'])) {
+            $configuration['proc.'] = $this->convertPlainArrayToTypoScriptArray($configuration['processing']);
+        }
+        return $configuration;
+    }
+
+    /**
      * Return RTE section of page TS
      *
      * @param int $pid Page ts of given pid
@@ -105,6 +138,30 @@ class Richtext
     }
 
     /**
+     * Returns an array with Typoscript the old way (with dot)
+     * Since the functionality in Yaml is without the dots, but the new configuration is used without the dots
+     * this functionality adds also an explicit = 1 to the arrays
+     *
+     * @param array $plainArray An array
+     * @return array array with TypoScript as usual (with dot)
+     */
+    protected function convertPlainArrayToTypoScriptArray(array $plainArray)
+    {
+        $typoScriptArray = [];
+        foreach ($plainArray as $key => $value) {
+            if (is_array($value)) {
+                if (!isset($typoScriptArray[$key])) {
+                    $typoScriptArray[$key] = 1;
+                }
+                $typoScriptArray[$key . '.'] = $this->convertPlainArrayToTypoScriptArray($value);
+            } else {
+                $typoScriptArray[$key] = is_null($value) ? '' : $value;
+            }
+        }
+        return $typoScriptArray;
+    }
+
+    /**
      * @return BackendUserAuthentication
      */
     protected function getBackendUser() : BackendUserAuthentication
index ef9fd96..4d64c26 100644 (file)
@@ -163,7 +163,11 @@ class RteHtmlParser extends HtmlParser
     {
         $this->tsConfig = $thisConfig;
         $this->procOptions = (array)$thisConfig['proc.'];
-        $this->allowedClasses = GeneralUtility::trimExplode(',', $this->procOptions['allowedClasses'], true);
+        if (isset($this->procOptions['allowedClasses.'])) {
+            $this->allowedClasses = (array)$this->procOptions['allowedClasses.'];
+        } else {
+            $this->allowedClasses = GeneralUtility::trimExplode(',', $this->procOptions['allowedClasses'], true);
+        }
 
         // Dynamic configuration of blockElementList
         if ($this->procOptions['blockElementList']) {
@@ -171,17 +175,25 @@ class RteHtmlParser extends HtmlParser
         }
 
         // Define which attributes are allowed on <p> tags
-        if (isset($this->procOptions['keepPDIVattribs'])) {
+        if (isset($this->procOptions['allowAttributes.'])) {
+            $this->allowedAttributesForParagraphTags = $this->procOptions['allowAttributes.'];
+        } elseif (isset($this->procOptions['keepPDIVattribs'])) {
             $this->allowedAttributesForParagraphTags = GeneralUtility::trimExplode(',', strtolower($this->procOptions['keepPDIVattribs']), true);
         }
         // Override tags which are allowed outside of <p> tags
         if (isset($this->procOptions['allowTagsOutside'])) {
-            $this->allowedTagsOutsideOfParagraphs = GeneralUtility::trimExplode(',', strtolower($this->procOptions['allowTagsOutside']), true);
+            if (!isset($this->procOptions['allowTagsOutside.'])) {
+                $this->allowedTagsOutsideOfParagraphs = GeneralUtility::trimExplode(',', strtolower($this->procOptions['allowTagsOutside']), true);
+            } else {
+                $this->allowedTagsOutsideOfParagraphs = (array)$this->procOptions['allowTagsOutside.'];
+            }
         }
 
         // Setting modes / transformations to be called
         if ((string)$this->procOptions['overruleMode'] !== '') {
             $modes = GeneralUtility::trimExplode(',', $this->procOptions['overruleMode']);
+        } elseif (!empty($this->procOptions['mode'])) {
+            $modes = [$this->procOptions['mode']];
         } else {
             // Get parameters for rte_transformation:
             // @deprecated since TYPO3 v8, will be removed in TYPO3 v9 - the else{} part can be removed in v9
@@ -826,7 +838,12 @@ class RteHtmlParser extends HtmlParser
             // Setting up allowed tags:
             // Default is to get allowed/denied tags from internal array of processing options:
             // Construct default list of tags to keep:
-            $keepTags = array_flip(GeneralUtility::trimExplode(',', $this->defaultAllowedTagsList . ',' . strtolower($this->procOptions['allowTags']), true));
+            if (is_array($this->procOptions['allowTags.'])) {
+                $keepTags = implode(',', $this->procOptions['allowTags.']);
+            } else {
+                $keepTags = $this->procOptions['allowTags'];
+            }
+            $keepTags = array_flip(GeneralUtility::trimExplode(',', $this->defaultAllowedTagsList . ',' . strtolower($keepTags), true));
             // For tags to deny, remove them from $keepTags array:
             $denyTags = GeneralUtility::trimExplode(',', $this->procOptions['denyTags'], true);
             foreach ($denyTags as $dKe) {
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-79216-AddYAMLConfigurationForCKEditorRTE.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-79216-AddYAMLConfigurationForCKEditorRTE.rst
new file mode 100644 (file)
index 0000000..8ec9d98
--- /dev/null
@@ -0,0 +1,47 @@
+.. include:: ../../Includes.txt
+
+=========================================================
+Feature: #79216 - Add YAML configuration for CKEditor RTE
+=========================================================
+
+See :issue:`79216`
+
+Description
+===========
+
+The CKEditor-flavored RTE can now be configured via YAML files, defined as presets.
+
+A preset contains both the RTE configuration and the HTML processing when storing the content
+in the database.
+
+A YAML file for RTE configurations can be registered by any extension in `ext_localconf.php`:
+
+$GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['default'] = 'EXT:rte_ckeditor/Configuration/RTE/Default.yaml';
+
+The TYPO3 Core ships with three flavors for the RTE configuration which can also be overriden via
+PageTSconfig on a per-field/type level:
+
+RTE.default.preset = minimal
+RTE.tt_content.types.textmedia.bodytext.preset = full
+
+The PageTSconfig allows to use the minimal configuration everywhere, but to use the full
+configuration on tt_content.bodytext field (but only for textmedia content types).
+
+With the YAML configuration files, a "imports" functionality is allowed to import other
+configuration and just override the necessary values for a custom configuration for a specific site.
+This way, the processing part of EXT:rte_ckeditor can be used directly (which acts as best practice)
+but the editor part can be completely customized.
+
+The YAML format thus states three important parts considered by the RTE configuration preset:
+
+1. "imports"
+Allows to import other files via the "resource" sub-property
+2. "processing"
+uses the former "proc" options to hand over to RteHtmlParser to sanitize the content - the option
+are the same as for RTEHtmlArea
+3. "editor"
+A configuration for CKEditor, where all CKEditor-related options can be set which are available
+from the ckeditor configuration specifications (see http://docs.ckeditor.com/#!/api/CKEDITOR.config
+for all options).
+
+.. index:: LocalConfiguration, RTE
\ No newline at end of file
diff --git a/typo3/sysext/core/Tests/Unit/Configuration/Loader/YamlFileLoaderTest.php b/typo3/sysext/core/Tests/Unit/Configuration/Loader/YamlFileLoaderTest.php
new file mode 100644 (file)
index 0000000..68ef4fd
--- /dev/null
@@ -0,0 +1,194 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Core\Tests\Unit\Configuration\Loader;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
+
+/**
+ * Test case for the yaml file loader class
+ */
+class YamlFileLoaderTest extends \TYPO3\CMS\Components\TestingFramework\Core\UnitTestCase
+{
+    /**
+     * Generic method to check if the load method returns an array from a YAML file
+     * @test
+     */
+    public function load()
+    {
+        $fileName = 'Berta.yml';
+        $fileContents = '
+options:
+    - option1
+    - option2
+betterthanbefore: 1
+';
+
+        $expected = [
+            'options' => [
+                'option1',
+                'option2'
+            ],
+            'betterthanbefore' => 1
+        ];
+
+        // Accessible mock to $subject since getFileContents calls GeneralUtility methods
+        $subject = $this->getAccessibleMock(YamlFileLoader::class, ['getFileContents']);
+        $subject->expects($this->once())->method('getFileContents')->with($fileName)->willReturn($fileContents);
+        $output = $subject->load($fileName);
+        $this->assertSame($expected, $output);
+    }
+
+    /**
+     * Method checking for imports that they have been processed properly
+     * @test
+     */
+    public function loadWithAnImport()
+    {
+        $fileName = 'Berta.yml';
+        $fileContents = '
+imports:
+    - { resource: Secondfile.yml }
+
+options:
+    - option1
+    - option2
+betterthanbefore: 1
+';
+
+        $importFileName = 'Secondfile.yml';
+        $importFileContents = '
+options:
+    - optionBefore
+betterthanbefore: 2
+';
+
+        $expected = [
+            'options' => [
+                'optionBefore',
+                'option1',
+                'option2'
+            ],
+            'betterthanbefore' => 1
+        ];
+
+        // Accessible mock to $subject since getFileContents calls GeneralUtility methods
+        $subject = $this->getAccessibleMock(YamlFileLoader::class, ['getFileContents']);
+        $subject->expects($this->at(0))->method('getFileContents')->with($fileName)->willReturn($fileContents);
+        $subject->expects($this->at(1))->method('getFileContents')->with($importFileName)->willReturn($importFileContents);
+        $output = $subject->load($fileName);
+        $this->assertSame($expected, $output);
+    }
+
+
+    /**
+     * Method checking for placeholders
+     * @test
+     */
+    public function loadWithPlacholders()
+    {
+        $fileName = 'Berta.yml';
+        $fileContents = '
+
+firstset:
+  myinitialversion: 13
+options:
+    - option1
+    - option2
+betterthanbefore: %firstset.myinitialversion%
+';
+
+        $expected = [
+            'firstset' => [
+                'myinitialversion' => 13
+            ],
+            'options' => [
+                'option1',
+                'option2'
+            ],
+            'betterthanbefore' => 13
+        ];
+
+        // Accessible mock to $subject since getFileContents calls GeneralUtility methods
+        $subject = $this->getAccessibleMock(YamlFileLoader::class, ['getFileContents']);
+        $subject->expects($this->once())->method('getFileContents')->with($fileName)->willReturn($fileContents);
+        $output = $subject->load($fileName);
+        $this->assertSame($expected, $output);
+    }
+
+    /**
+     * dataprovider for tests isPlaceholderTest
+     * @return array
+     */
+    public function isPlaceholderDataProvider()
+    {
+        return [
+            'regular string' => [
+                'berta13',
+                false
+            ],
+            'regular array' => [
+                ['berta13'],
+                false
+            ],
+            'regular float' => [
+                13.131313,
+                false
+            ],
+            'regular int' => [
+                13,
+                false
+            ],
+            'invalid placeholder with only % at the beginning' => [
+                '%cool',
+                false
+            ],
+            'invalid placeholder with only % at the end' => [
+                'cool%',
+                false
+            ],
+            'invalid placeholder with two % but not at the end' => [
+                '%cool%again',
+                false
+            ],
+            'invalid placeholder with two % but not at the beginning nor end' => [
+                'did%you%know',
+                false
+            ],
+            'valid placeholder with just numbers' => [
+                '%13%',
+                true
+            ],
+            'valid placeholder' => [
+                '%foo%baracks%',
+                true
+            ],
+        ];
+    }
+
+    /**
+     * @dataProvider isPlaceholderDataProvider
+     * @test
+     * @param mixed $placeholderValue
+     * @param bool $expected
+     * @skip
+     */
+    public function isPlaceholderTest($placeholderValue, bool $expected)
+    {
+        $subject = $this->getAccessibleMock(YamlFileLoader::class, ['dummy']);
+        $output = $subject->_call('isPlaceholder', $placeholderValue);
+        $this->assertSame($expected, $output);
+    }
+}
index 3cdca25..4138639 100644 (file)
@@ -19,7 +19,6 @@ use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Configuration\Richtext;
 use TYPO3\CMS\Core\LinkHandling\LinkService;
 use TYPO3\CMS\Core\Page\PageRenderer;
-use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Lang\LanguageService;
 use TYPO3\CMS\Recordlist\Controller\AbstractLinkBrowserController;
@@ -60,11 +59,10 @@ class BrowseLinksController extends AbstractLinkBrowserController
 
     /**
      * Used with the Rich Text Editor.
-     * Example value: "tt_content:NEW3fba58c969f5c:bodytext:23:text:23:"
      *
-     * @var string
+     * @var array
      */
-    protected $RTEtsConfigParams;
+    protected $editorDetails = [];
 
     /**
      * @var array
@@ -135,26 +133,22 @@ class BrowseLinksController extends AbstractLinkBrowserController
 
         $this->contentLanguageService->init($this->contentsLanguage);
 
-        // @todo: This needs refactoring to enable sane config in flex form, either transfer parts of 'config', or use data providers
-        $RTEtsConfigParts = explode(':', $this->RTEtsConfigParams);
-        $table = $RTEtsConfigParts[0];
-        $field = $RTEtsConfigParts[2];
-        $recordType = $RTEtsConfigParts[3];
-        $tcaConfigOfField = $GLOBALS['TCA'][$table][$field]['config'] ?? [];
-        $columnsOverridesConfigOfField = $GLOBALS['TCA'][$table]['types'][$recordType]['columnsOverrides'][$field]['config'] ?? [];
-        if (!empty($columnsOverridesConfigOfField)) {
-            ArrayUtility::mergeRecursiveWithOverrule($tcaConfigOfField, $columnsOverridesConfigOfField);
-        }
+        $this->editorDetails = [
+            'table' => $queryParameters['table'],
+            'uid' => $queryParameters['uid'],
+            'fieldName' => $queryParameters['fieldName'],
+            'pid' => $queryParameters['pid'],
+            'recordType' => $queryParameters['recordType'],
+        ];
+
         $richtextConfigurationProvider = GeneralUtility::makeInstance(Richtext::class);
-        $richtextConfiguration = $richtextConfigurationProvider->getConfiguration(
-            $RTEtsConfigParts[0],
-            $RTEtsConfigParts[2],
-            $RTEtsConfigParts[3],
-            $RTEtsConfigParts[4],
-            $tcaConfigOfField
+        $this->thisConfig = $richtextConfigurationProvider->getConfiguration(
+            $queryParameters['table'],
+            $queryParameters['fieldName'],
+            (int)$queryParameters['pid'],
+            $queryParameters['recordType'],
+            ['richtext' => true]
         );
-        $this->thisConfig = $richtextConfiguration;
-
         $this->buttonConfig = $this->thisConfig['buttons.']['link.'] ?? [];
     }
 
@@ -536,7 +530,7 @@ class BrowseLinksController extends AbstractLinkBrowserController
      */
     protected function getCurrentPageId()
     {
-        return explode(':', $this->RTEtsConfigParams)[5];
+        return (int)$this->editorDetails['uid'];
     }
 
     /**
@@ -571,11 +565,10 @@ class BrowseLinksController extends AbstractLinkBrowserController
      */
     public function getUrlParameters(array $overrides = null)
     {
-        return [
+        return array_merge($this->editorDetails, [
             'act' => isset($overrides['act']) ? $overrides['act'] : $this->displayedLinkHandlerId,
             'editorId' => $this->editorId,
             'contentsLanguage' => $this->contentsLanguage,
-            'RTEtsConfigParams' => $this->RTEtsConfigParams,
-        ];
+        ]);
     }
 }
index d68db75..727fe9f 100644 (file)
@@ -17,13 +17,9 @@ namespace TYPO3\CMS\RteCKEditor\Form\Element;
 
 use TYPO3\CMS\Backend\Form\Element\AbstractFormElement;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
-use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Localization\Locales;
-use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
-use TYPO3\CMS\Lang\LanguageService;
 
 /**
  * Render rich text editor in FormEngine
@@ -48,23 +44,19 @@ class RichTextElement extends AbstractFormElement
     ];
 
     /**
-     * pid of fixed versioned record.
-     * This is the pid of the record in normal cases, but is changed to the pid
-     * of the "mother" record in case the handled record is a versioned overlay
-     * and "mother" is located at a different pid.
+     * This property contains configuration related to the RTE
+     * But only the .editor configuration part
      *
-     * @var int
+     * @var array
      */
-    protected $pidOfVersionedMotherRecord;
+    protected $rteConfiguration = [];
 
     /**
-     * RTE configuration
-     * This property contains "processed" configuration
-     * where table and type specific RTE setup is merged into 'default.' array.
+     * The path to EXT:rte_ckeditor/Resources/Public/ where all assets etc. are stored.
      *
-     * @var array
+     * @var string
      */
-    protected $rteConfiguration = [];
+    protected $defaultResourcesPath;
 
     /**
      * Renders the ckeditor element
@@ -75,17 +67,9 @@ class RichTextElement extends AbstractFormElement
     public function render() : array
     {
         $resultArray = $this->initializeResultArray();
-
-        $row = $this->data['databaseRow'];
-        $this->pidOfVersionedMotherRecord = (int)$row['pid'];
-
-        $resourcesPath = PathUtility::getAbsoluteWebPath(
-            ExtensionManagementUtility::extPath('rte_ckeditor', 'Resources/Public/')
-        );
-        $table = $this->data['tableName'];
-        $row = $this->data['databaseRow'];
         $parameterArray = $this->data['parameterArray'];
         $config = $parameterArray['fieldConf']['config'];
+        $this->defaultResourcesPath = $this->resolveUrlPath('EXT:rte_ckeditor/Resources/Public/');
 
         $fieldId = $this->sanitizeFieldId($parameterArray['itemFormElName']);
         $itemFormElementName = $this->data['parameterArray']['itemFormElName'];
@@ -139,11 +123,10 @@ class RichTextElement extends AbstractFormElement
 
         $resultArray['html'] = implode(LF, $html);
 
-        $this->rteConfiguration = $parameterArray['fieldConf']['config']['richtextConfiguration'];
-
+        $this->rteConfiguration = $config['richtextConfiguration']['editor'];
         $resultArray['requireJsModules'] = [];
-        $resultArray['requireJsModules'][] =[
-            'ckeditor' => $this->getCkEditorRequireJsModuleCode($resourcesPath, $fieldId)
+        $resultArray['requireJsModules'][] = [
+            'ckeditor' => $this->getCkEditorRequireJsModuleCode($fieldId)
         ];
 
         return $resultArray;
@@ -154,168 +137,166 @@ class RichTextElement extends AbstractFormElement
      *
      * @return string
      */
-    protected function getContentsLanguage()
+    protected function getLanguageIsoCodeOfContent(): string
     {
-        $language = $this->getLanguageService()->lang;
-        if ($language === 'default' || !$language) {
-            $language = 'en';
-        }
         $currentLanguageUid = $this->data['databaseRow']['sys_language_uid'];
         if (is_array($currentLanguageUid)) {
             $currentLanguageUid = $currentLanguageUid[0];
         }
         $contentLanguageUid = (int)max($currentLanguageUid, 0);
         if ($contentLanguageUid) {
-            $contentISOLanguage = $language;
-            if (ExtensionManagementUtility::isLoaded('static_info_tables')) {
-                $tableA = 'sys_language';
-                $tableB = 'static_languages';
-
-                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-                    ->getQueryBuilderForTable($tableA);
-
-                $result = $queryBuilder
-                    ->select('a.uid', 'b.lg_iso_2', 'b.lg_country_iso_2')
-                    ->from($tableA, 'a')
-                    ->where('a.uid', (int)$contentLanguageUid)
-                    ->leftJoin(
-                        'a',
-                        $tableB,
-                        'b',
-                        $queryBuilder->expr()->eq('a.static_lang_isocode', $queryBuilder->quoteIdentifier('b.uid'))
-                    )
-                    ->execute();
-
-                while ($languageRow = $result->fetch()) {
-                    $contentISOLanguage = strtolower(trim($languageRow['lg_iso_2']) . (trim($languageRow['lg_country_iso_2']) ? '_' . trim($languageRow['lg_country_iso_2']) : ''));
-                }
-            }
+            $contentLanguage = $this->data['systemLanguageRows'][$currentLanguageUid]['iso'];
         } else {
-            $contentISOLanguage = trim($this->rteConfiguration['defaultContentLanguage'] ?? '') ?: 'en';
-            $languageCodeParts = explode('_', $contentISOLanguage);
-            $contentISOLanguage = strtolower($languageCodeParts[0]) . ($languageCodeParts[1] ? '_' . strtoupper($languageCodeParts[1]) : '');
+            $contentLanguage = $this->rteConfiguration['config']['defaultContentLanguage'] ?? 'en_US';
+            $languageCodeParts = explode('_', $contentLanguage);
+            $contentLanguage = strtolower($languageCodeParts[0]) . ($languageCodeParts[1] ? '_' . strtoupper($languageCodeParts[1]) : '');
             // Find the configured language in the list of localization locales
-            /** @var $locales Locales */
             $locales = GeneralUtility::makeInstance(Locales::class);
             // If not found, default to 'en'
-            if (!in_array($contentISOLanguage, $locales->getLocales(), true)) {
-                $contentISOLanguage = 'en';
+            if (!in_array($contentLanguage, $locales->getLocales(), true)) {
+                $contentLanguage = 'en';
             }
         }
-        return $contentISOLanguage;
+        return $contentLanguage;
     }
 
     /**
      * Gets the JavaScript code for CKEditor module
+     * Compiles the configuration, and then adds plugins
      *
-     * @param string $resourcesPath
      * @param string $fieldId
      * @return string
      */
-    protected function getCkEditorRequireJsModuleCode(string $resourcesPath, string $fieldId) : string
+    protected function getCkEditorRequireJsModuleCode(string $fieldId) : string
     {
-        $customConfig = [
-            'contentsCss' => $resourcesPath . 'Css/contents.css',
-            'customConfig' => $resourcesPath . 'JavaScript/defaultconfig.js',
-            'toolbar' => 'Basic',
-            'uiColor' => '#F8F8F8',
-            'stylesSet' => 'default',
-            'extraPlugins' => '',
-            'RTEtsConfigParams' => $this->getRTEtsConfigParams(),
-            'contentsLanguage' => $this->getContentsLanguage(),
-        ];
+        $configuration = $this->prepareConfigurationForEditor();
 
         $externalPlugins = '';
-        foreach ($this->getExternalPlugins() as $pluginName => $config) {
-            $customConfig[$pluginName] = $config['config'];
-            $customConfig['extraPlugins'] .= ',' . $pluginName;
+        foreach ($this->getExtraPlugins() as $pluginName => $config) {
+            $configuration[$pluginName] = $config['config'];
+            $configuration['extraPlugins'] .= ',' . $pluginName;
 
             $externalPlugins .= 'CKEDITOR.plugins.addExternal(';
             $externalPlugins .= GeneralUtility::quoteJSvalue($pluginName) . ',';
-            $externalPlugins .= GeneralUtility::quoteJSvalue($config['path']) . ',';
+            $externalPlugins .= GeneralUtility::quoteJSvalue($config['resource']) . ',';
             $externalPlugins .= '\'\');';
         }
 
         return 'function(CKEDITOR) {
-                CKEDITOR.config.height = 400;
-                CKEDITOR.contentsCss = "' . $resourcesPath . 'Css/contents.css";
-                CKEDITOR.config.width = "auto";
                 ' . $externalPlugins . '
-                CKEDITOR.replace("' . $fieldId . '", ' . json_encode($customConfig) . ');
+                CKEDITOR.replace("' . $fieldId . '", ' . json_encode($configuration) . ');
         }';
     }
 
     /**
-     * A list of parameters that is mostly given as GET/POST to other RTE controllers.
+     * Get configuration of external/additional plugins
      *
-     * @return string
+     * @return array
      */
-    protected function getRTEtsConfigParams() : string
+    protected function getExtraPlugins(): array
     {
-        $result = [
-            $this->data['tableName'],
-            $this->data['databaseRow']['uid'],
-            $this->data['fieldName'],
-            $this->pidOfVersionedMotherRecord,
-            $this->data['recordTypeValue'],
-            $this->data['effectivePid'],
+        $urlParameters = [
+            'table'      => $this->data['tableName'],
+            'uid'        => $this->data['databaseRow']['uid'],
+            'fieldName'  => $this->data['fieldName'],
+            'recordType' => $this->data['recordTypeValue'],
+            'pid'        => $this->data['effectivePid'],
         ];
-        return implode(':', $result);
+
+        $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;
+            }
+        }
+        return $pluginConfiguration;
     }
 
     /**
-     * Get configuration of external/additional plugins
+     * Add configuration to replace absolute EXT: paths with relative ones
+     * @param array $configuration
      *
      * @return array
      */
-    protected function getExternalPlugins() : array
+    protected function replaceAbsolutePathsToRelativeResourcesPath(array $configuration): array
     {
-        // todo: find new name for this option (do we still need this?)
-        // Initializing additional attributes
-        $additionalAttributes = [];
-        if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['rte_ckeditor']['plugins']['TYPO3Link']['additionalAttributes']) {
-            $additionalAttributes = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['rte_ckeditor']['plugins']['TYPO3Link']['additionalAttributes'], true);
+        foreach ($configuration as $key => $value) {
+            if (is_array($value)) {
+                $configuration[$key] = $this->replaceAbsolutePathsToRelativeResourcesPath($value);
+            } elseif (is_string($value) && substr($value, 0, 4) === 'EXT:') {
+                $configuration[$key] = $this->resolveUrlPath($value);
+            }
         }
-
-        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-        // todo: add api for this https://forge.typo3.org/issues/78929
-        $pluginPath = PathUtility::getAbsoluteWebPath(
-            ExtensionManagementUtility::extPath('rte_ckeditor', 'Resources/Public/JavaScript/Plugins/typo3link.js')
-        );
-        $externalPlugins = [
-            'typo3link' => [
-                'path' => $pluginPath,
-                'config' => [
-                    'routeUrl' => (string)$uriBuilder->buildUriFromRoute('rteckeditor_wizard_browse_links'),
-                    'additionalAttributes' => $additionalAttributes
-                ]
-            ]
-        ];
-
-        return $externalPlugins;
+        return $configuration;
     }
 
     /**
-     * @return LanguageService
+     * Resolves an EXT: syntax file to an absolute web URL
+     *
+     * @param string $value
+     * @return string
      */
-    protected function getLanguageService() : LanguageService
+    protected function resolveUrlPath(string $value): string
     {
-        return $GLOBALS['LANG'];
+        $value = GeneralUtility::getFileAbsFileName($value);
+        return PathUtility::getAbsoluteWebPath($value);
     }
 
     /**
-     * @return BackendUserAuthentication
+     * Compiles the configuration set from the outside
+     * to have it easily injected into the CKEditor.
+     *
+     * @return array the configuration
      */
-    protected function getBackendUserAuthentication() : BackendUserAuthentication
+    protected function prepareConfigurationForEditor(): array
     {
-        return $GLOBALS['BE_USER'];
+        // Set some good defaults
+        $configuration = [
+            'contentsCss' => $this->defaultResourcesPath . 'Css/contents.css',
+            'customConfig' => '', // do not load anything
+            'toolbar' => 'Basic',
+            'uiColor' => '#F8F8F8',
+            'stylesSet' => 'default',
+            'extraPlugins' => '',
+        ];
+
+        if (is_array($this->rteConfiguration['config'])) {
+            $configuration = array_replace_recursive($configuration, $this->rteConfiguration['config']);
+        }
+        $configuration['contentsLanguage'] = $this->getLanguageIsoCodeOfContent();
+
+        // replace all paths
+        $configuration = $this->replaceAbsolutePathsToRelativeResourcesPath($configuration);
+
+        // there are some places where we define an array, but it needs to be a list in order to work
+        if (is_array($configuration['extraPlugins'])) {
+            $configuration['extraPlugins'] = implode(',', $configuration['extraPlugins']);
+        }
+        if (is_array($configuration['removePlugins'])) {
+            $configuration['removePlugins'] = implode(',', $configuration['removePlugins']);
+        }
+        if (is_array($configuration['removeButtons'])) {
+            $configuration['removeButtons'] = implode(',', $configuration['removeButtons']);
+        }
+
+        return $configuration;
     }
 
     /**
      * @param string $itemFormElementName
      * @return string
      */
-    protected function sanitizeFieldId(string $itemFormElementName) : string
+    protected function sanitizeFieldId(string $itemFormElementName): string
     {
         $fieldId = preg_replace('/[^a-zA-Z0-9_:.-]/', '_', $itemFormElementName);
         return htmlspecialchars(preg_replace('/^[^a-zA-Z]/', 'x', $fieldId));
diff --git a/typo3/sysext/rte_ckeditor/Configuration/RTE/Default.yaml b/typo3/sysext/rte_ckeditor/Configuration/RTE/Default.yaml
new file mode 100644 (file)
index 0000000..0baba46
--- /dev/null
@@ -0,0 +1,42 @@
+# Load default processing options
+imports:
+    - { resource: "EXT:rte_ckeditor/Configuration/RTE/Processing.yaml" }
+    - { resource: "EXT:rte_ckeditor/Configuration/RTE/Editor/Base.yaml" }
+    - { resource: "EXT:rte_ckeditor/Configuration/RTE/Editor/Plugins.yaml" }
+
+# Add configuration for the editor
+# For complete documentation see http://docs.ckeditor.com/#!/api/CKEDITOR.config
+editor:
+  config:
+    stylesSet: []
+
+    format_tags: "p;h1;h2;h3;h4;h5;pre"
+
+    toolbarGroups:
+      - { name: styles, groups: [ styles, format ] }
+      - { name: basicstyles, groups: [ basicstyles ] }
+      - { name: paragraph, groups: [ list, indent, blocks, align ] }
+      - { name: links, groups: [ links ] }
+      - { name: clipboard, groups: [ clipboard, cleanup, undo ] }
+      - { name: editing, groups: [ spellchecker ] }
+      - { name: insert, groups: [ insert ] }
+      - { name: tools, groups: [ table, specialchar ] }
+      - { name: document, groups: [ source, document ] }
+
+    justifyClasses:
+      - align-left
+      - align-center
+      - align-right
+      - align-justify
+
+    extraPlugins:
+      - justify
+
+    removePlugins:
+      - image
+
+    removeButtons:
+      - Anchor
+      - Underline
+      - Strike
+      - Styles
diff --git a/typo3/sysext/rte_ckeditor/Configuration/RTE/Editor/Base.yaml b/typo3/sysext/rte_ckeditor/Configuration/RTE/Editor/Base.yaml
new file mode 100644 (file)
index 0000000..7255395
--- /dev/null
@@ -0,0 +1,13 @@
+# Add configuration for the editor for any configuration
+# For complete documentation see http://docs.ckeditor.com/#!/api/CKEDITOR.config
+editor:
+  config:
+    # the CSS file to be used inside the editor
+    contentsCss: "EXT:rte_ckeditor/Resources/Public/Css/contents.css"
+    # will be overridden by the record (if a language is set)
+    defaultContentLanguage: "en"
+    height: 300
+    width: "auto"
+    toolbar: "Basic"
+    uiColor: "#F8F8F8"
+    removeDialogTabs: "image:advanced;link:advanced"
diff --git a/typo3/sysext/rte_ckeditor/Configuration/RTE/Editor/Plugins.yaml b/typo3/sysext/rte_ckeditor/Configuration/RTE/Editor/Plugins.yaml
new file mode 100644 (file)
index 0000000..1d4c163
--- /dev/null
@@ -0,0 +1,5 @@
+# Register custom plugins for ckeditor
+# an example is the link plugin to select TYPO3-related links
+editor:
+  externalPlugins:
+    typo3link: { resource: "EXT:rte_ckeditor/Resources/Public/JavaScript/Plugins/typo3link.js", route: "rteckeditor_wizard_browse_links" }
diff --git a/typo3/sysext/rte_ckeditor/Configuration/RTE/Full.yaml b/typo3/sysext/rte_ckeditor/Configuration/RTE/Full.yaml
new file mode 100644 (file)
index 0000000..1318c5a
--- /dev/null
@@ -0,0 +1,64 @@
+####
+# Example of what CKEditor can all bring
+###
+
+# Load default processing options
+imports:
+    - { resource: "EXT:rte_ckeditor/Configuration/RTE/Processing.yaml" }
+    - { resource: "EXT:rte_ckeditor/Configuration/RTE/Editor/Base.yaml" }
+    - { resource: "EXT:rte_ckeditor/Configuration/RTE/Editor/Plugins.yaml" }
+
+# Add configuration for the editor
+# For complete documentation see http://docs.ckeditor.com/#!/api/CKEDITOR.config
+editor:
+  config:
+    # can be "default", but a custom stylesSet can be defined here, which fits TYPO3 best
+    stylesSet:
+      # block level styles
+      - { name: "Orange title H2", element: "h2", styles: { color: "orange", background: "blue" } }
+      - { name: "Orange title H3", element: "h3", styles: { color: "orange", background: "blue" } }
+      - { name: "Quote / Citation", element: "blockquote" }
+      - { name: "Code block", element: "code" }
+      # Inline styles
+      - { name: "Yellow marker", element: "span", styles: { background-color: "yellow" } }
+
+    format_tags: "p;h1;h2;h3;pre"
+
+    toolbarGroups:
+      - { name: clipboard, groups: [clipboard, undo] }
+      - { name: editing,   groups: [find, selection, spellchecker] }
+      - { name: links }
+      - { name: insert }
+      - { name: tools }
+      - { name: table }
+      - { name: tabletools }
+      - { name: document,  groups: [ mode, document, doctools ] }
+      - { name: others }
+      - "/"
+      - { name: basicstyles, groups: [ basicstyles, align, cleanup ] }
+      - { name: paragraph,   groups: [ list, indent, blocks, align, bidi ] }
+      - "/"
+      - { name: styles }
+
+    justifyClasses:
+      - align-left
+      - align-center
+      - align-right
+      - align-justify
+
+    extraPlugins:
+      - justify
+      - font
+      - find
+      - bidi
+
+    removePlugins:
+      - image
+
+    removeButtons:
+
+# Allow s and u tag
+processing:
+  allowTags:
+    - s
+    - u
diff --git a/typo3/sysext/rte_ckeditor/Configuration/RTE/Minimal.yaml b/typo3/sysext/rte_ckeditor/Configuration/RTE/Minimal.yaml
new file mode 100644 (file)
index 0000000..bd6cd45
--- /dev/null
@@ -0,0 +1,18 @@
+# Load default processing options
+imports:
+    - { resource: "EXT:rte_ckeditor/Configuration/RTE/Processing.yaml" }
+    - { resource: "EXT:rte_ckeditor/Configuration/RTE/Editor/Base.yaml" }
+
+# Minimal configuration for the editor
+editor:
+  config:
+    stylesSet: []
+    toolbarGroups:
+      - { name: basicstyles, groups: [ basicstyles] }
+      - { name: clipboard, groups: [clipboard, undo] }
+    removeButtons:
+      - Anchor
+      - Superscript
+      - Subscript
+      - Underline
+      - Strike
diff --git a/typo3/sysext/rte_ckeditor/Configuration/RTE/Processing.yaml b/typo3/sysext/rte_ckeditor/Configuration/RTE/Processing.yaml
new file mode 100644 (file)
index 0000000..1e16d05
--- /dev/null
@@ -0,0 +1,94 @@
+# ****************************************************
+# Sets the proc options for all default configurations
+# ****************************************************
+
+processing:
+  # previously known as "ts_css" for transformations
+  mode: default
+  # Tags that are allowed in the content in general
+  allowTags:
+    - a
+    - abbr
+    - acronym
+    - address
+    - article
+    - big
+    - blockquote
+    - br
+    - caption
+    - cite
+    - code
+    - col
+    - colgroup
+    - dd
+    - del
+    - dfn
+    - dl
+    - div
+    - dt
+    - em
+    - footer
+    - header
+    - h1
+    - h2
+    - h3
+    - h4
+    - h5
+    - h6
+    - hr
+    - i
+    - img
+    - ins
+    - kbd
+    - label
+    - li
+    - nav
+    - ol
+    - p
+    - pre
+    - q
+    - samp
+    - section
+    - small
+    - span
+    - strong
+    - style
+    - sub
+    - sup
+    - table
+    - thead
+    - tbody
+    - tfoot
+    - td
+    - th
+    - tr
+    - tt
+    - ul
+    - var
+
+  ## Tags that are allowed outside of paragraphs
+  allowTagsOutside: [address, article, aside, blockquote, footer, header, hr, nav, section, div]
+
+  ## allowed default attributes
+  allowAttributes: [class, id, title, dir, lang, xml:lang, itemscope, itemtype, itemprop]
+
+  ## CONTENT TO DATABASE
+  HTMLparser_db:
+    ## STRIP ALL ATTRIBUTES FROM THESE TAGS
+    ## If this list of tags is not set, it will default to: b,i,u,br,center,hr,sub,sup,strong,em,li,ul,ol,blockquote,strike.
+    ## However, we want to keep xml:lang attribute on most tags and tags from the default list were cleaned on entry.
+    noAttrib: br
+    # Can be disabled if you trust ckeditor (If Automatic Content Formatting is enabled, this should be OK)
+    # allowTags: %default%
+    denyTags: img
+    tags:
+      hr:
+        allowedAttribs:
+          - class
+
+    ## REMOVE OPEN OFFICE META DATA TAGS, WORD 2003 TAGS, LINK, META, STYLE AND TITLE TAGS, AND DEPRECATED HTML TAGS
+    ## We use this rule instead of the denyTags rule so that we can protect custom tags without protecting these unwanted tags.
+    removeTags: [center, font, link, meta, o:p, sdfield, strike, style, title, u]
+
+    ## PROTECT CUSTOM TAGS
+    keepNonMatchedTags: protect
index 66bc536..cc0dfe8 100644 (file)
@@ -211,3 +211,17 @@ a > img {
        max-height: 1080px;
        margin:0 auto;
 }
+
+/** justify **/
+.align-left {
+       text-align: left;
+}
+.align-center {
+       text-align: center;
+}
+.align-right {
+       text-align: right;
+}
+.align-justify {
+       text-align: justify;
+}
index eec31a4..a7907c3 100644 (file)
 
                return routeUrl
                        + (routeUrl.indexOf('?') === -1 ? '?' : '&')
-                       + 'RTEtsConfigParams=' + editor.config.RTEtsConfigParams
                        + '&contentsLanguage=' + editor.config.contentsLanguage
                        + '&editorId=' + editor.id
                        + (parameters ? parameters : '');
diff --git a/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/defaultconfig.js b/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/defaultconfig.js
deleted file mode 100644 (file)
index 09cd584..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or http://ckeditor.com/license
- */
-
-/**
- * ToDo - this needs to be VERY simple file, as it gets modified by the inline configuration
- * directly
- * @param config
- */
-CKEDITOR.editorConfig = function( config ) {
-       // Define changes to default configuration here.
-       // For complete reference see:
-       // http://docs.ckeditor.com/#!/api/CKEDITOR.config
-
-       // The toolbar groups arrangement, optimized for two toolbar rows.
-       config.toolbarGroups = [
-               { name: 'clipboard',   groups: [ 'clipboard', 'undo' ] },
-               { name: 'editing',     groups: [ 'find', 'selection', 'spellchecker' ] },
-               { name: 'links' },
-               { name: 'insert' },
-               { name: 'forms' },
-               { name: 'tools' },
-               { name: 'document',    groups: [ 'mode', 'document', 'doctools' ] },
-               { name: 'others' },
-               '/',
-               { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
-               { name: 'paragraph',   groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
-               { name: 'styles' },
-               { name: 'colors' },
-               { name: 'about' }
-       ];
-
-       // Remove some buttons provided by the standard plugins, which are
-       // not needed in the Standard(s) toolbar.
-       config.removeButtons = 'Underline,Subscript,Superscript,Anchor';
-
-       // Set the most common block elements.
-       config.format_tags = 'p;h1;h2;h3;pre';
-
-       // Simplify the dialog windows.
-       config.removeDialogTabs = 'image:advanced;link:advanced';
-
-};
-
-CKEDITOR.stylesSet.add('default', [
-       // Block-level styles
-       {name: 'Blue Title', element: 'h2', styles: {'color': 'Blue'}},
-       {name: 'Red Title', element: 'h3', styles: {'color': 'Red'}},
-       // Inline styles
-       {name: 'CSS Style', element: 'span', attributes: {'class': 'my_style'}},
-       {name: 'Marker: Yellow', element: 'span', styles: {'background-color': 'Yellow'}}
-]);
index c1f899a..73bc777 100644 (file)
@@ -20,3 +20,8 @@ $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeResolver'][1480314091] = [
         ) . 'ckeditor'
     ]
 ]);
+
+// Register the presets
+$GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['default'] = 'EXT:rte_ckeditor/Configuration/RTE/Default.yaml';
+$GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['minimal'] = 'EXT:rte_ckeditor/Configuration/RTE/Minimal.yaml';
+$GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['full'] = 'EXT:rte_ckeditor/Configuration/RTE/Full.yaml';