From 8482d77f609e8ea22ca72dd425d9fb75cc8aafd0 Mon Sep 17 00:00:00 2001
From: Jochen Roth <jochen.roth@b13.com>
Date: Tue, 6 Jul 2021 13:32:07 +0200
Subject: [PATCH] [BUGFIX] Set fallback for undefined array key in php8

After reset user preferences in settings module the
Configuration Module shows undefined array key warning.
An empty array has been added as fallback to workaround
this issue and improved scrollTo behavior when the user
opens a node in the configuration tree.

Also extended tests to cover such issues in the future.

Resolves: #94487
Releases: master
Change-Id: I3e907a2618765d314ff1ad8bcab74f1af46765c0
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69724
Tested-by: core-ci <typo3@b13.com>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: crell <larry@garfieldtech.com>
Reviewed-by: Benni Mack <benni@typo3.org>
---
 .../Public/TypeScript/ConfigurationView.ts    | 10 ++++-
 .../ConfigurationModuleProviderCest.php       | 44 +++++++++++++++++++
 .../Controller/ConfigurationController.php    |  2 +-
 .../Public/JavaScript/ConfigurationView.js    |  2 +-
 4 files changed, 55 insertions(+), 3 deletions(-)

diff --git a/Build/Sources/TypeScript/lowlevel/Resources/Public/TypeScript/ConfigurationView.ts b/Build/Sources/TypeScript/lowlevel/Resources/Public/TypeScript/ConfigurationView.ts
index 1dd4f53ea257..14b7af246130 100644
--- a/Build/Sources/TypeScript/lowlevel/Resources/Public/TypeScript/ConfigurationView.ts
+++ b/Build/Sources/TypeScript/lowlevel/Resources/Public/TypeScript/ConfigurationView.ts
@@ -35,7 +35,15 @@ class ConfigurationView {
 
     if (self.location.hash) {
       // scroll page down, so the just opened subtree is visible after reload and not hidden by doc header
-      $('html, body').scrollTop((document.documentElement.scrollTop || document.body.scrollTop) - 80);
+      // Determine scrollTo position, either first ".active" (search) or latest clicked element
+      let scrollElement = document.querySelector(self.location.hash);
+      if(document.querySelector('.list-tree .active ')) {
+        scrollElement = document.querySelector('.list-tree .active ');
+      } else {
+        document.querySelector(self.location.hash).parentElement.parentElement.classList.add('active');
+      }
+
+      scrollElement.scrollIntoView({ block: 'center' });
     }
   }
 }
diff --git a/typo3/sysext/core/Tests/Acceptance/Backend/ConfigurationModule/ConfigurationModuleProviderCest.php b/typo3/sysext/core/Tests/Acceptance/Backend/ConfigurationModule/ConfigurationModuleProviderCest.php
index b008d7e29205..b2fbdb2bcd97 100644
--- a/typo3/sysext/core/Tests/Acceptance/Backend/ConfigurationModule/ConfigurationModuleProviderCest.php
+++ b/typo3/sysext/core/Tests/Acceptance/Backend/ConfigurationModule/ConfigurationModuleProviderCest.php
@@ -68,4 +68,48 @@ class ConfigurationModuleProviderCest
         $I->seeCheckboxIsChecked('#lowlevel-regexSearch');
         $I->seeElement('li.active');
     }
