From 08669c3fbfb6b042e02d1ba432c6e86d4d2aba2d Mon Sep 17 00:00:00 2001 From: Oliver Hader <oliver@typo3.org> Date: Wed, 10 Nov 2021 21:24:56 +0100 Subject: [PATCH] [TASK] Reduce inline JavaScript in FormEngine AJAX responses Reduces amount of `requireJsModules` and `scriptCall` invocations in AJAX response handling and migrates to new `scriptItems` which is forwarded to JavaScriptHandler.js. Resolves: #95954 Releases: master Change-Id: I258da49fef46ccc36c602e0fd7c9a14ddb3cec1d Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72154 Tested-by: core-ci <typo3@b13.com> Tested-by: Oliver Bartsch <bo@cedev.de> Tested-by: Benni Mack <benni@typo3.org> Reviewed-by: Nikita Hovratov <nikita.h@live.de> Reviewed-by: Oliver Bartsch <bo@cedev.de> Reviewed-by: Benni Mack <benni@typo3.org> --- .../InlineRelation/AjaxDispatcher.ts | 10 +++- .../Public/TypeScript/FormEngineEvaluation.ts | 34 ++++++++++++ Build/types/TYPO3/index.d.ts | 1 + .../AbstractFormEngineAjaxController.php | 16 +++++- .../Controller/FormFlexAjaxController.php | 23 +++----- .../Controller/FormInlineAjaxController.php | 33 +++++++----- .../Controller/SiteInlineAjaxController.php | 24 ++++----- .../backend/Classes/Form/AbstractNode.php | 2 + .../Form/Element/CustomEvaluationTrait.php | 52 +++++++++++++++++++ .../Form/Element/InputColorPickerElement.php | 8 ++- .../Classes/Form/Element/InputLinkElement.php | 6 +-- .../Classes/Form/Element/InputTextElement.php | 6 +-- .../SelectMultipleSideBySideElement.php | 1 + .../Classes/Form/FormResultCompiler.php | 2 + .../InlineRelation/AjaxDispatcher.js | 2 +- .../Public/JavaScript/FormEngineValidation.js | 23 +++++++- .../Classes/Evaluation/SourceHost.php | 20 +++---- .../Public/JavaScript/FormEngineEvaluation.js | 13 +++++ 18 files changed, 206 insertions(+), 70 deletions(-) create mode 100644 Build/Sources/TypeScript/redirects/Resources/Public/TypeScript/FormEngineEvaluation.ts create mode 100644 typo3/sysext/backend/Classes/Form/Element/CustomEvaluationTrait.php create mode 100644 typo3/sysext/redirects/Resources/Public/JavaScript/FormEngineEvaluation.js diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/FormEngine/InlineRelation/AjaxDispatcher.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/FormEngine/InlineRelation/AjaxDispatcher.ts index 143126e02c64..3293d5d3d0aa 100644 --- a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/FormEngine/InlineRelation/AjaxDispatcher.ts +++ b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/FormEngine/InlineRelation/AjaxDispatcher.ts @@ -13,6 +13,7 @@ import {AjaxResponse} from 'TYPO3/CMS/Core/Ajax/AjaxResponse'; import AjaxRequest = require('TYPO3/CMS/Core/Ajax/AjaxRequest'); +import javaScriptHandler = require('TYPO3/CMS/Core/JavaScriptHandler'); import Notification = require('../../Notification'); import Utility = require('../../Utility'); @@ -33,6 +34,7 @@ interface Response { inlineData: object; requireJsModules: string[]; scriptCall: string[]; + scriptItems?: any[]; } export class AjaxDispatcher { @@ -117,14 +119,18 @@ export class AjaxDispatcher { TYPO3.settings.FormEngineInline = Utility.mergeDeep(TYPO3.settings.FormEngineInline, json.inlineData); } + if (json.scriptItems instanceof Array && json.scriptItems.length > 0) { + javaScriptHandler.processItems(json.scriptItems, true); + } + + // @todo deprecate or remove with TYPO3 v12.0 if (typeof json.requireJsModules === 'object') { for (let requireJsModule of json.requireJsModules) { - // @todo https://forge.typo3.org/issues/95874 new Function(requireJsModule)(); } } - // TODO: This is subject to be removed + // @todo deprecate or remove with TYPO3 v12.0 if (json.scriptCall && json.scriptCall.length > 0) { for (const scriptCall of json.scriptCall) { // eslint-disable-next-line no-eval diff --git a/Build/Sources/TypeScript/redirects/Resources/Public/TypeScript/FormEngineEvaluation.ts b/Build/Sources/TypeScript/redirects/Resources/Public/TypeScript/FormEngineEvaluation.ts new file mode 100644 index 000000000000..21a293e530be --- /dev/null +++ b/Build/Sources/TypeScript/redirects/Resources/Public/TypeScript/FormEngineEvaluation.ts @@ -0,0 +1,34 @@ +/* + * 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! + */ + +import FormEngineValidation = require('TYPO3/CMS/Backend/FormEngineValidation') + +/** + * Module: TYPO3/CMS/Redirects/FormEngineEvaluation + * @exports TYPO3/CMS/Redirects/FormEngineEvaluation + */ +export class FormEngineEvaluation { + static registerCustomEvaluation(name: string): void { + FormEngineValidation.registerCustomEvaluation(name, FormEngineEvaluation.evaluateSourceHost); + } + + public static evaluateSourceHost(value: string): string { + if (value === '*') { + return value; + } + if (!value.includes('://')) { + value = 'http://' + value; + } + return (new URL(value)).host; + } +} diff --git a/Build/types/TYPO3/index.d.ts b/Build/types/TYPO3/index.d.ts index c3e77596006d..35117edf8356 100644 --- a/Build/types/TYPO3/index.d.ts +++ b/Build/types/TYPO3/index.d.ts @@ -42,6 +42,7 @@ declare namespace TYPO3 { public readonly errorClass: string; public markFieldAsChanged(field: HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement|JQuery): void; public initializeInputFields(): void; + public registerCustomEvaluation(name: string, handler: Function): void; public validate(section?: Element): void; public validateField(field: HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement|JQuery, value?: string): void; } diff --git a/typo3/sysext/backend/Classes/Controller/AbstractFormEngineAjaxController.php b/typo3/sysext/backend/Classes/Controller/AbstractFormEngineAjaxController.php index 340ff8e6b4be..0100b26aca0e 100644 --- a/typo3/sysext/backend/Classes/Controller/AbstractFormEngineAjaxController.php +++ b/typo3/sysext/backend/Classes/Controller/AbstractFormEngineAjaxController.php @@ -20,6 +20,7 @@ namespace TYPO3\CMS\Backend\Controller; use TYPO3\CMS\Backend\Form\FormResultTrait; use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Localization\LocalizationFactory; +use TYPO3\CMS\Core\Page\JavaScriptItems; use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -41,9 +42,10 @@ abstract class AbstractFormEngineAjaxController * that need to be loaded and evaluated by JavaScript. * * @param array $result + * @param bool $skipInstructions whether to skip `JavaScriptModuleInstruction` * @return array */ - protected function createExecutableStringRepresentationOfRegisteredRequireJsModules(array $result): array + protected function createExecutableStringRepresentationOfRegisteredRequireJsModules(array $result, bool $skipInstructions = false): array { if (empty($result['requireJsModules'])) { return []; @@ -54,6 +56,9 @@ abstract class AbstractFormEngineAjaxController $callback = null; // @todo This is a temporary "solution" and shall be handled in JavaScript directly if ($module instanceof JavaScriptModuleInstruction) { + if ($skipInstructions) { + continue; + } $moduleName = $module->getName(); $callbackRef = $module->getExportName() ? '__esModule' : 'subjectRef'; $inlineCode = $this->serializeJavaScriptModuleInstructionItems($module); @@ -83,6 +88,15 @@ abstract class AbstractFormEngineAjaxController return $requireJs; } + protected function addRegisteredRequireJsModulesToJavaScriptItems(array $result, JavaScriptItems $items): void + { + foreach ($result['requireJsModules'] as $module) { + if ($module instanceof JavaScriptModuleInstruction) { + $items->addJavaScriptModuleInstruction($module); + } + } + } + /** * Resolve a CSS file position, possibly prefixed with 'EXT:' * diff --git a/typo3/sysext/backend/Classes/Controller/FormFlexAjaxController.php b/typo3/sysext/backend/Classes/Controller/FormFlexAjaxController.php index a9d4c86b4035..a94919a05133 100644 --- a/typo3/sysext/backend/Classes/Controller/FormFlexAjaxController.php +++ b/typo3/sysext/backend/Classes/Controller/FormFlexAjaxController.php @@ -25,6 +25,7 @@ use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord; use TYPO3\CMS\Backend\Form\NodeFactory; use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools; use TYPO3\CMS\Core\Http\JsonResponse; +use TYPO3\CMS\Core\Page\JavaScriptItems; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\StringUtility; @@ -157,13 +158,16 @@ class FormFlexAjaxController extends AbstractFormEngineAjaxController $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class); $formData['renderType'] = 'flexFormContainerContainer'; $newContainerResult = $nodeFactory->create($formData)->render(); + $scriptItems = GeneralUtility::makeInstance(JavaScriptItems::class); $jsonResult = [ 'html' => $newContainerResult['html'], 'stylesheetFiles' => [], + 'scriptItems' => $scriptItems, 'scriptCall' => [], ]; + // @todo deprecate with TYPO3 v12.0 foreach ($newContainerResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) { $jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost; } @@ -178,22 +182,11 @@ class FormFlexAjaxController extends AbstractFormEngineAjaxController $this->getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile) ); } - $javaScriptCode = []; - $javaScriptCode[] = 'if (typeof TYPO3 === \'undefined\' || typeof TYPO3.lang === \'undefined\') {'; - $javaScriptCode[] = ' TYPO3.lang = {}'; - $javaScriptCode[] = '}'; - $javaScriptCode[] = 'var additionalInlineLanguageLabels = ' . json_encode($labels) . ';'; - $javaScriptCode[] = 'for (var attributeName in additionalInlineLanguageLabels) {'; - $javaScriptCode[] = ' if (typeof TYPO3.lang[attributeName] === \'undefined\') {'; - $javaScriptCode[] = ' TYPO3.lang[attributeName] = additionalInlineLanguageLabels[attributeName]'; - $javaScriptCode[] = ' }'; - $javaScriptCode[] = '}'; - - $jsonResult['scriptCall'][] = implode(LF, $javaScriptCode); + $scriptItems->addGlobalAssignment(['TYPO3' => ['lang' => $labels]]); } - - $requireJsModule = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($newContainerResult); - $jsonResult['scriptCall'] = array_merge($requireJsModule, $jsonResult['scriptCall']); + $this->addRegisteredRequireJsModulesToJavaScriptItems($newContainerResult, $scriptItems); + // @todo deprecate modules with arbitrary JavaScript callback function in TYPO3 v12.0 + $jsonResult['requireJsModules'] = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($newContainerResult); return new JsonResponse($jsonResult); } diff --git a/typo3/sysext/backend/Classes/Controller/FormInlineAjaxController.php b/typo3/sysext/backend/Classes/Controller/FormInlineAjaxController.php index cc60ed36f1ef..bdf872b3f147 100644 --- a/typo3/sysext/backend/Classes/Controller/FormInlineAjaxController.php +++ b/typo3/sysext/backend/Classes/Controller/FormInlineAjaxController.php @@ -28,6 +28,8 @@ use TYPO3\CMS\Core\DataHandling\DataHandler; use TYPO3\CMS\Core\Http\JsonResponse; use TYPO3\CMS\Core\Messaging\AbstractMessage; use TYPO3\CMS\Core\Messaging\FlashMessageService; +use TYPO3\CMS\Core\Page\JavaScriptItems; +use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; @@ -144,6 +146,7 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController $jsonArray = [ 'data' => '', 'stylesheetFiles' => [], + 'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class), 'scriptCall' => [], 'compilerInput' => [ 'uid' => $childData['databaseRow']['uid'], @@ -216,6 +219,7 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController $jsonArray = [ 'data' => '', 'stylesheetFiles' => [], + 'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class), 'scriptCall' => [], ]; @@ -248,6 +252,7 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController $jsonArray = [ 'data' => '', 'stylesheetFiles' => [], + 'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class), 'compilerInput' => [ 'localize' => [], ], @@ -539,6 +544,9 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController */ protected function mergeChildResultIntoJsonResult(array $jsonResult, array $childResult) { + /** @var JavaScriptItems $scriptItems */ + $scriptItems = $jsonResult['scriptItems']; + $jsonResult['data'] .= $childResult['html']; $jsonResult['stylesheetFiles'] = []; foreach ($childResult['stylesheetFiles'] as $stylesheetFile) { @@ -547,6 +555,7 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController if (!empty($childResult['inlineData'])) { $jsonResult['inlineData'] = $childResult['inlineData']; } + // @todo deprecate with TYPO3 v12.0 foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) { $jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost; } @@ -558,20 +567,16 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController $this->getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile) ); } - $javaScriptCode = []; - $javaScriptCode[] = 'if (typeof TYPO3 === \'undefined\' || typeof TYPO3.lang === \'undefined\') {'; - $javaScriptCode[] = ' TYPO3.lang = {}'; - $javaScriptCode[] = '}'; - $javaScriptCode[] = 'var additionalInlineLanguageLabels = ' . json_encode($labels) . ';'; - $javaScriptCode[] = 'for (var attributeName in additionalInlineLanguageLabels) {'; - $javaScriptCode[] = ' if (typeof TYPO3.lang[attributeName] === \'undefined\') {'; - $javaScriptCode[] = ' TYPO3.lang[attributeName] = additionalInlineLanguageLabels[attributeName]'; - $javaScriptCode[] = ' }'; - $javaScriptCode[] = '}'; - - $jsonResult['scriptCall'][] = implode(LF, $javaScriptCode); + $scriptItems->addGlobalAssignment(['TYPO3' => ['lang' => $labels]]); + } + foreach ($childResult['requireJsModules'] ?? [] as $module) { + if ($module instanceof JavaScriptModuleInstruction) { + $scriptItems->addJavaScriptModuleInstruction($module); + } } - $jsonResult['requireJsModules'] = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult); + $this->addRegisteredRequireJsModulesToJavaScriptItems($childResult, $scriptItems); + // @todo deprecate modules with arbitrary JavaScript callback function in TYPO3 v12.0 + $jsonResult['requireJsModules'] = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult, true); return $jsonResult; } @@ -669,6 +674,8 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController * * @param string $message The error message to be shown * @return array The error message in a JSON array + * @todo remove with TYPO3 v12.0 + * @internal */ protected function getErrorMessageForAJAX($message) { diff --git a/typo3/sysext/backend/Classes/Controller/SiteInlineAjaxController.php b/typo3/sysext/backend/Classes/Controller/SiteInlineAjaxController.php index c14d5fb939e0..ad76f4e68778 100644 --- a/typo3/sysext/backend/Classes/Controller/SiteInlineAjaxController.php +++ b/typo3/sysext/backend/Classes/Controller/SiteInlineAjaxController.php @@ -26,6 +26,7 @@ use TYPO3\CMS\Backend\Form\InlineStackProcessor; use TYPO3\CMS\Backend\Form\NodeFactory; use TYPO3\CMS\Core\Http\JsonResponse; use TYPO3\CMS\Core\Localization\Locales; +use TYPO3\CMS\Core\Page\JavaScriptItems; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Utility\ArrayUtility; @@ -171,6 +172,7 @@ class SiteInlineAjaxController extends AbstractFormEngineAjaxController $jsonArray = [ 'data' => '', 'stylesheetFiles' => [], + 'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class), 'scriptCall' => [], 'compilerInput' => [ 'uid' => $childData['databaseRow']['uid'], @@ -243,6 +245,7 @@ class SiteInlineAjaxController extends AbstractFormEngineAjaxController $jsonArray = [ 'data' => '', 'stylesheetFiles' => [], + 'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class), 'scriptCall' => [], ]; @@ -316,6 +319,9 @@ class SiteInlineAjaxController extends AbstractFormEngineAjaxController */ protected function mergeChildResultIntoJsonResult(array $jsonResult, array $childResult): array { + /** @var JavaScriptItems $scriptItems */ + $scriptItems = $jsonResult['scriptItems']; + $jsonResult['data'] .= $childResult['html']; $jsonResult['stylesheetFiles'] = []; foreach ($childResult['stylesheetFiles'] as $stylesheetFile) { @@ -324,6 +330,7 @@ class SiteInlineAjaxController extends AbstractFormEngineAjaxController if (!empty($childResult['inlineData'])) { $jsonResult['inlineData'] = $childResult['inlineData']; } + // @todo deprecate with TYPO3 v12.0 foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) { $jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost; } @@ -335,20 +342,11 @@ class SiteInlineAjaxController extends AbstractFormEngineAjaxController $this->getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile) ); } - $javaScriptCode = []; - $javaScriptCode[] = 'if (typeof TYPO3 === \'undefined\' || typeof TYPO3.lang === \'undefined\') {'; - $javaScriptCode[] = ' TYPO3.lang = {}'; - $javaScriptCode[] = '}'; - $javaScriptCode[] = 'var additionalInlineLanguageLabels = ' . json_encode($labels) . ';'; - $javaScriptCode[] = 'for (var attributeName in additionalInlineLanguageLabels) {'; - $javaScriptCode[] = ' if (typeof TYPO3.lang[attributeName] === \'undefined\') {'; - $javaScriptCode[] = ' TYPO3.lang[attributeName] = additionalInlineLanguageLabels[attributeName]'; - $javaScriptCode[] = ' }'; - $javaScriptCode[] = '}'; - - $jsonResult['scriptCall'][] = implode(LF, $javaScriptCode); + $scriptItems->addGlobalAssignment(['TYPO3' => ['lang' => $labels]]); } - $jsonResult['requireJsModules'] = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult); + $this->addRegisteredRequireJsModulesToJavaScriptItems($childResult, $scriptItems); + // @todo deprecate modules with arbitrary JavaScript callback function in TYPO3 v12.0 + $jsonResult['requireJsModules'] = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult, true); return $jsonResult; } diff --git a/typo3/sysext/backend/Classes/Form/AbstractNode.php b/typo3/sysext/backend/Classes/Form/AbstractNode.php index b5a3ebdd6154..34cef0588dbe 100644 --- a/typo3/sysext/backend/Classes/Form/AbstractNode.php +++ b/typo3/sysext/backend/Classes/Form/AbstractNode.php @@ -95,6 +95,7 @@ abstract class AbstractNode implements NodeInterface, LoggerAwareInterface protected function initializeResultArray(): array { return [ + // @todo deprecate inline JavaScript in TYPO3 v12.0 'additionalJavaScriptPost' => [], 'additionalHiddenFields' => [], 'additionalInlineLanguageLabelFiles' => [], @@ -123,6 +124,7 @@ abstract class AbstractNode implements NodeInterface, LoggerAwareInterface if ($mergeHtml && !empty($childReturn['html'])) { $existing['html'] .= LF . $childReturn['html']; } + // @todo deprecate inline JavaScript in TYPO3 v12.0 foreach ($childReturn['additionalJavaScriptPost'] ?? [] as $value) { $existing['additionalJavaScriptPost'][] = $value; } diff --git a/typo3/sysext/backend/Classes/Form/Element/CustomEvaluationTrait.php b/typo3/sysext/backend/Classes/Form/Element/CustomEvaluationTrait.php new file mode 100644 index 000000000000..eccf56ef5a84 --- /dev/null +++ b/typo3/sysext/backend/Classes/Form/Element/CustomEvaluationTrait.php @@ -0,0 +1,52 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Backend\Form\Element; + +use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Trait handling custom `eval` implementations. + */ +trait CustomEvaluationTrait +{ + protected function resolveJavaScriptEvaluation(array $resultArray, string $name, ?object $evalObject): array + { + if (!is_object($evalObject) || !method_exists($evalObject, 'returnFieldJS')) { + return $resultArray; + } + + $javaScriptEvaluation = $evalObject->returnFieldJS(); + if ($javaScriptEvaluation instanceof JavaScriptModuleInstruction) { + // just use the module name and export-name + $resultArray['requireJsModules'][] = JavaScriptModuleInstruction::forRequireJS( + $javaScriptEvaluation->getName(), + $javaScriptEvaluation->getExportName() + )->invoke('registerCustomEvaluation', $name); + } else { + // @todo deprecate inline JavaScript in TYPO3 v12.0 + $resultArray['additionalJavaScriptPost'][] = sprintf( + 'TBE_EDITOR.customEvalFunctions[%s] = function(value) { %s };', + GeneralUtility::quoteJSvalue($name), + $javaScriptEvaluation + ); + } + + return $resultArray; + } +} diff --git a/typo3/sysext/backend/Classes/Form/Element/InputColorPickerElement.php b/typo3/sysext/backend/Classes/Form/Element/InputColorPickerElement.php index 1e5bc4e4580d..5c2eac87d6be 100644 --- a/typo3/sysext/backend/Classes/Form/Element/InputColorPickerElement.php +++ b/typo3/sysext/backend/Classes/Form/Element/InputColorPickerElement.php @@ -26,6 +26,8 @@ use TYPO3\CMS\Core\Utility\StringUtility; */ class InputColorPickerElement extends AbstractFormElement { + use CustomEvaluationTrait; + /** * Default field information enabled for this element. * @@ -67,7 +69,6 @@ class InputColorPickerElement extends AbstractFormElement */ public function render() { - $evalData = ''; $languageService = $this->getLanguageService(); $table = $this->data['tableName']; @@ -117,10 +118,7 @@ class InputColorPickerElement extends AbstractFormElement ]; $itemValue = $evalObj->deevaluateFieldValue($_params); } - if (method_exists($evalObj, 'returnFieldJS')) { - // @todo: variable $evalData must be replaced with $func - $resultArray['additionalJavaScriptPost'][] = 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($evalData) . '] = function(value) {' . $evalObj->returnFieldJS() . '};'; - } + $resultArray = $this->resolveJavaScriptEvaluation($resultArray, $func, $evalObj); } } } diff --git a/typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php b/typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php index d801e21917fe..a960e39f0d3b 100644 --- a/typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php +++ b/typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php @@ -39,6 +39,7 @@ use TYPO3\CMS\Frontend\Service\TypoLinkCodecService; */ class InputLinkElement extends AbstractFormElement { + use CustomEvaluationTrait; use OnFieldChangeTrait; /** @@ -144,10 +145,7 @@ class InputLinkElement extends AbstractFormElement ]; $itemValue = $evalObj->deevaluateFieldValue($_params); } - if (method_exists($evalObj, 'returnFieldJS')) { - $resultArray['additionalJavaScriptPost'][] = 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($func) . ']' - . ' = function(value) {' . $evalObj->returnFieldJS() . '};'; - } + $resultArray = $this->resolveJavaScriptEvaluation($resultArray, $func, $evalObj); } } } diff --git a/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php b/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php index 5226c8ea603d..95f9eb00c695 100644 --- a/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php +++ b/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php @@ -30,6 +30,7 @@ use TYPO3\CMS\Core\Utility\StringUtility; */ class InputTextElement extends AbstractFormElement { + use CustomEvaluationTrait; use OnFieldChangeTrait; /** @@ -135,10 +136,7 @@ class InputTextElement extends AbstractFormElement ]; $itemValue = $evalObj->deevaluateFieldValue($_params); } - if (method_exists($evalObj, 'returnFieldJS')) { - $resultArray['additionalJavaScriptPost'][] = 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($func) . ']' - . ' = function(value) {' . $evalObj->returnFieldJS() . '};'; - } + $resultArray = $this->resolveJavaScriptEvaluation($resultArray, $func, $evalObj); } } } diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php index 86740a8867d4..8bde10644ed3 100644 --- a/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php +++ b/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php @@ -97,6 +97,7 @@ class SelectMultipleSideBySideElement extends AbstractFormElement protected function renderFieldControl(): array { $alternativeResult = [ + // @todo deprecate inline JavaScript in TYPO3 v12.0 'additionalJavaScriptPost' => [], 'additionalHiddenFields' => [], 'additionalInlineLanguageLabelFiles' => [], diff --git a/typo3/sysext/backend/Classes/Form/FormResultCompiler.php b/typo3/sysext/backend/Classes/Form/FormResultCompiler.php index 95e56e04941b..10f5caaa2d27 100644 --- a/typo3/sysext/backend/Classes/Form/FormResultCompiler.php +++ b/typo3/sysext/backend/Classes/Form/FormResultCompiler.php @@ -93,6 +93,7 @@ class FormResultCompiler public function mergeResult(array $resultArray) { $this->doSaveFieldName = $resultArray['doSaveFieldName'] ?? ''; + // @todo deprecate inline JavaScript in TYPO3 v12.0 foreach ($resultArray['additionalJavaScriptPost'] as $element) { $this->additionalJavaScriptPost[] = $element; } @@ -253,6 +254,7 @@ class FormResultCompiler if (!empty($this->inlineData)) { $pageRenderer->addInlineSettingArray('FormEngineInline', $this->inlineData); } + // @todo deprecate inline JavaScript in TYPO3 v12.0 $out = LF . implode(LF, $this->additionalJavaScriptPost); return $html . LF . "\t" . GeneralUtility::wrapJS($out); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/InlineRelation/AjaxDispatcher.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/InlineRelation/AjaxDispatcher.js index a2924f46feca..f69b1d5a1353 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/InlineRelation/AjaxDispatcher.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/InlineRelation/AjaxDispatcher.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -define(["require","exports","TYPO3/CMS/Core/Ajax/AjaxRequest","../../Notification","../../Utility"],(function(require,exports,AjaxRequest,Notification,Utility){"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.AjaxDispatcher=void 0;class AjaxDispatcher{constructor(e){this.objectGroup=null,this.objectGroup=e}newRequest(e){return new AjaxRequest(e)}getEndpoint(e){if(void 0!==TYPO3.settings.ajaxUrls[e])return TYPO3.settings.ajaxUrls[e];throw'Undefined endpoint for route "'+e+'"'}send(e,t){const s=e.post(this.createRequestBody(t)).then(async e=>this.processResponse(await e.resolve()));return s.catch(e=>{Notification.error("Error "+e.message)}),s}createRequestBody(e){const t={};for(let s=0;s<e.length;s++)t["ajax["+s+"]"]=e[s];return t["ajax[context]"]=JSON.stringify(this.getContext()),t}getContext(){let e;return void 0!==TYPO3.settings.FormEngineInline.config[this.objectGroup]&&void 0!==TYPO3.settings.FormEngineInline.config[this.objectGroup].context&&(e=TYPO3.settings.FormEngineInline.config[this.objectGroup].context),e}processResponse(json){if(json.hasErrors)for(const e of json.messages)Notification.error(e.title,e.message);if(json.stylesheetFiles)for(const[e,t]of json.stylesheetFiles.entries()){if(!t)break;const s=document.createElement("link");s.rel="stylesheet",s.type="text/css",s.href=t,document.querySelector("head").appendChild(s),delete json.stylesheetFiles[e]}if("object"==typeof json.inlineData&&(TYPO3.settings.FormEngineInline=Utility.mergeDeep(TYPO3.settings.FormEngineInline,json.inlineData)),"object"==typeof json.requireJsModules)for(let e of json.requireJsModules)new Function(e)();if(json.scriptCall&&json.scriptCall.length>0)for(const scriptCall of json.scriptCall)eval(scriptCall);return json}}exports.AjaxDispatcher=AjaxDispatcher})); \ No newline at end of file +define(["require","exports","TYPO3/CMS/Core/Ajax/AjaxRequest","TYPO3/CMS/Core/JavaScriptHandler","../../Notification","../../Utility"],(function(require,exports,AjaxRequest,javaScriptHandler,Notification,Utility){"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.AjaxDispatcher=void 0;class AjaxDispatcher{constructor(e){this.objectGroup=null,this.objectGroup=e}newRequest(e){return new AjaxRequest(e)}getEndpoint(e){if(void 0!==TYPO3.settings.ajaxUrls[e])return TYPO3.settings.ajaxUrls[e];throw'Undefined endpoint for route "'+e+'"'}send(e,t){const s=e.post(this.createRequestBody(t)).then(async e=>this.processResponse(await e.resolve()));return s.catch(e=>{Notification.error("Error "+e.message)}),s}createRequestBody(e){const t={};for(let s=0;s<e.length;s++)t["ajax["+s+"]"]=e[s];return t["ajax[context]"]=JSON.stringify(this.getContext()),t}getContext(){let e;return void 0!==TYPO3.settings.FormEngineInline.config[this.objectGroup]&&void 0!==TYPO3.settings.FormEngineInline.config[this.objectGroup].context&&(e=TYPO3.settings.FormEngineInline.config[this.objectGroup].context),e}processResponse(json){if(json.hasErrors)for(const e of json.messages)Notification.error(e.title,e.message);if(json.stylesheetFiles)for(const[e,t]of json.stylesheetFiles.entries()){if(!t)break;const s=document.createElement("link");s.rel="stylesheet",s.type="text/css",s.href=t,document.querySelector("head").appendChild(s),delete json.stylesheetFiles[e]}if("object"==typeof json.inlineData&&(TYPO3.settings.FormEngineInline=Utility.mergeDeep(TYPO3.settings.FormEngineInline,json.inlineData)),json.scriptItems instanceof Array&&json.scriptItems.length>0&&javaScriptHandler.processItems(json.scriptItems,!0),"object"==typeof json.requireJsModules)for(let e of json.requireJsModules)new Function(e)();if(json.scriptCall&&json.scriptCall.length>0)for(const scriptCall of json.scriptCall)eval(scriptCall);return json}}exports.AjaxDispatcher=AjaxDispatcher})); \ No newline at end of file diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js index 10b842d41553..d1dc1accf8f4 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js @@ -45,6 +45,11 @@ define([ passwordDummy: '********' }; + /** + * @type {Map<string, Function>} + */ + const customEvaluations = new Map(); + /** * Initialize validation for the first time */ @@ -133,6 +138,16 @@ define([ $humanReadableField.attr('data-formengine-input-initialized', 'true'); }; + /** + * @param {string} name + * @param {Function} handler + */ + FormEngineValidation.registerCustomEvaluation = function(name, handler) { + if (!customEvaluations.has(name)) { + customEvaluations.set(name, handler); + } + } + /** * Format field value * @@ -507,8 +522,12 @@ define([ // password is only a display evaluation, we ignore it break; default: - if (typeof TBE_EDITOR.customEvalFunctions !== 'undefined' && typeof TBE_EDITOR.customEvalFunctions[command] === 'function') { - returnValue = TBE_EDITOR.customEvalFunctions[command](value); + if (typeof TBE_EDITOR.customEvalFunctions !== 'undefined') { + if (customEvaluations.has(command)) { + returnValue = customEvaluations.get(command).call(null, value); + } else if (typeof TBE_EDITOR.customEvalFunctions[command] === 'function') { + returnValue = TBE_EDITOR.customEvalFunctions[command](value); + } } } return returnValue; diff --git a/typo3/sysext/redirects/Classes/Evaluation/SourceHost.php b/typo3/sysext/redirects/Classes/Evaluation/SourceHost.php index 52c07eef07be..250f20a6a48b 100644 --- a/typo3/sysext/redirects/Classes/Evaluation/SourceHost.php +++ b/typo3/sysext/redirects/Classes/Evaluation/SourceHost.php @@ -17,6 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Redirects\Evaluation; +use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction; use TYPO3\CMS\Core\Utility\PathUtility; /** @@ -28,19 +29,18 @@ use TYPO3\CMS\Core\Utility\PathUtility; class SourceHost { /** - * JavaScript code for client side validation/evaluation - * (invoked by FormEngine when editing redirect entities) + * Returns JavaScript instruction for client side validation/evaluation + * (invoked by FormEngine when editing redirect entities). * - * @return string JavaScript code for client side validation/evaluation + * Returned `JavaScriptModuleInstruction` delegates handling to corresponding + * RequireJS module, having a method `evaluateSourceHost` that deals with that + * evaluation request. + * + * @return JavaScriptModuleInstruction */ - public function returnFieldJS(): string + public function returnFieldJS(): JavaScriptModuleInstruction { - $jsCode = []; - $jsCode[] = 'if (value === \'*\') {return value;}'; - $jsCode[] = 'var parser = document.createElement(\'a\');'; - $jsCode[] = 'parser.href = value.indexOf(\'://\') != -1 ? value : \'http://\' + value;'; - $jsCode[] = 'return parser.host;'; - return implode(' ', $jsCode); + return JavaScriptModuleInstruction::forRequireJS('TYPO3/CMS/Redirects/FormEngineEvaluation', 'FormEngineEvaluation'); } /** diff --git a/typo3/sysext/redirects/Resources/Public/JavaScript/FormEngineEvaluation.js b/typo3/sysext/redirects/Resources/Public/JavaScript/FormEngineEvaluation.js new file mode 100644 index 000000000000..a54e08b445c4 --- /dev/null +++ b/typo3/sysext/redirects/Resources/Public/JavaScript/FormEngineEvaluation.js @@ -0,0 +1,13 @@ +/* + * 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! + */ +define(["require","exports","TYPO3/CMS/Backend/FormEngineValidation"],(function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.FormEngineEvaluation=void 0;class o{static registerCustomEvaluation(e){i.registerCustomEvaluation(e,o.evaluateSourceHost)}static evaluateSourceHost(e){return"*"===e?e:(e.includes("://")||(e="http://"+e),new URL(e).host)}}t.FormEngineEvaluation=o})); \ No newline at end of file -- GitLab