From f3b3342fa4c9de56b5678101d9f44ec92d4e5bf3 Mon Sep 17 00:00:00 2001
From: Fabian Galinski <fabian@sgalinski.de>
Date: Thu, 14 Sep 2017 02:44:54 +0200
Subject: [PATCH] [FEATURE] Integration of the tinyMCE for TYPO3 8

---
 .../Form/Element/CkEditorRichTextElement.php  | 376 ++++++++++++++++++
 ...ent.php => RtehtmlareaRichTextElement.php} |   2 +-
 .../Form/Resolver/RichTextNodeResolver.php    |  73 ++--
 Classes/Utility/VersionUtility.php            |  83 ++++
 ext_localconf.php                             |   8 +-
 5 files changed, 512 insertions(+), 30 deletions(-)
 create mode 100644 Classes/Form/Element/CkEditorRichTextElement.php
 rename Classes/Form/Element/{RichTextElement.php => RtehtmlareaRichTextElement.php} (99%)
 create mode 100644 Classes/Utility/VersionUtility.php

diff --git a/Classes/Form/Element/CkEditorRichTextElement.php b/Classes/Form/Element/CkEditorRichTextElement.php
new file mode 100644
index 0000000..4a2336b
--- /dev/null
+++ b/Classes/Form/Element/CkEditorRichTextElement.php
@@ -0,0 +1,376 @@
+<?php
+declare(strict_types=1);
+
+namespace SGalinski\Tinymce4Rte\Form\Element;
+
+/*
+ * 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 SGalinski\Tinymce\Loader;
+use TYPO3\CMS\Backend\Form\Element\AbstractFormElement;
+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;
+
+/**
+ * Render rich text editor in FormEngine
+ */
+class CkEditorRichTextElement extends AbstractFormElement {
+	/**
+	 * Default field wizards enabled for this element.
+	 *
+	 * @var array
+	 */
+	protected $defaultFieldWizard = [
+		'localizationStateSelector' => [
+			'renderType' => 'localizationStateSelector',
+		],
+		'otherLanguageContent' => [
+			'renderType' => 'otherLanguageContent',
+			'after' => [
+				'localizationStateSelector'
+			],
+		],
+		'defaultLanguageDifferences' => [
+			'renderType' => 'defaultLanguageDifferences',
+			'after' => [
+				'otherLanguageContent',
+			],
+		],
+	];
+
+	/**
+	 * This property contains configuration related to the RTE
+	 * But only the .editor configuration part
+	 *
+	 * @var array
+	 */
+	protected $rteConfiguration = [];
+
+	/**
+	 * Renders the ckeditor element
+	 *
+	 * @return array
+	 * @throws \UnexpectedValueException
+	 * @throws \BadFunctionCallException
+	 * @throws \InvalidArgumentException
+	 */
+	public function render(): array {
+		$resultArray = $this->initializeResultArray();
+		$parameterArray = $this->data['parameterArray'];
+		$config = $parameterArray['fieldConf']['config'];
+
+		$fieldId = $this->sanitizeFieldId($parameterArray['itemFormElName']);
+		$itemFormElementName = $this->data['parameterArray']['itemFormElName'];
+
+		$value = $this->data['parameterArray']['itemFormElValue'] ?? '';
+
+		$legacyWizards = $this->renderWizards();
+		$legacyFieldControlHtml = implode(LF, $legacyWizards['fieldControl']);
+		$legacyFieldWizardHtml = implode(LF, $legacyWizards['fieldWizard']);
+
+		$fieldInformationResult = $this->renderFieldInformation();
+		$fieldInformationHtml = $fieldInformationResult['html'];
+		$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, FALSE);
+
+		$fieldControlResult = $this->renderFieldControl();
+		$fieldControlHtml = $legacyFieldControlHtml . $fieldControlResult['html'];
+		$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, FALSE);
+
+		$fieldWizardResult = $this->renderFieldWizard();
+		$fieldWizardHtml = $legacyFieldWizardHtml . $fieldWizardResult['html'];
+		$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, FALSE);
+
+//		$attributes = [
+//			'style' => 'display:none',
+//			'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
+//			'id' => $fieldId,
+//			'name' => htmlspecialchars($itemFormElementName),
+//		];
+
+		// Modified by fgalinski - Start
+		$attributes = [
+			'id' => 'RTEarea' . $fieldId,
+			'name' => htmlspecialchars($itemFormElementName),
+			'class' => 'tinymce4_rte',
+		];
+		// Modified by fgalinski - End
+
+		$html = [];
+		$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
+		$html[] = $fieldInformationHtml;
+		$html[] = '<div class="form-control-wrap">';
+		$html[] = '<div class="form-wizards-wrap">';
+		$html[] = '<div class="form-wizards-element">';
+		$html[] = '<textarea ' . GeneralUtility::implodeAttributes($attributes, TRUE) . '>';
+		$html[] = htmlspecialchars($value);
+		$html[] = '</textarea>';
+		$html[] = '</div>';
+		$html[] = '<div class="form-wizards-items-aside">';
+		$html[] = '<div class="btn-group">';
+		$html[] = $fieldControlHtml;
+		$html[] = '</div>';
+		$html[] = '</div>';
+		$html[] = '<div class="form-wizards-items-bottom">';
+		$html[] = $fieldWizardHtml;
+		$html[] = '</div>';
+		$html[] = '</div>';
+		$html[] = '</div>';
+		$html[] = '</div>';
+
+		$resultArray['html'] = implode(LF, $html);
+
+		$this->rteConfiguration = $config['richtextConfiguration']['editor'];
+
+		// Modified by fgalinski - Start
+		/** @var Loader $tinyMCE */
+		$tinyMCE = GeneralUtility::makeInstance(Loader::class);
+		$tinyMCE->loadConfiguration($config['richtextConfiguration']['tinymceConfiguration']);
+		$contentCssArray = $config['richtextConfiguration']['contentCSS.'];
+		if (is_array($contentCssArray) && count($contentCssArray) > 0) {
+			$contentCssFileArray = [];
+			foreach ($contentCssArray as $contentCssKey => $contentCssFile) {
+				$contentCssFileAbs = GeneralUtility::getFileAbsFileName(trim($contentCssFile));
+				if (is_file($contentCssFileAbs)) {
+					$contentCssFileArray[] = GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . PathUtility::stripPathSitePrefix($contentCssFileAbs) . '?' . filemtime($contentCssFileAbs);
+				}
+			}
+			$tinyMCE->addConfigurationOption('content_css', implode(',', $contentCssFileArray));
+
+		}
+
+		$tinyMCE->addConfigurationOption(
+			'changeMethod', 'function() {
+				var TBE_EDITOR = window.TBE_EDITOR || null;
+				if (TBE_EDITOR && TBE_EDITOR.fieldChanged && typeof TBE_EDITOR.fieldChanged === \'function\') {
+					TBE_EDITOR.fieldChanged();
+				}
+			}'
+		);
+
+		$tinyMCE->addConfigurationOption('editornumber', $fieldId);
+
+		// IRRE
+		$resultArray['requireJsModules'] = $tinyMCE->loadJsViaRequireJS();
+		// Modified by fgalinski - End
+
+		return $resultArray;
+	}
+
+	/**
+	 * @param string $itemFormElementName
+	 *
+	 * @return string
+	 */
+	protected function sanitizeFieldId(string $itemFormElementName): string {
+		$fieldId = preg_replace('/[^a-zA-Z0-9_:.-]/', '_', $itemFormElementName);
+		return htmlspecialchars(preg_replace('/^[^a-zA-Z]/', 'x', $fieldId));
+	}
+
+	/**
+	 * Gets the JavaScript code for CKEditor module
+	 * Compiles the configuration, and then adds plugins
+	 *
+	 * @param string $fieldId
+	 *
+	 * @return string
+	 */
+	protected function getCkEditorRequireJsModuleCode(string $fieldId): string {
+		$configuration = $this->prepareConfigurationForEditor();
+
+		$externalPlugins = '';
+		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['resource']) . ',';
+			$externalPlugins .= '\'\');';
+		}
+
+		return 'function(CKEDITOR) {
+                ' . $externalPlugins . '
+                $(function(){
+                    CKEDITOR.replace("' . $fieldId . '", ' . json_encode($configuration) . ');
+                    require([\'jquery\', \'TYPO3/CMS/Backend/FormEngine\'], function($, FormEngine) {
+                        CKEDITOR.instances["' . $fieldId . '"].on(\'change\', function() {
+                            CKEDITOR.instances["' . $fieldId . '"].updateElement();
+                            FormEngine.Validation.validate();
+                            FormEngine.Validation.markFieldAsChanged($(\'#' . $fieldId . '\'));
+                        });
+                    });
+                });
+        }';
+	}
+
+	/**
+	 * Compiles the configuration set from the outside
+	 * to have it easily injected into the CKEditor.
+	 *
+	 * @return array the configuration
+	 */
+	protected function prepareConfigurationForEditor(): array {
+		// Ensure custom config is empty so nothing additional is loaded
+		// Of course this can be overridden by the editor configuration below
+		$configuration = [
+			'customConfig' => '',
+		];
+
+		if (is_array($this->rteConfiguration['config'])) {
+			$configuration = array_replace_recursive($configuration, $this->rteConfiguration['config']);
+		}
+		// 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');
+		}
+		$configuration['contentsLanguage'] = $this->getLanguageIsoCodeOfContent();
+
+		// Replace all label references
+		$configuration = $this->replaceLanguageFileReferences($configuration);
+		// 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;
+	}
+
+	/**
+	 * @return BackendUserAuthentication
+	 */
+	protected function getBackendUser() {
+		return $GLOBALS['BE_USER'];
+	}
+
+	/**
+	 * Determine the contents language iso code
+	 *
+	 * @return string
+	 */
+	protected function getLanguageIsoCodeOfContent(): string {
+		$currentLanguageUid = $this->data['databaseRow']['sys_language_uid'];
+		if (is_array($currentLanguageUid)) {
+			$currentLanguageUid = $currentLanguageUid[0];
+		}
+		$contentLanguageUid = (int) max($currentLanguageUid, 0);
+		if ($contentLanguageUid) {
+			$contentLanguage = $this->data['systemLanguageRows'][$currentLanguageUid]['iso'];
+		} else {
+			$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
+			$locales = GeneralUtility::makeInstance(Locales::class);
+			// If not found, default to 'en'
+			if (!in_array($contentLanguage, $locales->getLocales(), TRUE)) {
+				$contentLanguage = 'en';
+			}
+		}
+		return $contentLanguage;
+	}
+
+	/**
+	 * Add configuration to replace LLL: references with the translated value
+	 *
+	 * @param array $configuration
+	 *
+	 * @return array
+	 */
+	protected function replaceLanguageFileReferences(array $configuration): array {
+		foreach ($configuration as $key => $value) {
+			if (is_array($value)) {
+				$configuration[$key] = $this->replaceLanguageFileReferences($value);
+			} elseif (is_string($value) && stripos($value, 'LLL:') === 0) {
+				$configuration[$key] = $this->getLanguageService()->sL($value);
+			}
+		}
+		return $configuration;
+	}
+
+	/**
+	 * Add configuration to replace absolute EXT: paths with relative ones
+	 *
+	 * @param array $configuration
+	 *
+	 * @return array
+	 */
+	protected function replaceAbsolutePathsToRelativeResourcesPath(array $configuration): array {
+		foreach ($configuration as $key => $value) {
+			if (is_array($value)) {
+				$configuration[$key] = $this->replaceAbsolutePathsToRelativeResourcesPath($value);
+			} elseif (is_string($value) && stripos($value, 'EXT:') === 0) {
+				$configuration[$key] = $this->resolveUrlPath($value);
+			}
+		}
+		return $configuration;
+	}
+
+	/**
+	 * Resolves an EXT: syntax file to an absolute web URL
+	 *
+	 * @param string $value
+	 *
+	 * @return string
+	 */
+	protected function resolveUrlPath(string $value): string {
+		$value = GeneralUtility::getFileAbsFileName($value);
+		return PathUtility::getAbsoluteWebPath($value);
+	}
+
+	/**
+	 * Get configuration of external/additional plugins
+	 *
+	 * @return array
+	 */
+	protected function getExtraPlugins(): array {
+		$urlParameters = [
+			'P' => [
+				'table' => $this->data['tableName'],
+				'uid' => $this->data['databaseRow']['uid'],
+				'fieldName' => $this->data['fieldName'],
+				'recordType' => $this->data['recordTypeValue'],
+				'pid' => $this->data['effectivePid'],
+			]
+		];
+
+		$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;
+	}
+}
diff --git a/Classes/Form/Element/RichTextElement.php b/Classes/Form/Element/RtehtmlareaRichTextElement.php
similarity index 99%
rename from Classes/Form/Element/RichTextElement.php
rename to Classes/Form/Element/RtehtmlareaRichTextElement.php
index d3a9ab6..3ceee7f 100644
--- a/Classes/Form/Element/RichTextElement.php
+++ b/Classes/Form/Element/RtehtmlareaRichTextElement.php
@@ -39,7 +39,7 @@ use TYPO3\CMS\Lang\LanguageService;
 /**
  * Render rich text editor in FormEngine
  */
