Commit e52196de authored by Oliver Hader's avatar Oliver Hader Committed by Oliver Bartsch
Browse files

[TASK] Avoid inline JavaScript in DispatchNotificationHook

DispatchNotificationHook in ext:redirects has been adjusted
to trigger events using a script-less implementation.

Resolves: #96003
Releases: master, 11.5
Change-Id: I496331820813150cad610f07adae60ff538be3bb
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72240

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Jochen's avatarJochen <rothjochen@gmail.com>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Jochen's avatarJochen <rothjochen@gmail.com>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
parent 8a8f1413
......@@ -28,6 +28,11 @@ class EventHandler {
);
}
public dispatchCustomEvent(name: string, detail: any = null): void {
const event = new CustomEvent(name, {detail: detail});
document.dispatchEvent(event);
}
public onSlugChanged(detail: any): void {
let actions: any = [];
const correlations = detail.correlations;
......
......@@ -21,7 +21,15 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
class JavaScriptModuleInstruction implements \JsonSerializable
{
/**
* Indicates a requireJS module shall be loaded.
* @todo In future versions this might be ES6 module as well
*/
public const FLAG_LOAD_REQUIRE_JS = 1;
/**
* Indicates all actions shall be applied globally to `top.window`.
*/
public const FLAG_USE_TOP_WINDOW = 16;
public const ITEM_ASSIGN = 'assign';
public const ITEM_INVOKE = 'invoke';
......@@ -84,6 +92,16 @@ class JavaScriptModuleInstruction implements \JsonSerializable
return $this->items;
}
/**
* @param int ...$flags
* @return $this
*/
public function addFlags(int ...$flags): self
{
$this->flags += array_sum($flags);
return $this;
}
/**
* @param array $assignments key-value assignments
* @return static
......@@ -129,4 +147,9 @@ class JavaScriptModuleInstruction implements \JsonSerializable
{
return ($this->flags & self::FLAG_LOAD_REQUIRE_JS) === self::FLAG_LOAD_REQUIRE_JS;
}
public function shallUseTopWindow(): bool
{
return ($this->flags & self::FLAG_USE_TOP_WINDOW) === self::FLAG_USE_TOP_WINDOW;
}
}
......@@ -26,6 +26,7 @@
}
const FLAG_LOAD_REQUIRE_JS = 1;
const FLAG_USE_TOP_WINDOW = 16;
const deniedProperties = ['__proto__', 'prototype', 'constructor'];
const allowedRequireJsItemTypes = ['assign', 'invoke', 'instance'];
const allowedRequireJsNames = ['globalAssignment', 'javaScriptModuleInstruction'];
......@@ -44,8 +45,9 @@
if (!json.name) {
throw new Error('RequireJS module name is required');
}
const windowRef = (json.flags & FLAG_USE_TOP_WINDOW) === FLAG_USE_TOP_WINDOW ? top.window : window;
if (!json.items) {
require([json.name]);
windowRef.require([json.name]);
return;
}
const exportName = json.exportName;
......@@ -75,7 +77,7 @@
}
}
});
require(
windowRef.require(
[json.name],
(subjectRef) => items.forEach((item) => item.call(null, subjectRef))
);
......
......@@ -17,6 +17,10 @@ declare(strict_types=1);
namespace TYPO3\CMS\Redirects\Hooks;
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* @internal
*/
......@@ -30,14 +34,13 @@ final class DispatchNotificationHook
*/
public function dispatchNotification(&$params)
{
// @todo https://forge.typo3.org/issues/96003
$code = '
// Ensure the event handler is ready and listening to events
top.window.require(["TYPO3/CMS/Redirects/EventHandler"], function() {
top.document.dispatchEvent(new CustomEvent("typo3:redirects:slugChanged", { detail: %s }));
});
';
$payload = json_encode($params['parameter']);
$params['JScode'] = sprintf($code, $payload);
$javaScriptRenderer = GeneralUtility::makeInstance(PageRenderer::class)->getJavaScriptRenderer();
$javaScriptRenderer->addJavaScriptModuleInstruction(
// Ensures event handler is ready and listening to events
JavaScriptModuleInstruction::forRequireJS('TYPO3/CMS/Redirects/EventHandler')
->addFlags(JavaScriptModuleInstruction::FLAG_USE_TOP_WINDOW)
->invoke('dispatchCustomEvent', 'typo3:redirects:slugChanged', $params['parameter'])
);
// not modifying `$params`, since instruction is added to global `PageRenderer`
}
}
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
define(["require","exports","TYPO3/CMS/Core/Ajax/AjaxRequest","TYPO3/CMS/Backend/Notification","TYPO3/CMS/Backend/ActionButton/DeferredAction"],(function(e,t,r,n,i){"use strict";return new class{constructor(){document.addEventListener("typo3:redirects:slugChanged",e=>this.onSlugChanged(e.detail))}onSlugChanged(e){let t=[];const r=e.correlations;e.autoUpdateSlugs&&t.push({label:TYPO3.lang["notification.redirects.button.revert_update"],action:new i(()=>this.revert([r.correlationIdSlugUpdate,r.correlationIdRedirectCreation]))}),e.autoCreateRedirects&&t.push({label:TYPO3.lang["notification.redirects.button.revert_redirect"],action:new i(()=>this.revert([r.correlationIdRedirectCreation]))});let a=TYPO3.lang["notification.slug_only.title"],o=TYPO3.lang["notification.slug_only.message"];e.autoCreateRedirects&&(a=TYPO3.lang["notification.slug_and_redirects.title"],o=TYPO3.lang["notification.slug_and_redirects.message"]),n.info(a,o,0,t)}revert(e){const t=new r(TYPO3.settings.ajaxUrls.redirects_revert_correlation).withQueryArguments({correlation_ids:e}).get();return t.then(async e=>{const t=await e.resolve();"ok"===t.status&&n.success(t.title,t.message),"error"===t.status&&n.error(t.title,t.message)}).catch(()=>{n.error(TYPO3.lang.redirects_error_title,TYPO3.lang.redirects_error_message)}),t}}}));
\ No newline at end of file
define(["require","exports","TYPO3/CMS/Core/Ajax/AjaxRequest","TYPO3/CMS/Backend/Notification","TYPO3/CMS/Backend/ActionButton/DeferredAction"],(function(e,t,r,n,i){"use strict";return new class{constructor(){document.addEventListener("typo3:redirects:slugChanged",e=>this.onSlugChanged(e.detail))}dispatchCustomEvent(e,t=null){const r=new CustomEvent(e,{detail:t});document.dispatchEvent(r)}onSlugChanged(e){let t=[];const r=e.correlations;e.autoUpdateSlugs&&t.push({label:TYPO3.lang["notification.redirects.button.revert_update"],action:new i(()=>this.revert([r.correlationIdSlugUpdate,r.correlationIdRedirectCreation]))}),e.autoCreateRedirects&&t.push({label:TYPO3.lang["notification.redirects.button.revert_redirect"],action:new i(()=>this.revert([r.correlationIdRedirectCreation]))});let o=TYPO3.lang["notification.slug_only.title"],a=TYPO3.lang["notification.slug_only.message"];e.autoCreateRedirects&&(o=TYPO3.lang["notification.slug_and_redirects.title"],a=TYPO3.lang["notification.slug_and_redirects.message"]),n.info(o,a,0,t)}revert(e){const t=new r(TYPO3.settings.ajaxUrls.redirects_revert_correlation).withQueryArguments({correlation_ids:e}).get();return t.then(async e=>{const t=await e.resolve();"ok"===t.status&&n.success(t.title,t.message),"error"===t.status&&n.error(t.title,t.message)}).catch(()=>{n.error(TYPO3.lang.redirects_error_title,TYPO3.lang.redirects_error_message)}),t}}}));
\ No newline at end of file
Markdown is supported
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