Commit 08669c3f authored by Oliver Hader's avatar Oliver Hader Committed by Benni Mack
Browse files

[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's avatarcore-ci <typo3@b13.com>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Nikita Hovratov's avatarNikita Hovratov <nikita.h@live.de>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 7306506e
......@@ -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
......
/*
* 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;
}
}
......@@ -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;
}
......
......@@ -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:'
*
......
......@@ -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);
}
......
......@@ -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)
{
......
......@@ -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;
}
......
......@@ -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;
}
......
<?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;
}
}
......@@ -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);
}
}
}
......
......@@ -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);
}
}
}
......
......@@ -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);
}
}
}
......
......@@ -97,6 +97,7 @@ class SelectMultipleSideBySideElement extends AbstractFormElement
protected function renderFieldControl(): array
{
$alternativeResult = [
// @todo deprecate inline JavaScript in TYPO3 v12.0
'additionalJavaScriptPost' => [],
'additionalHiddenFields' => [],
'additionalInlineLanguageLabelFiles' => [],
......
......@@ -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);
......
......@@ -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
......@@ -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;
......
......@@ -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