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 { ...@@ -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 { public onSlugChanged(detail: any): void {
let actions: any = []; let actions: any = [];
const correlations = detail.correlations; const correlations = detail.correlations;
......
...@@ -21,7 +21,15 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; ...@@ -21,7 +21,15 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
class JavaScriptModuleInstruction implements \JsonSerializable 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; 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_ASSIGN = 'assign';
public const ITEM_INVOKE = 'invoke'; public const ITEM_INVOKE = 'invoke';
...@@ -84,6 +92,16 @@ class JavaScriptModuleInstruction implements \JsonSerializable ...@@ -84,6 +92,16 @@ class JavaScriptModuleInstruction implements \JsonSerializable
return $this->items; 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 * @param array $assignments key-value assignments
* @return static * @return static
...@@ -129,4 +147,9 @@ class JavaScriptModuleInstruction implements \JsonSerializable ...@@ -129,4 +147,9 @@ class JavaScriptModuleInstruction implements \JsonSerializable
{ {
return ($this->flags & self::FLAG_LOAD_REQUIRE_JS) === self::FLAG_LOAD_REQUIRE_JS; 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 @@ ...@@ -26,6 +26,7 @@
} }
const FLAG_LOAD_REQUIRE_JS = 1; const FLAG_LOAD_REQUIRE_JS = 1;
const FLAG_USE_TOP_WINDOW = 16;
const deniedProperties = ['__proto__', 'prototype', 'constructor']; const deniedProperties = ['__proto__', 'prototype', 'constructor'];
const allowedRequireJsItemTypes = ['assign', 'invoke', 'instance']; const allowedRequireJsItemTypes = ['assign', 'invoke', 'instance'];
const allowedRequireJsNames = ['globalAssignment', 'javaScriptModuleInstruction']; const allowedRequireJsNames = ['globalAssignment', 'javaScriptModuleInstruction'];
...@@ -44,8 +45,9 @@ ...@@ -44,8 +45,9 @@
if (!json.name) { if (!json.name) {
throw new Error('RequireJS module name is required'); 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) { if (!json.items) {
require([json.name]); windowRef.require([json.name]);
return; return;
} }
const exportName = json.exportName; const exportName = json.exportName;
...@@ -75,7 +77,7 @@ ...@@ -75,7 +77,7 @@
} }
} }
}); });
require( windowRef.require(
[json.name], [json.name],
(subjectRef) => items.forEach((item) => item.call(null, subjectRef)) (subjectRef) => items.forEach((item) => item.call(null, subjectRef))
); );
......
...@@ -17,6 +17,10 @@ declare(strict_types=1); ...@@ -17,6 +17,10 @@ declare(strict_types=1);
namespace TYPO3\CMS\Redirects\Hooks; namespace TYPO3\CMS\Redirects\Hooks;
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/** /**
* @internal * @internal
*/ */
...@@ -30,14 +34,13 @@ final class DispatchNotificationHook ...@@ -30,14 +34,13 @@ final class DispatchNotificationHook
*/ */
public function dispatchNotification(&$params) public function dispatchNotification(&$params)
{ {
// @todo https://forge.typo3.org/issues/96003 $javaScriptRenderer = GeneralUtility::makeInstance(PageRenderer::class)->getJavaScriptRenderer();
$code = ' $javaScriptRenderer->addJavaScriptModuleInstruction(
// Ensure the event handler is ready and listening to events // Ensures event handler is ready and listening to events
top.window.require(["TYPO3/CMS/Redirects/EventHandler"], function() { JavaScriptModuleInstruction::forRequireJS('TYPO3/CMS/Redirects/EventHandler')
top.document.dispatchEvent(new CustomEvent("typo3:redirects:slugChanged", { detail: %s })); ->addFlags(JavaScriptModuleInstruction::FLAG_USE_TOP_WINDOW)
}); ->invoke('dispatchCustomEvent', 'typo3:redirects:slugChanged', $params['parameter'])
'; );
$payload = json_encode($params['parameter']); // not modifying `$params`, since instruction is added to global `PageRenderer`
$params['JScode'] = sprintf($code, $payload);
} }
} }
...@@ -10,4 +10,4 @@ ...@@ -10,4 +10,4 @@
* *
* The TYPO3 project - inspiring people to share! * 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}}})); 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 \ No newline at end of file
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