+
+    /**
+     * @param BackendTester $I
+     */
+    public function canOpenTreeNodeAndScrollTo(BackendTester $I): void
+    {
+        $I->selectOption('select[name=tree]', '$GLOBALS[\'TYPO3_CONF_VARS\'] (Global Configuration)');
+        $I->click('.list-tree > li:first-child .list-tree-control');
+        $I->see('checkStoredRecordsLoose', '.list-tree-group');
+        $I->see('BE', '.active > .list-tree-group');
+    }
+
+    /**
+     * @param BackendTester $I
+     */
+    public function seeAllPagesInDropDown(BackendTester $I): void
+    {
+        foreach ($this->dropDownPagesDataProvider() as $item) {
+            $I->selectOption('select[name=tree]', $item);
+            $I->see($item, 'h2');
+        }
+    }
+
+    protected function dropDownPagesDataProvider(): array
+    {
+        return [
+            '$GLOBALS[\'TYPO3_CONF_VARS\'] (Global Configuration)',
+            '$GLOBALS[\'TCA\'] (Table configuration array)',
+            '$GLOBALS[\'TCA_DESCR\'] (Table Help Description)',
+            '$GLOBALS[\'T3_SERVICES\'] (Registered Services)',
+            '$GLOBALS[\'TBE_MODULES\'] (BE Modules)',
+            '$GLOBALS[\'TBE_MODULES_EXT\'] (BE Modules Extensions)',
+            '$GLOBALS[\'TBE_STYLES\'] (Skinning Styles)',
+            '$GLOBALS[\'TYPO3_USER_SETTINGS\'] (User Settings Configuration)',
+            '$GLOBALS[\'PAGES_TYPES\'] (Table permissions by page type)',
+            '$GLOBALS[\'BE_USER\']->uc (User Settings)',
+            '$GLOBALS[\'BE_USER\']->getTSConfig() (User TSconfig)',
+            'Backend Routes',
+            'HTTP Middlewares (PSR-15)',
+            'Site Configuration',
+            'Event Listeners (PSR-14)',
+            'MFA providers'
+        ];
+    }
 }
diff --git a/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php b/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php
index 01b3262e8761..87dd045277b8 100644
--- a/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php
+++ b/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php
@@ -101,7 +101,7 @@ class ConfigurationController
         if ($searchString) {
             $arrayBrowser->depthKeys = $arrayBrowser->getSearchKeys($configurationArray, '', $searchString, []);
         } elseif (is_array($node)) {
-            $newExpandCollapse = $arrayBrowser->depthKeys($node, $moduleState['node_' . $configurationProviderIdentifier]);
+            $newExpandCollapse = $arrayBrowser->depthKeys($node, $moduleState['node_' . $configurationProviderIdentifier] ?? []);
             $arrayBrowser->depthKeys = $newExpandCollapse;
             $moduleState['node_' . $configurationProviderIdentifier] = $newExpandCollapse;
         } else {
diff --git a/typo3/sysext/lowlevel/Resources/Public/JavaScript/ConfigurationView.js b/typo3/sysext/lowlevel/Resources/Public/JavaScript/ConfigurationView.js
index 4660883c3145..6b79a9b67ef7 100644
--- a/typo3/sysext/lowlevel/Resources/Public/JavaScript/ConfigurationView.js
+++ b/typo3/sysext/lowlevel/Resources/Public/JavaScript/ConfigurationView.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require","exports","TYPO3/CMS/Core/DocumentService","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,r,t,s){"use strict";return new class{constructor(){this.searchForm=document.querySelector("#ConfigurationView"),this.searchField=this.searchForm.querySelector('input[name="searchString"]'),this.searchResultShown=""!==this.searchField.value,t.ready().then(()=>{new s("search",()=>{""===this.searchField.value&&this.searchResultShown&&this.searchForm.submit()}).bindTo(this.searchField)}),self.location.hash&&$("html, body").scrollTop((document.documentElement.scrollTop||document.body.scrollTop)-80)}}}));
\ No newline at end of file
+define(["require","exports","TYPO3/CMS/Core/DocumentService","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,r,s){"use strict";return new class{constructor(){if(this.searchForm=document.querySelector("#ConfigurationView"),this.searchField=this.searchForm.querySelector('input[name="searchString"]'),this.searchResultShown=""!==this.searchField.value,r.ready().then(()=>{new s("search",()=>{""===this.searchField.value&&this.searchResultShown&&this.searchForm.submit()}).bindTo(this.searchField)}),self.location.hash){let e=document.querySelector(self.location.hash);document.querySelector(".list-tree .active ")?e=document.querySelector(".list-tree .active "):document.querySelector(self.location.hash).parentElement.parentElement.classList.add("active"),e.scrollIntoView({block:"center"})}}}}));
\ No newline at end of file
-- 
GitLab