diff --git a/config/v9/typo3-90.php b/config/v9/typo3-90.php index 4600db60f..4e50a7275 100644 --- a/config/v9/typo3-90.php +++ b/config/v9/typo3-90.php @@ -23,6 +23,7 @@ use Ssch\TYPO3Rector\Rector\v9\v0\ReplaceAnnotationRector; use Ssch\TYPO3Rector\Rector\v9\v0\SubstituteCacheWrapperMethodsRector; use Ssch\TYPO3Rector\Rector\v9\v0\SubstituteConstantParsetimeStartRector; +use Ssch\TYPO3Rector\Rector\v9\v0\UseExtensionConfigurationApiRector; use Ssch\TYPO3Rector\Rector\v9\v0\UseLogMethodInsteadOfNewLog2Rector; use Ssch\TYPO3Rector\Rector\v9\v0\UseNewComponentIdForPageTreeRector; use Ssch\TYPO3Rector\Rector\v9\v0\UseRenderingContextGetControllerContextRector; @@ -94,4 +95,6 @@ $services->set(UseNewComponentIdForPageTreeRector::class); $services->set(RefactorBackendUtilityGetPagesTSconfigRector::class); + + $services->set(UseExtensionConfigurationApiRector::class); }; diff --git a/src/Helper/Typo3NodeResolver.php b/src/Helper/Typo3NodeResolver.php index b90fc63ad..ce7ec7c05 100644 --- a/src/Helper/Typo3NodeResolver.php +++ b/src/Helper/Typo3NodeResolver.php @@ -53,7 +53,7 @@ final class Typo3NodeResolver /** * @var string */ - private const GLOBALS = 'GLOBALS'; + public const GLOBALS = 'GLOBALS'; public function isMethodCallOnGlobals(Node $node, string $methodCall, string $global): bool { diff --git a/src/Rector/v9/v0/UseExtensionConfigurationApiRector.php b/src/Rector/v9/v0/UseExtensionConfigurationApiRector.php new file mode 100644 index 000000000..ce2176ac6 --- /dev/null +++ b/src/Rector/v9/v0/UseExtensionConfigurationApiRector.php @@ -0,0 +1,134 @@ +isName($node->name, 'unserialize')) { + return null; + } + + $firstArgument = array_shift($node->args); + + if (null === $firstArgument) { + return null; + } + + if (! $firstArgument->value instanceof ArrayDimFetch) { + return null; + } + + $extensionConfiguration = $firstArgument->value; + + if ($this->shouldSkip($extensionConfiguration)) { + return null; + } + + return $this->createMethodCall($this->createStaticCall(GeneralUtility::class, 'makeInstance', [ + $this->createClassConstantReference(ExtensionConfiguration::class), + ]), 'get', [$extensionConfiguration->dim]); + } + + /** + * @codeCoverageIgnore + */ + public function getDefinition(): RectorDefinition + { + return new RectorDefinition( + 'Use the new ExtensionConfiguration API instead of $GLOBALS[\'TYPO3_CONF_VARS\'][\'EXT\'][\'extConf\'][\'foo\']', + [ + new CodeSample(<<<'PHP' +$extensionConfiguration2 = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['foo'], ['allowed_classes' => false]); +PHP + , <<<'PHP' +use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; +use TYPO3\CMS\Core\Utility\GeneralUtility; +$extensionConfiguration2 = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('foo'); +PHP + ), + + ]); + } + + private function shouldSkip(ArrayDimFetch $node): bool + { + $extConf = $node->var; + if (! $extConf instanceof ArrayDimFetch) { + return true; + } + + if (null === $extConf->dim) { + return true; + } + + if (! $this->isValue($extConf->dim, 'extConf')) { + return true; + } + + if (! property_exists($node->var, 'var')) { + return true; + } + + $ext = $node->var->var; + if (! $ext instanceof ArrayDimFetch) { + return true; + } + + if (null === $ext->dim) { + return true; + } + + if (! $this->isValue($ext->dim, 'EXT')) { + return true; + } + + $typo3ConfVars = $node->var->var->var; + if (! $typo3ConfVars instanceof ArrayDimFetch) { + return true; + } + + if (null === $typo3ConfVars->dim) { + return true; + } + + if (! $this->isValue($typo3ConfVars->dim, 'TYPO3_CONF_VARS')) { + return true; + } + + $globals = $node->var->var->var->var; + if (! $this->isName($globals, Typo3NodeResolver::GLOBALS)) { + return true; + } + + if (null === $node->dim) { + return true; + } + + return ! $this->isName($node->dim, '_EXTKEY') && null === $this->getValue($node->dim); + } +} diff --git a/stubs/Core/Configuration/ExtensionConfiguration.php b/stubs/Core/Configuration/ExtensionConfiguration.php new file mode 100644 index 000000000..3a093218f --- /dev/null +++ b/stubs/Core/Configuration/ExtensionConfiguration.php @@ -0,0 +1,25 @@ + false]); + +$_EXTKEY = 'foo'; +call_user_func(static function () use($_EXTKEY) { + $_EXTCONF = isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY]) ? unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY], ['allowed_classes' => false]) : []; +}); + +?> +----- +get('foo'); +$_EXTKEY = 'foo'; +call_user_func(static function () use($_EXTKEY) { + $_EXTCONF = isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$_EXTKEY]) ? GeneralUtility::makeInstance(ExtensionConfiguration::class)->get($_EXTKEY) : []; +}); + +?> diff --git a/tests/Rector/v9/v0/UseExtensionConfigurationApi/UseExtensionConfigurationApiRectorTest.php b/tests/Rector/v9/v0/UseExtensionConfigurationApi/UseExtensionConfigurationApiRectorTest.php new file mode 100644 index 000000000..106ba8c1e --- /dev/null +++ b/tests/Rector/v9/v0/UseExtensionConfigurationApi/UseExtensionConfigurationApiRectorTest.php @@ -0,0 +1,31 @@ +doTestFileInfo($fileInfo); + } + + public function provideDataForTest(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return UseExtensionConfigurationApiRector::class; + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 78bf53987..ea675c173 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -38,6 +38,7 @@ $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'] = 'password'; $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'] = 'host'; $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] = FileNameValidator::DEFAULT_FILE_DENY_PATTERN; +$GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['foo'] = 'a:6:{s:9:"loginLogo";s:8:"logo.jpg";s:19:"loginHighlightColor";s:7:"#000000";s:20:"loginBackgroundImage";s:8:"logo.jpg";s:13:"loginFootnote";s:8:"Footnote";s:11:"backendLogo";s:0:"";s:14:"backendFavicon";s:0:"";}'; define('TYPO3_MODE', 'BE'); define('TYPO3_URL_MAILINGLISTS', 'http://lists.typo3.org/cgi-bin/mailman/listinfo');