-class RichTextElement extends AbstractFormElement {
+class RtehtmlareaRichTextElement extends AbstractFormElement {
 	/**
 	 * Main result array as defined in initializeResultArray() of AbstractNode
 	 *
diff --git a/Classes/Form/Resolver/RichTextNodeResolver.php b/Classes/Form/Resolver/RichTextNodeResolver.php
index f1830d8..2501ab1 100644
--- a/Classes/Form/Resolver/RichTextNodeResolver.php
+++ b/Classes/Form/Resolver/RichTextNodeResolver.php
@@ -1,4 +1,5 @@
 <?php
+
 namespace SGalinski\Tinymce4Rte\Form\Resolver;
 
 /*
@@ -14,7 +15,9 @@ namespace SGalinski\Tinymce4Rte\Form\Resolver;
  * The TYPO3 project - inspiring people to share!
  */
 
-use SGalinski\Tinymce4Rte\Form\Element\RichTextElement;
+use SGalinski\Tinymce4Rte\Form\Element\CkEditorRichTextElement;
+use SGalinski\Tinymce4Rte\Form\Element\RtehtmlareaRichTextElement;
+use SGalinski\Tinymce4Rte\Utility\VersionUtility;
 use TYPO3\CMS\Backend\Form\NodeFactory;
 use TYPO3\CMS\Backend\Form\NodeResolverInterface;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
@@ -44,7 +47,7 @@ class RichTextNodeResolver implements NodeResolverInterface {
 	/**
 	 * Returns RichTextElement as class name if RTE widget should be rendered.
 	 *
-	 * @return string|void New class name or void if this resolver does not change current class name.
+	 * @return string|NULL New class name or void if this resolver does not change current class name.
 	 */
 	public function resolve() {
 		$table = $this->data['tableName'];
@@ -52,32 +55,54 @@ class RichTextNodeResolver implements NodeResolverInterface {
 		$row = $this->data['databaseRow'];
 		$parameterArray = $this->data['parameterArray'];
 		$backendUser = $this->getBackendUserAuthentication();
-
-		if (// This field is not read only
-			!$parameterArray['fieldConf']['config']['readOnly']
-			// If RTE is generally enabled by user settings and RTE object registry can return something valid
-			&& $backendUser->isRTE()
-		) {
-			// @todo: Most of this stuff is prepared by data providers within $this->data already
-			$specialConfiguration = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
-			// If "richtext" is within defaultExtras
-			if (isset($specialConfiguration['richtext'])) {
-				// Operates by reference on $row! 'pid' is changed ...
-				BackendUtility::fixVersioningPid($table, $row);
-				list($recordPid, $tsConfigPid) = BackendUtility::getTSCpidCached($table, $row['uid'], $row['pid']);
-				// If the pid-value is not negative (that is, a pid could NOT be fetched)
-				if ($tsConfigPid >= 0) {
-					// Fetch page ts config and do some magic with it to find out if RTE is disabled on TS level.
-					$rteSetup = $backendUser->getTSConfig('RTE', BackendUtility::getPagesTSconfig($recordPid));
-					$rteTcaTypeValue = $this->data['recordTypeValue'];
-					$rteSetupConfiguration = BackendUtility::RTEsetup($rteSetup['properties'], $table, $fieldName, $rteTcaTypeValue);
-					if (!$rteSetupConfiguration['disabled']) {
-						// Finally, we're sure the editor should really be rendered ...
-						return RichtextElement::class;
+		if (VersionUtility::isVersion870OrHigher()) {
+			$parameterArray = $this->data['parameterArray'];
+			$backendUser = $this->getBackendUserAuthentication();
+			if (// This field is not read only
+				!$parameterArray['fieldConf']['config']['readOnly']
+				// If RTE is generally enabled by user settings and RTE object registry can return something valid
+				&& $backendUser->isRTE()
+				// If RTE is enabled for field
+				&& isset($parameterArray['fieldConf']['config']['enableRichtext'])
+				&& (bool) $parameterArray['fieldConf']['config']['enableRichtext'] === TRUE
+				// If RTE config is found (prepared by TcaText data provider)
+				&& isset($parameterArray['fieldConf']['config']['richtextConfiguration'])
+				&& is_array($parameterArray['fieldConf']['config']['richtextConfiguration'])
+				// If RTE is not disabled on configuration level
+				&& !$parameterArray['fieldConf']['config']['richtextConfiguration']['disabled']
+			) {
+				return CkEditorRichTextElement::class;
+			}
+		} else {
+			if (// This field is not read only
+				!$parameterArray['fieldConf']['config']['readOnly']
+				// If RTE is generally enabled by user settings and RTE object registry can return something valid
+				&& $backendUser->isRTE()
+			) {
+				// @todo: Most of this stuff is prepared by data providers within $this->data already
+				$specialConfiguration = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
+				// If "richtext" is within defaultExtras
+				if (isset($specialConfiguration['richtext'])) {
+					// Operates by reference on $row! 'pid' is changed ...
+					BackendUtility::fixVersioningPid($table, $row);
+					list($recordPid, $tsConfigPid) = BackendUtility::getTSCpidCached($table, $row['uid'], $row['pid']);
+					// If the pid-value is not negative (that is, a pid could NOT be fetched)
+					if ($tsConfigPid >= 0) {
+						// Fetch page ts config and do some magic with it to find out if RTE is disabled on TS level.
+						$rteSetup = $backendUser->getTSConfig('RTE', BackendUtility::getPagesTSconfig($recordPid));
+						$rteTcaTypeValue = $this->data['recordTypeValue'];
+						$rteSetupConfiguration = BackendUtility::RTEsetup(
+							$rteSetup['properties'], $table, $fieldName, $rteTcaTypeValue
+						);
+						if (!$rteSetupConfiguration['disabled']) {
+							// Finally, we're sure the editor should really be rendered ...
+							return RtehtmlareaRichTextElement::class;
+						}
 					}
 				}
 			}
 		}
+
 		return NULL;
 	}
 
diff --git a/Classes/Utility/VersionUtility.php b/Classes/Utility/VersionUtility.php
new file mode 100644
index 0000000..1ba637f
--- /dev/null
+++ b/Classes/Utility/VersionUtility.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace SGalinski\Tinymce4Rte\Utility;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) sgalinski Internet Services (https://www.sgalinski.de)
+ *
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use TYPO3\CMS\Core\Utility\VersionNumberUtility;
+
+/**
+ * Helper class to detect the used TYPO3 version.
+ */
+class VersionUtility {
+	/**
+	 * Returns true if the current version ts TYPO3 6.2.
+	 *
+	 * @return bool
+	 */
+	public static function isVersion62() {
+		$versionNumber = self::getVersion();
+		return ($versionNumber >= 6002000 && $versionNumber < 7000000);
+	}
+
+	/**
+	 * Returns true if the current version ts TYPO3 7.6 and less version 8
+	 *
+	 * @return bool
+	 */
+	public static function isVersion76() {
+		$versionNumber = self::getVersion();
+		return ($versionNumber >= 7006000 && $versionNumber < 8000000);
+	}
+
+	/**
+	 * Returns true if the current version ts TYPO3 7.6 or later
+	 *
+	 * @return bool
+	 */
+	public static function isVersion76OOrHigher() {
+		return (self::getVersion() >= 7006000);
+	}
+
+	/**
+	 * Returns true if the current version ts TYPO3 8.7 or later
+	 *
+	 * @return bool
+	 */
+	public static function isVersion870OrHigher() {
+		return (self::getVersion() >= 8007000);
+	}
+
+	/**
+	 * Returns the current version as an integer.
+	 *
+	 * @return int
+	 */
+	protected static function getVersion() {
+		return VersionNumberUtility::convertVersionNumberToInteger(
+			VersionNumberUtility::getNumericTypo3Version()
+		);
+	}
+}
diff --git a/ext_localconf.php b/ext_localconf.php
index bbb2822..8121163 100644
--- a/ext_localconf.php
+++ b/ext_localconf.php
@@ -10,11 +10,11 @@ if (!$GLOBALS['TYPO3_CONF_VARS']['BE']['RTEenabled']) {
 }
 
 // Register FormEngine node type resolver hook to render RTE in FormEngine if enabled
-$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeResolver'][1442500255] = array(
+$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeResolver'][1442500255] = [
 	'nodeName' => 'text',
-	'priority' => 40,
+	'priority' => 60,
 	'class' => \SGalinski\Tinymce4Rte\Form\Resolver\RichTextNodeResolver::class,
-);
+];
 
 // load default PageTS config
 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig(
@@ -37,5 +37,3 @@ $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeResolver'][1442500255] = a
 // Registering soft reference parser for img tags in RTE content
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['softRefParser']['rtehtmlarea_images'] =
 	\SGalinski\Tinymce4Rte\Hook\SoftReferenceHook::class;
-
-?>
\ No newline at end of file
-- 
GitLab