Commit 8943e7ed authored by Fabian Galinski's avatar Fabian Galinski 😾
Browse files

[FEATURE] Better implementation for the TYPO3 8 integration

parent f3b3342f
<?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;
}
}
......@@ -39,7 +39,7 @@ use TYPO3\CMS\Lang\LanguageService;
/**
* Render rich text editor in FormEngine
*/
class RtehtmlareaRichTextElement extends AbstractFormElement {
class RichTextElement extends AbstractFormElement {
/**
* Main result array as defined in initializeResultArray() of AbstractNode
*
......@@ -359,55 +359,55 @@ class RtehtmlareaRichTextElement extends AbstractFormElement {
* @return void
*/
protected function enableRegisteredPlugins() {
$plugins = [
'TYPO3Image' => [
'objectReference' => Typo3Image::class,
],
'TYPO3Link' => [
'objectReference' => TYPO3Link::class,
],
];
foreach ($plugins as $pluginId => $pluginObjectConfiguration) {
if (is_array($pluginObjectConfiguration) && isset($pluginObjectConfiguration['objectReference'])) {
/** @var RteHtmlAreaApi $plugin */
$plugin = GeneralUtility::makeInstance($pluginObjectConfiguration['objectReference']);
$configuration = array(
'language' => $this->language,
'contentTypo3Language' => $this->contentTypo3Language,
'contentISOLanguage' => $this->contentISOLanguage,
'contentLanguageUid' => $this->contentLanguageUid,
'RTEsetup' => $this->vanillaRteTsConfig,
'client' => $this->client,
'thisConfig' => $this->processedRteConfiguration,
'specConf' => $this->defaultExtras,
);
if ($plugin->main($configuration)) {
$this->registeredPlugins[$pluginId] = $plugin;
// Override buttons from previously registered plugins
$pluginButtons = GeneralUtility::trimExplode(',', $plugin->getPluginButtons(), TRUE);
foreach ($this->pluginButton as $previousPluginId => $buttonList) {
$this->pluginButton[$previousPluginId] = implode(',', array_diff(GeneralUtility::trimExplode(',', $this->pluginButton[$previousPluginId], TRUE), $pluginButtons));
}
$this->pluginButton[$pluginId] = $plugin->getPluginButtons();
$pluginLabels = GeneralUtility::trimExplode(',', $plugin->getPluginLabels(), TRUE);
foreach ($this->pluginLabel as $previousPluginId => $labelList) {
$this->pluginLabel[$previousPluginId] = implode(',', array_diff(GeneralUtility::trimExplode(',', $this->pluginLabel[$previousPluginId], TRUE), $pluginLabels));
}
$this->pluginLabel[$pluginId] = $plugin->getPluginLabels();
$this->pluginEnabledArray[] = $pluginId;
}
}
}
// Process overrides
$hidePlugins = array();
foreach ($this->registeredPlugins as $pluginId => $plugin) {
/** @var RteHtmlAreaApi $plugin */
if ($plugin->addsButtons() && !$this->pluginButton[$pluginId]) {
$hidePlugins[] = $pluginId;
}
}
$this->pluginEnabledArray = array_unique(array_diff($this->pluginEnabledArray, $hidePlugins));
// $plugins = [
// 'TYPO3Image' => [
// 'objectReference' => Typo3Image::class,
// ],
// 'TYPO3Link' => [
// 'objectReference' => TYPO3Link::class,
// ],
// ];
// foreach ($plugins as $pluginId => $pluginObjectConfiguration) {
// if (is_array($pluginObjectConfiguration) && isset($pluginObjectConfiguration['objectReference'])) {
// /** @var RteHtmlAreaApi $plugin */
// $plugin = GeneralUtility::makeInstance($pluginObjectConfiguration['objectReference']);
// $configuration = array(
// 'language' => $this->language,
// 'contentTypo3Language' => $this->contentTypo3Language,
// 'contentISOLanguage' => $this->contentISOLanguage,
// 'contentLanguageUid' => $this->contentLanguageUid,
// 'RTEsetup' => $this->vanillaRteTsConfig,
// 'client' => $this->client,
// 'thisConfig' => $this->processedRteConfiguration,
// 'specConf' => $this->defaultExtras,
// );
// if ($plugin->main($configuration)) {
// $this->registeredPlugins[$pluginId] = $plugin;
// // Override buttons from previously registered plugins
// $pluginButtons = GeneralUtility::trimExplode(',', $plugin->getPluginButtons(), TRUE);
// foreach ($this->pluginButton as $previousPluginId => $buttonList) {
// $this->pluginButton[$previousPluginId] = implode(',', array_diff(GeneralUtility::trimExplode(',', $this->pluginButton[$previousPluginId], TRUE), $pluginButtons));
// }
// $this->pluginButton[$pluginId] = $plugin->getPluginButtons();
// $pluginLabels = GeneralUtility::trimExplode(',', $plugin->getPluginLabels(), TRUE);
// foreach ($this->pluginLabel as $previousPluginId => $labelList) {
// $this->pluginLabel[$previousPluginId] = implode(',', array_diff(GeneralUtility::trimExplode(',', $this->pluginLabel[$previousPluginId], TRUE), $pluginLabels));
// }
// $this->pluginLabel[$pluginId] = $plugin->getPluginLabels();
// $this->pluginEnabledArray[] = $pluginId;
// }
// }
// }
//
// // Process overrides
// $hidePlugins = array();
// foreach ($this->registeredPlugins as $pluginId => $plugin) {
// /** @var RteHtmlAreaApi $plugin */
// if ($plugin->addsButtons() && !$this->pluginButton[$pluginId]) {
// $hidePlugins[] = $pluginId;
// }
// }
// $this->pluginEnabledArray = array_unique(array_diff($this->pluginEnabledArray, $hidePlugins));
}
/**
......
......@@ -15,8 +15,7 @@ namespace SGalinski\Tinymce4Rte\Form\Resolver;
* The TYPO3 project - inspiring people to share!
*/
use SGalinski\Tinymce4Rte\Form\Element\CkEditorRichTextElement;
use SGalinski\Tinymce4Rte\Form\Element\RtehtmlareaRichTextElement;
use SGalinski\Tinymce4Rte\Form\Element\RichTextElement;
use SGalinski\Tinymce4Rte\Utility\VersionUtility;
use TYPO3\CMS\Backend\Form\NodeFactory;
use TYPO3\CMS\Backend\Form\NodeResolverInterface;
......@@ -71,7 +70,7 @@ class RichTextNodeResolver implements NodeResolverInterface {
// If RTE is not disabled on configuration level
&& !$parameterArray['fieldConf']['config']['richtextConfiguration']['disabled']
) {
return CkEditorRichTextElement::class;
return RichTextElement::class;
}
} else {
if (// This field is not read only
......@@ -96,7 +95,7 @@ class RichTextNodeResolver implements NodeResolverInterface {
);
if (!$rteSetupConfiguration['disabled']) {
// Finally, we're sure the editor should really be rendered ...
return RtehtmlareaRichTextElement::class;
return RichTextElement::class;
}
}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment