From 3b1982e62178148562b54a8faa58ab26f3edfed6 Mon Sep 17 00:00:00 2001
From: Oliver Bartsch <bo@cedev.de>
Date: Wed, 13 Mar 2024 16:22:50 +0100
Subject: [PATCH] [BUGFIX] Prevent TypeErrors in FormEngine for missing
 elements

Resolves: #103391
Releases: main, 12.4
Change-Id: I48d55c7b66669f9836886f257aefba2171b6910e
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/83455
Tested-by: Jochen Roth <rothjochen@gmail.com>
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Jochen Roth <rothjochen@gmail.com>
Tested-by: Andreas Kienast <a.fernandez@scripting-base.de>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Tested-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Andreas Kienast <a.fernandez@scripting-base.de>
---
 .../TypeScript/backend/form-engine/element/group-element.ts    | 3 +++
 .../element/select-multiple-side-by-side-element.ts            | 3 +++
 .../backend/form-engine/element/select-single-element.ts       | 3 +++
 .../TypeScript/backend/form-engine/field-control/link-popup.ts | 3 +++
 .../Public/JavaScript/form-engine/element/group-element.js     | 2 +-
 .../element/select-multiple-side-by-side-element.js            | 2 +-
 .../JavaScript/form-engine/element/select-single-element.js    | 2 +-
 .../Public/JavaScript/form-engine/field-control/link-popup.js  | 2 +-
 8 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/Build/Sources/TypeScript/backend/form-engine/element/group-element.ts b/Build/Sources/TypeScript/backend/form-engine/element/group-element.ts
index cb807ca30753..71e61da0a312 100644
--- a/Build/Sources/TypeScript/backend/form-engine/element/group-element.ts
+++ b/Build/Sources/TypeScript/backend/form-engine/element/group-element.ts
@@ -23,6 +23,9 @@ class GroupElement extends AbstractSortableSelectItems {
 
     DocumentService.ready().then((): void => {
       this.element = <HTMLSelectElement>document.getElementById(elementId);
+      if (this.element === null) {
+        return;
+      }
       this.registerEventHandler();
       this.registerSuggest();
     });
diff --git a/Build/Sources/TypeScript/backend/form-engine/element/select-multiple-side-by-side-element.ts b/Build/Sources/TypeScript/backend/form-engine/element/select-multiple-side-by-side-element.ts
index a45c7b62870d..9fbc1e5ba4d1 100644
--- a/Build/Sources/TypeScript/backend/form-engine/element/select-multiple-side-by-side-element.ts
+++ b/Build/Sources/TypeScript/backend/form-engine/element/select-multiple-side-by-side-element.ts
@@ -27,6 +27,9 @@ class SelectMultipleSideBySideElement extends AbstractSortableSelectItems {
     DocumentService.ready().then((document: Document): void => {
       this.selectedOptionsElement = <HTMLSelectElement>document.getElementById(selectedOptionsElementId);
       this.availableOptionsElement = <HTMLSelectElement>document.getElementById(availableOptionsElementId);
+      if (this.selectedOptionsElement === null || this.availableOptionsElement === null) {
+        return;
+      }
       this.registerEventHandler();
     });
   }
diff --git a/Build/Sources/TypeScript/backend/form-engine/element/select-single-element.ts b/Build/Sources/TypeScript/backend/form-engine/element/select-single-element.ts
index 85c077e5e9ac..4354208e3db7 100644
--- a/Build/Sources/TypeScript/backend/form-engine/element/select-single-element.ts
+++ b/Build/Sources/TypeScript/backend/form-engine/element/select-single-element.ts
@@ -34,6 +34,9 @@ class SelectSingleElement {
 
   public initialize = (elementSelector: string, options: SelectSingleElementOptions): void => {
     const selectElement: HTMLSelectElement = document.querySelector(elementSelector);
+    if (selectElement === null) {
+      return;
+    }
     options = options || {};
 
     new RegularEvent('change', (e: Event): void => {
diff --git a/Build/Sources/TypeScript/backend/form-engine/field-control/link-popup.ts b/Build/Sources/TypeScript/backend/form-engine/field-control/link-popup.ts
index 5d54e97a2b92..98340691aba6 100644
--- a/Build/Sources/TypeScript/backend/form-engine/field-control/link-popup.ts
+++ b/Build/Sources/TypeScript/backend/form-engine/field-control/link-popup.ts
@@ -24,6 +24,9 @@ class LinkPopup {
   constructor(controlElementId: string) {
     DocumentService.ready().then((): void => {
       this.controlElement = <HTMLElement>document.querySelector(controlElementId);
+      if (this.controlElement === null) {
+        return;
+      }
       this.controlElement.addEventListener('click', this.handleControlClick);
     });
   }
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/group-element.js b/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/group-element.js
index c29a1a02f5e8..1e25091bd629 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/group-element.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/group-element.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import{AbstractSortableSelectItems}from"@typo3/backend/form-engine/element/abstract-sortable-select-items.js";import DocumentService from"@typo3/core/document-service.js";import FormEngineSuggest from"@typo3/backend/form-engine-suggest.js";class GroupElement extends AbstractSortableSelectItems{constructor(e){super(),this.element=null,DocumentService.ready().then((()=>{this.element=document.getElementById(e),this.registerEventHandler(),this.registerSuggest()}))}registerEventHandler(){this.registerSortableEventHandler(this.element)}registerSuggest(){let e;null!==(e=this.element.closest(".t3js-formengine-field-item").querySelector(".t3-form-suggest"))&&new FormEngineSuggest(e)}}export default GroupElement;
\ No newline at end of file
+import{AbstractSortableSelectItems}from"@typo3/backend/form-engine/element/abstract-sortable-select-items.js";import DocumentService from"@typo3/core/document-service.js";import FormEngineSuggest from"@typo3/backend/form-engine-suggest.js";class GroupElement extends AbstractSortableSelectItems{constructor(e){super(),this.element=null,DocumentService.ready().then((()=>{this.element=document.getElementById(e),null!==this.element&&(this.registerEventHandler(),this.registerSuggest())}))}registerEventHandler(){this.registerSortableEventHandler(this.element)}registerSuggest(){let e;null!==(e=this.element.closest(".t3js-formengine-field-item").querySelector(".t3-form-suggest"))&&new FormEngineSuggest(e)}}export default GroupElement;
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/select-multiple-side-by-side-element.js b/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/select-multiple-side-by-side-element.js
index ccc77c062fde..be8b7a62b3cf 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/select-multiple-side-by-side-element.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/select-multiple-side-by-side-element.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import{AbstractSortableSelectItems}from"@typo3/backend/form-engine/element/abstract-sortable-select-items.js";import DocumentService from"@typo3/core/document-service.js";import FormEngine from"@typo3/backend/form-engine.js";import SelectBoxFilter from"@typo3/backend/form-engine/element/extra/select-box-filter.js";import RegularEvent from"@typo3/core/event/regular-event.js";class SelectMultipleSideBySideElement extends AbstractSortableSelectItems{constructor(e,t){super(),this.selectedOptionsElement=null,this.availableOptionsElement=null,DocumentService.ready().then((n=>{this.selectedOptionsElement=n.getElementById(e),this.availableOptionsElement=n.getElementById(t),this.registerEventHandler()}))}registerEventHandler(){this.registerSortableEventHandler(this.selectedOptionsElement),this.registerKeyboardEvents(),this.availableOptionsElement.addEventListener("click",(e=>{const t=e.currentTarget;this.handleOptionChecked(t)})),new SelectBoxFilter(this.availableOptionsElement)}handleOptionChecked(e){const t=e.dataset.relatedfieldname;if(t){const n=e.dataset.exclusivevalues,l=e.querySelectorAll("option:checked");l.length>0&&l.forEach((e=>{FormEngine.setSelectOptionFromExternalSource(t,e.value,e.textContent,e.getAttribute("title"),n,e)}))}}registerKeyboardEvents(){new RegularEvent("keydown",(e=>{const t=e.currentTarget;"Enter"===e.code&&(e.preventDefault(),this.handleOptionChecked(t))})).bindTo(this.availableOptionsElement)}}export default SelectMultipleSideBySideElement;
\ No newline at end of file
+import{AbstractSortableSelectItems}from"@typo3/backend/form-engine/element/abstract-sortable-select-items.js";import DocumentService from"@typo3/core/document-service.js";import FormEngine from"@typo3/backend/form-engine.js";import SelectBoxFilter from"@typo3/backend/form-engine/element/extra/select-box-filter.js";import RegularEvent from"@typo3/core/event/regular-event.js";class SelectMultipleSideBySideElement extends AbstractSortableSelectItems{constructor(e,t){super(),this.selectedOptionsElement=null,this.availableOptionsElement=null,DocumentService.ready().then((l=>{this.selectedOptionsElement=l.getElementById(e),this.availableOptionsElement=l.getElementById(t),null!==this.selectedOptionsElement&&null!==this.availableOptionsElement&&this.registerEventHandler()}))}registerEventHandler(){this.registerSortableEventHandler(this.selectedOptionsElement),this.registerKeyboardEvents(),this.availableOptionsElement.addEventListener("click",(e=>{const t=e.currentTarget;this.handleOptionChecked(t)})),new SelectBoxFilter(this.availableOptionsElement)}handleOptionChecked(e){const t=e.dataset.relatedfieldname;if(t){const l=e.dataset.exclusivevalues,n=e.querySelectorAll("option:checked");n.length>0&&n.forEach((e=>{FormEngine.setSelectOptionFromExternalSource(t,e.value,e.textContent,e.getAttribute("title"),l,e)}))}}registerKeyboardEvents(){new RegularEvent("keydown",(e=>{const t=e.currentTarget;"Enter"===e.code&&(e.preventDefault(),this.handleOptionChecked(t))})).bindTo(this.availableOptionsElement)}}export default SelectMultipleSideBySideElement;
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/select-single-element.js b/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/select-single-element.js
index 8b4426e96441..ffcf8ad8b532 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/select-single-element.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/select-single-element.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import RegularEvent from"@typo3/core/event/regular-event.js";import DocumentService from"@typo3/core/document-service.js";import FormEngine from"@typo3/backend/form-engine.js";import{selector}from"@typo3/core/literals.js";class SelectSingleElement{constructor(){this.initialize=(e,t)=>{const n=document.querySelector(e);t=t||{},new RegularEvent("change",(e=>{const t=e.target,n=t.parentElement.querySelector(".input-group-icon");null!==n&&(n.innerHTML=t.options[t.selectedIndex].dataset.icon);const i=t.closest(".t3js-formengine-field-item").querySelector(".t3js-forms-select-single-icons");if(null!==i){const e=i.querySelector(".form-wizard-icon-list-item a.active");null!==e&&e.classList.remove("active");const n=i.querySelector(selector`[data-select-index="${t.selectedIndex.toString(10)}"]`);null!==n&&n.closest(".form-wizard-icon-list-item a").classList.add("active")}})).bindTo(n),t.onChange instanceof Array&&new RegularEvent("change",(()=>FormEngine.processOnFieldChange(t.onChange))).bindTo(n),new RegularEvent("click",((e,t)=>{const i=t.closest(".t3js-forms-select-single-icons").querySelector(".form-wizard-icon-list-item a.active");null!==i&&i.classList.remove("active"),n.selectedIndex=parseInt(t.dataset.selectIndex,10),n.dispatchEvent(new Event("change")),t.closest(".form-wizard-icon-list-item a").classList.add("active")})).delegateTo(n.closest(".form-control-wrap"),".t3js-forms-select-single-icons .form-wizard-icon-list-item a:not(.active)")}}initializeOnReady(e,t){DocumentService.ready().then((()=>{this.initialize(e,t)}))}}export default new SelectSingleElement;
\ No newline at end of file
+import RegularEvent from"@typo3/core/event/regular-event.js";import DocumentService from"@typo3/core/document-service.js";import FormEngine from"@typo3/backend/form-engine.js";import{selector}from"@typo3/core/literals.js";class SelectSingleElement{constructor(){this.initialize=(e,t)=>{const n=document.querySelector(e);null!==n&&(t=t||{},new RegularEvent("change",(e=>{const t=e.target,n=t.parentElement.querySelector(".input-group-icon");null!==n&&(n.innerHTML=t.options[t.selectedIndex].dataset.icon);const i=t.closest(".t3js-formengine-field-item").querySelector(".t3js-forms-select-single-icons");if(null!==i){const e=i.querySelector(".form-wizard-icon-list-item a.active");null!==e&&e.classList.remove("active");const n=i.querySelector(selector`[data-select-index="${t.selectedIndex.toString(10)}"]`);null!==n&&n.closest(".form-wizard-icon-list-item a").classList.add("active")}})).bindTo(n),t.onChange instanceof Array&&new RegularEvent("change",(()=>FormEngine.processOnFieldChange(t.onChange))).bindTo(n),new RegularEvent("click",((e,t)=>{const i=t.closest(".t3js-forms-select-single-icons").querySelector(".form-wizard-icon-list-item a.active");null!==i&&i.classList.remove("active"),n.selectedIndex=parseInt(t.dataset.selectIndex,10),n.dispatchEvent(new Event("change")),t.closest(".form-wizard-icon-list-item a").classList.add("active")})).delegateTo(n.closest(".form-control-wrap"),".t3js-forms-select-single-icons .form-wizard-icon-list-item a:not(.active)"))}}initializeOnReady(e,t){DocumentService.ready().then((()=>{this.initialize(e,t)}))}}export default new SelectSingleElement;
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/field-control/link-popup.js b/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/field-control/link-popup.js
index 2ff0b89b99d9..d0c0966a16e6 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/field-control/link-popup.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/field-control/link-popup.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import DocumentService from"@typo3/core/document-service.js";import FormEngine from"@typo3/backend/form-engine.js";import Modal from"@typo3/backend/modal.js";class LinkPopup{constructor(e){this.controlElement=null,this.handleControlClick=e=>{e.preventDefault();const t=this.controlElement.dataset.itemName,o=this.controlElement.getAttribute("href")+"&P[currentValue]="+encodeURIComponent(document.forms.namedItem("editform")[t].value)+"&P[currentSelectedValues]="+encodeURIComponent(FormEngine.getFieldElement(t).val());Modal.advanced({type:Modal.types.iframe,content:o,size:Modal.sizes.large})},DocumentService.ready().then((()=>{this.controlElement=document.querySelector(e),this.controlElement.addEventListener("click",this.handleControlClick)}))}}export default LinkPopup;
\ No newline at end of file
+import DocumentService from"@typo3/core/document-service.js";import FormEngine from"@typo3/backend/form-engine.js";import Modal from"@typo3/backend/modal.js";class LinkPopup{constructor(e){this.controlElement=null,this.handleControlClick=e=>{e.preventDefault();const t=this.controlElement.dataset.itemName,o=this.controlElement.getAttribute("href")+"&P[currentValue]="+encodeURIComponent(document.forms.namedItem("editform")[t].value)+"&P[currentSelectedValues]="+encodeURIComponent(FormEngine.getFieldElement(t).val());Modal.advanced({type:Modal.types.iframe,content:o,size:Modal.sizes.large})},DocumentService.ready().then((()=>{this.controlElement=document.querySelector(e),null!==this.controlElement&&this.controlElement.addEventListener("click",this.handleControlClick)}))}}export default LinkPopup;
\ No newline at end of file
-- 
GitLab