From 32cb648073f28184679fb656ed7acfae3b3351ef Mon Sep 17 00:00:00 2001
From: Thomas Hohn <thomas@hohn.dk>
Date: Fri, 24 Feb 2017 08:26:56 +0100
Subject: [PATCH] [BUGFIX] DB Check causes PHP warnings and errors

In DatabaseIntegrityCheck the method selectNonEmptyRecordsWithFkeys
also needs to take the BLOB into account or else it will fail with
a PHP warning. Furthermore the returned tableColumns list may contain
quoted fields names if the field name can be interpreted as a keyword
for the used connection platform. This patch ensures the correct
column information is received before further processing is started.

Additionally the "Record Statics" module is fixed by checking for an
empty TCA table field which invokes an exception.

Resolves: #79347
Resolves: #79991
Resolves: #79992
Releases: master
Change-Id: Ie455abd2da90d7dabc773ec345b5c8a87b0b5836
Reviewed-on: https://review.typo3.org/51813
Reviewed-by: Christer V <cvi@systime.dk>
Tested-by: Christer V <cvi@systime.dk>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Claus Due <claus@phpmind.net>
Reviewed-by: Nicole Cordes <typo3@cordes.co>
Tested-by: Nicole Cordes <typo3@cordes.co>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Anders Kostending <aha@systime.dk>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 .../Query/Expression/ExpressionBuilder.php    | 12 +++++++++
 .../Integrity/DatabaseIntegrityCheck.php      | 26 ++++++++++++++++---
 .../Expression/ExpressionBuilderTest.php      | 17 ++++++++++++
 3 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/typo3/sysext/core/Classes/Database/Query/Expression/ExpressionBuilder.php b/typo3/sysext/core/Classes/Database/Query/Expression/ExpressionBuilder.php
index ad5cf5bdb840..692dd25b795a 100644
--- a/typo3/sysext/core/Classes/Database/Query/Expression/ExpressionBuilder.php
+++ b/typo3/sysext/core/Classes/Database/Query/Expression/ExpressionBuilder.php
@@ -437,6 +437,18 @@ class ExpressionBuilder
         return $this->calculation('COUNT', $fieldName, $alias);
     }
 
+    /**
+     * Creates a LENGTH expression for the given field/alias.
+     *
+     * @param string $fieldName
+     * @param string|null $alias
+     * @return string
+     */
+    public function length(string $fieldName, string $alias = null): string
+    {
+        return $this->calculation('LENGTH', $fieldName, $alias);
+    }
+
     /**
      * Create a SQL aggregate function.
      *
diff --git a/typo3/sysext/core/Classes/Integrity/DatabaseIntegrityCheck.php b/typo3/sysext/core/Classes/Integrity/DatabaseIntegrityCheck.php
index 82be6f70cbb6..34eb270763e8 100644
--- a/typo3/sysext/core/Classes/Integrity/DatabaseIntegrityCheck.php
+++ b/typo3/sysext/core/Classes/Integrity/DatabaseIntegrityCheck.php
@@ -18,6 +18,7 @@ use Doctrine\DBAL\Types\Type;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\RelationHandler;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -227,7 +228,11 @@ class DatabaseIntegrityCheck
                 }
                 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
                 $queryBuilder->getRestrictions()->removeAll();
-                $queryResult = $queryBuilder->select('uid', 'pid', $GLOBALS['TCA'][$table]['ctrl']['label'])
+                $selectFields = ['uid', 'pid'];
+                if (!empty($GLOBALS['TCA'][$table]['ctrl']['label'])) {
+                    $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['label'];
+                }
+                $queryResult = $queryBuilder->select(...$selectFields)
                     ->from($table)
                     ->where(
                         $queryBuilder->expr()->notIn(
@@ -422,7 +427,8 @@ class DatabaseIntegrityCheck
             $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
             foreach ($fkey_arrays as $table => $field_list) {
                 if ($GLOBALS['TCA'][$table] && trim($field_list)) {
-                    $schemaManager = $connectionPool->getConnectionForTable($table)->getSchemaManager();
+                    $connection = $connectionPool->getConnectionForTable($table);
+                    $schemaManager = $connection->getSchemaManager();
                     $tableColumns = $schemaManager->listTableColumns($table);
 
                     $queryBuilder = $connectionPool->getQueryBuilderForTable($table);
@@ -433,9 +439,13 @@ class DatabaseIntegrityCheck
                     $queryBuilder->select('uid')
                         ->from($table);
                     $whereClause = [];
+
                     foreach ($fields as $fieldName) {
                         // The array index of $tableColumns is the lowercased column name!
-                        $fieldType = $tableColumns[strtolower($fieldName)]->getType()->getName();
+                        // It is quoted for keywords
+                        $column = $tableColumns[strtolower($fieldName)]
+                            ?? $tableColumns[$connection->quoteIdentifier(strtolower($fieldName))];
+                        $fieldType = $column->getType()->getName();
                         if (in_array(
                             $fieldType,
                             [Type::BIGINT, Type::INTEGER, Type::SMALLINT, Type::DECIMAL, Type::FLOAT],
@@ -456,6 +466,16 @@ class DatabaseIntegrityCheck
                                     $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
                                 )
                             );
+                        } elseif (in_array($fieldType, [Type::BLOB], true)) {
+                            $whereClause[] = $queryBuilder->expr()->andX(
+                                $queryBuilder->expr()->isNotNull($fieldName),
+                                $queryBuilder->expr()
+                                    ->comparison(
+                                        $queryBuilder->expr()->length($fieldName),
+                                        ExpressionBuilder::GT,
+                                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                                    )
+                            );
                         }
                     }
                     $queryResult = $queryBuilder->orWhere(...$whereClause)->execute();
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Expression/ExpressionBuilderTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Expression/ExpressionBuilderTest.php
index 30798ba895e9..4926d90b5db0 100644
--- a/typo3/sysext/core/Tests/Unit/Database/Query/Expression/ExpressionBuilderTest.php
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/Expression/ExpressionBuilderTest.php
@@ -488,6 +488,23 @@ class ExpressionBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCa
         );
     }
 
+    /**
+     * @test
+     */
+    public function lengthQuotesIdentifier()
+    {
+        $this->connectionProphet->quoteIdentifier(Argument::cetera())->will(function ($args) {
+            $platform = new MockPlatform();
+            return $platform->quoteIdentifier($args[0]);
+        });
+
+        $this->assertSame('LENGTH("tableName"."fieldName")', $this->subject->length('tableName.fieldName'));
+        $this->assertSame(
+            'LENGTH("tableName"."fieldName") AS "anAlias"',
+            $this->subject->length('tableName.fieldName', 'anAlias')
+        );
+    }
+
     /**
      * @test
      */
-- 
GitLab