From 4c0a78512ae1955414840ad4c8297981d99ae862 Mon Sep 17 00:00:00 2001
From: Benjamin Franzke <bfr@qbus.de>
Date: Mon, 20 Dec 2021 07:42:02 +0100
Subject: [PATCH] [TASK] Use plain script-loading for CKEditor v4

CKEditor is now loaded via a plain <script async> tag in
preparation for a transition from requirejs to ES6 modules.

CKEditor v4 is not available as ES6 module and can not easily
be transformed into a strict-mode compatible module.
CKEditor v4 requires `this` to be the global window object
in various functions, but `this` is not bound to the global
window object in strict-mode (as implied by <script type="module">).

Note: CKEditor is not a real AMD module anyway, but is configured
to be shimed by requirejs to be loadable as AMD module,
therefore it can safely be loaded using good old <script>.

Releases: main
Resolves: #96394
Related: #96323
Change-Id: Ife0b476ba29b85f85dfd654ea096cdf30c47b6ef
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72733
Tested-by: core-ci <typo3@b13.com>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Benjamin Franzke <bfr@qbus.de>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Benjamin Franzke <bfr@qbus.de>
---
 .../Public/TypeScript/CKEditorLoader.ts       | 22 +++++++++++++++++++
 .../TypeScript/FormEngineInitializer.ts       |  3 ++-
 .../Public/TypeScript/RteLinkBrowser.ts       |  1 -
 Build/types/TYPO3/index.d.ts                  |  1 -
 .../Hook/PageRendererRenderPreProcess.php     |  2 ++
 .../Public/JavaScript/CKEditorLoader.js       | 13 +++++++++++
 .../JavaScript/FormEngineInitializer.js       |  2 +-
 .../Public/JavaScript/RteLinkBrowser.js       |  2 +-
 8 files changed, 41 insertions(+), 5 deletions(-)
 create mode 100644 Build/Sources/TypeScript/rte_ckeditor/Resources/Public/TypeScript/CKEditorLoader.ts
 create mode 100644 typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/CKEditorLoader.js

diff --git a/Build/Sources/TypeScript/rte_ckeditor/Resources/Public/TypeScript/CKEditorLoader.ts b/Build/Sources/TypeScript/rte_ckeditor/Resources/Public/TypeScript/CKEditorLoader.ts
new file mode 100644
index 000000000000..5d9c3637300b
--- /dev/null
+++ b/Build/Sources/TypeScript/rte_ckeditor/Resources/Public/TypeScript/CKEditorLoader.ts
@@ -0,0 +1,22 @@
+import module from 'module';
+
+let ckeditorPromise: Promise<typeof window.CKEDITOR>|null = null;
+
+function loadScript(url: string): Promise<Event> {
+  return new Promise((resolve, reject) => {
+    const newScript = document.createElement('script');
+    newScript.async = true
+    newScript.onerror = reject;
+    newScript.onload = (ev: Event) => resolve(ev);
+    newScript.src = url;
+    document.head.appendChild(newScript);
+  });
+}
+
+export function loadCKEditor(): Promise<typeof window.CKEDITOR> {
+  if (ckeditorPromise === null) {
+    const scriptUrl = module.uri.replace(/\/[^\/]+\.js/, '/Contrib/ckeditor.js')
+    ckeditorPromise = loadScript(scriptUrl).then(() => window.CKEDITOR);
+  }
+  return ckeditorPromise;
+}
diff --git a/Build/Sources/TypeScript/rte_ckeditor/Resources/Public/TypeScript/FormEngineInitializer.ts b/Build/Sources/TypeScript/rte_ckeditor/Resources/Public/TypeScript/FormEngineInitializer.ts
index d58eee634ff9..4ee3f57696ff 100644
--- a/Build/Sources/TypeScript/rte_ckeditor/Resources/Public/TypeScript/FormEngineInitializer.ts
+++ b/Build/Sources/TypeScript/rte_ckeditor/Resources/Public/TypeScript/FormEngineInitializer.ts
@@ -11,6 +11,7 @@
  * The TYPO3 project - inspiring people to share!
  */
 
+import {loadCKEditor} from 'TYPO3/CMS/RteCkeditor/CKEditorLoader';
 import $ from 'jquery';
 import FormEngine = require('TYPO3/CMS/Backend/FormEngine');
 
