-
-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEATURE] Add MigrateRequiredFlagSiteConfigRector
Resolves: #2944 Releases: 3, 2
- Loading branch information
1 parent
c3ec82f
commit 9269cde
Showing
7 changed files
with
421 additions
and
199 deletions.
There are no files selected for viewing
128 changes: 128 additions & 0 deletions
128
rules/TYPO312/v0/MigrateRequiredFlagSiteConfigRector.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Ssch\TYPO3Rector\TYPO312\v0; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\ArrayItem; | ||
use PhpParser\Node\Expr\Array_; | ||
use PhpParser\Node\Expr\ArrayDimFetch; | ||
use PhpParser\Node\Expr\Assign; | ||
use PhpParser\Node\Expr\ConstFetch; | ||
use PhpParser\Node\Expr\Variable; | ||
use PhpParser\Node\Name; | ||
use PhpParser\Node\Scalar\String_; | ||
use Ssch\TYPO3Rector\Helper\ArrayUtility; | ||
use Ssch\TYPO3Rector\Helper\StringUtility; | ||
use Ssch\TYPO3Rector\Rector\AbstractArrayDimFetchTcaRector; | ||
use Symplify\RuleDocGenerator\Contract\DocumentedRuleInterface; | ||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; | ||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; | ||
|
||
/** | ||
* @changelog https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/12.0/Deprecation-97035-RequiredOptionInEvalKeyword.html | ||
* @changelog https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/12.0/Feature-97035-UtilizeRequiredDirectlyInTCAFieldConfiguration.html | ||
* @see \Ssch\TYPO3Rector\Tests\Rector\v12\v0\MigrateRequiredFlagSiteConfigRector\MigrateRequiredFlagSiteConfigRectorTest | ||
*/ | ||
final class MigrateRequiredFlagSiteConfigRector extends AbstractArrayDimFetchTcaRector implements DocumentedRuleInterface | ||
{ | ||
/** | ||
* @var string | ||
*/ | ||
private const REQUIRED = 'required'; | ||
|
||
public function getRuleDefinition(): RuleDefinition | ||
{ | ||
return new RuleDefinition('Migrate required flag', [new CodeSample( | ||
<<<'CODE_SAMPLE' | ||
$GLOBALS['SiteConfiguration']['site']['columns']['required_column1'] = [ | ||
'required_column' => [ | ||
'config' => [ | ||
'eval' => 'trim,required', | ||
], | ||
], | ||
]; | ||
CODE_SAMPLE | ||
, | ||
<<<'CODE_SAMPLE' | ||
$GLOBALS['SiteConfiguration']['site']['columns']['required_column1'] = [ | ||
'required_column' => [ | ||
'config' => [ | ||
'eval' => 'trim', | ||
'required' = true, | ||
], | ||
], | ||
]; | ||
CODE_SAMPLE | ||
)]); | ||
} | ||
|
||
/** | ||
* @param Assign $node | ||
*/ | ||
public function refactor(Node $node): ?Node | ||
{ | ||
$columnName = $node->var; | ||
if (! $columnName instanceof ArrayDimFetch) { | ||
return null; | ||
} | ||
|
||
if (! $columnName->dim instanceof String_ && ! $columnName->dim instanceof Variable) { | ||
return null; | ||
} | ||
|
||
$rootLine = ['SiteConfiguration', 'site', 'columns']; | ||
$result = $this->isInRootLine($columnName, $rootLine); | ||
if (! $result) { | ||
return null; | ||
} | ||
|
||
$columnTca = $node->expr; | ||
|
||
$configArray = $this->extractSubArrayByKey($columnTca, self::CONFIG); | ||
if (! $configArray instanceof Array_) { | ||
return null; | ||
} | ||
|
||
if (! $this->hasKey($configArray, 'eval')) { | ||
return null; | ||
} | ||
|
||
$evalArrayItem = $this->extractArrayItemByKey($configArray, 'eval'); | ||
if (! $evalArrayItem instanceof ArrayItem) { | ||
return null; | ||
} | ||
|
||
$value = $this->valueResolver->getValue($evalArrayItem->value); | ||
if (! is_string($value)) { | ||
return null; | ||
} | ||
|
||
if (! StringUtility::inList($value, self::REQUIRED)) { | ||
return null; | ||
} | ||
|
||
$evalList = ArrayUtility::trimExplode(',', $value, true); | ||
|
||
// Remove "required" from $evalList | ||
$evalList = array_filter($evalList, static fn (string $eval) => $eval !== self::REQUIRED); | ||
|
||
if ($evalList !== []) { | ||
// Write back filtered 'eval' | ||
$evalArrayItem->value = new String_(implode(',', $evalList)); | ||
} else { | ||
$this->removeArrayItemFromArrayByKey($configArray, 'eval'); | ||
} | ||
|
||
// If required config exists already do not add one again | ||
$requiredItemToRemove = $this->extractArrayItemByKey($configArray, self::REQUIRED); | ||
if ($requiredItemToRemove instanceof ArrayItem) { | ||
return null; | ||
} | ||
|
||
$configArray->items[] = new ArrayItem(new ConstFetch(new Name('true')), new String_(self::REQUIRED)); | ||
|
||
return $node; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Ssch\TYPO3Rector\Helper; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\ArrayItem; | ||
use PhpParser\Node\Expr; | ||
use PhpParser\Node\Expr\Array_; | ||
use PhpParser\Node\Scalar\String_; | ||
|
||
trait TcaHelperTrait | ||
{ | ||
protected function isConfigType(Array_ $columnItemConfigurationArray, string $type): bool | ||
{ | ||
return $this->hasKeyValuePair($columnItemConfigurationArray, 'type', $type); | ||
} | ||
|
||
protected function configIsOfRenderType(Array_ $configValueArray, string $expectedRenderType): bool | ||
{ | ||
return $this->hasKeyValuePair($configValueArray, 'renderType', $expectedRenderType); | ||
} | ||
|
||
protected function changeTcaType(Array_ $configArray, string $type): void | ||
{ | ||
$toChangeArrayItem = $this->extractArrayItemByKey($configArray, 'type'); | ||
if ($toChangeArrayItem instanceof ArrayItem) { | ||
$toChangeArrayItem->value = new String_($type); | ||
} | ||
} | ||
|
||
protected function hasRenderType(Array_ $columnItemConfigurationArray): bool | ||
{ | ||
$renderTypeItem = $this->extractArrayItemByKey($columnItemConfigurationArray, 'renderType'); | ||
return $renderTypeItem instanceof ArrayItem; | ||
} | ||
|
||
protected function hasInternalType(Array_ $columnItemConfigurationArray): bool | ||
{ | ||
$internalType = $this->extractArrayItemByKey($columnItemConfigurationArray, 'internal_type'); | ||
return $internalType instanceof ArrayItem; | ||
} | ||
|
||
protected function configIsOfInternalType(Array_ $configValueArray, string $expectedType): bool | ||
{ | ||
return $this->hasKeyValuePair($configValueArray, 'internal_type', $expectedType); | ||
} | ||
|
||
/** | ||
* @param string|int $key | ||
*/ | ||
protected function extractArrayValueByKey(?Node $node, $key): ?Expr | ||
{ | ||
return (($extractArrayItemByKey = $this->extractArrayItemByKey( | ||
$node, | ||
$key | ||
)) instanceof ArrayItem) ? $extractArrayItemByKey->value : null; | ||
} | ||
|
||
/** | ||
* @param string|int $key | ||
*/ | ||
protected function extractSubArrayByKey(?Node $node, $key): ?Array_ | ||
{ | ||
if (! $node instanceof Node) { | ||
return null; | ||
} | ||
|
||
$arrayItem = $this->extractArrayItemByKey($node, $key); | ||
if (! $arrayItem instanceof ArrayItem) { | ||
return null; | ||
} | ||
|
||
$columnItems = $arrayItem->value; | ||
if (! $columnItems instanceof Array_) { | ||
return null; | ||
} | ||
|
||
return $columnItems; | ||
} | ||
|
||
/** | ||
* @param string|int $key | ||
*/ | ||
protected function extractArrayItemByKey(?Node $node, $key): ?ArrayItem | ||
{ | ||
if (! $node instanceof Node) { | ||
return null; | ||
} | ||
|
||
if (! $node instanceof Array_) { | ||
return null; | ||
} | ||
|
||
foreach ($node->items as $item) { | ||
if (! $item instanceof ArrayItem) { | ||
continue; | ||
} | ||
|
||
if (! $item->key instanceof Expr) { | ||
continue; | ||
} | ||
|
||
$itemKey = $this->getValue($item->key); | ||
if ($key === $itemKey) { | ||
return $item; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Removes an array key directly from the first level of an array. | ||
* | ||
* ``` | ||
* $this->removeArrayItemFromArrayByKey($configArray, 'myKeyToBeRemoved'); | ||
* ``` | ||
* | ||
* If the key to be removed is in a sub array of the current one | ||
* use `extractSubArrayByKey` to extract the sub array first: | ||
* | ||
* ``` | ||
* $appearanceArray = $this->extractSubArrayByKey($configArray, 'appearance'); | ||
* if (! $appearanceArray instanceof Array_) { | ||
* return; | ||
* } | ||
* $this->removeArrayItemFromArrayByKey($appearanceArray, 'showRemovedLocalizationRecords'); | ||
* ``` | ||
* | ||
* Attention: Strict comparison is used for the key. key with int 42 will | ||
* not remove string "42"! | ||
* | ||
* @param string|int $key | ||
*/ | ||
protected function removeArrayItemFromArrayByKey(Array_ $array, $key): void | ||
{ | ||
$arrayItemToRemove = $this->extractArrayItemByKey($array, $key); | ||
if (! $arrayItemToRemove instanceof ArrayItem) { | ||
return; | ||
} | ||
|
||
foreach ($array->items as $arrayItemKey => $arrayItem) { | ||
if ($arrayItem === $arrayItemToRemove) { | ||
unset($array->items[$arrayItemKey]); | ||
$this->hasAstBeenChanged = true; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @param string|int $configKey | ||
*/ | ||
protected function hasKey(Array_ $configValuesArray, $configKey): bool | ||
{ | ||
foreach ($configValuesArray->items as $configItemValue) { | ||
if (! $configItemValue instanceof ArrayItem) { | ||
continue; | ||
} | ||
|
||
if (! $configItemValue->key instanceof Expr) { | ||
continue; | ||
} | ||
|
||
if ($this->isValue($configItemValue->key, $configKey)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* @param mixed $expectedValue | ||
*/ | ||
protected function hasKeyValuePair(Array_ $configValueArray, string $configKey, $expectedValue): bool | ||
{ | ||
foreach ($configValueArray->items as $configItemValue) { | ||
if (! $configItemValue instanceof ArrayItem) { | ||
continue; | ||
} | ||
|
||
if (! $configItemValue->key instanceof Expr) { | ||
continue; | ||
} | ||
|
||
if ($this->isValue($configItemValue->key, $configKey) | ||
&& $this->isValue($configItemValue->value, $expectedValue) | ||
) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* @param mixed $value | ||
*/ | ||
private function isValue(Expr $expr, $value): bool | ||
{ | ||
return $this->valueResolver->isValue($expr, $value); | ||
} | ||
|
||
/** | ||
* @return mixed|null | ||
*/ | ||
private function getValue(Expr $expr) | ||
{ | ||
return $this->valueResolver->getValue($expr); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.