From 9ea6ad02d70cf010f748fa50425f9d0da115b560 Mon Sep 17 00:00:00 2001
From: Jochen Roth <jochen.roth@b13.com>
Date: Wed, 7 Jul 2021 18:23:54 +0200
Subject: [PATCH] [BUGFIX] Fix array undefined warnings in "DB Check" Module

Add fallback for undefined array keys to make "DB Check"
compatible with PHP8.

Also basic tests have been added to check each page in
the module.

Resolves: #94498
Releases: master
Change-Id: I98a598d0e809077cd89e7620efe31a498b3fbfa1
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69759
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: crell <larry@garfieldtech.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
---
 .../core/Classes/Database/ReferenceIndex.php  |   2 +-
 .../Backend/DbCheck/DbCheckModuleCest.php     | 189 ++++++++++++++++++
 .../DatabaseIntegrityController.php           |  16 +-
 .../Classes/Database/QueryGenerator.php       |  96 ++++-----
 .../Integrity/DatabaseIntegrityCheck.php      |  12 +-
 5 files changed, 257 insertions(+), 58 deletions(-)
 create mode 100644 typo3/sysext/core/Tests/Acceptance/Backend/DbCheck/DbCheckModuleCest.php

diff --git a/typo3/sysext/core/Classes/Database/ReferenceIndex.php b/typo3/sysext/core/Classes/Database/ReferenceIndex.php
index 5caa60870526..4a9b26b4c434 100644
--- a/typo3/sysext/core/Classes/Database/ReferenceIndex.php
+++ b/typo3/sysext/core/Classes/Database/ReferenceIndex.php
@@ -408,7 +408,7 @@ class ReferenceIndex implements LoggerAwareInterface
         foreach ($keys as $spKey => $elements) {
             if (is_array($elements)) {
                 foreach ($elements as $subKey => $el) {
-                    if (is_array($el['subst'])) {
+                    if (is_array($el['subst'] ?? false)) {
                         switch ((string)$el['subst']['type']) {
                             case 'db':
                                 [$referencedTable, $referencedUid] = explode(':', $el['subst']['recordRef']);
diff --git a/typo3/sysext/core/Tests/Acceptance/Backend/DbCheck/DbCheckModuleCest.php b/typo3/sysext/core/Tests/Acceptance/Backend/DbCheck/DbCheckModuleCest.php
new file mode 100644
index 000000000000..617fa77510f5
--- /dev/null
+++ b/typo3/sysext/core/Tests/Acceptance/Backend/DbCheck/DbCheckModuleCest.php
@@ -0,0 +1,189 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * 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!
+ */
+
+namespace TYPO3\CMS\Core\Tests\Acceptance\Backend\DbCheck;
+
+use Facebook\WebDriver\Remote\RemoteWebDriver;
+use Facebook\WebDriver\Remote\RemoteWebElement;
+use TYPO3\CMS\Core\Tests\Acceptance\Support\BackendTester;
+use TYPO3\CMS\Core\Tests\Acceptance\Support\Helper\ModalDialog;
+
+/**
+ * Tests concerning Reports Module
+ */
+class DbCheckModuleCest
+{
+    protected static string $defaultDashboardTitle = 'My Dashboard';
+
+    /**
+     * @param BackendTester $I
+     */
+    public function _before(BackendTester $I)
+    {
+        $I->useExistingSession('admin');
+        $I->click('#system_dbint');
+        $I->switchToContentFrame();
+    }
+
+    /**
+     * @param BackendTester $I
+     */
+    public function seeOverview(BackendTester $I)
+    {
+        $I->see('Database integrity check', 'h1');
+        $I->see('Records Statistics', 'a');
+        $I->see('Relations', 'a');
+        $I->see('Search', 'a');
+        $I->see('Check and update global reference index', 'a');
+    }
+
+    /**
+     * @param BackendTester $I
+     */
+    public function seeRecordStatistics(BackendTester $I)
+    {
+        $this->goToPageAndSeeHeadline($I, 'Record Statistics', 'Records Statistics');
+
+        foreach ($this->recordStatisticsDataProvider() as $statistic) {
+            $count = $this->getCountByRowName($I, $statistic['name'])->getText();
+            // Use >= here to make sure we don't get in trouble with other tests creating db entries
+            $I->assertGreaterThanOrEqual($statistic['count'], $count);
+        }
+    }
+
+    /**
+     * @param BackendTester $I
+     */
+    public function seeDatabaseRelations(BackendTester $I)
+    {
+        $this->goToPageAndSeeHeadline($I, 'Database Relations', 'Relations');
+        $I->see('Select fields', 'h2');
+        $I->see('Group fields', 'h2');
+    }
+
+    /**
+     * @param BackendTester $I
+     */
+    public function seeFullSearch(BackendTester $I, ModalDialog $modalDialog)
+    {
+        $this->goToPageAndSeeHeadline($I, 'Full search', 'Search whole Database');
+        $I->see('Search options', 'h2');
+        $I->see('Result', 'h2');
+
+        // Fill in search phrase and check results
+        $I->fillField('input[name="SET[sword]"]', 'styleguide demo group 1');
+        $I->click('Search All Records');
+        $I->see('styleguide demo group 1', 'td');
+        $I->dontSee('styleguide demo group 2', 'td');
+
+        // Open info modal and see text in card
+        $I->click('a[data-dispatch-args-list]');
+        $modalDialog->canSeeDialog();
+        $I->switchToIFrame('.modal-iframe');
+        $I->see('styleguide demo group 1', '.card-title');
+    }
+
+    /**
+     * @param BackendTester $I
+     */
+    public function seeManageReferenceIndex(BackendTester $I)
+    {
+        $this->goToPageAndSeeHeadline($I, 'Manage Reference Index', 'Manage Reference Index');
+
+        $I->click('Check reference index');
+        $I->waitForElement('.alert-danger');
+
+        $I->click('Update reference index');
+        $I->waitForElement('.alert-danger');
+
+        $I->click('Check reference index');
+        $I->waitForElement('.alert-success');
+        $I->see('Index Integrity was perfect!', '.alert-success');
+
+        $I->click('Update reference index');
+        $I->see('Index Integrity was perfect!', '.alert-success');
+    }
+
+    /**
+     * @return array[]
+     */
+    protected function recordStatisticsDataProvider(): array
+    {
+        return [
+            [
+                'name' => 'Total number of default language pages',
+                'count' => 84,
+            ],
+            [
+                'name' => 'Total number of translated pages',
+                'count' => 132,
+            ],
+            [
+                'name' => 'Marked-deleted pages',
+                'count' => 0,
+            ],
+            [
+                'name' => 'Hidden pages',
+                'count' => 1,
+            ],
+            [
+                'name' => 'Standard',
+                'count' => 1,
+            ],
+            [
+                'name' => 'Backend User Section',
+                'count' => 0,
+            ],
+            [
+                'name' => 'Link to External URL',
+                'count' => 0,
+            ],
+        ];
+    }
+
+    /**
+     * @param string $select
+     * @param string $headline
+     * @param BackendTester $I
+     */
+    protected function goToPageAndSeeHeadline(BackendTester $I, string $select, string $headline): void
+    {
+        $I->selectOption('select[name=DatabaseJumpMenu]', $select);
+        $I->see($headline, 'h1');
+    }
+
+    /**
+     * Find count of table row by name
+     *
+     * @param BackendTester $I
+     * @param string $fieldLabel
+     * @return RemoteWebElement
+     */
+    protected function getCountByRowName(BackendTester $I, string $rowName, int $sibling = 1): RemoteWebElement
+    {
+        $I->comment('Get context for table row "' . $rowName . '"');
+        return $I->executeInSelenium(
+            function (RemoteWebDriver $webDriver) use ($rowName, $sibling) {
+                return $webDriver->findElement(
+                    \Facebook\WebDriver\WebDriverBy::xpath(
+                        '//td[contains(text(),"' . $rowName . '")]/following-sibling::td[' . $sibling . ']'
+                    )
+                );
+            }
+        );
+    }
+}
diff --git a/typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php b/typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php
index 898d8705bd25..ec7de8e48804 100644
--- a/typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php
+++ b/typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php
@@ -360,11 +360,11 @@ class DatabaseIntegrityController
         }
         $submenu .= '</div>';
         if ($this->MOD_SETTINGS['search'] === 'query') {
-            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[search_query_smallparts]', $this->MOD_SETTINGS['search_query_smallparts'], '', '', 'id="checkSearch_query_smallparts"') . '<label class="form-check-label" for="checkSearch_query_smallparts">' . $lang->getLL('showSQL') . '</label></div>';
-            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[search_result_labels]', $this->MOD_SETTINGS['search_result_labels'], '', '', 'id="checkSearch_result_labels"') . '<label class="form-check-label" for="checkSearch_result_labels">' . $lang->getLL('useFormattedStrings') . '</label></div>';
-            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[labels_noprefix]', $this->MOD_SETTINGS['labels_noprefix'], '', '', 'id="checkLabels_noprefix"') . '<label class="form-check-label" for="checkLabels_noprefix">' . $lang->getLL('dontUseOrigValues') . '</label></div>';
-            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[options_sortlabel]', $this->MOD_SETTINGS['options_sortlabel'], '', '', 'id="checkOptions_sortlabel"') . '<label class="form-check-label" for="checkOptions_sortlabel">' . $lang->getLL('sortOptions') . '</label></div>';
-            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[show_deleted]', $this->MOD_SETTINGS['show_deleted'], '', '', 'id="checkShow_deleted"') . '<label class="form-check-label" for="checkShow_deleted">' . $lang->getLL('showDeleted') . '</label></div>';
+            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[search_query_smallparts]', $this->MOD_SETTINGS['search_query_smallparts'] ?? '', '', '', 'id="checkSearch_query_smallparts"') . '<label class="form-check-label" for="checkSearch_query_smallparts">' . $lang->getLL('showSQL') . '</label></div>';
+            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[search_result_labels]', $this->MOD_SETTINGS['search_result_labels'] ?? '', '', '', 'id="checkSearch_result_labels"') . '<label class="form-check-label" for="checkSearch_result_labels">' . $lang->getLL('useFormattedStrings') . '</label></div>';
+            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[labels_noprefix]', $this->MOD_SETTINGS['labels_noprefix'] ?? '', '', '', 'id="checkLabels_noprefix"') . '<label class="form-check-label" for="checkLabels_noprefix">' . $lang->getLL('dontUseOrigValues') . '</label></div>';
+            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[options_sortlabel]', $this->MOD_SETTINGS['options_sortlabel'] ?? '', '', '', 'id="checkOptions_sortlabel"') . '<label class="form-check-label" for="checkOptions_sortlabel">' . $lang->getLL('sortOptions') . '</label></div>';
+            $submenu .= '<div class="form-check">' . BackendUtility::getFuncCheck(0, 'SET[show_deleted]', $this->MOD_SETTINGS['show_deleted'] ?? 0, '', '', 'id="checkShow_deleted"') . '<label class="form-check-label" for="checkShow_deleted">' . $lang->getLL('showDeleted') . '</label></div>';
         }
         $this->view->assign('submenu', $submenu);
         $this->view->assign('searchMode', $searchMode);
@@ -441,7 +441,7 @@ class DatabaseIntegrityController
         $countArr = $admin->countRecords($id_list);
         if (is_array($GLOBALS['TCA'])) {
             foreach ($GLOBALS['TCA'] as $t => $value) {
-                if ($GLOBALS['TCA'][$t]['ctrl']['hideTable']) {
+                if ($GLOBALS['TCA'][$t]['ctrl']['hideTable'] ?? false) {
                     continue;
                 }
                 if ($t === 'pages' && $admin->getLostPagesList() !== '') {
@@ -449,13 +449,13 @@ class DatabaseIntegrityController
                 } else {
                     $lostRecordCount = isset($admin->getLRecords()[$t]) ? count($admin->getLRecords()[$t]) : 0;
                 }
-                if ($countArr['all'][$t]) {
+                if ($countArr['all'][$t] ?? false) {
                     $theNumberOfRe = (int)$countArr['non_deleted'][$t] . '/' . $lostRecordCount;
                 } else {
                     $theNumberOfRe = '';
                 }
                 $lr = '';
-                if (is_array($admin->getLRecords()[$t])) {
+                if (is_array($admin->getLRecords()[$t] ?? false)) {
                     foreach ($admin->getLRecords()[$t] as $data) {
                         if (!GeneralUtility::inList($admin->getLostPagesList(), $data['pid'])) {
                             $lr .= '<div class="record"><a href="' . htmlspecialchars((string)$this->uriBuilder->buildUriFromRoute('system_dbint') . '&SET[function]=records&fixLostRecords_table=' . $t . '&fixLostRecords_uid=' . $data['uid']) . '" title="' . htmlspecialchars($lang->getLL('fixLostRecord')) . '">' . $this->iconFactory->getIcon('status-dialog-error', Icon::SIZE_SMALL)->render() . '</a>uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) . '</div>';
diff --git a/typo3/sysext/lowlevel/Classes/Database/QueryGenerator.php b/typo3/sysext/lowlevel/Classes/Database/QueryGenerator.php
index 6ea7325f29f3..f9461fb4af66 100644
--- a/typo3/sysext/lowlevel/Classes/Database/QueryGenerator.php
+++ b/typo3/sysext/lowlevel/Classes/Database/QueryGenerator.php
@@ -318,7 +318,7 @@ class QueryGenerator
         $markup = [];
         $markup[] = '<div class="form-group">';
         $markup[] = '<input placeholder="Search Word" class="form-control" type="search" name="SET[sword]" value="'
-            . htmlspecialchars($this->settings['sword']) . '">';
+            . htmlspecialchars($this->settings['sword'] ?? '') . '">';
         $markup[] = '</div>';
         $markup[] = '<div class="form-group">';
         $markup[] = '<input class="btn btn-default" type="submit" name="submit" value="Search All Records">';
@@ -337,13 +337,13 @@ class QueryGenerator
         $this->hookArray = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3lib_fullsearch'] ?? [];
         $msg = $this->procesStoreControl();
         $userTsConfig = $this->getBackendUserAuthentication()->getTSConfig();
-        if (!$userTsConfig['mod.']['dbint.']['disableStoreControl']) {
+        if (!($userTsConfig['mod.']['dbint.']['disableStoreControl'] ?? false)) {
             $output .= '<h2>Load/Save Query</h2>';
             $output .= '<div>' . $this->makeStoreControl() . '</div>';
             $output .= $msg;
         }
         // Query Maker:
-        $this->init('queryConfig', $this->settings['queryTable'], '', $this->settings);
+        $this->init('queryConfig', $this->settings['queryTable'] ?? '', '', $this->settings);
         if ($this->formName) {
             $this->setFormName($this->formName);
         }
@@ -379,7 +379,7 @@ class QueryGenerator
                         $fullQueryString = $selectQueryString;
                         $dataRows = $connection->executeQuery($selectQueryString)->fetchAll();
                     }
-                    if (!$userTsConfig['mod.']['dbint.']['disableShowSQLQuery']) {
+                    if (!($userTsConfig['mod.']['dbint.']['disableShowSQLQuery'] ?? false)) {
                         $output .= '<h2>SQL query</h2><div><pre>' . htmlspecialchars($fullQueryString) . '</pre></div>';
                     }
                     $cPR = $this->getQueryResultCode($mQ, $dataRows, $this->table);
@@ -405,7 +405,7 @@ class QueryGenerator
      */
     public function search()
     {
-        $swords = $this->settings['sword'];
+        $swords = $this->settings['sword'] ?? '';
         $out = '';
         if ($swords) {
             foreach ($GLOBALS['TCA'] as $table => $value) {
@@ -547,7 +547,7 @@ class QueryGenerator
         $storeArray = [
             '0' => '[New]'
         ];
-        $savedStoreArray = unserialize($this->settings['storeArray'], ['allowed_classes' => false]);
+        $savedStoreArray = unserialize($this->settings['storeArray'] ?? '', ['allowed_classes' => false]);
         if (is_array($savedStoreArray)) {
             $storeArray = array_merge($storeArray, $savedStoreArray);
         }
@@ -667,14 +667,14 @@ class QueryGenerator
         $languageService = $this->getLanguageService();
         $flashMessage = null;
         $storeArray = $this->initStoreArray();
-        $storeQueryConfigs = unserialize($this->settings['storeQueryConfigs'], ['allowed_classes' => false]);
+        $storeQueryConfigs = unserialize($this->settings['storeQueryConfigs'] ?? '', ['allowed_classes' => false]);
         $storeControl = GeneralUtility::_GP('storeControl');
-        $storeIndex = (int)$storeControl['STORE'];
+        $storeIndex = (int)($storeControl['STORE'] ?? 0);
         $saveStoreArray = 0;
         $writeArray = [];
         $msg = '';
         if (is_array($storeControl)) {
-            if ($storeControl['LOAD']) {
+            if ($storeControl['LOAD'] ?? false) {
                 if ($storeIndex > 0) {
                     $writeArray = $this->loadStoreQueryConfigs($storeQueryConfigs, $storeIndex, $writeArray);
                     $saveStoreArray = 1;
@@ -698,7 +698,7 @@ class QueryGenerator
                         );
                     }
                 }
-            } elseif ($storeControl['SAVE']) {
+            } elseif ($storeControl['SAVE'] ?? false) {
                 if ($storeIndex < 0) {
                     $qOK = $this->saveQueryInAction(abs($storeIndex));
                     if ($qOK) {
@@ -731,7 +731,7 @@ class QueryGenerator
                         );
                     }
                 }
-            } elseif ($storeControl['REMOVE']) {
+            } elseif ($storeControl['REMOVE'] ?? false) {
                 if ($storeIndex > 0) {
                     $flashMessage = GeneralUtility::makeInstance(
                         FlashMessage::class,
@@ -788,7 +788,7 @@ class QueryGenerator
                 foreach ($dataRows as $dataRow) {
                     $rowArr[] = $this->resultRowDisplay($dataRow, $GLOBALS['TCA'][$table], $table);
                 }
-                if (is_array($this->hookArray['beforeResultTable'])) {
+                if (is_array($this->hookArray['beforeResultTable'] ?? false)) {
                     foreach ($this->hookArray['beforeResultTable'] as $_funcRef) {
                         $out .= GeneralUtility::callUserFunction($_funcRef, $this->settings);
                     }
@@ -882,12 +882,12 @@ class QueryGenerator
         $languageService = $this->getLanguageService();
         $out = '<tr>';
         foreach ($row as $fieldName => $fieldValue) {
-            if (GeneralUtility::inList($this->settings['queryFields'], $fieldName)
-                || !$this->settings['queryFields']
+            if (GeneralUtility::inList($this->settings['queryFields'] ?? '', $fieldName)
+                || !($this->settings['queryFields'] ?? false)
                 && $fieldName !== 'pid'
                 && $fieldName !== 'deleted'
             ) {
-                if ($this->settings['search_result_labels']) {
+                if ($this->settings['search_result_labels'] ?? false) {
                     $fVnew = $this->getProcessedValueExtra($table, $fieldName, $fieldValue, $conf, '<br />');
                 } else {
                     $fVnew = htmlspecialchars($fieldValue);
@@ -898,7 +898,7 @@ class QueryGenerator
         $out .= '<td>';
         $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
 
-        if (!$row['deleted']) {
+        if (!($row['deleted'] ?? false)) {
             $out .= '<div class="btn-group" role="group">';
             $url = (string)$uriBuilder->buildUriFromRoute('record_edit', [
                 'edit' => [
@@ -955,7 +955,7 @@ class QueryGenerator
             $out .= '</div>';
         }
         $_params = [$table => $row];
-        if (is_array($this->hookArray['additionalButtons'])) {
+        if (is_array($this->hookArray['additionalButtons'] ?? false)) {
             foreach ($this->hookArray['additionalButtons'] as $_funcRef) {
                 $out .= GeneralUtility::callUserFunction($_funcRef, $_params);
             }
@@ -1357,12 +1357,12 @@ class QueryGenerator
         $tableHeader[] = '<thead><tr>';
         // Iterate over given columns
         foreach ($row as $fieldName => $fieldValue) {
-            if (GeneralUtility::inList($this->settings['queryFields'], $fieldName)
-                || !$this->settings['queryFields']
+            if (GeneralUtility::inList($this->settings['queryFields'] ?? '', $fieldName)
+                || !($this->settings['queryFields'] ?? false)
                 && $fieldName !== 'pid'
                 && $fieldName !== 'deleted'
             ) {
-                if ($this->settings['search_result_labels']) {
+                if ($this->settings['search_result_labels'] ?? false) {
                     $title = $languageService->sL($conf['columns'][$fieldName]['label']
                         ?: $fieldName);
                 } else {
@@ -1411,7 +1411,7 @@ class QueryGenerator
             if ($GLOBALS['TCA'][$this->table]['ctrl']['cruser_id']) {
                 $fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['cruser_id'];
             }
-            if ($GLOBALS['TCA'][$this->table]['ctrl']['sortby']) {
+            if ($GLOBALS['TCA'][$this->table]['ctrl']['sortby'] ?? false) {
                 $fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['sortby'];
             }
         }
@@ -1429,25 +1429,25 @@ class QueryGenerator
     protected function init($name, $table, $fieldList = '', array $settings = [])
     {
         // Analysing the fields in the table.
-        if (is_array($GLOBALS['TCA'][$table])) {
+        if (is_array($GLOBALS['TCA'][$table] ?? false)) {
             $this->name = $name;
             $this->table = $table;
             $this->fieldList = $fieldList ?: $this->makeFieldList();
             $this->settings = $settings;
             $fieldArr = GeneralUtility::trimExplode(',', $this->fieldList, true);
             foreach ($fieldArr as $fieldName) {
-                $fC = $GLOBALS['TCA'][$this->table]['columns'][$fieldName];
-                $this->fields[$fieldName] = $fC['config'];
-                $this->fields[$fieldName]['exclude'] = $fC['exclude'];
-                if ($this->fields[$fieldName]['type'] === 'user' && !isset($this->fields[$fieldName]['type']['userFunc'])
-                    || $this->fields[$fieldName]['type'] === 'none'
+                $fC = $GLOBALS['TCA'][$this->table]['columns'][$fieldName] ?? [];
+                $this->fields[$fieldName] = $fC['config'] ?? [];
+                $this->fields[$fieldName]['exclude'] = $fC['exclude'] ?? '';
+                if (($this->fields[$fieldName]['type'] ?? '') === 'user' && !isset($this->fields[$fieldName]['type']['userFunc'])
+                    || ($this->fields[$fieldName]['type'] ?? '') === 'none'
                 ) {
                     // Do not list type=none "virtual" fields or query them from db,
                     // and if type is user without defined userFunc
                     unset($this->fields[$fieldName]);
                     continue;
                 }
-                if (is_array($fC) && $fC['label']) {
+                if (is_array($fC) && ($fC['label'] ?? false)) {
                     $this->fields[$fieldName]['label'] = rtrim(trim($this->getLanguageService()->sL($fC['label'])), ':');
                     switch ($this->fields[$fieldName]['type']) {
                         case 'input':
@@ -1473,10 +1473,10 @@ class QueryGenerator
                             break;
                         case 'select':
                             $this->fields[$fieldName]['type'] = 'multiple';
-                            if ($this->fields[$fieldName]['foreign_table']) {
+                            if ($this->fields[$fieldName]['foreign_table'] ?? false) {
                                 $this->fields[$fieldName]['type'] = 'relation';
                             }
-                            if ($this->fields[$fieldName]['special']) {
+                            if ($this->fields[$fieldName]['special'] ?? false) {
                                 $this->fields[$fieldName]['type'] = 'text';
                             }
                             break;
@@ -1583,7 +1583,7 @@ class QueryGenerator
         $this->queryConfig = $qC;
         $POST = GeneralUtility::_POST();
         // If delete...
-        if ($POST['qG_del']) {
+        if ($POST['qG_del'] ?? false) {
             // Initialize array to work on, save special parameters
             $ssArr = $this->getSubscript($POST['qG_del']);
             $workArr = &$this->queryConfig;
@@ -1601,7 +1601,7 @@ class QueryGenerator
             }
         }
         // If insert...
-        if ($POST['qG_ins']) {
+        if ($POST['qG_ins'] ?? false) {
             // Initialize array to work on, save special parameters
             $ssArr = $this->getSubscript($POST['qG_ins']);
             $workArr = &$this->queryConfig;
@@ -1620,7 +1620,7 @@ class QueryGenerator
             $workArr[$ssArr[$i] + 1]['type'] = 'FIELD_';
         }
         // If move up...
-        if ($POST['qG_up']) {
+        if ($POST['qG_up'] ?? false) {
             // Initialize array to work on
             $ssArr = $this->getSubscript($POST['qG_up']);
             $workArr = &$this->queryConfig;
@@ -1635,7 +1635,7 @@ class QueryGenerator
             $workArr[$ssArr[$i] - 1] = $qG_tmp;
         }
         // If new level...
-        if ($POST['qG_nl']) {
+        if ($POST['qG_nl'] ?? false) {
             // Initialize array to work on
             $ssArr = $this->getSubscript($POST['qG_nl']);
             $workArr = &$this->queryConfig;
@@ -1657,7 +1657,7 @@ class QueryGenerator
             }
         }
         // If collapse level...
-        if ($POST['qG_remnl']) {
+        if ($POST['qG_remnl'] ?? false) {
             // Initialize array to work on
             $ssArr = $this->getSubscript($POST['qG_remnl']);
             $workArr = &$this->queryConfig;
@@ -2535,7 +2535,7 @@ class QueryGenerator
         $userTsConfig = $this->getBackendUserAuthentication()->getTSConfig();
 
         // Make output
-        if (in_array('table', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableSelectATable']) {
+        if (in_array('table', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableSelectATable'] ?? false)) {
             $out[] = '<div class="form-group">';
             $out[] = '	<label for="SET[queryTable]">Select a table:</label>';
             $out[] =    $this->mkTableSelect('SET[queryTable]', $this->table);
@@ -2543,16 +2543,16 @@ class QueryGenerator
         }
         if ($this->table) {
             // Init fields:
-            $this->setAndCleanUpExternalLists('queryFields', $modSettings['queryFields'], 'uid,' . $this->getLabelCol());
-            $this->setAndCleanUpExternalLists('queryGroup', $modSettings['queryGroup']);
-            $this->setAndCleanUpExternalLists('queryOrder', $modSettings['queryOrder'] . ',' . $modSettings['queryOrder2']);
+            $this->setAndCleanUpExternalLists('queryFields', $modSettings['queryFields'] ?? '', 'uid,' . $this->getLabelCol());
+            $this->setAndCleanUpExternalLists('queryGroup', $modSettings['queryGroup'] ?? '');
+            $this->setAndCleanUpExternalLists('queryOrder', ($modSettings['queryOrder'] ?? '') . ',' . ($modSettings['queryOrder2'] ?? ''));
             // Limit:
-            $this->extFieldLists['queryLimit'] = $modSettings['queryLimit'];
+            $this->extFieldLists['queryLimit'] = $modSettings['queryLimit'] ?? '';
             if (!$this->extFieldLists['queryLimit']) {
                 $this->extFieldLists['queryLimit'] = 100;
             }
             $parts = GeneralUtility::intExplode(',', $this->extFieldLists['queryLimit']);
-            if ($parts[1]) {
+            if ($parts[1] ?? false) {
                 $this->limitBegin = $parts[0];
                 $this->limitLength = $parts[1];
             } else {
@@ -2570,35 +2570,35 @@ class QueryGenerator
                 $this->extFieldLists['queryOrder_SQL'] = implode(',', $reList);
             }
             // Query Generator:
-            $this->procesData($modSettings['queryConfig'] ? unserialize($modSettings['queryConfig'], ['allowed_classes' => false]) : []);
+            $this->procesData(($modSettings['queryConfig'] ?? false) ? unserialize($modSettings['queryConfig'] ?? '', ['allowed_classes' => false]) : []);
             $this->queryConfig = $this->cleanUpQueryConfig($this->queryConfig);
             $this->enableQueryParts = (bool)$modSettings['search_query_smallparts'];
             $codeArr = $this->getFormElements();
             $queryCode = $this->printCodeArray($codeArr);
-            if (in_array('fields', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableSelectFields']) {
+            if (in_array('fields', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableSelectFields'] ?? false)) {
                 $out[] = '<div class="form-group form-group-with-button-addon">';
                 $out[] = '	<label for="SET[queryFields]">Select fields:</label>';
                 $out[] =    $this->mkFieldToInputSelect('SET[queryFields]', $this->extFieldLists['queryFields']);
                 $out[] = '</div>';
             }
-            if (in_array('query', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableMakeQuery']) {
+            if (in_array('query', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableMakeQuery'] ?? false)) {
                 $out[] = '<div class="form-group">';
                 $out[] = '	<label>Make Query:</label>';
                 $out[] =    $queryCode;
                 $out[] = '</div>';
             }
-            if (in_array('group', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableGroupBy']) {
+            if (in_array('group', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableGroupBy'] ?? false)) {
                 $out[] = '<div class="form-group form-inline">';
                 $out[] = '	<label for="SET[queryGroup]">Group By:</label>';
                 $out[] =     $this->mkTypeSelect('SET[queryGroup]', $this->extFieldLists['queryGroup'], '');
                 $out[] = '</div>';
             }
-            if (in_array('order', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableOrderBy']) {
+            if (in_array('order', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableOrderBy'] ?? false)) {
                 $orderByArr = explode(',', $this->extFieldLists['queryOrder']);
                 $orderBy = [];
                 $orderBy[] = $this->mkTypeSelect('SET[queryOrder]', $orderByArr[0], '');
                 $orderBy[] = '<div class="form-check">';
-                $orderBy[] =    BackendUtility::getFuncCheck(0, 'SET[queryOrderDesc]', $modSettings['queryOrderDesc'], '', '', 'id="checkQueryOrderDesc"');
+                $orderBy[] =    BackendUtility::getFuncCheck(0, 'SET[queryOrderDesc]', $modSettings['queryOrderDesc'] ?? '', '', '', 'id="checkQueryOrderDesc"');
                 $orderBy[] = '	<label class="form-check-label" for="checkQueryOrderDesc">Descending</label>';
                 $orderBy[] = '</div>';
 
@@ -2614,7 +2614,7 @@ class QueryGenerator
                 $out[] =     implode(LF, $orderBy);
                 $out[] = '</div>';
             }
-            if (in_array('limit', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableLimit']) {
+            if (in_array('limit', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableLimit'] ?? false)) {
                 $limit = [];
                 $limit[] = '<div class="input-group">';
                 $limit[] = '	<span class="input-group-btn">';
diff --git a/typo3/sysext/lowlevel/Classes/Integrity/DatabaseIntegrityCheck.php b/typo3/sysext/lowlevel/Classes/Integrity/DatabaseIntegrityCheck.php
index 3efc373bf5ef..dc722272bb37 100644
--- a/typo3/sysext/lowlevel/Classes/Integrity/DatabaseIntegrityCheck.php
+++ b/typo3/sysext/lowlevel/Classes/Integrity/DatabaseIntegrityCheck.php
@@ -147,9 +147,19 @@ class DatabaseIntegrityCheck
             if ($row['deleted']) {
                 $this->recStats['deleted']['pages'][$newID] = $newID;
             }
+
+            if (!isset($this->recStats['hidden'])) {
+                $this->recStats['hidden'] = 0;
+            }
+
             if ($row['hidden']) {
                 $this->recStats['hidden']++;
             }
+
+            if (!is_array($this->recStats['doktype'][$row['doktype']] ?? false)) {
+                $this->recStats['doktype'][$row['doktype']] = 0;
+            }
+
             $this->recStats['doktype'][$row['doktype']]++;
             // If all records should be shown, do so:
             if ($this->genTreeIncludeRecords) {
@@ -436,7 +446,7 @@ class DatabaseIntegrityCheck
 
             while ($row = $queryResult->fetch()) {
                 foreach ($fields as $field) {
-                    if (trim($row[$field])) {
+                    if (trim($row[$field] ?? '')) {
                         $fieldConf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
                         if ($fieldConf['type'] === 'group' && $fieldConf['internal_type'] === 'db') {
                             $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
-- 
GitLab