From c3f1d49cd0caed95261ecbe8ac2eb95f49dd1d26 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Mon, 19 Aug 2024 14:40:18 +0200 Subject: [PATCH 1/5] Add TYPO3 13 LTS Support --- .github/workflows/ci.yaml | 23 +- .php-cs-fixer.dist.php | 3 + .../Command/ImportConfigurationCommand.php | 2 +- .../CustomAnnotationExtractor.php | 912 +++++++++++++----- Classes/Domain/Import/Importer/SaveData.php | 8 +- .../Typo3Converter/GeneralConverter.php | 6 +- .../Model/Backend/ImportConfiguration.php | 9 +- .../Backend/OrganisationRepository.php | 4 - Classes/Extension.php | 39 +- .../DataProcessing/ResolveEntities.php | 6 + .../ImportConfiguration/ContainsPlace.xml | 31 +- .../FlexForm/ImportConfiguration/Static.xml | 29 +- .../ImportConfiguration/SyncScope.xml | 29 +- .../FlexForm/Pages/tourist_attraction.xml | 24 +- Configuration/TCA/Overrides/pages.php | 2 +- .../TCA/tx_thuecat_tourist_attraction.php | 14 + .../User/All.tsconfig => user.tsconfig} | 0 Documentation/Changelog/3.1.0.rst | 28 + Documentation/Index.rst | 1 + Documentation/Settings.cfg | 52 - Documentation/guides.xml | 35 + Tests/Acceptance/BackendConfigurationCest.php | 70 -- Tests/Acceptance/Data/.gitkeep | 0 Tests/Acceptance/Data/BasicDatabase.php | 87 -- .../Acceptance/Data/Sites/default/config.yaml | 47 - Tests/Acceptance/Support/AcceptanceTester.php | 54 -- Tests/Acceptance/Support/Environment.php | 60 -- Tests/Acceptance/_output/.gitignore | 2 - Tests/Functional/AbstractImportTestCase.php | 20 + Tests/Functional/FrontendTest.php | 11 +- .../ImportConfigurationCommandTest.php | 4 + Tests/Functional/ImportTest.php | 13 + codeception.dist.yml | 44 - composer.json | 25 +- ext_emconf.php | 2 +- phpstan-baseline.neon | 15 - shell.nix | 21 - 37 files changed, 907 insertions(+), 825 deletions(-) rename Configuration/{TSconfig/User/All.tsconfig => user.tsconfig} (100%) create mode 100644 Documentation/Changelog/3.1.0.rst delete mode 100644 Documentation/Settings.cfg create mode 100644 Documentation/guides.xml delete mode 100644 Tests/Acceptance/BackendConfigurationCest.php delete mode 100644 Tests/Acceptance/Data/.gitkeep delete mode 100644 Tests/Acceptance/Data/BasicDatabase.php delete mode 100644 Tests/Acceptance/Data/Sites/default/config.yaml delete mode 100644 Tests/Acceptance/Support/AcceptanceTester.php delete mode 100644 Tests/Acceptance/Support/Environment.php delete mode 100644 Tests/Acceptance/_output/.gitignore delete mode 100644 codeception.dist.yml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2ade17ba..73fc0084 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -91,6 +91,10 @@ jobs: typo3-version: '^12.4' - php-version: '8.3' typo3-version: '^12.4' + - php-version: '8.2' + typo3-version: '^13.4' + - php-version: '8.3' + typo3-version: '^13.4' steps: - uses: actions/checkout@v3 @@ -103,7 +107,6 @@ jobs: - name: Install dependencies with expected TYPO3 version run: |- composer require --no-interaction --prefer-dist --no-progress "typo3/cms-backend:${{ matrix.typo3-version }}" "typo3/cms-core:${{ matrix.typo3-version }}" "typo3/cms-extbase:${{ matrix.typo3-version }}" "typo3/cms-frontend:${{ matrix.typo3-version }}" "typo3/cms-fluid-styled-content:${{ matrix.typo3-version }}" - ./vendor/bin/codecept build - name: Code Quality (by PHPStan) run: ./vendor/bin/phpstan analyse @@ -125,6 +128,12 @@ jobs: - php-version: '8.3' typo3-version: '^12.4' db-version: '8' + - php-version: '8.2' + typo3-version: '^13.4' + db-version: '8' + - php-version: '8.3' + typo3-version: '^13.4' + db-version: '8' steps: - uses: actions/checkout@v3 @@ -158,15 +167,3 @@ jobs: export typo3DatabaseUsername="root" export typo3DatabasePassword="root" ./vendor/bin/phpunit - - tests-acceptance: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - uses: cachix/install-nix-action@v24 - with: - nix_path: nixpkgs=channel:nixos-unstable - - - name: Run Acceptance Tests - run: nix-shell --run project-test-acceptance diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 5bd6dc42..a847a6cc 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -2,6 +2,9 @@ $finder = (new PhpCsFixer\Finder()) ->ignoreVCSIgnored(true) ->in(realpath(__DIR__)) + ->notPath([ + 'Classes/Domain/Import/EntityMapper/CustomAnnotationExtractor.php', + ]); ; return (new \PhpCsFixer\Config()) diff --git a/Classes/Command/ImportConfigurationCommand.php b/Classes/Command/ImportConfigurationCommand.php index c8b1630f..0d2f2168 100644 --- a/Classes/Command/ImportConfigurationCommand.php +++ b/Classes/Command/ImportConfigurationCommand.php @@ -52,7 +52,7 @@ protected function configure(): void ); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { Bootstrap::initializeBackendAuthentication(); diff --git a/Classes/Domain/Import/EntityMapper/CustomAnnotationExtractor.php b/Classes/Domain/Import/EntityMapper/CustomAnnotationExtractor.php index 936b6fa5..ac6b0a46 100644 --- a/Classes/Domain/Import/EntityMapper/CustomAnnotationExtractor.php +++ b/Classes/Domain/Import/EntityMapper/CustomAnnotationExtractor.php @@ -23,12 +23,13 @@ namespace WerkraumMedia\ThueCat\Domain\Import\EntityMapper; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory; use function in_array; use InvalidArgumentException; use LogicException; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; -use phpDocumentor\Reflection\DocBlock\Tags\Param; use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\Types\Context; @@ -42,7 +43,7 @@ use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\PropertyInfo\Util\PhpDocTypeHelper; /** @@ -52,339 +53,754 @@ * * Make updating the file contents easier by keeping the original file contents as close as possible. */ -class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, ConstructorArgumentTypeExtractorInterface -{ - final public const PROPERTY = 0; - final public const ACCESSOR = 1; - final public const MUTATOR = 2; - - /** - * @var array - */ - private array $docBlocks = []; - - /** - * @var Context[] - */ - private array $contexts = []; - - private readonly \phpDocumentor\Reflection\DocBlockFactoryInterface $docBlockFactory; - private readonly \phpDocumentor\Reflection\Types\ContextFactory $contextFactory; - private readonly \Symfony\Component\PropertyInfo\Util\PhpDocTypeHelper $phpDocTypeHelper; - private readonly array $mutatorPrefixes; - private readonly array $accessorPrefixes; - private readonly array $arrayMutatorPrefixes; - - /** - * @param string[]|null $mutatorPrefixes - * @param string[]|null $accessorPrefixes - * @param string[]|null $arrayMutatorPrefixes - */ - public function __construct(DocBlockFactoryInterface $docBlockFactory = null, array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null) + +if (class_exists(TypeContextFactory::class)) { + class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, ConstructorArgumentTypeExtractorInterface { - if (!class_exists(DocBlockFactory::class)) { - throw new LogicException(sprintf('Unable to use the "%s" class as the "phpdocumentor/reflection-docblock" package is not installed. Try running composer require "phpdocumentor/reflection-docblock".', self::class)); + public const PROPERTY = 0; + public const ACCESSOR = 1; + public const MUTATOR = 2; + + /** + * @var array + */ + private array $docBlocks = []; + + /** + * @var Context[] + */ + private array $contexts = []; + + private DocBlockFactoryInterface $docBlockFactory; + private ContextFactory $contextFactory; + private TypeContextFactory $typeContextFactory; + private PhpDocTypeHelper $phpDocTypeHelper; + private array $mutatorPrefixes; + private array $accessorPrefixes; + private array $arrayMutatorPrefixes; + + /** + * @param string[]|null $mutatorPrefixes + * @param string[]|null $accessorPrefixes + * @param string[]|null $arrayMutatorPrefixes + */ + public function __construct(?DocBlockFactoryInterface $docBlockFactory = null, ?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null) + { + if (!class_exists(DocBlockFactory::class)) { + throw new \LogicException(sprintf('Unable to use the "%s" class as the "phpdocumentor/reflection-docblock" package is not installed. Try running composer require "phpdocumentor/reflection-docblock".', __CLASS__)); + } + + $this->docBlockFactory = $docBlockFactory ?: DocBlockFactory::createInstance(); + $this->contextFactory = new ContextFactory(); + $this->typeContextFactory = new TypeContextFactory(); + $this->phpDocTypeHelper = new PhpDocTypeHelper(); + $this->mutatorPrefixes = $mutatorPrefixes ?? ReflectionExtractor::$defaultMutatorPrefixes; + $this->accessorPrefixes = $accessorPrefixes ?? ReflectionExtractor::$defaultAccessorPrefixes; + $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? ReflectionExtractor::$defaultArrayMutatorPrefixes; } - $this->docBlockFactory = $docBlockFactory ?: DocBlockFactory::createInstance(); - $this->contextFactory = new ContextFactory(); - $this->phpDocTypeHelper = new PhpDocTypeHelper(); - $this->mutatorPrefixes = $mutatorPrefixes ?? ReflectionExtractor::$defaultMutatorPrefixes; - $this->accessorPrefixes = $accessorPrefixes ?? ReflectionExtractor::$defaultAccessorPrefixes; - $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? ReflectionExtractor::$defaultArrayMutatorPrefixes; - } + public function getShortDescription(string $class, string $property, array $context = []): ?string + { + /** @var $docBlock DocBlock */ + [$docBlock] = $this->findDocBlock($class, $property); + if (!$docBlock) { + return null; + } + + $shortDescription = $docBlock->getSummary(); + + if ($shortDescription) { + return $shortDescription; + } + + foreach ($docBlock->getTagsByName('var') as $var) { + if ($var && !$var instanceof InvalidTag) { + $varDescription = $var->getDescription()->render(); + + if ($varDescription) { + return $varDescription; + } + } + } - /** - * {@inheritdoc} - */ - public function getShortDescription(string $class, string $property, array $context = []): ?string - { - /** @var $docBlock DocBlock */ - [$docBlock] = $this->getDocBlock($class, $property); - if (!$docBlock) { return null; } - $shortDescription = $docBlock->getSummary(); + public function getLongDescription(string $class, string $property, array $context = []): ?string + { + /** @var $docBlock DocBlock */ + [$docBlock] = $this->findDocBlock($class, $property); + if (!$docBlock) { + return null; + } + + $contents = $docBlock->getDescription()->render(); - if (!empty($shortDescription)) { - return $shortDescription; + return '' === $contents ? null : $contents; } - foreach ($docBlock->getTagsByName('var') as $var) { - if ($var && !$var instanceof InvalidTag) { - $varDescription = $var->getDescription()->render(); + public function getTypes(string $class, string $property, array $context = []): ?array + { + /** @var $docBlock DocBlock */ + [$docBlock, $source, $prefix] = $this->findDocBlock($class, $property); + if (!$docBlock) { + return null; + } + + $tag = match ($source) { + self::PROPERTY => 'var', + self::ACCESSOR => 'return', + self::MUTATOR => 'param', + }; + + $parentClass = null; + $types = []; + /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ + foreach ($docBlock->getTagsByName($tag) as $tag) { + if ($tag && !$tag instanceof InvalidTag && null !== $tag->getType()) { + foreach ($this->phpDocTypeHelper->getTypes($tag->getType()) as $type) { + switch ($type->getClassName()) { + case 'self': + case 'static': + $resolvedClass = $class; + break; + + case 'parent': + if (false !== $resolvedClass = $parentClass ??= get_parent_class($class)) { + break; + } + // no break + + default: + $types[] = $type; + continue 2; + } - if (!empty($varDescription)) { - return $varDescription; + $types[] = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes()); + } } } - } - return null; - } + if (!isset($types[0])) { + return null; + } - /** - * {@inheritdoc} - */ - public function getLongDescription(string $class, string $property, array $context = []): ?string - { - /** @var $docBlock DocBlock */ - [$docBlock] = $this->getDocBlock($class, $property); - if (!$docBlock) { - return null; + if (!\in_array($prefix, $this->arrayMutatorPrefixes, true)) { + return $types; + } + + return [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $types[0])]; } - $contents = $docBlock->getDescription()->render(); + public function getTypesFromConstructor(string $class, string $property): ?array + { + $docBlock = $this->getDocBlockFromConstructor($class, $property); - return $contents === '' ? null : $contents; - } + if (!$docBlock) { + return null; + } - /** - * {@inheritdoc} - */ - public function getTypes(string $class, string $property, array $context = []): ?array - { - /** @var $docBlock DocBlock */ - [$docBlock, $source, $prefix] = $this->getDocBlock($class, $property); - if (!$docBlock) { - return null; + $types = []; + /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ + foreach ($docBlock->getTagsByName('param') as $tag) { + if ($tag && null !== $tag->getType()) { + $types[] = $this->phpDocTypeHelper->getTypes($tag->getType()); + } + } + + if (!isset($types[0]) || [] === $types[0]) { + return null; + } + + return array_merge([], ...$types); } - switch ($source) { - case self::PROPERTY: - $tag = 'var'; - break; + /** + * @experimental + */ + public function getType(string $class, string $property, array $context = []): ?Type + { + /** @var $docBlock DocBlock */ + [$docBlock, $source, $prefix] = $this->findDocBlock($class, $property); + if (!$docBlock) { + return null; + } - case self::ACCESSOR: - $tag = 'return'; - break; + $tag = match ($source) { + self::PROPERTY => 'var', + self::ACCESSOR => 'return', + self::MUTATOR => 'param', + }; - case self::MUTATOR: - $tag = 'param'; - break; - } + $types = []; + $typeContext = $this->typeContextFactory->createFromClassName($class); - $parentClass = null; - $types = []; - /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ - foreach ($docBlock->getTagsByName($tag) as $tag) { - if ($tag && !$tag instanceof InvalidTag && $tag->getType() !== null) { - foreach ($this->phpDocTypeHelper->getTypes($tag->getType()) as $type) { - switch ($type->getClassName()) { - case 'self': - case 'static': - $resolvedClass = $class; - break; - - case 'parent': - if (false !== $resolvedClass = $parentClass ?? $parentClass = get_parent_class($class)) { - break; - } - // no break + /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ + foreach ($docBlock->getTagsByName($tag) as $tag) { + if ($tag instanceof InvalidTag || !$tagType = $tag->getType()) { + continue; + } + + $type = $this->phpDocTypeHelper->getType($tagType); - default: - $types[] = $type; - continue 2; + if (!$type instanceof ObjectType) { + $types[] = $type; + + continue; + } + + $normalizedClassName = match ($type->getClassName()) { + 'self' => $typeContext->getDeclaringClass(), + 'static' => $typeContext->getCalledClass(), + default => $type->getClassName(), + }; + + if ('parent' === $normalizedClassName) { + try { + $normalizedClassName = $typeContext->getParentClass(); + } catch (LogicException) { + // if there is no parent for the current class, we keep the "parent" raw string } + } + + $types[] = $type->isNullable() ? Type::nullable(Type::object($normalizedClassName)) : Type::object($normalizedClassName); + } + + if (null === $type = $types[0] ?? null) { + return null; + } + + if (!\in_array($prefix, $this->arrayMutatorPrefixes, true)) { + return $type; + } - $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes()); + return Type::list($type); + } + + /** + * @experimental + */ + public function getTypeFromConstructor(string $class, string $property): ?Type + { + if (!$docBlock = $this->getDocBlockFromConstructor($class, $property)) { + return null; + } + + $types = []; + /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ + foreach ($docBlock->getTagsByName('param') as $tag) { + if ($tag instanceof InvalidTag || !$tagType = $tag->getType()) { + continue; } + + $types[] = $this->phpDocTypeHelper->getType($tagType); } + + return $types[0] ?? null; } - if (!isset($types[0])) { - return null; + public function getDocBlock(string $class, string $property): ?DocBlock + { + $output = $this->findDocBlock($class, $property); + + return $output[0]; } - if (!in_array($prefix, $this->arrayMutatorPrefixes)) { - return $types; + private function getDocBlockFromConstructor(string $class, string $property): ?DocBlock + { + try { + $reflectionClass = new \ReflectionClass($class); + } catch (\ReflectionException) { + return null; + } + $reflectionConstructor = $reflectionClass->getConstructor(); + if (!$reflectionConstructor) { + return null; + } + + try { + $docBlock = $this->docBlockFactory->create($reflectionConstructor, $this->contextFactory->createFromReflector($reflectionConstructor)); + + return $this->filterDocBlockParams($docBlock, $property); + } catch (\InvalidArgumentException) { + return null; + } } - return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $types[0])]; - } + private function filterDocBlockParams(DocBlock $docBlock, string $allowedParam): DocBlock + { + $tags = array_values(array_filter($docBlock->getTagsByName('param'), fn ($tag) => $tag instanceof DocBlock\Tags\Param && $allowedParam === $tag->getVariableName())); - /** - * {@inheritdoc} - */ - public function getTypesFromConstructor(string $class, string $property): ?array - { - $docBlock = $this->getDocBlockFromConstructor($class, $property); + return new DocBlock($docBlock->getSummary(), $docBlock->getDescription(), $tags, $docBlock->getContext(), + $docBlock->getLocation(), $docBlock->isTemplateStart(), $docBlock->isTemplateEnd()); + } - if (!$docBlock) { - return null; + /** + * @return array{DocBlock|null, int|null, string|null} + */ + private function findDocBlock(string $class, string $property): array + { + $propertyHash = sprintf('%s::%s', $class, $property); + + if (isset($this->docBlocks[$propertyHash])) { + return $this->docBlocks[$propertyHash]; + } + + try { + $reflectionProperty = new \ReflectionProperty($class, $property); + } catch (\ReflectionException) { + $reflectionProperty = null; + } + + $ucFirstProperty = ucfirst($property); + + switch (true) { + // We re order the different cases + case [$docBlock, $prefix] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR): + $data = [$docBlock, self::MUTATOR, $prefix]; + break; + + case [$docBlock] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR): + $data = [$docBlock, self::ACCESSOR, null]; + break; + + case $reflectionProperty?->isPromoted() && $docBlock = $this->getDocBlockFromConstructor($class, $property): + $data = [$docBlock, self::MUTATOR, null]; + break; + + case $docBlock = $this->getDocBlockFromProperty($class, $property): + $data = [$docBlock, self::PROPERTY, null]; + break; + + default: + $data = [null, null, null]; + } + + return $this->docBlocks[$propertyHash] = $data; } - $types = []; - /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ - foreach ($docBlock->getTagsByName('param') as $tag) { - if ($tag && $tag->getType() !== null) { - $types[] = $this->phpDocTypeHelper->getTypes($tag->getType()); + private function getDocBlockFromProperty(string $class, string $property): ?DocBlock + { + // Use a ReflectionProperty instead of $class to get the parent class if applicable + try { + $reflectionProperty = new \ReflectionProperty($class, $property); + } catch (\ReflectionException) { + return null; + } + + $reflector = $reflectionProperty->getDeclaringClass(); + + foreach ($reflector->getTraits() as $trait) { + if ($trait->hasProperty($property)) { + return $this->getDocBlockFromProperty($trait->getName(), $property); + } + } + + try { + return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflector)); + } catch (\InvalidArgumentException|\RuntimeException) { + return null; } } - if (!isset($types[0]) || $types[0] === []) { - return null; + /** + * @return array{DocBlock, string}|null + */ + private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array + { + $prefixes = self::ACCESSOR === $type ? $this->accessorPrefixes : $this->mutatorPrefixes; + $prefix = null; + + foreach ($prefixes as $prefix) { + $methodName = $prefix.$ucFirstProperty; + + try { + $reflectionMethod = new \ReflectionMethod($class, $methodName); + if ($reflectionMethod->isStatic()) { + continue; + } + + if ( + (self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters()) + || (self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1) + ) { + break; + } + } catch (\ReflectionException) { + // Try the next prefix if the method doesn't exist + } + } + + if (!isset($reflectionMethod)) { + return null; + } + + $reflector = $reflectionMethod->getDeclaringClass(); + + foreach ($reflector->getTraits() as $trait) { + if ($trait->hasMethod($methodName)) { + return $this->getDocBlockFromMethod($trait->getName(), $ucFirstProperty, $type); + } + } + + try { + return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflector)), $prefix]; + } catch (\InvalidArgumentException|\RuntimeException) { + return null; + } } - return array_merge([], ...$types); - } + /** + * Prevents a lot of redundant calls to ContextFactory::createForNamespace(). + */ + private function createFromReflector(\ReflectionClass $reflector): Context + { + $cacheKey = $reflector->getNamespaceName().':'.$reflector->getFileName(); - private function getDocBlockFromConstructor(string $class, string $property): ?DocBlock - { - try { - $reflectionClass = new ReflectionClass($class); - } catch (ReflectionException $e) { - return null; + if (isset($this->contexts[$cacheKey])) { + return $this->contexts[$cacheKey]; + } + + $this->contexts[$cacheKey] = $this->contextFactory->createFromReflector($reflector); + + return $this->contexts[$cacheKey]; } - $reflectionConstructor = $reflectionClass->getConstructor(); - if (!$reflectionConstructor) { - return null; + } +} else { + class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, ConstructorArgumentTypeExtractorInterface + { + final public const PROPERTY = 0; + final public const ACCESSOR = 1; + final public const MUTATOR = 2; + + /** + * @var array + */ + private array $docBlocks = []; + + /** + * @var Context[] + */ + private array $contexts = []; + + private readonly \phpDocumentor\Reflection\DocBlockFactoryInterface $docBlockFactory; + private readonly \phpDocumentor\Reflection\Types\ContextFactory $contextFactory; + private readonly \Symfony\Component\PropertyInfo\Util\PhpDocTypeHelper $phpDocTypeHelper; + private readonly array $mutatorPrefixes; + private readonly array $accessorPrefixes; + private readonly array $arrayMutatorPrefixes; + + /** + * @param string[]|null $mutatorPrefixes + * @param string[]|null $accessorPrefixes + * @param string[]|null $arrayMutatorPrefixes + */ + public function __construct(DocBlockFactoryInterface $docBlockFactory = null, array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null) + { + if (!class_exists(DocBlockFactory::class)) { + throw new LogicException(sprintf('Unable to use the "%s" class as the "phpdocumentor/reflection-docblock" package is not installed. Try running composer require "phpdocumentor/reflection-docblock".', self::class)); + } + + $this->docBlockFactory = $docBlockFactory ?: DocBlockFactory::createInstance(); + $this->contextFactory = new ContextFactory(); + $this->phpDocTypeHelper = new PhpDocTypeHelper(); + $this->mutatorPrefixes = $mutatorPrefixes ?? ReflectionExtractor::$defaultMutatorPrefixes; + $this->accessorPrefixes = $accessorPrefixes ?? ReflectionExtractor::$defaultAccessorPrefixes; + $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? ReflectionExtractor::$defaultArrayMutatorPrefixes; } - try { - $docBlock = $this->docBlockFactory->create($reflectionConstructor, $this->contextFactory->createFromReflector($reflectionConstructor)); + /** + * {@inheritdoc} + */ + public function getShortDescription(string $class, string $property, array $context = []): ?string + { + /** @var $docBlock DocBlock */ + [$docBlock] = $this->getDocBlock($class, $property); + if (!$docBlock) { + return null; + } + + $shortDescription = $docBlock->getSummary(); + + if (!empty($shortDescription)) { + return $shortDescription; + } + + foreach ($docBlock->getTagsByName('var') as $var) { + if ($var && !$var instanceof InvalidTag) { + $varDescription = $var->getDescription()->render(); + + if (!empty($varDescription)) { + return $varDescription; + } + } + } - return $this->filterDocBlockParams($docBlock, $property); - } catch (InvalidArgumentException) { return null; } - } - private function filterDocBlockParams(DocBlock $docBlock, string $allowedParam): DocBlock - { - $tags = array_values(array_filter($docBlock->getTagsByName('param'), function ($tag) use ($allowedParam) { - return $tag instanceof Param && $allowedParam === $tag->getVariableName(); - })); - - return new DocBlock( - $docBlock->getSummary(), - $docBlock->getDescription(), - $tags, - $docBlock->getContext(), - $docBlock->getLocation(), - $docBlock->isTemplateStart(), - $docBlock->isTemplateEnd() - ); - } + /** + * {@inheritdoc} + */ + public function getLongDescription(string $class, string $property, array $context = []): ?string + { + /** @var $docBlock DocBlock */ + [$docBlock] = $this->getDocBlock($class, $property); + if (!$docBlock) { + return null; + } - /** - * @return array{DocBlock|null, int|null, string|null} - */ - private function getDocBlock(string $class, string $property): array - { - $propertyHash = sprintf('%s::%s', $class, $property); + $contents = $docBlock->getDescription()->render(); - if (isset($this->docBlocks[$propertyHash])) { - return $this->docBlocks[$propertyHash]; + return $contents === '' ? null : $contents; } - $ucFirstProperty = ucfirst($property); + /** + * {@inheritdoc} + */ + public function getTypes(string $class, string $property, array $context = []): ?array + { + /** @var $docBlock DocBlock */ + [$docBlock, $source, $prefix] = $this->getDocBlock($class, $property); + if (!$docBlock) { + return null; + } + + switch ($source) { + case self::PROPERTY: + $tag = 'var'; + break; + + case self::ACCESSOR: + $tag = 'return'; + break; - switch (true) { - case [$docBlock, $prefix] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR): - $data = [$docBlock, self::MUTATOR, $prefix]; - break; + case self::MUTATOR: + $tag = 'param'; + break; + } + + $parentClass = null; + $types = []; + /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ + foreach ($docBlock->getTagsByName($tag) as $tag) { + if ($tag && !$tag instanceof InvalidTag && $tag->getType() !== null) { + foreach ($this->phpDocTypeHelper->getTypes($tag->getType()) as $type) { + switch ($type->getClassName()) { + case 'self': + case 'static': + $resolvedClass = $class; + break; - case [$docBlock] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR): - $data = [$docBlock, self::ACCESSOR, null]; - break; + case 'parent': + if (false !== $resolvedClass = $parentClass ?? $parentClass = get_parent_class($class)) { + break; + } + // no break - case $docBlock = $this->getDocBlockFromProperty($class, $property): - $data = [$docBlock, self::PROPERTY, null]; - break; + default: + $types[] = $type; + continue 2; + } - default: - $data = [null, null, null]; + $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes()); + } + } + } + + if (!isset($types[0])) { + return null; + } + + if (!in_array($prefix, $this->arrayMutatorPrefixes)) { + return $types; + } + + return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $types[0])]; } - return $this->docBlocks[$propertyHash] = $data; - } + /** + * {@inheritdoc} + */ + public function getTypesFromConstructor(string $class, string $property): ?array + { + $docBlock = $this->getDocBlockFromConstructor($class, $property); - private function getDocBlockFromProperty(string $class, string $property): ?DocBlock - { - // Use a ReflectionProperty instead of $class to get the parent class if applicable - try { - $reflectionProperty = new ReflectionProperty($class, $property); - } catch (ReflectionException $e) { - return null; + if (!$docBlock) { + return null; + } + + $types = []; + /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ + foreach ($docBlock->getTagsByName('param') as $tag) { + if ($tag && $tag->getType() !== null) { + $types[] = $this->phpDocTypeHelper->getTypes($tag->getType()); + } + } + + if (!isset($types[0]) || $types[0] === []) { + return null; + } + + return array_merge([], ...$types); } - $reflector = $reflectionProperty->getDeclaringClass(); + private function getDocBlockFromConstructor(string $class, string $property): ?DocBlock + { + try { + $reflectionClass = new ReflectionClass($class); + } catch (ReflectionException $e) { + return null; + } + $reflectionConstructor = $reflectionClass->getConstructor(); + if (!$reflectionConstructor) { + return null; + } + + try { + $docBlock = $this->docBlockFactory->create($reflectionConstructor, $this->contextFactory->createFromReflector($reflectionConstructor)); - foreach ($reflector->getTraits() as $trait) { - if ($trait->hasProperty($property)) { - return $this->getDocBlockFromProperty($trait->getName(), $property); + return $this->filterDocBlockParams($docBlock, $property); + } catch (InvalidArgumentException) { + return null; } } - try { - return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflector)); - } catch (InvalidArgumentException|RuntimeException) { - return null; + private function filterDocBlockParams(DocBlock $docBlock, string $allowedParam): DocBlock + { + $tags = array_values(array_filter($docBlock->getTagsByName('param'), function ($tag) use ($allowedParam) { + return $tag instanceof Param && $allowedParam === $tag->getVariableName(); + })); + + return new DocBlock( + $docBlock->getSummary(), + $docBlock->getDescription(), + $tags, + $docBlock->getContext(), + $docBlock->getLocation(), + $docBlock->isTemplateStart(), + $docBlock->isTemplateEnd() + ); } - } - /** - * @return array{DocBlock, string}|null - */ - private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array - { - $prefixes = $type === self::ACCESSOR ? $this->accessorPrefixes : $this->mutatorPrefixes; - $prefix = null; + /** + * @return array{DocBlock|null, int|null, string|null} + */ + private function getDocBlock(string $class, string $property): array + { + $propertyHash = sprintf('%s::%s', $class, $property); - foreach ($prefixes as $prefix) { - $methodName = $prefix . $ucFirstProperty; + if (isset($this->docBlocks[$propertyHash])) { + return $this->docBlocks[$propertyHash]; + } - try { - $reflectionMethod = new ReflectionMethod($class, $methodName); - if ($reflectionMethod->isStatic()) { - continue; - } + $ucFirstProperty = ucfirst($property); - if ( - ($type === self::ACCESSOR && $reflectionMethod->getNumberOfRequiredParameters() === 0) || - ($type === self::MUTATOR && $reflectionMethod->getNumberOfParameters() >= 1) - ) { + switch (true) { + case [$docBlock, $prefix] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR): + $data = [$docBlock, self::MUTATOR, $prefix]; break; - } - } catch (ReflectionException) { - // Try the next prefix if the method doesn't exist + + case [$docBlock] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR): + $data = [$docBlock, self::ACCESSOR, null]; + break; + + case $docBlock = $this->getDocBlockFromProperty($class, $property): + $data = [$docBlock, self::PROPERTY, null]; + break; + + default: + $data = [null, null, null]; } - } - if (!isset($reflectionMethod)) { - return null; + return $this->docBlocks[$propertyHash] = $data; } - $reflector = $reflectionMethod->getDeclaringClass(); + private function getDocBlockFromProperty(string $class, string $property): ?DocBlock + { + // Use a ReflectionProperty instead of $class to get the parent class if applicable + try { + $reflectionProperty = new ReflectionProperty($class, $property); + } catch (ReflectionException $e) { + return null; + } + + $reflector = $reflectionProperty->getDeclaringClass(); - foreach ($reflector->getTraits() as $trait) { - if ($trait->hasMethod($methodName)) { - return $this->getDocBlockFromMethod($trait->getName(), $ucFirstProperty, $type); + foreach ($reflector->getTraits() as $trait) { + if ($trait->hasProperty($property)) { + return $this->getDocBlockFromProperty($trait->getName(), $property); + } } - } - try { - return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflector)), $prefix]; - } catch (InvalidArgumentException|RuntimeException) { - return null; + try { + return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflector)); + } catch (InvalidArgumentException|RuntimeException) { + return null; + } } - } - /** - * Prevents a lot of redundant calls to ContextFactory::createForNamespace(). - */ - private function createFromReflector(ReflectionClass $reflector): Context - { - $cacheKey = $reflector->getNamespaceName() . ':' . $reflector->getFileName(); + /** + * @return array{DocBlock, string}|null + */ + private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array + { + $prefixes = $type === self::ACCESSOR ? $this->accessorPrefixes : $this->mutatorPrefixes; + $prefix = null; + + foreach ($prefixes as $prefix) { + $methodName = $prefix . $ucFirstProperty; + + try { + $reflectionMethod = new ReflectionMethod($class, $methodName); + if ($reflectionMethod->isStatic()) { + continue; + } - if (isset($this->contexts[$cacheKey])) { - return $this->contexts[$cacheKey]; + if ( + ($type === self::ACCESSOR && $reflectionMethod->getNumberOfRequiredParameters() === 0) || + ($type === self::MUTATOR && $reflectionMethod->getNumberOfParameters() >= 1) + ) { + break; + } + } catch (ReflectionException) { + // Try the next prefix if the method doesn't exist + } + } + + if (!isset($reflectionMethod)) { + return null; + } + + $reflector = $reflectionMethod->getDeclaringClass(); + + foreach ($reflector->getTraits() as $trait) { + if ($trait->hasMethod($methodName)) { + return $this->getDocBlockFromMethod($trait->getName(), $ucFirstProperty, $type); + } + } + + try { + return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflector)), $prefix]; + } catch (InvalidArgumentException|RuntimeException) { + return null; + } } - $this->contexts[$cacheKey] = $this->contextFactory->createFromReflector($reflector); + /** + * Prevents a lot of redundant calls to ContextFactory::createForNamespace(). + */ + private function createFromReflector(ReflectionClass $reflector): Context + { + $cacheKey = $reflector->getNamespaceName() . ':' . $reflector->getFileName(); + + if (isset($this->contexts[$cacheKey])) { + return $this->contexts[$cacheKey]; + } + + $this->contexts[$cacheKey] = $this->contextFactory->createFromReflector($reflector); - return $this->contexts[$cacheKey]; + return $this->contexts[$cacheKey]; + } } } diff --git a/Classes/Domain/Import/Importer/SaveData.php b/Classes/Domain/Import/Importer/SaveData.php index 10a7a640..7cf8b215 100644 --- a/Classes/Domain/Import/Importer/SaveData.php +++ b/Classes/Domain/Import/Importer/SaveData.php @@ -161,10 +161,10 @@ private function getEntityData(Entity $entity): array private function getExistingUid(Entity $entity): int { - $tableColumns = $this->connectionPool + $table = $this->connectionPool ->getConnectionForTable($entity->getTypo3DatabaseTableName()) - ->getSchemaManager() - ->listTableColumns($entity->getTypo3DatabaseTableName()) + ->getSchemaInformation() + ->introspectTable($entity->getTypo3DatabaseTableName()) ; $queryBuilder = $this->connectionPool->getQueryBuilderForTable($entity->getTypo3DatabaseTableName()); @@ -175,7 +175,7 @@ private function getExistingUid(Entity $entity): int 'remote_id', $queryBuilder->createNamedParameter($entity->getRemoteId()) )); - if (isset($tableColumns['sys_language_uid'])) { + if ($table->hasColumn('sys_language_uid')) { $queryBuilder->andWhere($queryBuilder->expr()->eq( 'sys_language_uid', $queryBuilder->createNamedParameter($entity->getTypo3SystemLanguageUid()) diff --git a/Classes/Domain/Import/Typo3Converter/GeneralConverter.php b/Classes/Domain/Import/Typo3Converter/GeneralConverter.php index 95715884..97388ea5 100644 --- a/Classes/Domain/Import/Typo3Converter/GeneralConverter.php +++ b/Classes/Domain/Import/Typo3Converter/GeneralConverter.php @@ -228,9 +228,9 @@ private function getManagerUid(object $entity): string [$entity->getManagedBy()] ) ); - $manager = $this->organisationRepository->findOneByRemoteId( - $entity->getManagedBy()->getId() - ); + $manager = $this->organisationRepository->findOneBy([ + 'remoteId' => $entity->getManagedBy()->getId(), + ]); return $manager ? (string)$manager->getUid() : ''; } diff --git a/Classes/Domain/Model/Backend/ImportConfiguration.php b/Classes/Domain/Model/Backend/ImportConfiguration.php index 895a4724..6083413e 100644 --- a/Classes/Domain/Model/Backend/ImportConfiguration.php +++ b/Classes/Domain/Model/Backend/ImportConfiguration.php @@ -25,6 +25,7 @@ use DateTimeImmutable; use Exception; +use RuntimeException; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; @@ -162,7 +163,13 @@ private function getEntries(): array private function getConfigurationAsArray(): array { - return GeneralUtility::xml2array($this->configuration); + $asArray = GeneralUtility::xml2array($this->configuration); + + if (is_array($asArray) === false) { + throw new RuntimeException('Could not parse the configuration: ' . $asArray, 1729148214); + } + + return $asArray; } /** diff --git a/Classes/Domain/Repository/Backend/OrganisationRepository.php b/Classes/Domain/Repository/Backend/OrganisationRepository.php index 446cb6c5..bd80b388 100644 --- a/Classes/Domain/Repository/Backend/OrganisationRepository.php +++ b/Classes/Domain/Repository/Backend/OrganisationRepository.php @@ -25,11 +25,7 @@ use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings; use TYPO3\CMS\Extbase\Persistence\Repository; -use WerkraumMedia\ThueCat\Domain\Model\Backend\Organisation; -/** - * @method Organisation|null findOneByRemoteId(string $remoteId) - */ class OrganisationRepository extends Repository { public function __construct( diff --git a/Classes/Extension.php b/Classes/Extension.php index 7e1edd8c..ec9e3839 100644 --- a/Classes/Extension.php +++ b/Classes/Extension.php @@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend; use TYPO3\CMS\Core\DataHandling\PageDoktypeRegistry; +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -59,22 +60,25 @@ private static function addContentElements(): void { $languagePath = self::getLanguagePath() . 'locallang_tca.xlf:tt_content'; - ExtensionManagementUtility::addPageTSConfig(' - mod.wizards.newContentElement.wizardItems.thuecat { - header = ' . $languagePath . '.group - show = * - elements { - thuecat_tourist_attraction{ - title = ' . $languagePath . '.thuecat_tourist_attraction - description = ' . $languagePath . '.thuecat_tourist_attraction.description - iconIdentifier = tt_content_thuecat_tourist_attraction - tt_content_defValues { - CType = thuecat_tourist_attraction + // TODO: typo3/cms-core:14.0 Remove this code block as CEs are auto registered. + if (version_compare(GeneralUtility::makeInstance(Typo3Version::class)->__toString(), '13.0', '<')) { + ExtensionManagementUtility::addPageTSConfig(' + mod.wizards.newContentElement.wizardItems.thuecat { + header = ' . $languagePath . '.group + show = * + elements { + thuecat_tourist_attraction{ + title = ' . $languagePath . '.thuecat_tourist_attraction + description = ' . $languagePath . '.thuecat_tourist_attraction.description + iconIdentifier = tt_content_thuecat_tourist_attraction + tt_content_defValues { + CType = thuecat_tourist_attraction + } } } } - } - '); + '); + } } private static function addPageTypes(): void @@ -88,9 +92,12 @@ private static function addPageTypes(): void ] ); - ExtensionManagementUtility::addUserTSConfig( - "@import 'EXT:" . self::EXTENSION_KEY . "/Configuration/TSconfig/User/All.tsconfig'" - ); + // TODO: typo3/cms-core:14.0 Remove this code block as Configuration/user.tsconfig will be loaded since 13.x + if (version_compare(GeneralUtility::makeInstance(Typo3Version::class)->__toString(), '13.0', '<')) { + ExtensionManagementUtility::addUserTSConfig( + "@import 'EXT:" . self::EXTENSION_KEY . '/Configuration/user.tsconfig' + ); + } } private static function addCaching(): void diff --git a/Classes/Frontend/DataProcessing/ResolveEntities.php b/Classes/Frontend/DataProcessing/ResolveEntities.php index 3d7797af..252929ca 100644 --- a/Classes/Frontend/DataProcessing/ResolveEntities.php +++ b/Classes/Frontend/DataProcessing/ResolveEntities.php @@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Domain\Repository\PageRepository; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; @@ -79,6 +80,11 @@ private function resolveEntities(string $tableName, array $uids): array $rows = []; foreach ($queryBuilder->executeQuery()->iterateAssociative() as $row) { + // TODO: typo3/cms-core:14.0 Remove this condition, should always be an instance now. + if (!$this->tsfe->sys_page instanceof PageRepository) { + continue; + } + $row = $this->tsfe->sys_page->getLanguageOverlay($tableName, $row); if (is_array($row)) { $rows[] = $row; diff --git a/Configuration/FlexForm/ImportConfiguration/ContainsPlace.xml b/Configuration/FlexForm/ImportConfiguration/ContainsPlace.xml index 0218b0eb..fe23de10 100644 --- a/Configuration/FlexForm/ImportConfiguration/ContainsPlace.xml +++ b/Configuration/FlexForm/ImportConfiguration/ContainsPlace.xml @@ -5,29 +5,24 @@ - - LLL:EXT:thuecat/Resources/Private/Language/locallang_flexform.xlf:importConfiguration.containsPlace.sheetTitle - + LLL:EXT:thuecat/Resources/Private/Language/locallang_flexform.xlf:importConfiguration.containsPlace.sheetTitle array - - - - input - int,required - - + + + number + 1 + - - - LLL:EXT:thuecat/Resources/Private/Language/locallang_flexform.xlf:importConfiguration.containsPlace.containsPlaceId.description - - input - trim,required - - + + LLL:EXT:thuecat/Resources/Private/Language/locallang_flexform.xlf:importConfiguration.containsPlace.containsPlaceId.description + + input + trim + 1 + diff --git a/Configuration/FlexForm/ImportConfiguration/Static.xml b/Configuration/FlexForm/ImportConfiguration/Static.xml index 02c7732f..87403b06 100644 --- a/Configuration/FlexForm/ImportConfiguration/Static.xml +++ b/Configuration/FlexForm/ImportConfiguration/Static.xml @@ -5,19 +5,15 @@ - - LLL:EXT:thuecat/Resources/Private/Language/locallang_flexform.xlf:importConfiguration.static.sheetTitle - + LLL:EXT:thuecat/Resources/Private/Language/locallang_flexform.xlf:importConfiguration.static.sheetTitle array - - - - input - int,required - - + + + number + 1 + LLL:EXT:thuecat/Resources/Private/Language/locallang_flexform.xlf:importConfiguration.static.urls @@ -29,13 +25,12 @@ array - - - - input - required,trim - - + + + input + trim + 1 + diff --git a/Configuration/FlexForm/ImportConfiguration/SyncScope.xml b/Configuration/FlexForm/ImportConfiguration/SyncScope.xml index 59e715cd..689204f3 100644 --- a/Configuration/FlexForm/ImportConfiguration/SyncScope.xml +++ b/Configuration/FlexForm/ImportConfiguration/SyncScope.xml @@ -5,28 +5,23 @@ - - LLL:EXT:thuecat/Resources/Private/Language/locallang_flexform.xlf:importConfiguration.syncScope.sheetTitle - + LLL:EXT:thuecat/Resources/Private/Language/locallang_flexform.xlf:importConfiguration.syncScope.sheetTitle array - - - - input - int,required - - + + + number + 1 + - - - - input - trim,required - - + + + input + trim + 1 + diff --git a/Configuration/FlexForm/Pages/tourist_attraction.xml b/Configuration/FlexForm/Pages/tourist_attraction.xml index 317d6763..6ce5260f 100644 --- a/Configuration/FlexForm/Pages/tourist_attraction.xml +++ b/Configuration/FlexForm/Pages/tourist_attraction.xml @@ -5,23 +5,19 @@ - - LLL:EXT:thuecat/Resources/Private/Language/locallang_flexform.xlf:pages.tourist_attraction.sheetTitle - + LLL:EXT:thuecat/Resources/Private/Language/locallang_flexform.xlf:pages.tourist_attraction.sheetTitle array - - - - select - selectMultipleSideBySide - tx_thuecat_tourist_attraction - AND {#tx_thuecat_tourist_attraction}.{#sys_language_uid} IN (0,-1) - 1 - 1 - - + + + select + selectMultipleSideBySide + tx_thuecat_tourist_attraction + AND {#tx_thuecat_tourist_attraction}.{#sys_language_uid} IN (0,-1) + 1 + 1 + diff --git a/Configuration/TCA/Overrides/pages.php b/Configuration/TCA/Overrides/pages.php index 87b10268..dde2772b 100644 --- a/Configuration/TCA/Overrides/pages.php +++ b/Configuration/TCA/Overrides/pages.php @@ -25,7 +25,7 @@ 'type' => 'flex', 'ds_pointerField' => 'doktype', 'ds' => [ - 'default' => ' array input 48 ', + 'default' => ' array input 48 ', ], ], ], diff --git a/Configuration/TCA/tx_thuecat_tourist_attraction.php b/Configuration/TCA/tx_thuecat_tourist_attraction.php index 8a93e9f5..3050f2dd 100644 --- a/Configuration/TCA/tx_thuecat_tourist_attraction.php +++ b/Configuration/TCA/tx_thuecat_tourist_attraction.php @@ -56,6 +56,20 @@ 'type' => 'passthrough', ], ], + 'disable' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.enabled', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'default' => 0, + 'items' => [ + [ + 'label' => '', + 'invertStateDisplay' => true, + ], + ], + ], + ], 'title' => [ 'label' => $languagePath . '.title', diff --git a/Configuration/TSconfig/User/All.tsconfig b/Configuration/user.tsconfig similarity index 100% rename from Configuration/TSconfig/User/All.tsconfig rename to Configuration/user.tsconfig diff --git a/Documentation/Changelog/3.1.0.rst b/Documentation/Changelog/3.1.0.rst new file mode 100644 index 00000000..b8ae2afd --- /dev/null +++ b/Documentation/Changelog/3.1.0.rst @@ -0,0 +1,28 @@ +3.1.0 +===== + +Breaking +-------- + +Nothing + +Features +-------- + +* Add TYPO3 v13 LTS Support. + +Fixes +----- + +Nothing + +Tasks +----- + +Nothing + +Deprecation +----------- + +Nothing + diff --git a/Documentation/Index.rst b/Documentation/Index.rst index 59bc1fa2..4a5ef2a2 100644 --- a/Documentation/Index.rst +++ b/Documentation/Index.rst @@ -20,4 +20,5 @@ Table of Contents Configuration Integration Changelog + Maintenance Sitemap diff --git a/Documentation/Settings.cfg b/Documentation/Settings.cfg deleted file mode 100644 index a392c73b..00000000 --- a/Documentation/Settings.cfg +++ /dev/null @@ -1,52 +0,0 @@ -[general] - -# ................................................................................. -# ... (required) title (displayed in left sidebar (desktop) or top panel (mobile) -# ................................................................................. -project = TYPO3 EXT:thuecat - -# ................................................................................. -# ... (recommended) version, displayed next to title (desktop) and in + + + + + + + diff --git a/Tests/Acceptance/BackendConfigurationCest.php b/Tests/Acceptance/BackendConfigurationCest.php deleted file mode 100644 index 2b302a4a..00000000 --- a/Tests/Acceptance/BackendConfigurationCest.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -namespace WerkraumMedia\ThueCat\Tests\Acceptance; - -use WerkraumMedia\ThueCat\Tests\Acceptance\Support\AcceptanceTester; - -class BackendConfigurationCest -{ - public function _before(AcceptanceTester $I): void - { - $I->amOnPage('/typo3'); - $I->submitForm('#typo3-login-form', [ - 'username' => 'admin', - 'p_field' => 'password', - ]); - $I->waitForText('New TYPO3 site'); - } - - public function showsIndex(AcceptanceTester $I): void - { - $I->click('Configurations'); - $I->switchToContentFrame(); - $I->see('ThüCAT - Configurations'); - $I->see('Example Configuration'); - $I->see('Please provide an import configuration and trigger import to create an organisation.'); - } - - public function allowsToImportExistingConfiguration(AcceptanceTester $I): void - { - $I->click('Configurations'); - $I->switchToContentFrame(); - $I->see('Example Configuration'); - $I->see('Never'); - $I->click('Import based on import configuration'); - - $I->see('Import finished'); - $I->see('Imported configuration "Example Configuration".'); - $I->see('Tourist-Information Schmalkalden'); - } - - public function showsExecutedImport(AcceptanceTester $I): void - { - $I->click('Imports'); - $I->switchToContentFrame(); - $I->see('ThüCAT - Imports'); - - $I->see('Example Configuration'); - } -} diff --git a/Tests/Acceptance/Data/.gitkeep b/Tests/Acceptance/Data/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Tests/Acceptance/Data/BasicDatabase.php b/Tests/Acceptance/Data/BasicDatabase.php deleted file mode 100644 index 88e9cae8..00000000 --- a/Tests/Acceptance/Data/BasicDatabase.php +++ /dev/null @@ -1,87 +0,0 @@ - [ - 0 => [ - 'uid' => '1', - 'pid' => '0', - 'tstamp' => '1366642540', - 'username' => 'admin', - 'password' => '$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1', - 'admin' => '1', - 'disable' => '0', - 'starttime' => '0', - 'endtime' => '0', - 'options' => '0', - 'crdate' => '1366642540', - 'workspace_perms' => '1', - 'deleted' => '0', - 'TSconfig' => null, - 'lastlogin' => '1371033743', - 'workspace_id' => '0', - ], - ], - 'pages' => [ - 0 => [ - 'uid' => '1', - 'pid' => '0', - 'doktype' => PageRepository::DOKTYPE_DEFAULT, - 'slug' => '/', - 'title' => 'Rootpage', - ], - 1 => [ - 'uid' => '2', - 'pid' => '1', - 'doktype' => PageRepository::DOKTYPE_SYSFOLDER, - 'slug' => '/storage', - 'title' => 'Storage', - ], - ], - 'tx_thuecat_import_configuration' => [ - 0 => [ - 'uid' => '1', - 'pid' => '2', - 'title' => 'Example Configuration', - 'type' => 'static', - 'configuration' => ' - - - - - 2 - - - - - - - - https://thuecat.org/resources/644315157726-jmww - - - - 0 - - - - - - https://thuecat.org/resources/072778761562-kwah - - - - 0 - - - - - - - ', - ], - ], -]; diff --git a/Tests/Acceptance/Data/Sites/default/config.yaml b/Tests/Acceptance/Data/Sites/default/config.yaml deleted file mode 100644 index f33946fc..00000000 --- a/Tests/Acceptance/Data/Sites/default/config.yaml +++ /dev/null @@ -1,47 +0,0 @@ -base: 'http://localhost:8080' -languages: - - - title: Deutsch - enabled: true - base: / - typo3Language: de - locale: de_DE.UTF-8 - iso-639-1: de - navigationTitle: Deutsch - hreflang: de-DE - direction: '' - flag: de - websiteTitle: '' - languageId: 0 - - - title: English - enabled: true - base: /en - typo3Language: default - locale: en_GB.UTF-8 - iso-639-1: en - websiteTitle: '' - navigationTitle: English - hreflang: en-GB - direction: '' - flag: gb - languageId: 1 - fallbackType: strict - fallbacks: '0' - - - title: French - enabled: true - base: /fr/ - typo3Language: fr - locale: fr_FR.ytf8 - iso-639-1: fr - websiteTitle: '' - navigationTitle: '' - hreflang: fr-FR - direction: '' - fallbackType: strict - fallbacks: '1,0' - flag: fr - languageId: 2 -rootPageId: 1 -websiteTitle: 'Example Website' diff --git a/Tests/Acceptance/Support/AcceptanceTester.php b/Tests/Acceptance/Support/AcceptanceTester.php deleted file mode 100644 index 5ebb41e9..00000000 --- a/Tests/Acceptance/Support/AcceptanceTester.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -namespace WerkraumMedia\ThueCat\Tests\Acceptance\Support; - -use Codeception\Actor; -use TYPO3\TestingFramework\Core\Acceptance\Step\FrameSteps; -use WerkraumMedia\ThueCat\Tests\Acceptance\Support\_generated\AcceptanceTesterActions; - -/** - * Inherited Methods - * - * @method void wantToTest($text) - * @method void wantTo($text) - * @method void execute($callable) - * @method void expectTo($prediction) - * @method void expect($prediction) - * @method void amGoingTo($argumentation) - * @method void am($role) - * @method void lookForwardTo($achieveValue) - * @method void comment($description) - * @method void pause() - * - * @SuppressWarnings(PHPMD) -*/ -class AcceptanceTester extends Actor -{ - use FrameSteps; - use AcceptanceTesterActions; - - /** - * Define custom actions here - */ -} diff --git a/Tests/Acceptance/Support/Environment.php b/Tests/Acceptance/Support/Environment.php deleted file mode 100644 index 64877100..00000000 --- a/Tests/Acceptance/Support/Environment.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -namespace WerkraumMedia\ThueCat\Tests\Acceptance\Support; - -use Codappix\Typo3PhpDatasets\TestingFramework; -use Codeception\Event\SuiteEvent; -use TYPO3\TestingFramework\Core\Acceptance\Extension\BackendEnvironment; - -/** - * Load various core extensions and extension under test. - */ -class Environment extends BackendEnvironment -{ - use TestingFramework; - - protected $localConfig = [ - 'coreExtensionsToLoad' => [ - 'install', - 'core', - 'backend', - 'extbase', - 'frontend', - 'fluid', - ], - 'testExtensionsToLoad' => [ - 'werkraummedia/thuecat', - ], - 'pathsToLinkInTestInstance' => [ - '/../../../../../../Tests/Acceptance/Data/Sites/' => 'typo3conf/sites', - ], - ]; - - public function bootstrapTypo3Environment(SuiteEvent $suiteEvent) - { - parent::bootstrapTypo3Environment($suiteEvent); - - $this->importPHPDataSet(__DIR__ . '/../Data/BasicDatabase.php'); - } -} diff --git a/Tests/Acceptance/_output/.gitignore b/Tests/Acceptance/_output/.gitignore deleted file mode 100644 index c96a04f0..00000000 --- a/Tests/Acceptance/_output/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/Tests/Functional/AbstractImportTestCase.php b/Tests/Functional/AbstractImportTestCase.php index b2615fa3..2510f061 100644 --- a/Tests/Functional/AbstractImportTestCase.php +++ b/Tests/Functional/AbstractImportTestCase.php @@ -24,7 +24,10 @@ namespace WerkraumMedia\ThueCat\Tests\Functional; use Codappix\Typo3PhpDatasets\TestingFramework; +use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder; +use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Core\Localization\LanguageServiceFactory; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; abstract class AbstractImportTestCase extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase { @@ -116,4 +119,21 @@ protected function getErrorLogFile(): string { return self::getInstancePath() . '/typo3temp/var/log/typo3_error_0493d91d8e.log'; } + + /** + * Workaround ConfigurationManager requiring request + */ + protected function workaroundExtbaseConfiguration(): void + { + $fakeRequest = new ServerRequest(); + $fakeRequest = $fakeRequest->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE); + $configurationManager = $this->get(ConfigurationManagerInterface::class); + + // TODO: typo3/cms-core:14.0 Remove condition, the method should always be available + if (method_exists($configurationManager, 'setRequest') === false) { + return; + } + + $configurationManager->setRequest($fakeRequest); + } } diff --git a/Tests/Functional/FrontendTest.php b/Tests/Functional/FrontendTest.php index 7848a6a4..3fa3cd59 100644 --- a/Tests/Functional/FrontendTest.php +++ b/Tests/Functional/FrontendTest.php @@ -26,6 +26,7 @@ use Codappix\Typo3PhpDatasets\TestingFramework; use DateTimeImmutable; use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; @@ -683,12 +684,18 @@ public function editorialImagesOfTouristAttractionAreRenderedForDefaultLanguage( $html = (string)$this->executeFrontendSubRequest($request)->getBody(); + $imgPrefix = ''; + // TODO: typo3/cms-core:14.0 Remove $imgPrefix + if (version_compare((new Typo3Version())->__toString(), '13.0', '<')) { + $imgPrefix = '/'; + } + self::assertStringContainsString( - '', + '', $html ); self::assertStringContainsString( - '', + '', $html, ); } diff --git a/Tests/Functional/ImportConfigurationCommandTest.php b/Tests/Functional/ImportConfigurationCommandTest.php index 5289f2b4..004219e1 100644 --- a/Tests/Functional/ImportConfigurationCommandTest.php +++ b/Tests/Functional/ImportConfigurationCommandTest.php @@ -35,6 +35,8 @@ final class ImportConfigurationCommandTest extends AbstractImportTestCase #[Test] public function canImport(): void { + $this->workaroundExtbaseConfiguration(); + $subject = $this->getContainer()->get(ImportConfigurationCommand::class); self::assertInstanceOf(Command::class, $subject); @@ -50,6 +52,8 @@ public function canImport(): void #[Test] public function throwsExceptionOnNoneExistingConfiguration(): void { + $this->workaroundExtbaseConfiguration(); + $subject = $this->getContainer()->get(ImportConfigurationCommand::class); self::assertInstanceOf(Command::class, $subject); diff --git a/Tests/Functional/ImportTest.php b/Tests/Functional/ImportTest.php index 4b142d46..ab535e70 100644 --- a/Tests/Functional/ImportTest.php +++ b/Tests/Functional/ImportTest.php @@ -23,7 +23,11 @@ * 02110-1301, USA. */ +use DateTimeImmutable; +use DateTimeZone; use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Context\Context; +use TYPO3\CMS\Core\Context\DateTimeAspect; use WerkraumMedia\ThueCat\Domain\Import\ImportConfiguration; use WerkraumMedia\ThueCat\Domain\Import\Importer; use WerkraumMedia\ThueCat\Domain\Repository\Backend\ImportConfigurationRepository; @@ -387,6 +391,15 @@ public function importsWithBrokenOpeningHour(): void private function importConfiguration(): void { + $this->workaroundExtbaseConfiguration(); + + $this->get(Context::class)->setAspect( + 'date', + new DateTimeAspect( + new DateTimeImmutable('2024-03-03 00:00:00', new DateTimeZone('UTC')) + ) + ); + $configuration = $this->get(ImportConfigurationRepository::class)->findByUid(1); self::assertInstanceOf(ImportConfiguration::class, $configuration); $this->get(Importer::class)->importConfiguration($configuration); diff --git a/codeception.dist.yml b/codeception.dist.yml deleted file mode 100644 index f9f7b80e..00000000 --- a/codeception.dist.yml +++ /dev/null @@ -1,44 +0,0 @@ -namespace: 'WerkraumMedia\ThueCat\Tests\Acceptance\Support' - -paths: - tests: 'Tests/Acceptance' - data: 'Tests/Acceptance/Data' - output: '.Build/web/typo3temp/var/tests/AcceptanceReports' - support: 'Tests/Acceptance/Support' - -settings: - debug: true - -extensions: - enabled: - - 'Codeception\Extension\RunFailed' - -suites: - acceptance: - actor: 'AcceptanceTester' - path: . - extensions: - enabled: - - 'Codeception\Extension\RunProcess': - - 'geckodriver > .Build/web/typo3temp/var/tests/AcceptanceReports/geckodriver.log 2>&1' - - 'TYPO3_PATH_APP="$INSTANCE_PATH" TYPO3_PATH_ROOT="$INSTANCE_PATH" php -S 127.0.0.1:8080 -t "$INSTANCE_PATH" > .Build/web/typo3temp/var/tests/AcceptanceReports/php.log 2>&1' - - 'WerkraumMedia\ThueCat\Tests\Acceptance\Support\Environment': - 'typo3DatabaseUsername': 'testing' - 'typo3DatabasePassword': 'testing' - - modules: - enabled: - - WebDriver: - url: 'http://localhost:8080' - browser: 'firefox' - restart: true - path: '' - wait: 5 - capabilities: - moz:firefoxOptions: - args: - - '-headless' - - '\TYPO3\TestingFramework\Core\Acceptance\Helper\Acceptance' - step_decorators: - - 'Codeception\Step\Retry' - diff --git a/composer.json b/composer.json index 41ea9ae4..0185bfce 100644 --- a/composer.json +++ b/composer.json @@ -42,28 +42,27 @@ "psr/http-factory": "^1.0", "psr/http-message": "^2.0", "psr/log": "^2.0 || ^3.0", - "symfony/console": "^6.4", - "symfony/dependency-injection": "^6.4", - "symfony/property-access": "^6.4", - "symfony/property-info": "^6.4", + "symfony/console": "^6.4 || ^7.1", + "symfony/dependency-injection": "^6.4 || ^7.1", + "symfony/property-access": "^6.4 || ^7.1", + "symfony/property-info": "^6.4 || ^7.1", "symfony/serializer": "^6.4", - "typo3/cms-backend": "^12.4", - "typo3/cms-core": "^12.4", - "typo3/cms-extbase": "^12.4", - "typo3/cms-frontend": "^12.4", - "typo3/cms-install": "^12.4" + "typo3/cms-backend": "^12.4 || ^13.4", + "typo3/cms-core": "^12.4 || ^13.4", + "typo3/cms-extbase": "^12.4 || ^13.4", + "typo3/cms-frontend": "^12.4 || ^13.4", + "typo3/cms-install": "^12.4 || ^13.4" }, "require-dev": { "codappix/typo3-php-datasets": "^1.4", - "codeception/codeception": "^5.0", - "codeception/module-webdriver": "^4.0", "friendsofphp/php-cs-fixer": "^3.40", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "1.10.46", "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^10.4", + "phpunit/phpunit": "^10.4 || ^11.4", "saschaegerer/phpstan-typo3": "^1.9", - "typo3/cms-fluid-styled-content": "^12.4", + "staabm/phpstan-todo-by": "^0.1.32", + "typo3/cms-fluid-styled-content": "^12.4 || ^13.4", "typo3/testing-framework": "^8.0" }, "config": { diff --git a/ext_emconf.php b/ext_emconf.php index bc50bb69..fd4bc2f6 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -10,7 +10,7 @@ 'author' => 'Daniel Siepmann', 'author_email' => 'coding@daniel-siepmann.de', 'author_company' => '', - 'version' => '3.0.1', + 'version' => '3.1.0', 'constraints' => [ 'depends' => [ 'core' => '', diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4a66df72..cf3b6468 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -40,11 +40,6 @@ parameters: count: 1 path: Classes/Domain/Import/Typo3Converter/GeneralConverter.php - - - message: "#^Method WerkraumMedia\\\\ThueCat\\\\Domain\\\\Model\\\\Backend\\\\ImportConfiguration\\:\\:getConfigurationAsArray\\(\\) should return array but returns mixed\\.$#" - count: 1 - path: Classes/Domain/Model/Backend/ImportConfiguration.php - - message: "#^Method WerkraumMedia\\\\ThueCat\\\\Domain\\\\Model\\\\Backend\\\\ImportConfiguration\\:\\:getEntries\\(\\) should return array but returns mixed\\.$#" count: 1 @@ -215,11 +210,6 @@ parameters: count: 1 path: Classes/Domain/Model/Frontend/OpeningHours.php - - - message: "#^Cannot call method getLanguageOverlay\\(\\) on string\\|TYPO3\\\\CMS\\\\Core\\\\Domain\\\\Repository\\\\PageRepository\\.$#" - count: 1 - path: Classes/Frontend/DataProcessing/ResolveEntities.php - - message: "#^Parameter \\#1 \\$className of method TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\Generic\\\\Mapper\\\\DataMapper\\:\\:map\\(\\) expects class\\-string\\, string given\\.$#" count: 1 @@ -234,8 +224,3 @@ parameters: message: "#^Parameter \\#1 \\$mods of method WerkraumMedia\\\\ThueCat\\\\Updates\\\\BackendModuleUserPermission\\:\\:updateMods\\(\\) expects string, mixed given\\.$#" count: 1 path: Classes/Updates/BackendModuleUserPermission.php - - - - message: "#^Method WerkraumMedia\\\\ThueCat\\\\Tests\\\\Acceptance\\\\Support\\\\Environment\\:\\:bootstrapTypo3Environment\\(\\) has no return type specified\\.$#" - count: 1 - path: Tests/Acceptance/Support/Environment.php diff --git a/shell.nix b/shell.nix index 9ca6aee8..d6a860cd 100644 --- a/shell.nix +++ b/shell.nix @@ -49,26 +49,6 @@ let ''; }; - projectTestAcceptance = pkgs.writeShellApplication { - name = "project-test-acceptance"; - runtimeInputs = [ - projectInstall - pkgs.sqlite - pkgs.firefox - pkgs.geckodriver - php - ]; - text = '' - project-install - - export INSTANCE_PATH="$PROJECT_ROOT/.Build/web/typo3temp/var/tests/acceptance" - export typo3DatabaseDriver=pdo_sqlite - - mkdir -p "$INSTANCE_PATH" - ./vendor/bin/codecept run - ''; - }; - in pkgs.mkShell { name = "TYPO3 Extension ThüCAT"; buildInputs = [ @@ -77,7 +57,6 @@ in pkgs.mkShell { projectInstall projectCgl projectCglFix - projectTestAcceptance ]; shellHook = '' From e36f01b9fe0facf9e76e60ebb2e5ff95753ac01a Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Mon, 4 Nov 2024 14:47:10 +0100 Subject: [PATCH 2/5] Fix broken icon for CE Resolves: #11481 --- Configuration/TCA/Overrides/tt_content_tourist_attraction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/TCA/Overrides/tt_content_tourist_attraction.php b/Configuration/TCA/Overrides/tt_content_tourist_attraction.php index 85b9c526..a385b4b7 100644 --- a/Configuration/TCA/Overrides/tt_content_tourist_attraction.php +++ b/Configuration/TCA/Overrides/tt_content_tourist_attraction.php @@ -60,7 +60,7 @@ [ 'label' => $languagePath, 'value' => $cType, - 'icon' => Extension::getIconPath() . 'tt_content_' . $cType . '.svg', + 'icon' => 'tt_content_' . $cType, 'group' => Extension::TCA_SELECT_GROUP_IDENTIFIER, ] ); From 94e862ddcc12b2af5e6d72d754102f5cda49ef89 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Mon, 4 Nov 2024 14:48:50 +0100 Subject: [PATCH 3/5] Fix broken backend module icon with new backend themes Resolves: #11478 --- Resources/Public/Icons/ModuleGroup.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Public/Icons/ModuleGroup.svg b/Resources/Public/Icons/ModuleGroup.svg index a8811731..3e8f40b8 100644 --- a/Resources/Public/Icons/ModuleGroup.svg +++ b/Resources/Public/Icons/ModuleGroup.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From f6b153116db68efdafd124479d86d32b10f9f229 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Mon, 4 Nov 2024 15:07:48 +0100 Subject: [PATCH 4/5] Still deliver expected template for backend module Keep old logic to auto determine the expected templates. Remove no longer used configuration. Resolves: #11479 --- Classes/Controller/Backend/AbstractController.php | 7 ++++++- Configuration/TypoScript/Default/Setup.typoscript | 5 ----- .../Private/Templates/Backend/Configuration/Index.html | 4 ++-- Resources/Private/Templates/Backend/Import/Index.html | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Classes/Controller/Backend/AbstractController.php b/Classes/Controller/Backend/AbstractController.php index fe4ed3cd..7d6361a7 100644 --- a/Classes/Controller/Backend/AbstractController.php +++ b/Classes/Controller/Backend/AbstractController.php @@ -47,6 +47,11 @@ protected function initializeView(): void protected function htmlResponse(?string $html = null): ResponseInterface { - return parent::htmlResponse($html ?? $this->moduleTemplate->render()); + $action = sprintf( + '%s/%s', + str_replace('\\', '/', $this->request->getControllerName()), + ucfirst($this->request->getControllerActionName()), + ); + return parent::htmlResponse($html ?? $this->moduleTemplate->render($action)); } } diff --git a/Configuration/TypoScript/Default/Setup.typoscript b/Configuration/TypoScript/Default/Setup.typoscript index b05171bb..28807d3a 100644 --- a/Configuration/TypoScript/Default/Setup.typoscript +++ b/Configuration/TypoScript/Default/Setup.typoscript @@ -5,10 +5,5 @@ module { tx_thuecat_import_configuration = 0 } } - view { - templateRootPaths { - 0 = EXT:thuecat/Resources/Private/Templates/ - } - } } } diff --git a/Resources/Private/Templates/Backend/Configuration/Index.html b/Resources/Private/Templates/Backend/Configuration/Index.html index 160692e5..fd190220 100644 --- a/Resources/Private/Templates/Backend/Configuration/Index.html +++ b/Resources/Private/Templates/Backend/Configuration/Index.html @@ -54,7 +54,7 @@

{f:translate(id: 'module.organisations.headline')}

-
+
@@ -104,7 +104,7 @@

{f:translate(id: 'module.organisations.headline')}

-
+
diff --git a/Resources/Private/Templates/Backend/Import/Index.html b/Resources/Private/Templates/Backend/Import/Index.html index 14e12146..612e3c8f 100644 --- a/Resources/Private/Templates/Backend/Import/Index.html +++ b/Resources/Private/Templates/Backend/Import/Index.html @@ -24,7 +24,7 @@

{f:translate(id: 'module.imports.headline')}

-
+
From d323a60e7a381f702a8c47f6bc6b6bc29a250116 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Mon, 4 Nov 2024 15:35:47 +0100 Subject: [PATCH 5/5] Fix broken permission for tt_content The actual registry needs to be within ext_tables.php after loading TCA. Resolves: #11480 --- Classes/Extension.php | 14 +++++++++++--- ext_localconf.php | 2 +- ext_tables.php | 5 +++++ 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 ext_tables.php diff --git a/Classes/Extension.php b/Classes/Extension.php index ec9e3839..42f2c885 100644 --- a/Classes/Extension.php +++ b/Classes/Extension.php @@ -44,11 +44,16 @@ public static function getLanguagePath(): string return 'LLL:EXT:' . self::EXTENSION_KEY . '/Resources/Private/Language/'; } - public static function registerConfig(): void + public static function registerExtLocalconfConfigConfig(): void { self::addCaching(); self::addContentElements(); - self::addPageTypes(); + self::addPageTypesExtLocalconf(); + } + + public static function registerExtTablesConfig(): void + { + self::addPageTypesExtTables(); } public static function getIconPath(): string @@ -81,7 +86,7 @@ private static function addContentElements(): void } } - private static function addPageTypes(): void + private static function addPageTypesExtTables(): void { $registry = GeneralUtility::makeInstance(PageDoktypeRegistry::class); $registry->add( @@ -91,7 +96,10 @@ private static function addPageTypes(): void 'allowedTables' => '*', ] ); + } + private static function addPageTypesExtLocalconf(): void + { // TODO: typo3/cms-core:14.0 Remove this code block as Configuration/user.tsconfig will be loaded since 13.x if (version_compare(GeneralUtility::makeInstance(Typo3Version::class)->__toString(), '13.0', '<')) { ExtensionManagementUtility::addUserTSConfig( diff --git a/ext_localconf.php b/ext_localconf.php index 537e04fd..3e5ed651 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -8,7 +8,7 @@ defined('TYPO3') or die(); -Extension::registerConfig(); +Extension::registerExtLocalconfConfigConfig(); (static function (string $extensionKey) { ExtensionManagementUtility::addTypoScriptSetup( diff --git a/ext_tables.php b/ext_tables.php new file mode 100644 index 00000000..3808ffd2 --- /dev/null +++ b/ext_tables.php @@ -0,0 +1,5 @@ +