From dbd804dc5573c688632cef6dfbd9dce10a71cb29 Mon Sep 17 00:00:00 2001 From: jakotadesigngroup <suchowski@jakota.de> Date: Sun, 12 Mar 2023 09:49:29 +0100 Subject: [PATCH] [BUGFIX] Respect nullable date time fields Nullable time fields with value null (not set) are reset to 0 on update. This changes the value from not set to midnight. It is not so problematic for date fields as there reset value, for the time being, is null also. But this behavior is wrong anyway. Resolves: #99847 Releases: main, 12.4, 11.5 Change-Id: I080b6bb1ad00a025967d9626632ff71b8fb2193a Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/79748 Tested-by: Benni Mack <benni@typo3.org> Reviewed-by: Benni Mack <benni@typo3.org> Tested-by: core-ci <typo3@b13.com> --- .../DatabaseRowDateTimeFields.php | 38 ++++-- .../DatabaseRowDateTimeFieldsTest.php | 128 ++++++++++++++++++ 2 files changed, 151 insertions(+), 15 deletions(-) diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRowDateTimeFields.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRowDateTimeFields.php index 90635004e52c..2ab245ef663e 100644 --- a/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRowDateTimeFields.php +++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRowDateTimeFields.php @@ -34,24 +34,32 @@ class DatabaseRowDateTimeFields implements FormDataProviderInterface $dateTimeFormats = QueryHelper::getDateTimeFormats(); foreach ($result['processedTca']['columns'] as $column => $columnConfig) { - if (($columnConfig['config']['type'] ?? '') !== 'datetime') { + if (($columnConfig['config']['type'] ?? '') !== 'datetime' + || !in_array($columnConfig['config']['dbType'] ?? '', $dateTimeTypes, true) + ) { + // it's a UNIX timestamp! We do not modify this here, as it will only be treated as a datetime because + // of eval being set to "date" or "datetime". This is handled in InputTextElement then. continue; } - if (in_array($columnConfig['config']['dbType'] ?? '', $dateTimeTypes, true)) { - $format = $dateTimeFormats[$columnConfig['config']['dbType']] ?? []; - if (!empty($result['databaseRow'][$column]) - && $result['databaseRow'][$column] !== ($format['empty'] ?? null) - ) { - // Create an ISO-8601 date from current field data; the database always contains UTC - // The field value is something like "2016-01-01" or "2016-01-01 10:11:12", so appending "UTC" - // makes date() treat it as a UTC date (which is what we store in the database). - $result['databaseRow'][$column] = date('c', (int)strtotime($result['databaseRow'][$column] . ' UTC')); - } else { - $result['databaseRow'][$column] = $format['reset'] ?? null; - } + // ensure the column's value is set + $result['databaseRow'][$column] = $result['databaseRow'][$column] ?? null; + + // Nullable fields do not need treatment + $isNullable = $columnConfig['config']['nullable'] ?? false; + if ($isNullable && $result['databaseRow'][$column] === null) { + continue; + } + + $format = $dateTimeFormats[$columnConfig['config']['dbType']] ?? []; + $emptyValueFormat = $format['empty'] ?? null; + if (!empty($result['databaseRow'][$column]) && $result['databaseRow'][$column] !== $emptyValueFormat) { + // Create an ISO-8601 date from current field data; the database always contains UTC + // The field value is something like "2016-01-01" or "2016-01-01 10:11:12", so appending "UTC" + // makes date() treat it as a UTC date (which is what we store in the database). + $result['databaseRow'][$column] = date('c', (int)strtotime($result['databaseRow'][$column] . ' UTC')); + } else { + $result['databaseRow'][$column] = $format['reset'] ?? null; } - // its a UNIX timestamp! We do not modify this here, as it will only be treated as a datetime because - // of eval being set to "date" or "datetime". This is handled in InputTextElement then. } return $result; } diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRowDateTimeFieldsTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRowDateTimeFieldsTest.php index f392d63185d7..113bc8dfb518 100644 --- a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRowDateTimeFieldsTest.php +++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRowDateTimeFieldsTest.php @@ -46,6 +46,38 @@ final class DatabaseRowDateTimeFieldsTest extends UnitTestCase self::assertEquals($expected, (new DatabaseRowDateTimeFields())->addData($input)); } + /** + * @test + */ + public function addDataSetsTimestampNullForDefaultDateField(): void + { + $input = [ + 'tableName' => 'aTable', + 'processedTca' => [ + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'datetime', + 'dbType' => 'date', + 'nullable' => true, + ], + ], + ], + ], + ]; + + $expected = $input; + $expected['databaseRow']['aField'] = null; + + $actual = (new DatabaseRowDateTimeFields())->addData($input); + + self::assertEquals($expected, $actual); + + $expected = null; + + self::assertSame($expected, $actual['databaseRow']['aField']); + } + /** * @test */ @@ -69,6 +101,40 @@ final class DatabaseRowDateTimeFieldsTest extends UnitTestCase self::assertEquals($expected, (new DatabaseRowDateTimeFields())->addData($input)); } + /** + * @test + */ + public function addDataSetsTimestampNullForDefaultDateTimeField(): void + { + $input = [ + 'tableName' => 'aTable', + 'processedTca' => [ + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'datetime', + 'dbType' => 'datetime', + 'nullable' => true, + ], + ], + ], + ], + 'databaseRow' => [ + 'aField' => null, + ], + ]; + $expected = $input; + $expected['databaseRow']['aField'] = null; + + $actual = (new DatabaseRowDateTimeFields())->addData($input); + + self::assertEquals($expected, $actual); + + $expected = null; + + self::assertSame($expected, $actual['databaseRow']['aField']); + } + /** * @test */ @@ -92,6 +158,37 @@ final class DatabaseRowDateTimeFieldsTest extends UnitTestCase self::assertEquals($expected, (new DatabaseRowDateTimeFields())->addData($input)); } + /** + * @test + */ + public function addDataSetsTimestampNullForDefaultTimeField(): void + { + $input = [ + 'tableName' => 'aTable', + 'processedTca' => [ + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'datetime', + 'dbType' => 'time', + 'nullable' => true, + ], + ], + ], + ], + ]; + $expected = $input; + $expected['databaseRow']['aField'] = null; + + $actual = (new DatabaseRowDateTimeFields())->addData($input); + + self::assertEquals($expected, $actual); + + $expected = null; + + self::assertSame($expected, $actual['databaseRow']['aField']); + } + /** * @test */ @@ -179,6 +276,37 @@ final class DatabaseRowDateTimeFieldsTest extends UnitTestCase date_default_timezone_set($oldTimezone); } + /** + * @test + */ + public function addDataConvertsMidnightTimeStringOfNullableFieldToTimestamp(): void + { + $oldTimezone = date_default_timezone_get(); + date_default_timezone_set('UTC'); + $input = [ + 'tableName' => 'aTable', + 'processedTca' => [ + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'datetime', + 'dbType' => 'time', + 'nullable' => true, + ], + ], + ], + ], + 'databaseRow' => [ + 'aField' => '00:00:00', + ], + ]; + $expected = $input; + $expected['databaseRow']['aField'] = 0; + + self::assertEquals($expected, (new DatabaseRowDateTimeFields())->addData($input)); + date_default_timezone_set($oldTimezone); + } + /** * @test */ -- GitLab