@@ -31,7 +32,7 @@ interface CKEditorExternalPlugin {
  */
 export class FormEngineInitializer {
   public static initializeCKEditor(options: CKEditorOptions): void {
-    import('ckeditor').then(({default: CKEDITOR}) => {
+    loadCKEditor().then((CKEDITOR) => {
       CKEDITOR.timestamp += '-' + options.configurationHash;
       options.externalPlugins
         .forEach((item: CKEditorExternalPlugin) => CKEDITOR.plugins.addExternal(item.name, item.resource, ''));
diff --git a/Build/Sources/TypeScript/rte_ckeditor/Resources/Public/TypeScript/RteLinkBrowser.ts b/Build/Sources/TypeScript/rte_ckeditor/Resources/Public/TypeScript/RteLinkBrowser.ts
index dccaadfd6eb7..522e0c158035 100644
--- a/Build/Sources/TypeScript/rte_ckeditor/Resources/Public/TypeScript/RteLinkBrowser.ts
+++ b/Build/Sources/TypeScript/rte_ckeditor/Resources/Public/TypeScript/RteLinkBrowser.ts
@@ -13,7 +13,6 @@
 
 import $ from 'jquery';
 import LinkBrowser = require('TYPO3/CMS/Recordlist/LinkBrowser');
-import 'ckeditor';
 import Modal = require('TYPO3/CMS/Backend/Modal');
 
 /**
diff --git a/Build/types/TYPO3/index.d.ts b/Build/types/TYPO3/index.d.ts
index db9ff14ed110..ca20acb1d028 100644
--- a/Build/types/TYPO3/index.d.ts
+++ b/Build/types/TYPO3/index.d.ts
@@ -138,7 +138,6 @@ interface Window {
  * Needed type declarations for provided libs
  */
 declare module 'muuri';
-declare module 'ckeditor';
 declare module 'codemirror';
 declare module 'flatpickr/flatpickr.min';
 declare module 'flatpickr/locales';
diff --git a/typo3/sysext/rte_ckeditor/Classes/Hook/PageRendererRenderPreProcess.php b/typo3/sysext/rte_ckeditor/Classes/Hook/PageRendererRenderPreProcess.php
index 0534522eb2db..d178bd227654 100644
--- a/typo3/sysext/rte_ckeditor/Classes/Hook/PageRendererRenderPreProcess.php
+++ b/typo3/sysext/rte_ckeditor/Classes/Hook/PageRendererRenderPreProcess.php
@@ -31,6 +31,8 @@ final class PageRendererRenderPreProcess
     {
         // @todo: Add an event to PageRenderer for registration of RequireJS configuration, see #93236
         if ($pageRenderer->getApplicationType() === 'BE') {
+            // @todo: Unused in TYPO3 core, but kept for requirejs compatibility in backend modules.
+            //        Remove/deprecate when requirejs is deprecated.
             $pageRenderer->addRequireJsConfiguration([
                 'shim' => [
                     'ckeditor' => ['exports' => 'CKEDITOR'],
diff --git a/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/CKEditorLoader.js b/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/CKEditorLoader.js
new file mode 100644
index 000000000000..7541a6d73696
--- /dev/null
+++ b/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/CKEditorLoader.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!
+ */
+var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","module"],(function(e,t,o){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.loadCKEditor=void 0,o=__importDefault(o);let r=null;t.loadCKEditor=function(){if(null===r){const t=o.default.uri.replace(/\/[^\/]+\.js/,"/Contrib/ckeditor.js");r=(e=t,new Promise((t,o)=>{const r=document.createElement("script");r.async=!0,r.onerror=o,r.onload=e=>t(e),r.src=e,document.head.appendChild(r)})).then(()=>window.CKEDITOR)}var e;return r}}));
\ No newline at end of file
diff --git a/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/FormEngineInitializer.js b/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/FormEngineInitializer.js
index 293254bdba95..27ced9c13cce 100644
--- a/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/FormEngineInitializer.js
+++ b/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/FormEngineInitializer.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-var __createBinding=this&&this.__createBinding||(Object.create?function(e,t,i,n){void 0===n&&(n=i),Object.defineProperty(e,n,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,n){void 0===n&&(n=i),e[n]=t[i]}),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),__importStar=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&__createBinding(t,e,i);return __setModuleDefault(t,e),t},__importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","jquery","TYPO3/CMS/Backend/FormEngine"],(function(e,t,i,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.FormEngineInitializer=void 0,i=__importDefault(i);t.FormEngineInitializer=class{static initializeCKEditor(t){new Promise((t,i)=>{e(["ckeditor"],t,i)}).then(__importStar).then(({default:e})=>{e.timestamp+="-"+t.configurationHash,t.externalPlugins.forEach(t=>e.plugins.addExternal(t.name,t.resource,"")),i.default(()=>{const a=t.fieldId,r="#"+i.default.escapeSelector(a);e.replace(a,t.configuration);const o=e.instances[a];o.on("change",e=>{let t=e.sender.commands;o.updateElement(),n.Validation.validateField(i.default(r)),n.Validation.markFieldAsChanged(i.default(r)),void 0!==t.maximize&&1===t.maximize.state&&o.on("maximize",e=>{i.default(this).off("maximize"),n.Validation.markFieldAsChanged(i.default(r))})}),o.on("mode",e=>{if("source"===e.editor.mode){const e=o.editable();e.attachListener(e,"change",()=>{n.Validation.markFieldAsChanged(i.default(r))})}}),document.addEventListener("inline:sorting-changed",()=>{o.destroy(),e.replace(a,t.configuration)}),document.addEventListener("formengine:flexform:sorting-changed",()=>{o.destroy(),e.replace(a,t.configuration)})})})}}}));
\ No newline at end of file
+var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","TYPO3/CMS/RteCkeditor/CKEditorLoader","jquery","TYPO3/CMS/Backend/FormEngine"],(function(e,i,t,a,n){"use strict";Object.defineProperty(i,"__esModule",{value:!0}),i.FormEngineInitializer=void 0,a=__importDefault(a);i.FormEngineInitializer=class{static initializeCKEditor(e){t.loadCKEditor().then(i=>{i.timestamp+="-"+e.configurationHash,e.externalPlugins.forEach(e=>i.plugins.addExternal(e.name,e.resource,"")),a.default(()=>{const t=e.fieldId,o="#"+a.default.escapeSelector(t);i.replace(t,e.configuration);const d=i.instances[t];d.on("change",e=>{let i=e.sender.commands;d.updateElement(),n.Validation.validateField(a.default(o)),n.Validation.markFieldAsChanged(a.default(o)),void 0!==i.maximize&&1===i.maximize.state&&d.on("maximize",e=>{a.default(this).off("maximize"),n.Validation.markFieldAsChanged(a.default(o))})}),d.on("mode",e=>{if("source"===e.editor.mode){const e=d.editable();e.attachListener(e,"change",()=>{n.Validation.markFieldAsChanged(a.default(o))})}}),document.addEventListener("inline:sorting-changed",()=>{d.destroy(),i.replace(t,e.configuration)}),document.addEventListener("formengine:flexform:sorting-changed",()=>{d.destroy(),i.replace(t,e.configuration)})})})}}}));
\ No newline at end of file
diff --git a/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/RteLinkBrowser.js b/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/RteLinkBrowser.js
index 88f4a7f5e503..eabdbe30f496 100644
--- a/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/RteLinkBrowser.js
+++ b/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/RteLinkBrowser.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};define(["require","exports","jquery","TYPO3/CMS/Recordlist/LinkBrowser","TYPO3/CMS/Backend/Modal","ckeditor"],(function(t,e,i,n,s){"use strict";i=__importDefault(i);class l{constructor(){this.plugin=null,this.CKEditor=null,this.ranges=[],this.siteUrl=""}initialize(t){let e=s.currentModal.data("ckeditor");if(void 0!==e)this.CKEditor=e;else{let e;e=void 0!==top.TYPO3.Backend&&void 0!==top.TYPO3.Backend.ContentContainer.get()?top.TYPO3.Backend.ContentContainer.get():window.parent,i.default.each(e.CKEDITOR.instances,(e,i)=>{i.id===t&&(this.CKEditor=i)})}window.addEventListener("beforeunload",()=>{this.CKEditor.getSelection().selectRanges(this.ranges)}),this.ranges=this.CKEditor.getSelection().getRanges(),i.default.extend(l,i.default("body").data()),i.default(".t3js-class-selector").on("change",()=>{i.default("option:selected",this).data("linkTitle")&&i.default(".t3js-linkTitle").val(i.default("option:selected",this).data("linkTitle"))}),i.default(".t3js-removeCurrentLink").on("click",t=>{t.preventDefault(),this.CKEditor.execCommand("unlink"),s.dismiss()})}finalizeFunction(t){const e=this.CKEditor.document.createElement("a"),l=n.getLinkAttributeValues();let a=l.params?l.params:"";l.target&&e.setAttribute("target",l.target),l.class&&e.setAttribute("class",l.class),l.title&&e.setAttribute("title",l.title),delete l.title,delete l.class,delete l.target,delete l.params,i.default.each(l,(t,i)=>{e.setAttribute(t,i)});const r=t.match(/^([a-z0-9]+:\/\/[^:\/?#]+(?:\/?[^?#]*)?)(\??[^#]*)(#?.*)$/);if(r&&r.length>0){t=r[1]+r[2];const e=r[2].length>0?"&":"?";a.length>0&&("&"===a[0]&&(a=a.substr(1)),a.length>0&&(t+=e+a)),t+=r[3]}e.setAttribute("href",t);const o=this.CKEditor.getSelection();o.selectRanges(this.ranges),o&&""===o.getSelectedText()&&o.selectElement(o.getStartElement()),o&&o.getSelectedText()?e.setText(o.getSelectedText()):e.setText(e.getAttribute("href")),this.CKEditor.insertElement(e),s.dismiss()}}let a=new l;return n.finalizeFunction=t=>{a.finalizeFunction(t)},a}));
\ No newline at end of file
+var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};define(["require","exports","jquery","TYPO3/CMS/Recordlist/LinkBrowser","TYPO3/CMS/Backend/Modal"],(function(t,e,i,n,s){"use strict";i=__importDefault(i);class l{constructor(){this.plugin=null,this.CKEditor=null,this.ranges=[],this.siteUrl=""}initialize(t){let e=s.currentModal.data("ckeditor");if(void 0!==e)this.CKEditor=e;else{let e;e=void 0!==top.TYPO3.Backend&&void 0!==top.TYPO3.Backend.ContentContainer.get()?top.TYPO3.Backend.ContentContainer.get():window.parent,i.default.each(e.CKEDITOR.instances,(e,i)=>{i.id===t&&(this.CKEditor=i)})}window.addEventListener("beforeunload",()=>{this.CKEditor.getSelection().selectRanges(this.ranges)}),this.ranges=this.CKEditor.getSelection().getRanges(),i.default.extend(l,i.default("body").data()),i.default(".t3js-class-selector").on("change",()=>{i.default("option:selected",this).data("linkTitle")&&i.default(".t3js-linkTitle").val(i.default("option:selected",this).data("linkTitle"))}),i.default(".t3js-removeCurrentLink").on("click",t=>{t.preventDefault(),this.CKEditor.execCommand("unlink"),s.dismiss()})}finalizeFunction(t){const e=this.CKEditor.document.createElement("a"),l=n.getLinkAttributeValues();let a=l.params?l.params:"";l.target&&e.setAttribute("target",l.target),l.class&&e.setAttribute("class",l.class),l.title&&e.setAttribute("title",l.title),delete l.title,delete l.class,delete l.target,delete l.params,i.default.each(l,(t,i)=>{e.setAttribute(t,i)});const r=t.match(/^([a-z0-9]+:\/\/[^:\/?#]+(?:\/?[^?#]*)?)(\??[^#]*)(#?.*)$/);if(r&&r.length>0){t=r[1]+r[2];const e=r[2].length>0?"&":"?";a.length>0&&("&"===a[0]&&(a=a.substr(1)),a.length>0&&(t+=e+a)),t+=r[3]}e.setAttribute("href",t);const o=this.CKEditor.getSelection();o.selectRanges(this.ranges),o&&""===o.getSelectedText()&&o.selectElement(o.getStartElement()),o&&o.getSelectedText()?e.setText(o.getSelectedText()):e.setText(e.getAttribute("href")),this.CKEditor.insertElement(e),s.dismiss()}}let a=new l;return n.finalizeFunction=t=>{a.finalizeFunction(t)},a}));
\ No newline at end of file
-- 
GitLab