From 7eaa9095f9726987fe10c5c6708263385ccc527b Mon Sep 17 00:00:00 2001 From: Marianne Moser Date: Sun, 22 Oct 2023 16:49:15 +0200 Subject: [PATCH] [FEATURE] New TCA type "datetime" (#3556) --- config/v12/tca-120.php | 2 + .../v12/v0/tca/MigrateInputDateTimeRector.php | 199 ++++++++++++++++++ .../Fixture/fixture.php.inc | 165 +++++++++++++++ .../MigrateInputDateTimeRectorTest.php | 32 +++ .../config/configured_rule.php | 11 + 5 files changed, 409 insertions(+) create mode 100644 src/Rector/v12/v0/tca/MigrateInputDateTimeRector.php create mode 100644 tests/Rector/v12/v0/tca/MigrateInputDateTimeRector/Fixture/fixture.php.inc create mode 100644 tests/Rector/v12/v0/tca/MigrateInputDateTimeRector/MigrateInputDateTimeRectorTest.php create mode 100644 tests/Rector/v12/v0/tca/MigrateInputDateTimeRector/config/configured_rule.php diff --git a/config/v12/tca-120.php b/config/v12/tca-120.php index 74a21a4a6..2a2949eea 100644 --- a/config/v12/tca-120.php +++ b/config/v12/tca-120.php @@ -4,6 +4,7 @@ use Rector\Config\RectorConfig; use Ssch\TYPO3Rector\Rector\v12\v0\tca\MigrateColsToSizeForTcaTypeNoneRector; +use Ssch\TYPO3Rector\Rector\v12\v0\tca\MigrateInputDateTimeRector; use Ssch\TYPO3Rector\Rector\v12\v0\tca\MigrateInternalTypeRector; use Ssch\TYPO3Rector\Rector\v12\v0\tca\MigrateNullFlagRector; use Ssch\TYPO3Rector\Rector\v12\v0\tca\MigrateRequiredFlagRector; @@ -15,6 +16,7 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->import(__DIR__ . '/../config.php'); $rectorConfig->rule(MigrateColsToSizeForTcaTypeNoneRector::class); + $rectorConfig->rule(MigrateInputDateTimeRector::class); $rectorConfig->rule(MigrateInternalTypeRector::class); $rectorConfig->rule(MigrateNullFlagRector::class); $rectorConfig->rule(MigrateRequiredFlagRector::class); diff --git a/src/Rector/v12/v0/tca/MigrateInputDateTimeRector.php b/src/Rector/v12/v0/tca/MigrateInputDateTimeRector.php new file mode 100644 index 000000000..a95685c33 --- /dev/null +++ b/src/Rector/v12/v0/tca/MigrateInputDateTimeRector.php @@ -0,0 +1,199 @@ +> + */ + private const DATETIME_EMPTY_VALUES = [ + 'date' => [ + 'empty' => '0000-00-00', + ], + 'datetime' => [ + 'empty' => '0000-00-00 00:00:00', + ], + 'time' => [ + 'empty' => '00:00:00', + ], + ]; + + /** + * @codeCoverageIgnore + */ + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Migrate renderType inputDateTime to new TCA type datetime', [new CodeSample( + <<<'CODE_SAMPLE' +'a_datetime_field' => [ + 'label' => 'Datetime field', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'required' => true, + 'size' => 20, + 'max' => 1024, + 'eval' => 'date,int', + 'default' => 0, + ], + ], +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +'a_datetime_field' => [ + 'label' => 'Datetime field', + 'config' => [ + 'type' => 'datetime', + 'format' => 'date', + 'required' => true, + 'size' => 20, + 'default' => 0, + ] + ] +CODE_SAMPLE + )]); + } + + protected function refactorColumn(Expr $columnName, Expr $columnTca): void + { + $configArray = $this->extractSubArrayByKey($columnTca, self::CONFIG); + if (! $configArray instanceof Array_) { + return; + } + + // Early return in case column is not of type=input with renderType=inputDateTime + if (! $this->isConfigType($configArray, 'input')) { + return; + } + + if (! $this->configIsOfRenderType($configArray, self::INPUT_DATE_TIME)) { + return; + } + + // Set the TCA type to "datetime" + $typeArrayItem = $this->extractArrayItemByKey($configArray, 'type'); + if (! $typeArrayItem instanceof ArrayItem) { + return; + } + + $typeArrayItem->value = new String_('datetime'); + + // Remove 'renderType' => 'inputDateTime', + $renderTypeArrayItem = $this->extractArrayItemByKey($configArray, 'renderType'); + + if (! $renderTypeArrayItem instanceof ArrayItem) { + return; + } + + $renderTypeValue = $this->valueResolver->getValue($renderTypeArrayItem->value); + + if (! is_string($renderTypeValue)) { + return; + } + + $this->removeNode($renderTypeArrayItem); + + // Remove 'max' config + $maxArrayItem = $this->extractArrayItemByKey($configArray, 'max'); + if ($maxArrayItem instanceof ArrayItem) { + $this->removeNode($maxArrayItem); + } + + $evalList = []; + $evalArrayItem = $this->extractArrayItemByKey($configArray, 'eval'); + if ($evalArrayItem instanceof ArrayItem) { + $evalString = $this->valueResolver->getValue($evalArrayItem->value); + + if (! is_string($evalString)) { + return; + } + + $evalList = ArrayUtility::trimExplode(',', $evalString, true); + + // Remove 'eval' config + $this->removeNode($evalArrayItem); + } + + // Remove format config, if set + $formatArrayItem = $this->extractArrayItemByKey($configArray, 'format'); + if ($formatArrayItem instanceof ArrayItem) { + $this->removeNode($formatArrayItem); + } + + // Set the "format" based on "eval" + // If no 'format' config is set it will fall back to 'datetime' + if ($evalList !== []) { + if (in_array('date', $evalList, true)) { + $configArray->items[] = new ArrayItem(new String_('date'), new String_('format')); + } elseif (in_array('time', $evalList, true)) { + $configArray->items[] = new ArrayItem(new String_('time'), new String_('format')); + } elseif (in_array('timesec', $evalList, true)) { + $configArray->items[] = new ArrayItem(new String_('timesec'), new String_('format')); + } + } + + // Removes option [config][default], if the default is the native "empty" value + + $defaultArrayItem = $this->extractArrayItemByKey($configArray, 'default'); + + if ($defaultArrayItem instanceof ArrayItem) { + $dbTypeValue = null; + $defaultValue = $this->valueResolver->getValue($defaultArrayItem->value); + $dbTypeArrayItem = $this->extractArrayItemByKey($configArray, 'dbType'); + + if ($dbTypeArrayItem instanceof ArrayItem) { + $dbTypeValue = $this->valueResolver->getValue($dbTypeArrayItem->value); + } + + if (in_array($dbTypeValue, self::DATETIME_TYPES, true)) { + if ($defaultValue === self::DATETIME_EMPTY_VALUES[$dbTypeValue]['empty']) { + $this->removeNode($defaultArrayItem); + } + } elseif (! is_int($defaultValue)) { + if ($defaultValue === '') { + // Always use int as default (string values are no longer supported for "datetime") + $defaultArrayItem->value = new LNumber(0); + } elseif (MathUtility::canBeInterpretedAsInteger($defaultValue)) { + // Cast default to int, in case it can be interpreted as integer + $defaultArrayItem->value = new LNumber((int) $defaultValue); + } else { + // Unset default in case it's a no longer supported string + $this->removeNode($defaultArrayItem); + } + } + } + + $this->hasAstBeenChanged = true; + } +} diff --git a/tests/Rector/v12/v0/tca/MigrateInputDateTimeRector/Fixture/fixture.php.inc b/tests/Rector/v12/v0/tca/MigrateInputDateTimeRector/Fixture/fixture.php.inc new file mode 100644 index 000000000..387fedc04 --- /dev/null +++ b/tests/Rector/v12/v0/tca/MigrateInputDateTimeRector/Fixture/fixture.php.inc @@ -0,0 +1,165 @@ + [], + 'columns' => [ + 'a_date_field' => [ + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'required' => true, + 'size' => 20, + 'max' => 1024, + 'eval' => 'date,int', + 'default' => 0, + ], + ], + 'a_datetime_field' => [ + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'eval' => 'datetime', + 'size' => 20, + 'required' => true, + ], + ], + 'a_timesec_field' => [ + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'eval' => 'timesec', + ], + ], + 'format_datetime' => [ + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'format' => 'datetime', + ], + ], + 'default_string_zero' => [ + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'default' => '0', + ], + ], + 'default_string_cast' => [ + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'default' => '9', + ], + ], + 'default_empty' => [ + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'default' => '', + ], + ], + 'default_empty_date' => [ + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'dbType' => 'date', + 'default' => '0000-00-00', + ], + ], + 'default_empty_datetime' => [ + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'dbType' => 'datetime', + 'default' => '0000-00-00 00:00:00', + ], + ], + 'default_empty_time' => [ + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'dbType' => 'time', + 'default' => '00:00:00', + ], + ], + ], +]; + +?> +----- + [], + 'columns' => [ + 'a_date_field' => [ + 'config' => [ + 'type' => 'datetime', + 'required' => true, + 'size' => 20, + 'default' => 0, + 'format' => 'date', + ], + ], + 'a_datetime_field' => [ + 'config' => [ + 'type' => 'datetime', + 'size' => 20, + 'required' => true, + ], + ], + 'a_timesec_field' => [ + 'config' => [ + 'type' => 'datetime', + 'format' => 'timesec', + ], + ], + 'format_datetime' => [ + 'config' => [ + 'type' => 'datetime', + ], + ], + 'default_string_zero' => [ + 'config' => [ + 'type' => 'datetime', + 'default' => 0, + ], + ], + 'default_string_cast' => [ + 'config' => [ + 'type' => 'datetime', + 'default' => 9, + ], + ], + 'default_empty' => [ + 'config' => [ + 'type' => 'datetime', + 'default' => 0, + ], + ], + 'default_empty_date' => [ + 'config' => [ + 'type' => 'datetime', + 'dbType' => 'date', + ], + ], + 'default_empty_datetime' => [ + 'config' => [ + 'type' => 'datetime', + 'dbType' => 'datetime', + ], + ], + 'default_empty_time' => [ + 'config' => [ + 'type' => 'datetime', + 'dbType' => 'time', + ], + ], + ], +]; + +?> diff --git a/tests/Rector/v12/v0/tca/MigrateInputDateTimeRector/MigrateInputDateTimeRectorTest.php b/tests/Rector/v12/v0/tca/MigrateInputDateTimeRector/MigrateInputDateTimeRectorTest.php new file mode 100644 index 000000000..fa6b8ab28 --- /dev/null +++ b/tests/Rector/v12/v0/tca/MigrateInputDateTimeRector/MigrateInputDateTimeRectorTest.php @@ -0,0 +1,32 @@ +doTestFile($filePath); + } + + /** + * @return Iterator> + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Rector/v12/v0/tca/MigrateInputDateTimeRector/config/configured_rule.php b/tests/Rector/v12/v0/tca/MigrateInputDateTimeRector/config/configured_rule.php new file mode 100644 index 000000000..4fbe1242f --- /dev/null +++ b/tests/Rector/v12/v0/tca/MigrateInputDateTimeRector/config/configured_rule.php @@ -0,0 +1,11 @@ +import(__DIR__ . '/../../../../../../../config/config_test.php'); + $rectorConfig->rule(MigrateInputDateTimeRector::class); +};