From 7de0587c5e4d45ba0bc276f909c5a55d404d7a4d Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Wed, 25 Mar 2026 23:21:18 +0100 Subject: [PATCH 01/12] [TASK] Add TYPO3 v14 support --- .github/workflows/ci.yaml | 26 +++--- Build/phpunit.xml.dist | 18 ++++- Classes/Controller/ProductController.php | 14 ++-- Classes/Domain/Model/Product/BeVariant.php | 4 +- Classes/Domain/Model/Product/FeVariant.php | 6 +- Classes/Domain/Model/Product/Product.php | 10 +-- .../Product/ProductBackendVariantTrait.php | 4 +- .../Domain/Model/Product/QuantityDiscount.php | 6 +- Classes/Domain/Model/Product/SpecialPrice.php | 6 +- Classes/Domain/Model/WatchlistItemFactory.php | 2 +- .../EventListener/Order/Stock/FlushCache.php | 4 +- Classes/Hooks/DataHandler.php | 4 +- .../Updates/ImageReferencesUpgradeWizard.php | 12 +-- Classes/Updates/ListTypeToCTypeUpdate.php | 4 +- ...itchableControllerActionsPluginUpdater.php | 12 +-- .../ViewHelpers/CanonicalTagViewHelper.php | 28 +++---- .../ViewHelpers/Link/ProductViewHelper.php | 31 +++++--- .../IfBestSpecialPriceAvailableViewHelper.php | 4 +- .../ViewHelpers/Product/PageUidViewHelper.php | 4 +- Configuration/TCA/Overrides/tt_content.php | 36 +++------ ...roducts_domain_model_product_bevariant.php | 1 - ...omain_model_product_bevariantattribute.php | 1 - ...model_product_bevariantattributeoption.php | 1 - ...roducts_domain_model_product_fevariant.php | 3 +- ...tproducts_domain_model_product_product.php | 1 - ..._domain_model_product_quantitydiscount.php | 1 - ...ucts_domain_model_product_specialprice.php | 1 - Tests/Unit/Domain/Model/CategoryTest.php | 79 +------------------ .../Model/Product/BeVariantAttributeTest.php | 12 +-- .../Domain/Model/Product/BeVariantTest.php | 17 ++-- .../Domain/Model/Product/FeVariantTest.php | 12 +-- .../Unit/Domain/Model/Product/ProductTest.php | 16 ++-- .../Model/Product/QuantityDiscountTest.php | 12 +-- .../Domain/Model/Product/SpecialPriceTest.php | 12 +-- composer.json | 79 ++++++------------- rector.php | 13 +-- shell.nix | 16 ++-- 37 files changed, 188 insertions(+), 324 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f256ead6..585b6bbd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -61,7 +61,7 @@ jobs: run: composer install --prefer-dist --no-progress --no-suggest - name: Coding Guideline - run: vendor/bin/php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php -v --dry-run --using-cache=no --path-mode=intersection ./ + run: .build/bin/php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php -v --dry-run --using-cache=no --path-mode=intersection ./ code-quality: runs-on: ubuntu-latest @@ -71,13 +71,13 @@ jobs: matrix: include: - php-version: '8.2' - typo3-version: '^13.4' + typo3-version: '^14.1' - php-version: '8.3' - typo3-version: '^13.4' + typo3-version: '^14.1' - php-version: '8.4' - typo3-version: '^13.4' + typo3-version: '^14.1' - php-version: '8.5' - typo3-version: '^13.4' + typo3-version: '^14.1' steps: - uses: actions/checkout@v4 @@ -92,20 +92,20 @@ jobs: composer require --no-interaction --prefer-dist --no-progress "typo3/cms-core:${{ matrix.typo3-version }}" "typo3/cms-extbase:${{ matrix.typo3-version }}" "typo3/cms-frontend:${{ matrix.typo3-version }}" - name: Build codeception tester - run: vendor/bin/codecept build + run: .build/bin/codecept build - name: Code Quality (by PHPStan) - run: vendor/bin/phpstan analyse -c Build/phpstan.neon + run: .build/bin/phpstan analyse -c Build/phpstan.neon - test-php: + test-unit-and-functional: runs-on: ubuntu-latest needs: - coding-guideline - code-quality steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - - uses: cachix/install-nix-action@v17 + - uses: cachix/install-nix-action@v31 with: nix_path: nixpkgs=channel:nixos-unstable @@ -136,11 +136,11 @@ jobs: test-acceptance: runs-on: ubuntu-latest needs: - - test-php + - test-unit-and-functional steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - - uses: cachix/install-nix-action@v17 + - uses: cachix/install-nix-action@v31 with: nix_path: nixpkgs=channel:nixos-unstable diff --git a/Build/phpunit.xml.dist b/Build/phpunit.xml.dist index ec412cba..fc55fadc 100644 --- a/Build/phpunit.xml.dist +++ b/Build/phpunit.xml.dist @@ -1,5 +1,21 @@ - + diff --git a/Classes/Controller/ProductController.php b/Classes/Controller/ProductController.php index bb79303f..6e9f2f1e 100644 --- a/Classes/Controller/ProductController.php +++ b/Classes/Controller/ProductController.php @@ -125,7 +125,7 @@ protected function addCategoriesToDemandObjectFromSettings(ProductDemand $demand protected function isActionAllowed(string $action): bool { $frameworkConfiguration = $this->configurationManager->getConfiguration($this->configurationManager::CONFIGURATION_TYPE_FRAMEWORK); - $allowedActions = $frameworkConfiguration['controllerConfiguration'][\Extcode\CartProducts\Controller\ProductController::class]['actions'] ?? []; + $allowedActions = $frameworkConfiguration['controllerConfiguration'][ProductController::class]['actions'] ?? []; return in_array($action, $allowedActions, true); } @@ -184,8 +184,8 @@ public function listAction(int $currentPage = 1): ResponseInterface public function showAction(?Product $product = null): ResponseInterface { - if ((int)$GLOBALS['TSFE']->page['doktype'] === 183) { - $productUid = (int)$GLOBALS['TSFE']->page['cart_products_product']; + if ((int)$this->request->getAttribute('frontend.page.information')->getPageRecord()['doktype'] === 183) { + $productUid = (int)$this->request->getAttribute('frontend.page.information')->getPageRecord()['cart_products_product']; $product = $this->productRepository->findByUid($productUid); } @@ -254,17 +254,15 @@ protected function getProduct(): ?Product */ public function getProductUid(): mixed { - if ((int)$GLOBALS['TSFE']->page['doktype'] === 183) { - return (int)$GLOBALS['TSFE']->page['cart_products_product']; + if ((int)$this->request->getAttribute('frontend.page.information')->getPageRecord()['doktype'] === 183) { + return (int)$this->request->getAttribute('frontend.page.information')->getPageRecord()['cart_products_product']; } if ($this->request->getPluginName() !== 'ProductPartial') { return 0; } - $configurationManager = GeneralUtility::makeInstance( - ConfigurationManager::class - ); + $configurationManager = $this->configurationManager; $configuration = $configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_FRAMEWORK); $typoscriptService = GeneralUtility::makeInstance( diff --git a/Classes/Domain/Model/Product/BeVariant.php b/Classes/Domain/Model/Product/BeVariant.php index ca7e7242..dd03d003 100644 --- a/Classes/Domain/Model/Product/BeVariant.php +++ b/Classes/Domain/Model/Product/BeVariant.php @@ -10,7 +10,7 @@ */ use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Extbase\Annotation\ORM\Cascade; +use TYPO3\CMS\Extbase\Attribute\ORM\Cascade; use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; use TYPO3\CMS\Extbase\Persistence\ObjectStorage; @@ -29,7 +29,7 @@ class BeVariant extends AbstractEntity /** * @var ObjectStorage */ - #[Cascade(['value' => 'remove'])] + #[Cascade(value: 'remove')] protected ObjectStorage $specialPrices; protected int $priceCalcMethod = 0; diff --git a/Classes/Domain/Model/Product/FeVariant.php b/Classes/Domain/Model/Product/FeVariant.php index f5e72f2f..5c16b2f2 100644 --- a/Classes/Domain/Model/Product/FeVariant.php +++ b/Classes/Domain/Model/Product/FeVariant.php @@ -9,15 +9,15 @@ * LICENSE file that was distributed with this source code. */ -use TYPO3\CMS\Extbase\Annotation\Validate; +use TYPO3\CMS\Extbase\Attribute\Validate; use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; class FeVariant extends AbstractEntity { - #[Validate(['validator' => 'NotEmpty'])] + #[Validate(validator: 'NotEmpty')] protected string $sku = ''; - #[Validate(['validator' => 'NotEmpty'])] + #[Validate(validator: 'NotEmpty')] protected string $title = ''; protected string $description = ''; diff --git a/Classes/Domain/Model/Product/Product.php b/Classes/Domain/Model/Product/Product.php index cde5ac71..d83c8ee9 100644 --- a/Classes/Domain/Model/Product/Product.php +++ b/Classes/Domain/Model/Product/Product.php @@ -16,8 +16,8 @@ use Extcode\Cart\Domain\Model\Product\TagTrait; use Extcode\CartProducts\Domain\Model\Category; use Extcode\CartProducts\Domain\Model\TtContent; -use TYPO3\CMS\Extbase\Annotation\ORM\Cascade; -use TYPO3\CMS\Extbase\Annotation\ORM\Lazy; +use TYPO3\CMS\Extbase\Attribute\ORM\Cascade; +use TYPO3\CMS\Extbase\Attribute\ORM\Lazy; use TYPO3\CMS\Extbase\Persistence\ObjectStorage; class Product extends AbstractProduct @@ -48,13 +48,13 @@ class Product extends AbstractProduct /** * @var ObjectStorage */ - #[Cascade(['value' => 'remove'])] + #[Cascade(value: 'remove')] protected ObjectStorage $specialPrices; /** * @var ObjectStorage */ - #[Cascade(['value' => 'remove'])] + #[Cascade(value: 'remove')] protected ObjectStorage $quantityDiscounts; protected int $taxClassId = 1; @@ -62,7 +62,7 @@ class Product extends AbstractProduct /** * @var ObjectStorage */ - #[Cascade(['value' => 'remove'])] + #[Cascade(value: 'remove')] protected ObjectStorage $feVariants; /** diff --git a/Classes/Domain/Model/Product/ProductBackendVariantTrait.php b/Classes/Domain/Model/Product/ProductBackendVariantTrait.php index 4774eb08..8a34d65e 100644 --- a/Classes/Domain/Model/Product/ProductBackendVariantTrait.php +++ b/Classes/Domain/Model/Product/ProductBackendVariantTrait.php @@ -9,7 +9,7 @@ * LICENSE file that was distributed with this source code. */ -use TYPO3\CMS\Extbase\Annotation\ORM\Cascade; +use TYPO3\CMS\Extbase\Attribute\ORM\Cascade; use TYPO3\CMS\Extbase\Persistence\ObjectStorage; trait ProductBackendVariantTrait @@ -23,7 +23,7 @@ trait ProductBackendVariantTrait /** * @var ObjectStorage */ - #[Cascade(['value' => 'remove'])] + #[Cascade(value: 'remove')] protected ObjectStorage $beVariants; public function getBeVariantAttribute1(): ?BeVariantAttribute diff --git a/Classes/Domain/Model/Product/QuantityDiscount.php b/Classes/Domain/Model/Product/QuantityDiscount.php index 71e65339..8ec0222d 100644 --- a/Classes/Domain/Model/Product/QuantityDiscount.php +++ b/Classes/Domain/Model/Product/QuantityDiscount.php @@ -10,18 +10,18 @@ */ use Extcode\Cart\Domain\Model\FrontendUserGroup; -use TYPO3\CMS\Extbase\Annotation\Validate; +use TYPO3\CMS\Extbase\Attribute\Validate; use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; class QuantityDiscount extends AbstractEntity { - #[Validate(['validator' => 'NotEmpty'])] + #[Validate(validator: 'NotEmpty')] protected float $price = 0.0; /** * Quantity (lower bound) */ - #[Validate(['validator' => 'NotEmpty'])] + #[Validate(validator: 'NotEmpty')] protected int $quantity = 0; protected ?FrontendUserGroup $frontendUserGroup = null; diff --git a/Classes/Domain/Model/Product/SpecialPrice.php b/Classes/Domain/Model/Product/SpecialPrice.php index 5420fd13..5662901d 100644 --- a/Classes/Domain/Model/Product/SpecialPrice.php +++ b/Classes/Domain/Model/Product/SpecialPrice.php @@ -10,15 +10,15 @@ */ use Extcode\Cart\Domain\Model\FrontendUserGroup; -use TYPO3\CMS\Extbase\Annotation\Validate; +use TYPO3\CMS\Extbase\Attribute\Validate; use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; class SpecialPrice extends AbstractEntity { - #[Validate(['validator' => 'NotEmpty'])] + #[Validate(validator: 'NotEmpty')] protected string $title = ''; - #[Validate(['validator' => 'NotEmpty'])] + #[Validate(validator: 'NotEmpty')] protected float $price = 0.0; protected ?FrontendUserGroup $frontendUserGroup = null; diff --git a/Classes/Domain/Model/WatchlistItemFactory.php b/Classes/Domain/Model/WatchlistItemFactory.php index b5850ed1..1be62b39 100644 --- a/Classes/Domain/Model/WatchlistItemFactory.php +++ b/Classes/Domain/Model/WatchlistItemFactory.php @@ -22,7 +22,7 @@ public function __construct( public function createFromIdentifier( string $identifier, ): ?WatchlistItem { - list($uid, $detailPid) = explode('-', $identifier); + [$uid, $detailPid] = explode('-', $identifier); $product = $this->productRepository->findProductByUid((int)$uid); diff --git a/Classes/EventListener/Order/Stock/FlushCache.php b/Classes/EventListener/Order/Stock/FlushCache.php index 667da9e8..ff53c8ab 100644 --- a/Classes/EventListener/Order/Stock/FlushCache.php +++ b/Classes/EventListener/Order/Stock/FlushCache.php @@ -13,10 +13,10 @@ use Extcode\Cart\Event\Order\EventInterface; use TYPO3\CMS\Core\Cache\CacheManager; -use TYPO3\CMS\Core\Utility\GeneralUtility; class FlushCache { + public function __construct(private readonly CacheManager $cacheManager) {} public function __invoke(EventInterface $event): void { $cartProducts = $event->getCart()->getProducts(); @@ -26,7 +26,7 @@ public function __invoke(EventInterface $event): void $cartProductId = $cartProduct->getProductId(); $cacheTag = 'tx_cartproducts_product_' . $cartProductId; - $cacheManager = GeneralUtility::makeInstance(CacheManager::class); + $cacheManager = $this->cacheManager; $cacheManager->flushCachesInGroupByTag('pages', $cacheTag); } } diff --git a/Classes/Hooks/DataHandler.php b/Classes/Hooks/DataHandler.php index 29f14750..54add3eb 100644 --- a/Classes/Hooks/DataHandler.php +++ b/Classes/Hooks/DataHandler.php @@ -10,10 +10,10 @@ */ use TYPO3\CMS\Core\Cache\CacheManager; -use TYPO3\CMS\Core\Utility\GeneralUtility; class DataHandler { + public function __construct(private readonly CacheManager $cacheManager) {} /** * Flushes the cache if a news record was edited. * This happens on two levels: by UID and by PID. @@ -29,7 +29,7 @@ public function clearCachePostProc(array $params): void $cacheTagsToFlush[] = 'tx_cartproducts_product_' . $params['uid_page']; } - $cacheManager = GeneralUtility::makeInstance(CacheManager::class); + $cacheManager = $this->cacheManager; foreach ($cacheTagsToFlush as $cacheTag) { $cacheManager->flushCachesInGroupByTag('pages', $cacheTag); } diff --git a/Classes/Updates/ImageReferencesUpgradeWizard.php b/Classes/Updates/ImageReferencesUpgradeWizard.php index b354a37c..da6e1398 100644 --- a/Classes/Updates/ImageReferencesUpgradeWizard.php +++ b/Classes/Updates/ImageReferencesUpgradeWizard.php @@ -11,17 +11,17 @@ * LICENSE file that was distributed with this source code. */ +use TYPO3\CMS\Core\Attribute\UpgradeWizard; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; -use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Install\Attribute\UpgradeWizard; -use TYPO3\CMS\Install\Updates\UpgradeWizardInterface; +use TYPO3\CMS\Core\Upgrades\UpgradeWizardInterface; #[UpgradeWizard('cartProducts_updateImageReferences')] -final class ImageReferencesUpgradeWizard implements UpgradeWizardInterface +final readonly class ImageReferencesUpgradeWizard implements UpgradeWizardInterface { public const TABLE_NAME = 'sys_file_reference'; public const IDENTIFIER = 'tx_cartproducts_domain_model_product_product'; + public function __construct(private ConnectionPool $connectionPool) {} public function getTitle(): string { @@ -35,7 +35,7 @@ public function getDescription(): string public function executeUpdate(): bool { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference'); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_file_reference'); $queryBuilder->getRestrictions()->removeAll(); $queryBuilder ->update(self::TABLE_NAME) @@ -57,7 +57,7 @@ public function executeUpdate(): bool public function updateNecessary(): bool { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TABLE_NAME); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME); $queryBuilder->getRestrictions()->removeAll(); $count = $queryBuilder->count('uid') ->from(self::TABLE_NAME)->where( diff --git a/Classes/Updates/ListTypeToCTypeUpdate.php b/Classes/Updates/ListTypeToCTypeUpdate.php index 9bc14909..bdf85f75 100644 --- a/Classes/Updates/ListTypeToCTypeUpdate.php +++ b/Classes/Updates/ListTypeToCTypeUpdate.php @@ -2,8 +2,8 @@ namespace Extcode\CartProducts\Updates; -use TYPO3\CMS\Install\Attribute\UpgradeWizard; -use TYPO3\CMS\Install\Updates\AbstractListTypeToCTypeUpdate; +use TYPO3\CMS\Core\Attribute\UpgradeWizard; +use TYPO3\CMS\Core\Upgrades\AbstractListTypeToCTypeUpdate; /* * This file is part of the package extcode/cart. diff --git a/Classes/Updates/SwitchableControllerActionsPluginUpdater.php b/Classes/Updates/SwitchableControllerActionsPluginUpdater.php index 59285499..02ac640b 100644 --- a/Classes/Updates/SwitchableControllerActionsPluginUpdater.php +++ b/Classes/Updates/SwitchableControllerActionsPluginUpdater.php @@ -11,14 +11,14 @@ namespace Extcode\CartProducts\Updates; +use TYPO3\CMS\Core\Attribute\UpgradeWizard; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; use TYPO3\CMS\Core\Service\FlexFormService; +use TYPO3\CMS\Core\Upgrades\DatabaseUpdatedPrerequisite; +use TYPO3\CMS\Core\Upgrades\UpgradeWizardInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Install\Attribute\UpgradeWizard; -use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite; -use TYPO3\CMS\Install\Updates\UpgradeWizardInterface; #[UpgradeWizard('cartProducts_switchableControllerActionsPluginUpdater')] class SwitchableControllerActionsPluginUpdater implements UpgradeWizardInterface @@ -38,7 +38,7 @@ class SwitchableControllerActionsPluginUpdater implements UpgradeWizardInterface protected FlexFormService $flexFormService; - public function __construct() + public function __construct(private readonly ConnectionPool $connectionPool) { $this->flexFormService = GeneralUtility::makeInstance(FlexFormService::class); } @@ -133,7 +133,7 @@ protected function getMigrationRecords(): array { $checkListTypes = array_unique(array_column(self::MIGRATION_SETTINGS, 'sourceListType')); - $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); + $connectionPool = $this->connectionPool; $queryBuilder = $connectionPool->getQueryBuilderForTable('tt_content'); $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); @@ -193,7 +193,7 @@ protected function getAllowedSettingsFromFlexForm(string $listType): array */ protected function updateContentElement(int $uid, string $newListType, string $flexform): void { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content'); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content'); $queryBuilder->update('tt_content') ->set('list_type', $newListType) ->set('pi_flexform', $flexform) diff --git a/Classes/ViewHelpers/CanonicalTagViewHelper.php b/Classes/ViewHelpers/CanonicalTagViewHelper.php index cde73d46..280b829e 100644 --- a/Classes/ViewHelpers/CanonicalTagViewHelper.php +++ b/Classes/ViewHelpers/CanonicalTagViewHelper.php @@ -9,11 +9,8 @@ * LICENSE file that was distributed with this source code. */ use Extcode\CartProducts\Domain\Model\Product\Product; -use TYPO3\CMS\Core\Http\ApplicationType; use TYPO3\CMS\Core\Page\PageRenderer; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; -use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; class CanonicalTagViewHelper extends AbstractTagBasedViewHelper @@ -23,6 +20,13 @@ class CanonicalTagViewHelper extends AbstractTagBasedViewHelper */ protected $tagName = 'link'; + public function __construct( + private readonly UriBuilder $uriBuilder, + private readonly PageRenderer $pageRenderer + ) { + parent::__construct(); + } + public function initializeArguments(): void { parent::initializeArguments(); @@ -62,7 +66,7 @@ public function render(): string ], ]; - $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); + $uriBuilder = $this->uriBuilder; $uriBuilder->reset(); $canonicalUrl = $uriBuilder ->setTargetPageUid($pageUid) @@ -72,22 +76,8 @@ public function render(): string $this->tag->addAttribute('rel', 'canonical'); $this->tag->addAttribute('href', $canonicalUrl); - $this->getPageRenderer()->addHeaderData($this->tag->render()); + $this->pageRenderer->addHeaderData($this->tag->render()); return ''; } - - protected function getPageRenderer(): PageRenderer - { - if (ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend() && is_callable([$this->getTypoScriptFrontendController(), 'getPageRenderer'])) { - return $this->getTypoScriptFrontendController()->getPageRenderer(); - } - - return GeneralUtility::makeInstance(PageRenderer::class); - } - - protected function getTypoScriptFrontendController(): TypoScriptFrontendController - { - return $GLOBALS['TSFE']; - } } diff --git a/Classes/ViewHelpers/Link/ProductViewHelper.php b/Classes/ViewHelpers/Link/ProductViewHelper.php index f4926650..c5ca97f2 100644 --- a/Classes/ViewHelpers/Link/ProductViewHelper.php +++ b/Classes/ViewHelpers/Link/ProductViewHelper.php @@ -5,12 +5,12 @@ namespace Extcode\CartProducts\ViewHelpers\Link; use Extcode\CartProducts\Domain\Model\Product\Product; +use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Database\ConnectionPool; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Extbase\Mvc\RequestInterface; use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; -use TYPO3\CMS\Fluid\Core\Rendering\RenderingContext; +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContext; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; /* @@ -26,14 +26,17 @@ class ProductViewHelper extends AbstractTagBasedViewHelper */ protected $tagName = 'a'; + public function __construct( + private readonly UriBuilder $uriBuilder, + private readonly ConnectionPool $connectionPool + ) { + parent::__construct(); + } + public function initializeArguments(): void { parent::initializeArguments(); - $this->registerUniversalTagAttributes(); - $this->registerTagAttribute('name', 'string', 'Specifies the name of an anchor'); - $this->registerTagAttribute('rel', 'string', 'Specifies the relationship between the current document and the linked document'); - $this->registerTagAttribute('rev', 'string', 'Specifies the relationship between the linked document and the current document'); - $this->registerTagAttribute('target', 'string', 'Specifies where to open the linked document'); + $this->registerArgument('action', 'string', 'Target action'); $this->registerArgument('controller', 'string', 'Target controller. If NULL current controllerName is used'); $this->registerArgument('extensionName', 'string', 'Target Extension Name (without `tx_` prefix and no underscores). If NULL the current extension name is used'); @@ -58,8 +61,12 @@ public function render(): string { /** @var RenderingContext $renderingContext */ $renderingContext = $this->renderingContext; - $request = $renderingContext->getRequest(); - if (!$request instanceof RequestInterface) { + $request = null; + if ($renderingContext->hasAttribute(ServerRequestInterface::class)) { + $request = $renderingContext->getAttribute(ServerRequestInterface::class); + } + + if (($request instanceof RequestInterface) === false) { throw new \RuntimeException( 'ViewHelper f:link.action can be used only in extbase context and needs a request implementing extbase RequestInterface.', 1639818540 @@ -101,7 +108,7 @@ public function render(): string // A missing $pageUid means the product does not have a defined detail view via category or flexform // In this case the $pluginName of the extbase context should be used. if (!$pageUid) { - $pluginName = $renderingContext->getRequest()->getAttributes()['extbase']->getPluginName(); + $pluginName = $request->getAttributes()['extbase']->getPluginName(); } $action = 'show'; @@ -110,7 +117,7 @@ public function render(): string $parameters = $this->arguments['arguments']; - $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); + $uriBuilder = $this->uriBuilder; $uriBuilder ->reset() ->setRequest($request) @@ -151,7 +158,7 @@ public function render(): string */ protected function getProductPage(Product $product) { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages'); return $queryBuilder->select('*') ->from('pages') diff --git a/Classes/ViewHelpers/Product/IfBestSpecialPriceAvailableViewHelper.php b/Classes/ViewHelpers/Product/IfBestSpecialPriceAvailableViewHelper.php index 2a357478..9a464952 100644 --- a/Classes/ViewHelpers/Product/IfBestSpecialPriceAvailableViewHelper.php +++ b/Classes/ViewHelpers/Product/IfBestSpecialPriceAvailableViewHelper.php @@ -8,10 +8,10 @@ * For the full copyright and license information, please read the * LICENSE file that was distributed with this source code. */ - use Extcode\CartProducts\Domain\Model\Product\Product; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper; class IfBestSpecialPriceAvailableViewHelper extends AbstractConditionViewHelper @@ -30,7 +30,7 @@ public function initializeArguments(): void ); } - protected static function evaluateCondition(?array $arguments = null): bool + public static function verdict(array $arguments, RenderingContextInterface $renderingContext): bool { $product = $arguments['product']; $bestSpecialPrice = $product->getBestSpecialPrice(self::getFrontendUserGroupIds()); diff --git a/Classes/ViewHelpers/Product/PageUidViewHelper.php b/Classes/ViewHelpers/Product/PageUidViewHelper.php index 33908240..59cae9fd 100644 --- a/Classes/ViewHelpers/Product/PageUidViewHelper.php +++ b/Classes/ViewHelpers/Product/PageUidViewHelper.php @@ -19,7 +19,7 @@ public function __construct( private readonly PageRepository $pageRepository, ) {} - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); @@ -54,6 +54,6 @@ public function render(): string return $this->arguments['settings']['showPageUids']; } - return $GLOBALS['TSFE']->id; + return $GLOBALS['TYPO3_REQUEST']->getAttribute('frontend.page.information')->getId(); } } diff --git a/Configuration/TCA/Overrides/tt_content.php b/Configuration/TCA/Overrides/tt_content.php index fc8a3eec..15d1cc4e 100644 --- a/Configuration/TCA/Overrides/tt_content.php +++ b/Configuration/TCA/Overrides/tt_content.php @@ -2,7 +2,6 @@ defined('TYPO3') or die(); -use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Utility\ExtensionUtility; @@ -28,32 +27,23 @@ 'translationKeyPrefix' => $_LLL_be . 'tx_cartproducts.plugin.single_product', ], ]; + foreach ($pluginNames as $pluginName => $pluginConfig) { + $flexFormPath = 'EXT:cart_products/Configuration/FlexForms/' . $pluginName . 'Plugin.xml'; + if (file_exists(GeneralUtility::getFileAbsFileName($flexFormPath))) { + $flexFormPath = 'FILE:' . $flexFormPath; + } else { + $flexFormPath = ''; + } - foreach ($pluginNames as $pluginName => $pluginConf) { - $pluginSignature = ExtensionUtility::registerPlugin( - 'cart_products', + ExtensionUtility::registerPlugin( + 'CartProducts', $pluginName, - $pluginConf['translationKeyPrefix'] . '.title', - $pluginConf['pluginIcon'], + $pluginConfig['translationKeyPrefix'] . '.title', + $pluginConfig['pluginIcon'], 'cart', - $pluginConf['translationKeyPrefix'] . '.description', + $pluginConfig['translationKeyPrefix'] . '.description', + $flexFormPath ); - - $flexFormPath = 'EXT:cart_products/Configuration/FlexForms/' . $pluginName . 'Plugin.xml'; - if (file_exists(GeneralUtility::getFileAbsFileName($flexFormPath))) { - ExtensionManagementUtility::addToAllTCAtypes( - 'tt_content', - rtrim('--div--;Configuration,pi_flexform,' . ($pluginConf['additionalNewFields'] ?? ''), ','), - $pluginSignature, - 'after:subheader', - ); - - ExtensionManagementUtility::addPiFlexFormValue( - '*', - 'FILE:' . $flexFormPath, - $pluginSignature, - ); - } } $GLOBALS['TCA']['tt_content']['columns']['tx_cartproducts_domain_model_product_product']['config']['type'] = 'passthrough'; diff --git a/Configuration/TCA/tx_cartproducts_domain_model_product_bevariant.php b/Configuration/TCA/tx_cartproducts_domain_model_product_bevariant.php index 154b3917..fa789bd9 100644 --- a/Configuration/TCA/tx_cartproducts_domain_model_product_bevariant.php +++ b/Configuration/TCA/tx_cartproducts_domain_model_product_bevariant.php @@ -26,7 +26,6 @@ 'starttime' => 'starttime', 'endtime' => 'endtime', ], - 'searchFields' => 'title', 'iconfile' => 'EXT:cart_products/Resources/Public/Icons/Product/BeVariant.png', ], 'types' => [ diff --git a/Configuration/TCA/tx_cartproducts_domain_model_product_bevariantattribute.php b/Configuration/TCA/tx_cartproducts_domain_model_product_bevariantattribute.php index cafdefa6..87f7f511 100644 --- a/Configuration/TCA/tx_cartproducts_domain_model_product_bevariantattribute.php +++ b/Configuration/TCA/tx_cartproducts_domain_model_product_bevariantattribute.php @@ -23,7 +23,6 @@ 'starttime' => 'starttime', 'endtime' => 'endtime', ], - 'searchFields' => 'title,value,calc,', 'iconfile' => 'EXT:cart_products/Resources/Public/Icons/Product/BeVariantAttribute.png', ], 'types' => [ diff --git a/Configuration/TCA/tx_cartproducts_domain_model_product_bevariantattributeoption.php b/Configuration/TCA/tx_cartproducts_domain_model_product_bevariantattributeoption.php index e86ca561..6cba7d90 100644 --- a/Configuration/TCA/tx_cartproducts_domain_model_product_bevariantattributeoption.php +++ b/Configuration/TCA/tx_cartproducts_domain_model_product_bevariantattributeoption.php @@ -24,7 +24,6 @@ 'starttime' => 'starttime', 'endtime' => 'endtime', ], - 'searchFields' => 'title,', 'iconfile' => 'EXT:cart_products/Resources/Public/Icons/Product/BeVariantAttributeOption.png', ], 'types' => [ diff --git a/Configuration/TCA/tx_cartproducts_domain_model_product_fevariant.php b/Configuration/TCA/tx_cartproducts_domain_model_product_fevariant.php index 4faeae69..ce19cc9f 100644 --- a/Configuration/TCA/tx_cartproducts_domain_model_product_fevariant.php +++ b/Configuration/TCA/tx_cartproducts_domain_model_product_fevariant.php @@ -12,6 +12,8 @@ 'tstamp' => 'tstamp', 'crdate' => 'crdate', + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', 'transOrigPointerField' => 'l10n_parent', 'transOrigDiffSourceField' => 'l10n_diffsource', @@ -22,7 +24,6 @@ 'starttime' => 'starttime', 'endtime' => 'endtime', ], - 'searchFields' => 'title', 'iconfile' => 'EXT:cart_products/Resources/Public/Icons/Product/FeVariant.png', ], 'types' => [ diff --git a/Configuration/TCA/tx_cartproducts_domain_model_product_product.php b/Configuration/TCA/tx_cartproducts_domain_model_product_product.php index f3e94d08..a743750d 100644 --- a/Configuration/TCA/tx_cartproducts_domain_model_product_product.php +++ b/Configuration/TCA/tx_cartproducts_domain_model_product_product.php @@ -30,7 +30,6 @@ 'starttime' => 'starttime', 'endtime' => 'endtime', ], - 'searchFields' => 'sku,title,teaser,description,price,', 'iconfile' => 'EXT:cart_products/Resources/Public/Icons/tx_cartproducts_domain_model_product_product.svg', ], 'types' => [ diff --git a/Configuration/TCA/tx_cartproducts_domain_model_product_quantitydiscount.php b/Configuration/TCA/tx_cartproducts_domain_model_product_quantitydiscount.php index 3930dc20..c809791f 100644 --- a/Configuration/TCA/tx_cartproducts_domain_model_product_quantitydiscount.php +++ b/Configuration/TCA/tx_cartproducts_domain_model_product_quantitydiscount.php @@ -22,7 +22,6 @@ 'disabled' => 'hidden', 'fe_group' => 'frontend_user_group', ], - 'searchFields' => 'price', 'iconfile' => 'EXT:cart_products/Resources/Public/Icons/Product/QuantityDiscount.png', ], 'types' => [ diff --git a/Configuration/TCA/tx_cartproducts_domain_model_product_specialprice.php b/Configuration/TCA/tx_cartproducts_domain_model_product_specialprice.php index 144d0017..4bbb6a52 100644 --- a/Configuration/TCA/tx_cartproducts_domain_model_product_specialprice.php +++ b/Configuration/TCA/tx_cartproducts_domain_model_product_specialprice.php @@ -27,7 +27,6 @@ 'endtime' => 'endtime', 'fe_group' => 'frontend_user_group', ], - 'searchFields' => 'price', 'iconfile' => 'EXT:cart_products/Resources/Public/Icons/Product/SpecialPrice.png', ], 'types' => [ diff --git a/Tests/Unit/Domain/Model/CategoryTest.php b/Tests/Unit/Domain/Model/CategoryTest.php index 5f358532..271e93e9 100644 --- a/Tests/Unit/Domain/Model/CategoryTest.php +++ b/Tests/Unit/Domain/Model/CategoryTest.php @@ -4,94 +4,23 @@ use Extcode\CartProducts\Domain\Model\Category; use PHPUnit\Framework\Attributes\Test; -use PHPUnit\Framework\MockObject\MockObject; -use TYPO3\TestingFramework\Core\AccessibleObjectInterface; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; class CategoryTest extends UnitTestCase { - /** - * @var Category - */ - protected $category; - - protected function setUp(): void - { - $this->category = new Category(); - } - - protected function tearDown(): void - { - unset($this->category); - } - #[Test] public function getCartProductShowPidReturnsShowPid(): void { $cartProductShowPid = 123; - $category = $this->getAccessibleMock( - Category::class, - ['dummy'], - [], - '', - false - ); - - $category->_set('cartProductShowPid', $cartProductShowPid); + $category = new Category(); + $reflection = new \ReflectionClass($category); + $property = $reflection->getProperty('cartProductShowPid'); + $property->setValue($category, $cartProductShowPid); self::assertEquals( $cartProductShowPid, $category->getCartProductShowPid() ); } - - /** - * Creates a mock object which allows for calling protected methods and access of protected properties. - * - * Note: This method has no native return types on purpose, but only PHPDoc return type annotations. - * The reason is that the combination of "union types with generics in PHPDoc" and "a subset of those types as - * native types, but without the generics" tends to confuse PhpStorm's static type analysis (which we want to avoid). - * - * @template T of object - * @param class-string $originalClassName name of class to create the mock object of - * @param string[]|null $methods name of the methods to mock, null for "mock no methods" - * @param array $arguments arguments to pass to constructor - * @param string $mockClassName the class name to use for the mock class - * @param bool $callOriginalConstructor whether to call the constructor - * @param bool $callOriginalClone whether to call the __clone method - * @param bool $callAutoload whether to call any autoload function - * - * @return MockObject&AccessibleObjectInterface&T a mock of `$originalClassName` with access methods added - * - * @throws \InvalidArgumentException - */ - protected function getAccessibleMock( - string $originalClassName, - ?array $methods = [], - array $arguments = [], - string $mockClassName = '', - bool $callOriginalConstructor = true, - bool $callOriginalClone = true, - bool $callAutoload = true - ) { - $mockBuilder = $this->getMockBuilder($this->buildAccessibleProxy($originalClassName)) - ->addMethods($methods) - ->setConstructorArgs($arguments) - ->setMockClassName($mockClassName); - - if (!$callOriginalConstructor) { - $mockBuilder->disableOriginalConstructor(); - } - - if (!$callOriginalClone) { - $mockBuilder->disableOriginalClone(); - } - - if (!$callAutoload) { - $mockBuilder->disableAutoload(); - } - - return $mockBuilder->getMock(); - } } diff --git a/Tests/Unit/Domain/Model/Product/BeVariantAttributeTest.php b/Tests/Unit/Domain/Model/Product/BeVariantAttributeTest.php index 9512eca5..564cd91e 100644 --- a/Tests/Unit/Domain/Model/Product/BeVariantAttributeTest.php +++ b/Tests/Unit/Domain/Model/Product/BeVariantAttributeTest.php @@ -10,19 +10,13 @@ class BeVariantAttributeTest extends UnitTestCase { - /** - * @var BeVariantAttribute - */ - protected $beVariantAttribute; + private BeVariantAttribute $beVariantAttribute; public function setUp(): void { - $this->beVariantAttribute = new BeVariantAttribute(); - } + parent::setUp(); - public function tearDown(): void - { - unset($this->beVariantAttribute); + $this->beVariantAttribute = new BeVariantAttribute(); } #[Test] diff --git a/Tests/Unit/Domain/Model/Product/BeVariantTest.php b/Tests/Unit/Domain/Model/Product/BeVariantTest.php index 97e4132a..a5a06e8a 100644 --- a/Tests/Unit/Domain/Model/Product/BeVariantTest.php +++ b/Tests/Unit/Domain/Model/Product/BeVariantTest.php @@ -13,19 +13,13 @@ class BeVariantTest extends UnitTestCase { - /** - * @var BeVariant - */ - protected $beVariant; + private BeVariant $beVariant; public function setUp(): void { - $this->beVariant = new BeVariant(); - } + parent::setUp(); - public function tearDown(): void - { - unset($this->beVariant); + $this->beVariant = new BeVariant(); } #[Test] @@ -138,6 +132,7 @@ public function setPriceSetsPrice(): void #[Test] public function getSpecialPricesInitiallyReturnsEmptyObjectStorage(): void { + // @phpstan-ignore staticMethod.alreadyNarrowedType self::assertInstanceOf( ObjectStorage::class, $this->beVariant->getSpecialPrices() @@ -280,7 +275,7 @@ public function getBestPriceWithSpecialPriceAndDifferentPriceCalcMethodsReturnsB $specialPriceObj = $this->createMock( SpecialPrice::class ); - $specialPriceObj->expects(self::any())->method('getPrice')->willReturn($specialPrice); + $specialPriceObj->method('getPrice')->willReturn($specialPrice); $this->beVariant->setPrice($price); $this->beVariant->setPriceCalcMethod($priceCalcMethod); @@ -327,7 +322,7 @@ public function getBestPriceCalculatedWithPriceCalcMethod0ReturnsPrice( $specialPriceObj = $this->createMock( SpecialPrice::class ); - $specialPriceObj->expects(self::any())->method('getPrice')->willReturn($specialPrice); + $specialPriceObj->method('getPrice')->willReturn($specialPrice); $this->beVariant->setPrice($price); $this->beVariant->setPriceCalcMethod($priceCalcMethod); diff --git a/Tests/Unit/Domain/Model/Product/FeVariantTest.php b/Tests/Unit/Domain/Model/Product/FeVariantTest.php index 43243412..76e2fc61 100644 --- a/Tests/Unit/Domain/Model/Product/FeVariantTest.php +++ b/Tests/Unit/Domain/Model/Product/FeVariantTest.php @@ -8,19 +8,13 @@ class FeVariantTest extends UnitTestCase { - /** - * @var FeVariant - */ - protected $feVariant; + private FeVariant $feVariant; public function setUp(): void { - $this->feVariant = new FeVariant(); - } + parent::setUp(); - public function tearDown(): void - { - unset($this->feVariant); + $this->feVariant = new FeVariant(); } #[Test] diff --git a/Tests/Unit/Domain/Model/Product/ProductTest.php b/Tests/Unit/Domain/Model/Product/ProductTest.php index f7e23e61..260b13ad 100644 --- a/Tests/Unit/Domain/Model/Product/ProductTest.php +++ b/Tests/Unit/Domain/Model/Product/ProductTest.php @@ -13,19 +13,13 @@ class ProductTest extends UnitTestCase { - /** - * @var Product - */ - protected $product; + private Product $product; protected function setUp(): void { - $this->product = new Product(); - } + parent::setUp(); - protected function tearDown(): void - { - unset($this->product); + $this->product = new Product(); } /** @@ -504,7 +498,7 @@ public function isAvailableWithHandleStockAndHandleStockInVariantsIsEnabledAndBa $productBackendVariant = $this->createMock( BeVariant::class ); - $productBackendVariant->expects(self::any())->method('getIsAvailable')->willReturn(false); + $productBackendVariant->method('getIsAvailable')->willReturn(false); $product = new Product(); $product->addBeVariant($productBackendVariant); @@ -523,7 +517,7 @@ public function isAvailableWithHandleStockAndHandleStockInVariantsIsEnabledAndBa $productBackendVariant = $this->createMock( BeVariant::class ); - $productBackendVariant->expects(self::any())->method('getIsAvailable')->willReturn(true); + $productBackendVariant->method('getIsAvailable')->willReturn(true); $product = new Product(); $product->addBeVariant($productBackendVariant); diff --git a/Tests/Unit/Domain/Model/Product/QuantityDiscountTest.php b/Tests/Unit/Domain/Model/Product/QuantityDiscountTest.php index b7e545e0..41b8f088 100644 --- a/Tests/Unit/Domain/Model/Product/QuantityDiscountTest.php +++ b/Tests/Unit/Domain/Model/Product/QuantityDiscountTest.php @@ -8,19 +8,13 @@ class QuantityDiscountTest extends UnitTestCase { - /** - * @var QuantityDiscount - */ - protected $quantityDiscount; + private QuantityDiscount $quantityDiscount; public function setUp(): void { - $this->quantityDiscount = new QuantityDiscount(); - } + parent::setUp(); - public function tearDown(): void - { - unset($this->quantityDiscount); + $this->quantityDiscount = new QuantityDiscount(); } #[Test] diff --git a/Tests/Unit/Domain/Model/Product/SpecialPriceTest.php b/Tests/Unit/Domain/Model/Product/SpecialPriceTest.php index bdaa42bb..98ae35d8 100644 --- a/Tests/Unit/Domain/Model/Product/SpecialPriceTest.php +++ b/Tests/Unit/Domain/Model/Product/SpecialPriceTest.php @@ -8,19 +8,13 @@ class SpecialPriceTest extends UnitTestCase { - /** - * @var SpecialPrice - */ - protected $specialPrice; + private SpecialPrice $specialPrice; public function setUp(): void { - $this->specialPrice = new SpecialPrice(); - } + parent::setUp(); - public function tearDown(): void - { - unset($this->specialPrice); + $this->specialPrice = new SpecialPrice(); } #[Test] diff --git a/composer.json b/composer.json index b50ad326..d5c92fda 100644 --- a/composer.json +++ b/composer.json @@ -33,25 +33,31 @@ } }, "config": { + "bin-dir": ".build/bin", + "vendor-dir": ".build/vendor", "allow-plugins": { "typo3/cms-composer-installers": true, "typo3/class-alias-loader": true, - "sbuerk/typo3-cmscomposerinstallers-testingframework-bridge": true + "sbuerk/typo3-cmscomposerinstallers-testingframework-bridge": true, + "phpstan/extension-installer": true } }, "extra": { "typo3/cms": { "extension-key": "cart_products", - "web-dir": ".build/web" + "web-dir": ".build/public" } }, + "version": "8.0.0", + "minimum-stability": "dev", + "prefer-stable": true, "require": { "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "ext-pdo": "*", - "extcode/cart": "^11.0", - "typo3/cms-core": "^13.4", - "typo3/cms-extbase": "^13.4", - "typo3/cms-fluid": "^13.4" + "extcode/cart": "dev-12.x-dev as 12.0.0", + "typo3/cms-core": "^14.1", + "typo3/cms-extbase": "^14.1", + "typo3/cms-fluid": "^14.1" }, "require-dev": { "codappix/typo3-php-datasets": "^2.0", @@ -60,54 +66,19 @@ "codeception/module-webdriver": "^4.0", "friendsofphp/php-cs-fixer": "^3.64", "helmich/typo3-typoscript-lint": "^3.1", - "phpstan/phpstan": "^1.12", - "typo3/cms-dashboard": "^13.4", - "typo3/cms-fluid-styled-content": "^13.4", - "typo3/cms-install": "^13.4", - "typo3/cms-reactions": "^13.4", - "typo3/testing-framework": "^8.2", - "werkraummedia/watchlist": "^3.0" - }, - "scripts": { - "test:cgl": [ - "vendor/bin/php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php -v --using-cache=no --path-mode=intersection ./" - ], - "test:cgl:dry-run": [ - "vendor/bin/php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php -v --dry-run --using-cache=no --path-mode=intersection ./" - ], - "test:php:lint": [ - "find *.php Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l" - ], - "test:php:unit": [ - "vendor/bin/phpunit -c Build/phpunit.xml.dist --testsuite unit --display-warnings --display-deprecations --display-errors" - ], - "test:php:functional": [ - "typo3DatabaseDriver=\"pdo_sqlite\" vendor/bin/phpunit -c Build/phpunit.xml.dist --testsuite functional --display-warnings --display-deprecations --display-errors" - ], - "test:phpstan:analyse": [ - "vendor/bin/phpstan analyse -c Build/phpstan.neon --memory-limit 256M" - ], - "test:rector:process": [ - "vendor/bin/rector process *" - ], - "test:rector:process:dry-run": [ - "vendor/bin/rector process * --dry-run" - ], - "test:typoscript:lint": [ - "vendor/bin/typoscript-lint -c Build/typoscriptlint.yaml Configuration" - ], - "test:php": [ - "@test:php:lint", - "@test:php:unit", - "@test:php:functional" - ], - "test:all": [ - "@test:phpstan:analyse", - "@test:rector:process", - "@test:cgl", - "@test:typoscript:lint", - "@test:php" - ] + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "spaze/phpstan-disallowed-calls": "^4.7", + "staabm/phpstan-todo-by": "^0.3", + "typo3/cms-dashboard": "^14.1", + "typo3/cms-fluid-styled-content": "^14.1", + "typo3/cms-install": "^14.1", + "typo3/cms-reactions": "^14.1", + "typo3/testing-framework": "^9.3", + "werkraummedia/watchlist": "^3.0", + "ssch/typo3-rector": "^3.11" }, "suggest": { "werkraummedia/watchlist": "^3.0" diff --git a/rector.php b/rector.php index 237c9f3d..de36f697 100644 --- a/rector.php +++ b/rector.php @@ -2,12 +2,13 @@ declare(strict_types=1); +use Rector\CodeQuality\Rector\ClassMethod\OptionalParametersAfterRequiredRector; use Rector\Config\RectorConfig; use Rector\PostRector\Rector\NameImportingPostRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector; use Rector\ValueObject\PhpVersion; -use Ssch\TYPO3Rector\CodeQuality\General\ConvertImplicitVariablesToExplicitGlobalsRector; use Ssch\TYPO3Rector\CodeQuality\General\ExtEmConfRector; +use Ssch\TYPO3Rector\CodeQuality\General\InjectMethodToConstructorInjectionRector; use Ssch\TYPO3Rector\Configuration\Typo3Option; use Ssch\TYPO3Rector\Set\Typo3LevelSetList; use Ssch\TYPO3Rector\Set\Typo3SetList; @@ -22,7 +23,7 @@ __DIR__ . '/ext_localconf.php', ]) // uncomment to reach your current PHP version - ->withPhpSets(php82: true) + ->withPhpSets(php82: true) ->withPhpVersion(PhpVersion::PHP_82) ->withSets([ Typo3SetList::CODE_QUALITY, @@ -35,18 +36,20 @@ ]) ->withRules([ AddVoidReturnTypeWhereNoReturnRector::class, - ConvertImplicitVariablesToExplicitGlobalsRector::class, ]) ->withConfiguredRule(ExtEmConfRector::class, [ - ExtEmConfRector::PHP_VERSION_CONSTRAINT => '8.2.0-8.3.99', + ExtEmConfRector::PHP_VERSION_CONSTRAINT => '8.2.0-8.4.99', ExtEmConfRector::TYPO3_VERSION_CONSTRAINT => '13.4.0-13.4.99', ExtEmConfRector::ADDITIONAL_VALUES_TO_BE_REMOVED => [], ]) // If you use withImportNames(), you should consider excluding some TYPO3 files. ->withSkip([ - 'Classes/Updates/SlugUpdater.php', + // @see https://github.com/sabbelasichon/typo3-rector/issues/2536 + __DIR__ . '/**/Configuration/ExtensionBuilder/*', NameImportingPostRector::class => [ 'ClassAliasMap.php', ], + InjectMethodToConstructorInjectionRector::class, + OptionalParametersAfterRequiredRector::class, ]) ; diff --git a/shell.nix b/shell.nix index 90b90dd3..c5e56513 100644 --- a/shell.nix +++ b/shell.nix @@ -23,7 +23,7 @@ let composer ]; text = '' - rm -rf .Build/ vendor/ composer.lock + rm -rf .Build/ .build/ composer.lock composer update --prefer-dist --no-progress --working-dir="$PROJECT_ROOT" ''; }; @@ -36,7 +36,7 @@ let ]; text = '' - ./vendor/bin/php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php -v --dry-run --diff + ./.build/bin/php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php -v --dry-run --diff ''; }; @@ -48,7 +48,7 @@ let ]; text = '' - ./vendor/bin/php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php + ./.build/bin/php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php ''; }; @@ -72,7 +72,7 @@ let ]; text = '' - ./vendor/bin/phpstan analyse -c Build/phpstan.neon --memory-limit 256M + ./.build/bin/phpstan analyse -c Build/phpstan.neon --memory-limit 256M ''; }; @@ -84,7 +84,7 @@ let ]; text = '' project-install - ./vendor/bin/phpunit -c Build/phpunit.xml.dist --testsuite unit --display-warnings --display-deprecations --display-errors + ./.build/bin/phpunit -c Build/phpunit.xml.dist --testsuite unit --display-deprecations --display-warnings --display-errors ''; }; @@ -96,7 +96,7 @@ let ]; text = '' project-install - ./vendor/bin/phpunit -c Build/phpunit.xml.dist --testsuite functional --display-warnings --display-deprecations --display-errors + ./.build/bin/phpunit -c Build/phpunit.xml.dist --testsuite functional --display-deprecations --display-warnings --display-errors ''; }; @@ -108,7 +108,7 @@ let ]; text = '' project-install - XDEBUG_MODE=coverage ./vendor/bin/phpunit -c Build/phpunit.xml.dist --coverage-html=coverage_result + XDEBUG_MODE=coverage ./.build/bin/phpunit -c Build/phpunit.xml.dist --coverage-html=coverage_result ''; }; @@ -132,7 +132,7 @@ let export INSTANCE_PATH="$PROJECT_ROOT/.build/web/typo3temp/var/tests/acceptance" - ./vendor/bin/codecept run + ./.build/bin/codecept run pgrep -f "php -S" | xargs -r kill pgrep -f "geckodriver" | xargs -r kill From b48c51aacd496cab14fd7690c1955621352dfe1f Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Thu, 26 Mar 2026 10:01:56 +0100 Subject: [PATCH 02/12] [TASK] Remove own addCacheTags() method --- Classes/Controller/ProductController.php | 26 ------------------------ 1 file changed, 26 deletions(-) diff --git a/Classes/Controller/ProductController.php b/Classes/Controller/ProductController.php index 6e9f2f1e..758b83b9 100644 --- a/Classes/Controller/ProductController.php +++ b/Classes/Controller/ProductController.php @@ -51,16 +51,6 @@ protected function initializeAction(): void 'Cart' ); - if (!empty($GLOBALS['TSFE']) && is_object($GLOBALS['TSFE'])) { - static $cacheTagsSet = false; - - $typoScriptFrontendController = $GLOBALS['TSFE']; - if (!$cacheTagsSet) { - $typoScriptFrontendController->addCacheTags(['tx_cartproducts']); - $cacheTagsSet = true; - } - } - $this->settings['addToCartByAjax'] = isset($this->settings['addToCartByAjax']) ? (int)$this->settings['addToCartByAjax'] : 0; } @@ -178,7 +168,6 @@ public function listAction(int $currentPage = 1): ResponseInterface $this->assignCurrencyTranslationData(); - $this->addCacheTags($products); return $this->htmlResponse(); } @@ -194,7 +183,6 @@ public function showAction(?Product $product = null): ResponseInterface $this->assignCurrencyTranslationData(); - $this->addCacheTags([$product]); return $this->htmlResponse(); } @@ -220,7 +208,6 @@ public function teaserAction(): ResponseInterface $this->assignCurrencyTranslationData(); - $this->addCacheTags($products); return $this->htmlResponse(); } @@ -316,19 +303,6 @@ protected function assignCurrencyTranslationData() $this->view->assign('currencyTranslationData', $currencyTranslationData); } - protected function addCacheTags(iterable $products): void - { - $cacheTags = []; - - foreach ($products as $product) { - // cache tag for each product record - $cacheTags[] = 'tx_cartproducts_product_' . $product->getUid(); - } - if (count($cacheTags) > 0) { - $GLOBALS['TSFE']->addCacheTags($cacheTags); - } - } - protected function restoreSession(): void { $cart = $this->sessionHandler->restoreCart($this->cartConfiguration['settings']['cart']['pid']); From 664056e3242584ac0ee8479faa4bafa63a518292 Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Thu, 26 Mar 2026 10:48:31 +0100 Subject: [PATCH 03/12] [BUGFIX] Change path for acceptance tests --- .gitlab-ci.yml | 154 ------------------------------------------- codeception.dist.yml | 6 +- composer.json | 2 +- shell.nix | 10 +-- 4 files changed, 9 insertions(+), 163 deletions(-) delete mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 8ea0e3ca..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,154 +0,0 @@ -cache: - key: "$CI_JOB_STAGE-$CI_COMMIT_REF_NAME" - paths: - - .composer/ - - .php_cs.cache - -variables: - TYPO3_VERSION: ^13.4 - -before_script: - - apk add git --update - -stages: - - lint - - test - - documentation - -lint:cgl: - image: $CI_REGISTRY/containers/phpunit-with-php-8.2:main - stage: lint - before_script: - - composer remove typo3/cms-* --no-update - - composer install --no-progress --no-ansi --no-interaction - script: - - vendor/bin/php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php -v --dry-run --using-cache=no --path-mode=intersection ./ - - vendor/bin/typoscript-lint -c Build/typoscriptlint.yaml Configuration - -lint:yaml: - stage: lint - image: python:alpine3.7 - before_script: - - pip install yamllint==1.10.0 - script: - - yamllint -c Build/yamllint.yaml Configuration/ Resources/ - -.lint_php: &lint_php - stage: lint - image: $CONTAINER_IMAGE - script: - - find *.php Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l - -lint:php82: - <<: *lint_php - variables: - CONTAINER_IMAGE: php:8.2-alpine - -lint:php83: - <<: *lint_php - variables: - CONTAINER_IMAGE: php:8.3-alpine - -lint:php84: - <<: *lint_php - variables: - CONTAINER_IMAGE: php:8.4-alpine - -phpstan:analyse: - image: $CI_REGISTRY/containers/phpunit-with-php-8.2:main - stage: lint - before_script: - - sed -i -e "s#ssh://git@code.extco.de:22722#https://gitlab-ci-token:$CI_JOB_TOKEN@code.extco.de#g" composer.json - - composer config platform.php 8.2 - - composer install --no-progress --no-ansi --no-interaction - script: - - vendor/bin/codecept build - - vendor/bin/phpstan analyse -c Build/phpstan.neon --memory-limit 256M - -.test_php: &test_php - stage: test - services: - - mysql:5 - image: $CONTAINER_IMAGE - only: - - branches - before_script: - - composer config --no-plugins allow-plugins.typo3/cms-composer-installers true - - composer config --no-plugins allow-plugins.typo3/class-alias-loader true - - composer require typo3/cms-core="${TYPO3_VERSION}" - script: - - vendor/bin/phpunit -c Build/UnitTests.xml - - typo3DatabaseDriver=pdo_sqlite vendor/bin/phpunit -c Build/FunctionalTests.xml - -test:php82: - <<: *test_php - variables: - CONTAINER_IMAGE: $CI_REGISTRY/containers/phpunit-with-php-8.2:main - -test:php83: - <<: *test_php - variables: - CONTAINER_IMAGE: $CI_REGISTRY/containers/phpunit-with-php-8.3:main - -test:php84: - <<: *test_php - variables: - CONTAINER_IMAGE: $CI_REGISTRY/containers/phpunit-with-php-8.4:main - -.test_codeception: &test_codeception - stage: test - image: $CONTAINER_IMAGE - only: - - branches - before_script: - - sed -i -e "s#ssh://git@code.extco.de:22722#https://gitlab-ci-token:$CI_JOB_TOKEN@code.extco.de#g" composer.json - - composer config --no-plugins allow-plugins.typo3/cms-composer-installers true - - composer config --no-plugins allow-plugins.typo3/class-alias-loader true - - composer require typo3/cms-core="${TYPO3_VERSION}" - script: - - mkdir -p .build/public/typo3temp/var/tests/acceptance-sqlite-dbs - - export typo3DatabaseDriver=pdo_sqlite - - export PROJECT_ROOT="$(pwd)" - - export INSTANCE_PATH="$(pwd)/.build/web/typo3temp/var/tests/acceptance" - - mkdir -p "$INSTANCE_PATH" - - mkdir -p "$PROJECT_ROOT/.build/web/typo3temp/var/tests/acceptance-logs/" - - vendor/bin/codecept run - artifacts: - paths: - - .build - expire_in: 1 day - when: always - -codeception:php82: - <<: *test_codeception - variables: - CONTAINER_IMAGE: $CI_REGISTRY/containers/codeception-with-php-8.2:main - -codeception:php83: - <<: *test_codeception - needs: - - codeception:php82 - variables: - CONTAINER_IMAGE: $CI_REGISTRY/containers/codeception-with-php-8.3:main - -codeception:php84: - <<: *test_codeception - needs: - - codeception:php83 - variables: - CONTAINER_IMAGE: $CI_REGISTRY/containers/codeception-with-php-8.4:main - -documentation: - stage: documentation - image: - name: ghcr.io/typo3-documentation/render-guides:latest - entrypoint: [ "" ] - script: - - mkdir -p Documentation-GENERATED-temp - - /opt/guides/entrypoint.sh --config=Documentation --no-progress --fail-on-log - before_script: [] - artifacts: - paths: - - Documentation-GENERATED-temp/ - expire_in: 1 day - when: always diff --git a/codeception.dist.yml b/codeception.dist.yml index f397d77a..8781f87d 100644 --- a/codeception.dist.yml +++ b/codeception.dist.yml @@ -3,7 +3,7 @@ namespace: 'Extcode\CartProducts\Tests\Acceptance\Support' paths: tests: 'Tests/Acceptance' data: 'Tests/Acceptance/Data' - output: '.build/web/typo3temp/var/tests/acceptance-reports' + output: '.build/public/typo3temp/var/tests/acceptance-reports' support: 'Tests/Acceptance/Support' settings: @@ -13,8 +13,8 @@ extensions: enabled: - 'Codeception\Extension\RunProcess': - - 'geckodriver > .build/web/typo3temp/var/tests/acceptance-logs/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/acceptance-logs/php.log 2>&1' + - 'geckodriver > .build/public/typo3temp/var/tests/acceptance-logs/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/public/typo3temp/var/tests/acceptance-logs/php.log 2>&1' - 'Codeception\Extension\Recorder' - diff --git a/composer.json b/composer.json index d5c92fda..84082546 100644 --- a/composer.json +++ b/composer.json @@ -78,7 +78,7 @@ "typo3/cms-reactions": "^14.1", "typo3/testing-framework": "^9.3", "werkraummedia/watchlist": "^3.0", - "ssch/typo3-rector": "^3.11" + "ssch/typo3-rector": "^3.11" }, "suggest": { "werkraummedia/watchlist": "^3.0" diff --git a/shell.nix b/shell.nix index c5e56513..b5d24d82 100644 --- a/shell.nix +++ b/shell.nix @@ -125,12 +125,12 @@ let text = '' project-install - mkdir -p "$PROJECT_ROOT/.build/web/typo3temp/var/tests/acceptance" - mkdir -p "$PROJECT_ROOT/.build/web/typo3temp/var/tests/acceptance-logs" - mkdir -p "$PROJECT_ROOT/.build/web/typo3temp/var/tests/acceptance-reports" - mkdir -p "$PROJECT_ROOT/.build/web/typo3temp/var/tests/acceptance-sqlite-dbs" + mkdir -p "$PROJECT_ROOT/.build/public/typo3temp/var/tests/acceptance" + mkdir -p "$PROJECT_ROOT/.build/public/typo3temp/var/tests/acceptance-logs" + mkdir -p "$PROJECT_ROOT/.build/public/typo3temp/var/tests/acceptance-reports" + mkdir -p "$PROJECT_ROOT/.build/public/typo3temp/var/tests/acceptance-sqlite-dbs" - export INSTANCE_PATH="$PROJECT_ROOT/.build/web/typo3temp/var/tests/acceptance" + export INSTANCE_PATH="$PROJECT_ROOT/.build/public/typo3temp/var/tests/acceptance" ./.build/bin/codecept run From 99f0051d4bfbac71a945976ad6a3210f56354f76 Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Thu, 26 Mar 2026 12:21:19 +0100 Subject: [PATCH 04/12] [BUGFIX] Use longer cHash in links for acceptance tests --- ...figurableProductWithStockHandlingToCartCest.php | 6 +++--- ...urableProductWithoutStockHandlingToCartCest.php | 2 +- ...AddSimpleProductWithStockHandlingToCartCest.php | 6 +++--- Tests/Acceptance/ProductListCest.php | 14 +++++++------- Tests/Acceptance/ProductTeaserCest.php | 14 +++++++------- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Tests/Acceptance/AddConfigurableProductWithStockHandlingToCartCest.php b/Tests/Acceptance/AddConfigurableProductWithStockHandlingToCartCest.php index d2fa9aa3..dbc437b5 100644 --- a/Tests/Acceptance/AddConfigurableProductWithStockHandlingToCartCest.php +++ b/Tests/Acceptance/AddConfigurableProductWithStockHandlingToCartCest.php @@ -17,7 +17,7 @@ class AddConfigurableProductWithStockHandlingToCartCest { public function testAddDifferentItemsWithinAvailableAmountToCart(Tester $I): void { - $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=6&cHash=93520e7d2c5c85e6563ce0e5b8eba102'); + $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=6&cHash=91f0b865a7cd0161b2aed04e57831294d4874e03da49948d27b9a92f936f7440'); $I->see('Configurable Product 2', 'h1'); $I->see(' ', '#product-price .in-stock'); @@ -61,7 +61,7 @@ public function testAddDifferentItemsWithinAvailableAmountToCart(Tester $I): voi public function testAddMoreItemsThanInStockOfASimpleProductToCart(Tester $I): void { - $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=6&cHash=93520e7d2c5c85e6563ce0e5b8eba102'); + $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=6&cHash=91f0b865a7cd0161b2aed04e57831294d4874e03da49948d27b9a92f936f7440'); $I->see('Configurable Product 2', 'h1'); $I->see(' ', '#product-price .in-stock'); @@ -83,7 +83,7 @@ public function testAddMoreItemsThanInStockOfASimpleProductToCart(Tester $I): vo public function testAddOneAndThanMoreItemsThanInStockOfASimpleProductToCart(Tester $I): void { - $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=6&cHash=93520e7d2c5c85e6563ce0e5b8eba102'); + $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=6&cHash=91f0b865a7cd0161b2aed04e57831294d4874e03da49948d27b9a92f936f7440'); $I->see('Configurable Product 2', 'h1'); $I->see(' ', '#product-price .in-stock'); diff --git a/Tests/Acceptance/AddConfigurableProductWithoutStockHandlingToCartCest.php b/Tests/Acceptance/AddConfigurableProductWithoutStockHandlingToCartCest.php index bb28676b..b4200fb8 100644 --- a/Tests/Acceptance/AddConfigurableProductWithoutStockHandlingToCartCest.php +++ b/Tests/Acceptance/AddConfigurableProductWithoutStockHandlingToCartCest.php @@ -17,7 +17,7 @@ class AddConfigurableProductWithoutStockHandlingToCartCest { public function testAddDifferentItemsToCart(Tester $I): void { - $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=5&cHash=4c2d7ef7c3ec394907ad93a1a0434fc8'); + $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=5&cHash=31a347dca803764112a31aa21b85c7e9ddd124f69df6b8d3739fb9d431509bfc'); $I->see('Configurable Product 1', 'h1'); $I->dontSeeElement('#product-price .in-stock'); diff --git a/Tests/Acceptance/AddSimpleProductWithStockHandlingToCartCest.php b/Tests/Acceptance/AddSimpleProductWithStockHandlingToCartCest.php index b0e2ae20..c58a1413 100644 --- a/Tests/Acceptance/AddSimpleProductWithStockHandlingToCartCest.php +++ b/Tests/Acceptance/AddSimpleProductWithStockHandlingToCartCest.php @@ -17,7 +17,7 @@ class AddSimpleProductWithStockHandlingToCartCest { public function testAddDifferentItemsWithinAvailableAmountToCart(Tester $I): void { - $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=1&cHash=275cce22d935c04473314c31f46f7ada'); + $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=1&cHash=a0592042d989ca64fc9bb8593502c30cbcd458a2a4d415dcb75a701227213400'); $I->see('Simple Product 1', 'h1'); $I->see('10', '#product-price .in-stock'); @@ -46,7 +46,7 @@ public function testAddDifferentItemsWithinAvailableAmountToCart(Tester $I): voi public function testAddMoreItemsThanInStockOfASimpleProductToCart(Tester $I): void { - $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=1&cHash=275cce22d935c04473314c31f46f7ada'); + $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=1&cHash=a0592042d989ca64fc9bb8593502c30cbcd458a2a4d415dcb75a701227213400'); $I->see('Simple Product 1', 'h1'); $I->see('10', '#product-price .in-stock'); @@ -66,7 +66,7 @@ public function testAddMoreItemsThanInStockOfASimpleProductToCart(Tester $I): vo public function testAddOneAndThanMoreItemsThanInStockOfASimpleProductToCart(Tester $I): void { - $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=1&cHash=275cce22d935c04473314c31f46f7ada'); + $I->amOnUrl('http://127.0.0.1:8080/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=1&cHash=a0592042d989ca64fc9bb8593502c30cbcd458a2a4d415dcb75a701227213400'); $I->see('Simple Product 1', 'h1'); $I->see('10', '#product-price .in-stock'); diff --git a/Tests/Acceptance/ProductListCest.php b/Tests/Acceptance/ProductListCest.php index fd09df02..cc503c2b 100644 --- a/Tests/Acceptance/ProductListCest.php +++ b/Tests/Acceptance/ProductListCest.php @@ -22,11 +22,11 @@ public function testProductListAndDetailViewForSimpleProducts(Tester $I): void { $I->amOnUrl('http://127.0.0.1:8080/products/'); - $I->seeLink('Simple Product 1', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=1&cHash=275cce22d935c04473314c31f46f7ada'); + $I->seeLink('Simple Product 1', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=1&cHash=a0592042d989ca64fc9bb8593502c30cbcd458a2a4d415dcb75a701227213400'); $I->see('9,99 €'); - $I->seeLink('Simple Product 2', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=2&cHash=36af38e6c5bbac81dcd80c3a3c53f4a1'); + $I->seeLink('Simple Product 2', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=2&cHash=65f2e92995fa7df0eae115ab9767534305e54b973f7ee988c8fa8818eaf8801e'); $I->see('19,99 €'); - $I->seeLink('Simple Product 3', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=3&cHash=499063e1c85e8b41785e04d33a20e2db'); + $I->seeLink('Simple Product 3', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=3&cHash=0efa57e1d49c9fe19f480b426201ad0c4506ff9f63359448f42475df08b10aa0'); $I->see('29,99 €'); $I->dontSee('Simple Product 4'); @@ -43,14 +43,14 @@ public function testRelatedProductLinksOnListPluginDetailView(Tester $I): void $I->click('Simple Product 1'); $I->see('Simple Product 1', 'h1'); - $I->seeLink('Simple Product 2', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=2&cHash=36af38e6c5bbac81dcd80c3a3c53f4a1'); + $I->seeLink('Simple Product 2', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=2&cHash=65f2e92995fa7df0eae115ab9767534305e54b973f7ee988c8fa8818eaf8801e'); $I->dontSee('Simple Product 3', 'a'); $I->seeLink('Simple Product With Detail Page', '/detail-page-for-simple-product-with-detail-page'); $I->click('Simple Product With Detail Page'); $I->see('Simple Product With Detail Page', 'h1'); - $I->seeLink('Simple Product 1', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=1&cHash=275cce22d935c04473314c31f46f7ada'); + $I->seeLink('Simple Product 1', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=1&cHash=a0592042d989ca64fc9bb8593502c30cbcd458a2a4d415dcb75a701227213400'); $I->dontSee('Simple Product 2', 'a'); $I->dontSee('Simple Product 3', 'a'); @@ -59,9 +59,9 @@ public function testRelatedProductLinksOnListPluginDetailView(Tester $I): void public function testProductListAndDetailViewForConfigurableProducts(Tester $I): void { $I->amOnUrl('http://127.0.0.1:8080/products/'); - $I->seeLink('Configurable Product 1', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=5&cHash=4c2d7ef7c3ec394907ad93a1a0434fc8'); + $I->seeLink('Configurable Product 1', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=5&cHash=31a347dca803764112a31aa21b85c7e9ddd124f69df6b8d3739fb9d431509bfc'); $I->see('99,99 €'); - $I->seeLink('Configurable Product 2', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=6&cHash=93520e7d2c5c85e6563ce0e5b8eba102'); + $I->seeLink('Configurable Product 2', '/product?tx_cartproducts_showproduct%5Baction%5D=show&tx_cartproducts_showproduct%5Bcontroller%5D=Product&tx_cartproducts_showproduct%5Bproduct%5D=6&cHash=91f0b865a7cd0161b2aed04e57831294d4874e03da49948d27b9a92f936f7440'); $I->see('149,49 €'); $I->click('Configurable Product 1'); diff --git a/Tests/Acceptance/ProductTeaserCest.php b/Tests/Acceptance/ProductTeaserCest.php index 99cac0c8..dbaae34f 100644 --- a/Tests/Acceptance/ProductTeaserCest.php +++ b/Tests/Acceptance/ProductTeaserCest.php @@ -17,13 +17,13 @@ public function testProductTeaserViewForSimpleProducts(Tester $I): void { $I->amOnUrl('http://127.0.0.1:8080/product-teaser/'); - $I->seeLink('Simple Product 1', '/product-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=1&cHash=dab405401f299c9d90714d1f82893541'); + $I->seeLink('Simple Product 1', '/product-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=1&cHash=058c3485d31e466bb10915666308ba082ad838b4c8c3bf8b31168c29106f5add'); $I->see('9,99 €'); - $I->seeLink('Simple Product 2', '/product-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=2&cHash=d613d866189744dc46ff2a6be165b328'); + $I->seeLink('Simple Product 2', '/product-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=2&cHash=702e6da32eafeb6d0ecb7849f659005ffecf5b6fa321ea6db1f9332730cdf3b6'); $I->see('19,99 €'); - $I->seeLink('Simple Product 5', '/product-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=10&cHash=615b860ea077f52b0effad2348512845'); + $I->seeLink('Simple Product 5', '/product-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=10&cHash=cddbcf233521c83c6d0481d8f753afae8de0db9f02295d328c4286a3369cb2a9'); $I->see('29,99 €'); - $I->seeLink('Simple Product 3', '/product-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=3&cHash=672f872da3eb11fdbb9acd7e7d1c827a'); + $I->seeLink('Simple Product 3', '/product-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=3&cHash=688c9b64a005db1b72153619c3b66f33735297648de046c338c851b13e9b6cd8'); $I->see('29,99 €'); $I->seeAboveInPageSource('Simple Product 1', 'Simple Product 2'); @@ -35,11 +35,11 @@ public function testTranslatedProductTeaserViewForSimpleProducts(Tester $I): voi { $I->amOnUrl('http://127.0.0.1:8080/de/produkt-teaser/'); - $I->seeLink('Einfaches Produkt 1', '/de/produkt-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=1&cHash=dab405401f299c9d90714d1f82893541'); + $I->seeLink('Einfaches Produkt 1', '/de/produkt-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=1&cHash=058c3485d31e466bb10915666308ba082ad838b4c8c3bf8b31168c29106f5add'); $I->see('9,99 €'); - $I->seeLink('Einfaches Produkt 5', '/de/produkt-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=10&cHash=615b860ea077f52b0effad2348512845'); + $I->seeLink('Einfaches Produkt 5', '/de/produkt-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=10&cHash=cddbcf233521c83c6d0481d8f753afae8de0db9f02295d328c4286a3369cb2a9'); $I->see('29,99 €'); - $I->seeLink('Einfaches Produkt 3', '/de/produkt-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=3&cHash=672f872da3eb11fdbb9acd7e7d1c827a'); + $I->seeLink('Einfaches Produkt 3', '/de/produkt-teaser?tx_cartproducts_teaserproducts%5Baction%5D=show&tx_cartproducts_teaserproducts%5Bcontroller%5D=Product&tx_cartproducts_teaserproducts%5Bproduct%5D=3&cHash=688c9b64a005db1b72153619c3b66f33735297648de046c338c851b13e9b6cd8'); $I->see('29,99 €'); $I->seeAboveInPageSource('Einfaches Produkt 1', 'Einfaches Produkt 5'); From a87a88b8172e3adb28b3b3768108436be6a8978c Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Fri, 10 Apr 2026 21:36:53 +0200 Subject: [PATCH 05/12] [TASK] Change prices in database to fixed-point numbers --- Build/phpstan.neon | 1 - ext_tables.sql | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Build/phpstan.neon b/Build/phpstan.neon index f9aa6f76..2e5fd5a0 100644 --- a/Build/phpstan.neon +++ b/Build/phpstan.neon @@ -4,7 +4,6 @@ parameters: - ../Classes - ../Configuration - ../Tests - - ../ext_emconf.php - ../ext_localconf.php excludePaths: - ../Tests/Acceptance/Support/_generated diff --git a/ext_tables.sql b/ext_tables.sql index b220b609..4e102014 100644 --- a/ext_tables.sql +++ b/ext_tables.sql @@ -17,18 +17,18 @@ CREATE TABLE tx_cartproducts_domain_model_product_product ( min_number_in_order int(11) unsigned DEFAULT '0' NOT NULL, max_number_in_order int(11) unsigned DEFAULT '0' NOT NULL, - price double(11,2) DEFAULT '0.00' NOT NULL, + price decimal(17,5) DEFAULT '0.00' NOT NULL, is_net_price tinyint(4) unsigned DEFAULT '0' NOT NULL, special_prices int(11) unsigned DEFAULT '0' NOT NULL, quantity_discounts int(11) unsigned DEFAULT '0' NOT NULL, - price_measure double(11,2) DEFAULT '0.00' NOT NULL, + price_measure decimal(17,5) DEFAULT '0.00' NOT NULL, price_measure_unit varchar(8) DEFAULT '' NOT NULL, - base_price_measure double(11,2) DEFAULT '0.00' NOT NULL, + base_price_measure decimal(17,5) DEFAULT '0.00' NOT NULL, base_price_measure_unit varchar(8) DEFAULT '' NOT NULL, - service_attribute1 double(11,2) DEFAULT '0.00' NOT NULL, - service_attribute2 double(11,2) DEFAULT '0.00' NOT NULL, - service_attribute3 double(11,2) DEFAULT '0.00' NOT NULL, + service_attribute1 decimal(17,5) DEFAULT '0.00' NOT NULL, + service_attribute2 decimal(17,5) DEFAULT '0.00' NOT NULL, + service_attribute3 decimal(17,5) DEFAULT '0.00' NOT NULL, handle_stock tinyint(4) unsigned DEFAULT '0' NOT NULL, handle_stock_in_variants tinyint(4) unsigned DEFAULT '0' NOT NULL, @@ -99,7 +99,7 @@ CREATE TABLE tx_cartproducts_domain_model_product_specialprice ( frontend_user_group int(11) unsigned DEFAULT '0' NOT NULL, - price double(11,2) DEFAULT '0.00' NOT NULL, + price decimal(17,5) DEFAULT '0.00' NOT NULL, tstamp int(11) unsigned DEFAULT '0' NOT NULL, crdate int(11) unsigned DEFAULT '0' NOT NULL, @@ -140,7 +140,7 @@ CREATE TABLE tx_cartproducts_domain_model_product_quantitydiscount ( frontend_user_group int(11) unsigned DEFAULT '0' NOT NULL, - price double(11,2) DEFAULT '0.00' NOT NULL, + price decimal(17,5) DEFAULT '0.00' NOT NULL, quantity int(11) unsigned DEFAULT '0' NOT NULL, tstamp int(11) unsigned DEFAULT '0' NOT NULL, @@ -304,12 +304,12 @@ CREATE TABLE tx_cartproducts_domain_model_product_bevariant ( be_variant_attribute_option2 int(11) unsigned DEFAULT '0' NOT NULL, be_variant_attribute_option3 int(11) unsigned DEFAULT '0' NOT NULL, - price double(11,2) DEFAULT '0.00' NOT NULL, + price decimal(17,5) DEFAULT '0.00' NOT NULL, special_prices int(11) unsigned DEFAULT '0' NOT NULL, price_calc_method int(11) unsigned DEFAULT '0' NOT NULL, - price_measure double(11,2) DEFAULT '0.00' NOT NULL, + price_measure decimal(17,5) DEFAULT '0.00' NOT NULL, price_measure_unit varchar(8) DEFAULT '' NOT NULL, stock int(11) unsigned DEFAULT '0' NOT NULL, From b1890943f50d3021d576e7475ad47882914ecaed Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Fri, 10 Apr 2026 21:43:44 +0200 Subject: [PATCH 06/12] [TASK] Remove deprecated ext_emconf.php --- ext_emconf.php | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 ext_emconf.php diff --git a/ext_emconf.php b/ext_emconf.php deleted file mode 100644 index ba781938..00000000 --- a/ext_emconf.php +++ /dev/null @@ -1,20 +0,0 @@ - 'Cart - Products', - 'description' => 'Shopping Cart(s) for TYPO3 - Products', - 'category' => 'plugin', - 'version' => '7.3.0', - 'state' => 'stable', - 'author' => 'Daniel Gohlke', - 'author_email' => 'ext@extco.de', - 'author_company' => 'extco.de UG (haftungsbeschränkt)', - 'constraints' => [ - 'depends' => [ - 'typo3' => '13.4.0-13.4.99', - 'cart' => '11.0.0', - ], - 'conflicts' => [], - 'suggests' => [], - ], -]; From 30ba584fe400bf8d9f7b27b361174e5b978357fa Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Fri, 10 Apr 2026 21:49:16 +0200 Subject: [PATCH 07/12] [BUGFIX] Exclude folder from testing cgl --- Build/.php-cs-fixer.dist.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Build/.php-cs-fixer.dist.php b/Build/.php-cs-fixer.dist.php index 58818f6f..f33cf71a 100644 --- a/Build/.php-cs-fixer.dist.php +++ b/Build/.php-cs-fixer.dist.php @@ -11,6 +11,11 @@ (new PhpCsFixer\Finder()) ->ignoreVCSIgnored(true) ->in(__DIR__ . '/../') + ->exclude( + [ + 'var/', + ] + ) ) ->setRiskyAllowed(true) ->setRules([ From 6122a3fc91251b8582f31e9c1ca8390762717e77 Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Tue, 14 Apr 2026 21:01:03 +0200 Subject: [PATCH 08/12] [BUGFIX] Make DataHandler hook public to allow injecting the CacheManager --- Configuration/Services.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index e22e64fd..b2ed4440 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -74,6 +74,9 @@ services: identifier: 'cart-products--order--stock-check-stock' event: Extcode\Cart\Event\ProcessOrderCheckStockEvent + Extcode\CartProducts\Hooks\DataHandler: + public: true + Extcode\CartProducts\Reaction\UpdateStockReaction: tags: ['reactions.reaction'] public: true From 2abac9a86476b064d330f6a0fd36138fac5ff567 Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Tue, 14 Apr 2026 22:14:32 +0200 Subject: [PATCH 09/12] [BUGFIX] Add type configuration for page wizard --- Classes/Constants.php | 22 +++++++++ Configuration/TCA/Overrides/pages.php | 68 +++++++++++++++++++-------- 2 files changed, 71 insertions(+), 19 deletions(-) create mode 100644 Classes/Constants.php diff --git a/Classes/Constants.php b/Classes/Constants.php new file mode 100644 index 00000000..2caf1239 --- /dev/null +++ b/Classes/Constants.php @@ -0,0 +1,22 @@ + $_LLL_be . 'pages.doktype.182', - 'value' => 182, - 'icon' => 'apps-pagetree-page-cartproducts-products', - ]; - $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'][] = [ - 'label' => $_LLL_be . 'pages.doktype.183', - 'value' => 183, - 'icon' => 'apps-pagetree-page-cartproducts-products', - ]; - $GLOBALS['TCA']['pages']['columns']['module']['config']['items'][] = [ - 'label' => 'LLL:EXT:cart_products/Resources/Private/Language/locallang_be.xlf:tcarecords-pages-contains.cart_products', - 'value' => 'cartproducts', - 'icon' => 'apps-pagetree-folder-cartproducts-products', - ]; - - $GLOBALS['TCA']['pages']['ctrl']['typeicon_classes'][182] = 'apps-pagetree-page-cartproducts-products'; - $GLOBALS['TCA']['pages']['ctrl']['typeicon_classes'][183] = 'apps-pagetree-page-cartproducts-products'; - $GLOBALS['TCA']['pages']['ctrl']['typeicon_classes']['contains-cartproducts'] = 'apps-pagetree-folder-cartproducts-products'; + ArrayUtility::mergeRecursiveWithOverrule( + $GLOBALS['TCA']['pages'], + [ + 'columns' => [ + 'doktype' => [ + 'config' => [ + 'items' => [ + 1776196019 => [ + 'label' => $_LLL_be . 'pages.doktype.' . Constants::DOKTYPE_CARTPRODUCTS_PRODUCTS, + 'value' => Constants::DOKTYPE_CARTPRODUCTS_PRODUCTS, + 'icon' => 'apps-pagetree-page-cartproducts-products', + 'group' => 'default', + ], + 1776196036 => [ + 'label' => $_LLL_be . 'pages.doktype.' . Constants::DOKTYPE_CARTPRODUCTS_PRODUCT, + 'value' => Constants::DOKTYPE_CARTPRODUCTS_PRODUCT, + 'icon' => 'apps-pagetree-page-cartproducts-products', + 'group' => 'default', + ], + ], + ], + ], + 'module' => [ + 'config' => [ + 'items' => [ + 1776196062 => [ + 'label' => $_LLL_be . 'tcarecords-pages-contains.cart_products', + 'value' => 'cartproducts', + 'icon' => 'apps-pagetree-folder-cartproducts-products', + ], + ], + ], + ], + ], + 'ctrl' => [ + 'typeicon_classes' => [ + Constants::DOKTYPE_CARTPRODUCTS_PRODUCTS => 'apps-pagetree-page-cartproducts-products', + Constants::DOKTYPE_CARTPRODUCTS_PRODUCT => 'apps-pagetree-page-cartproducts-products', + 'contains-cartproducts' => 'apps-pagetree-folder-cartproducts-products', + ], + ], + 'types' => [ + Constants::DOKTYPE_CARTPRODUCTS_PRODUCTS => $GLOBALS['TCA']['pages']['types'][(string)\TYPO3\CMS\Core\Domain\Repository\PageRepository::DOKTYPE_DEFAULT], + Constants::DOKTYPE_CARTPRODUCTS_PRODUCT => $GLOBALS['TCA']['pages']['types'][(string)\TYPO3\CMS\Core\Domain\Repository\PageRepository::DOKTYPE_DEFAULT], + ], + ] + ); $newPagesColumns = [ 'cart_products_product' => [ From c7abcb245193605b4982cb6fc301ec13ca3b675c Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Wed, 22 Apr 2026 22:59:10 +0200 Subject: [PATCH 10/12] [TASK] Update required TYPO3 version to 14.3 --- Documentation/Changelog/8.0/Index.rst | 10 ++++++++++ Documentation/Changelog/Index.rst | 1 + Documentation/guides.xml | 4 ++-- README.md | 9 +++++---- composer.json | 16 ++++++++-------- 5 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 Documentation/Changelog/8.0/Index.rst diff --git a/Documentation/Changelog/8.0/Index.rst b/Documentation/Changelog/8.0/Index.rst new file mode 100644 index 00000000..799cb9ba --- /dev/null +++ b/Documentation/Changelog/8.0/Index.rst @@ -0,0 +1,10 @@ +.. include:: ../../Includes.rst.txt + +8.0 Changes +=========== + +**Table of contents** + +.. contents:: + :local: + :depth: 1 diff --git a/Documentation/Changelog/Index.rst b/Documentation/Changelog/Index.rst index a52b3543..ab8df6f6 100644 --- a/Documentation/Changelog/Index.rst +++ b/Documentation/Changelog/Index.rst @@ -10,6 +10,7 @@ ChangeLog :maxdepth: 5 :titlesonly: + 8.0/Index 7.2/Index 7.1/Index 7.0/Index diff --git a/Documentation/guides.xml b/Documentation/guides.xml index 37808288..f311e25b 100644 --- a/Documentation/guides.xml +++ b/Documentation/guides.xml @@ -11,8 +11,8 @@ interlink-shortcode="extcode/cart_products" /> diff --git a/README.md b/README.md index f2461d18..71c78996 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,11 @@ Sometimes minor versions also result in minor adjustments to own templates or co | Cart Products | TYPO3 | PHP | Support/Development | |---------------|------------|-----------|--------------------------------------| -| 7.x.x | 13.2 | 8.2 - 8.5 | Features, Bugfixes, Security Updates | -| 6.x.x | 12.4 | 8.1+ | Bugfixes, Security Updates | -| 5.x.x | 12.4 | 8.1+ | Security Updates | -| 4.x.x | 10.4, 11.5 | 7.2+ | Security Updates | +| 8.x.x | 14.3 | 8.2 - 8.5 | Features, Bugfixes, Security Updates | +| 7.x.x | 13.4 | 8.2 - 8.5 | Bugfixes, Security Updates | +| 6.x.x | 12.4 | 8.1+ | Security Updates | +| 5.x.x | 12.4 | 8.1+ | | +| 4.x.x | 10.4, 11.5 | 7.2+ | | | 3.x.x | 10.4 | 7.2 - 7.4 | | | 2.x.x | 9.5 | 7.2 - 7.4 | | | 1.x.x | 8.7 | 7.0 - 7.4 | | diff --git a/composer.json b/composer.json index 84082546..7362a402 100644 --- a/composer.json +++ b/composer.json @@ -54,10 +54,10 @@ "require": { "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "ext-pdo": "*", - "extcode/cart": "dev-12.x-dev as 12.0.0", - "typo3/cms-core": "^14.1", - "typo3/cms-extbase": "^14.1", - "typo3/cms-fluid": "^14.1" + "extcode/cart": "dev-main as 12.0.0", + "typo3/cms-core": "^14.3", + "typo3/cms-extbase": "^14.3", + "typo3/cms-fluid": "^14.3" }, "require-dev": { "codappix/typo3-php-datasets": "^2.0", @@ -72,10 +72,10 @@ "phpstan/phpstan-phpunit": "^2.0", "spaze/phpstan-disallowed-calls": "^4.7", "staabm/phpstan-todo-by": "^0.3", - "typo3/cms-dashboard": "^14.1", - "typo3/cms-fluid-styled-content": "^14.1", - "typo3/cms-install": "^14.1", - "typo3/cms-reactions": "^14.1", + "typo3/cms-dashboard": "^14.3", + "typo3/cms-fluid-styled-content": "^14.3", + "typo3/cms-install": "^14.3", + "typo3/cms-reactions": "^14.3", "typo3/testing-framework": "^9.3", "werkraummedia/watchlist": "^3.0", "ssch/typo3-rector": "^3.11" From 34d26d9412f998413e515aa29afc5f303d1a86cb Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Wed, 22 Apr 2026 23:15:44 +0200 Subject: [PATCH 11/12] [TASK] Move commands to composer and use them from ci and nix-shell --- .github/workflows/ci.yaml | 68 +++++++++++++++++---------------------- composer.json | 8 +++++ shell.nix | 18 +++++++---- 3 files changed, 50 insertions(+), 44 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 585b6bbd..89e8fb98 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -61,7 +61,7 @@ jobs: run: composer install --prefer-dist --no-progress --no-suggest - name: Coding Guideline - run: .build/bin/php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php -v --dry-run --using-cache=no --path-mode=intersection ./ + run: composer project:cgl code-quality: runs-on: ubuntu-latest @@ -69,15 +69,11 @@ jobs: - php-linting strategy: matrix: - include: - - php-version: '8.2' - typo3-version: '^14.1' - - php-version: '8.3' - typo3-version: '^14.1' - - php-version: '8.4' - typo3-version: '^14.1' - - php-version: '8.5' - typo3-version: '^14.1' + php-version: + - 8.2 + - 8.3 + - 8.4 + - 8.5 steps: - uses: actions/checkout@v4 @@ -89,54 +85,50 @@ jobs: - name: Install dependencies with expected TYPO3 version run: |- - composer require --no-interaction --prefer-dist --no-progress "typo3/cms-core:${{ matrix.typo3-version }}" "typo3/cms-extbase:${{ matrix.typo3-version }}" "typo3/cms-frontend:${{ matrix.typo3-version }}" + composer remove --dev ssch/typo3-rector \ + && composer require typo3/cms-install "*" \ + && composer install --no-progress --no-ansi --no-interaction - name: Build codeception tester run: .build/bin/codecept build - name: Code Quality (by PHPStan) - run: .build/bin/phpstan analyse -c Build/phpstan.neon + run: composer project:phpstan - test-unit-and-functional: + tests-unit-and-functional: runs-on: ubuntu-latest needs: - coding-guideline - code-quality + strategy: + matrix: + php-version: + - 8.2 + - 8.3 + - 8.4 + - 8.5 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v31 + - name: Install PHP + uses: shivammathur/setup-php@v2 with: - nix_path: nixpkgs=channel:nixos-unstable - - - name: Run Unit Tests PHP8.2 - run: nix-shell --arg phpVersion \"php82\" --pure --run project-test-unit - - - name: Run Unit Tests PHP8.3 - run: nix-shell --arg phpVersion \"php83\" --pure --run project-test-unit - - - name: Run Unit Tests PHP8.4 - run: nix-shell --arg phpVersion \"php84\" --pure --run project-test-unit - - - name: Run Unit Tests PHP8.5 - run: nix-shell --arg phpVersion \"php85\" --pure --run project-test-unit - - - name: Run Functional Tests PHP8.2 - run: nix-shell --arg phpVersion \"php82\" --pure --run project-test-functional + php-version: "${{ matrix.php-version }}" + tools: composer:v2 - - name: Run Functional Tests PHP8.3 - run: nix-shell --arg phpVersion \"php83\" --pure --run project-test-functional + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest - - name: Run Functional Tests PHP8.4 - run: nix-shell --arg phpVersion \"php84\" --pure --run project-test-functional + - name: Run unit tests + run: composer project:test:unit - - name: Run Functional Tests PHP8.5 - run: nix-shell --arg phpVersion \"php85\" --pure --run project-test-functional + - name: Run functional tests + run: export typo3DatabaseDriver=pdo_sqlite && composer project:test:functional test-acceptance: runs-on: ubuntu-latest needs: - - test-unit-and-functional + - tests-unit-and-functional steps: - uses: actions/checkout@v5 diff --git a/composer.json b/composer.json index 7362a402..eaa57c17 100644 --- a/composer.json +++ b/composer.json @@ -80,6 +80,14 @@ "werkraummedia/watchlist": "^3.0", "ssch/typo3-rector": "^3.11" }, + "scripts": { + "project:cgl": "php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php -v --dry-run --diff --using-cache=no", + "project:cgl:fix": "php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php --using-cache=no", + "project:lint:php": "find ./*.php Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l", + "project:phpstan": "phpstan analyse -c Build/phpstan.neon --memory-limit 256M", + "project:test:unit": "phpunit -c Build/phpunit.xml.dist --testsuite unit --display-warnings --display-deprecations --display-errors --display-notices", + "project:test:functional": "phpunit -c Build/phpunit.xml.dist --testsuite functional --display-warnings --display-deprecations --display-errors" + }, "suggest": { "werkraummedia/watchlist": "^3.0" } diff --git a/shell.nix b/shell.nix index b5d24d82..0b95c185 100644 --- a/shell.nix +++ b/shell.nix @@ -32,11 +32,12 @@ let name = "project-cgl"; runtimeInputs = [ + composer php ]; text = '' - ./.build/bin/php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php -v --dry-run --diff + composer project:cgl ''; }; @@ -44,11 +45,12 @@ let name = "project-cgl-fix"; runtimeInputs = [ + composer php ]; text = '' - ./.build/bin/php-cs-fixer fix --config=Build/.php-cs-fixer.dist.php + composer project:cgl:fix ''; }; @@ -56,11 +58,12 @@ let name = "project-lint"; runtimeInputs = [ + composer php ]; text = '' - find ./*.php Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l + composer project:lint:php ''; }; @@ -68,35 +71,38 @@ let name = "project-phpstan"; runtimeInputs = [ + composer php ]; text = '' - ./.build/bin/phpstan analyse -c Build/phpstan.neon --memory-limit 256M + composer project:phpstan ''; }; projectTestUnit = pkgs.writeShellApplication { name = "project-test-unit"; runtimeInputs = [ + composer php projectInstall ]; text = '' project-install - ./.build/bin/phpunit -c Build/phpunit.xml.dist --testsuite unit --display-deprecations --display-warnings --display-errors + composer project:test:unit ''; }; projectTestFunctional = pkgs.writeShellApplication { name = "project-test-functional"; runtimeInputs = [ + composer php projectInstall ]; text = '' project-install - ./.build/bin/phpunit -c Build/phpunit.xml.dist --testsuite functional --display-deprecations --display-warnings --display-errors + composer project:test:functional ''; }; From a889bdcbb4e6b9d497490fcb4e6e06c384779e86 Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Fri, 24 Apr 2026 17:58:43 +0200 Subject: [PATCH 12/12] [TASK] Inject all dependencies via php and remove Services.yaml --- Configuration/Services.php | 34 +++++ Configuration/Services.yaml | 85 ------------- Configuration/Services/EventListeners.php | 144 ++++++++++++++++++++++ 3 files changed, 178 insertions(+), 85 deletions(-) delete mode 100644 Configuration/Services.yaml create mode 100644 Configuration/Services/EventListeners.php diff --git a/Configuration/Services.php b/Configuration/Services.php index a6f964ba..0e32bd65 100644 --- a/Configuration/Services.php +++ b/Configuration/Services.php @@ -5,6 +5,8 @@ namespace Extcode\CartProducts\Configuration; use Extcode\CartProducts\Handler\WatchlistItemHandler; +use Extcode\CartProducts\Hooks\DataHandler; +use Extcode\CartProducts\Reaction\UpdateStockReaction; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; @@ -24,4 +26,36 @@ ] ); } + + $services = $containerConfigurator + ->services() + ->defaults() + ->autowire() + ->autoconfigure() + ; + + $services + ->load( + 'Extcode\\CartProducts\\', + '../Classes/*' + ) + ->exclude( + [ + '../Classes/Handler/WatchlistItemHandler.php', + ] + ) + ; + + $services + ->set(DataHandler::class) + ->public() + ; + + $services + ->set(UpdateStockReaction::class) + ->tag('reactions.reaction') + ->public() + ; + + $containerConfigurator->import('Services/EventListeners.php'); }; diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml deleted file mode 100644 index b2ed4440..00000000 --- a/Configuration/Services.yaml +++ /dev/null @@ -1,85 +0,0 @@ -services: - _defaults: - autowire: true - autoconfigure: true - public: false - - Extcode\CartProducts\: - resource: '../Classes/*' - exclude: - - '../Classes/Handler/WatchlistItemHandler.php' - - Extcode\CartProducts\EventListener\Create\CheckRequest: - tags: - - name: event.listener - identifier: 'cart-products--create--check-request' - event: Extcode\CartProducts\Event\RetrieveProductsFromRequestEvent - - Extcode\CartProducts\EventListener\Create\LoadProduct: - tags: - - name: event.listener - identifier: 'cart-products--create--load-product' - event: Extcode\CartProducts\Event\RetrieveProductsFromRequestEvent - after: 'cart-products--create--check-request' - - Extcode\CartProducts\EventListener\Create\CreateCartFrontendVariants: - tags: - - name: event.listener - identifier: 'cart-products--create--create-cart-frontend-variants' - event: Extcode\CartProducts\Event\RetrieveProductsFromRequestEvent - after: 'cart-products--create--load-product' - - Extcode\CartProducts\EventListener\Create\CreateCartProduct: - tags: - - name: event.listener - identifier: 'cart-products--create--create-cart-product' - event: Extcode\CartProducts\Event\RetrieveProductsFromRequestEvent - after: 'cart-products--create--create-cart-frontend-variants' - - Extcode\CartProducts\EventListener\Create\CreateCartBackendVariants: - tags: - - name: event.listener - identifier: 'cart-products--create--create-cart-backend-variants' - event: Extcode\CartProducts\Event\RetrieveProductsFromRequestEvent - after: 'cart-products--create--create-cart-product' - - Extcode\CartProducts\EventListener\Order\Stock\HandleStock: - tags: - - name: event.listener - identifier: 'cart-products--order--stock--handle-stock' - event: Extcode\Cart\Event\Order\StockEvent - - Extcode\CartProducts\EventListener\Order\Stock\FlushCache: - tags: - - name: event.listener - identifier: 'cart-products--order--stock--flush-cache' - event: Extcode\Cart\Event\Order\StockEvent - after: 'cart-books--order--stock--handle-stock' - - Extcode\CartProducts\EventListener\RetrieveProductsFromRequest: - tags: - - name: event.listener - identifier: 'cart-products--retrieve-products-from-request' - event: Extcode\Cart\Event\RetrieveProductsFromRequestEvent - - Extcode\CartProducts\EventListener\CheckProductAvailability: - tags: - - name: event.listener - identifier: 'cart-products--check-product-availability' - event: Extcode\Cart\Event\CheckProductAvailabilityEvent - - Extcode\CartProducts\EventListener\Order\Stock\CheckStock: - tags: - - name: event.listener - identifier: 'cart-products--order--stock-check-stock' - event: Extcode\Cart\Event\ProcessOrderCheckStockEvent - - Extcode\CartProducts\Hooks\DataHandler: - public: true - - Extcode\CartProducts\Reaction\UpdateStockReaction: - tags: ['reactions.reaction'] - public: true - - Extcode\CartProducts\Updates\SlugUpdater: - public: true diff --git a/Configuration/Services/EventListeners.php b/Configuration/Services/EventListeners.php new file mode 100644 index 00000000..b9fc351d --- /dev/null +++ b/Configuration/Services/EventListeners.php @@ -0,0 +1,144 @@ +services() + ->defaults() + ->autowire() + ->autoconfigure() + ; + + $services + ->set(CheckRequest::class) + ->tag( + 'event.listener', + [ + 'event' => CartProductsRetrieveProductsFromRequestEvent::class, + 'identifier' => 'cart-products--create--check-request', + ] + ) + ; + + $services + ->set(LoadProduct::class) + ->tag( + 'event.listener', + [ + 'event' => CartProductsRetrieveProductsFromRequestEvent::class, + 'identifier' => 'cart-products--create--load-product', + 'after' => 'cart-products--create--check-request', + ] + ) + ; + + $services + ->set(CreateCartFrontendVariants::class) + ->tag( + 'event.listener', + [ + 'event' => CartProductsRetrieveProductsFromRequestEvent::class, + 'identifier' => 'cart-products--create--create-cart-frontend-variants', + 'after' => 'cart-products--create--load-product', + ] + ) + ; + + $services + ->set(CreateCartProduct::class) + ->tag( + 'event.listener', + [ + 'event' => CartProductsRetrieveProductsFromRequestEvent::class, + 'identifier' => 'cart-products--create--create-cart-product', + 'after' => 'cart-products--create--create-cart-frontend-variants', + ] + ) + ; + + $services + ->set(CreateCartBackendVariants::class) + ->tag( + 'event.listener', + [ + 'event' => CartProductsRetrieveProductsFromRequestEvent::class, + 'identifier' => 'cart-products--create--create-cart-backend-variants', + 'after' => 'cart-products--create--create-cart-product', + ] + ) + ; + + $services + ->set(HandleStock::class) + ->tag( + 'event.listener', + [ + 'event' => StockEvent::class, + 'identifier' => 'cart-products--order--stock--handle-stock', + ] + ) + ; + + $services + ->set(FlushCache::class) + ->tag( + 'event.listener', + [ + 'event' => StockEvent::class, + 'identifier' => 'cart-products--order--stock--flush-cache', + 'after' => 'cart-books--order--stock--handle-stock', + ] + ) + ; + + $services + ->set(RetrieveProductsFromRequest::class) + ->tag( + 'event.listener', + [ + 'event' => CartRetrieveProductsFromRequestEvent::class, + 'identifier' => 'cart-products--retrieve-products-from-request', + ] + ) + ; + + $services + ->set(CheckProductAvailability::class) + ->tag( + 'event.listener', + [ + 'event' => CheckProductAvailabilityEvent::class, + 'identifier' => 'cart-products--check-product-availability', + ] + ) + ; + + $services + ->set(CheckStock::class) + ->tag( + 'event.listener', + [ + 'event' => ProcessOrderCheckStockEvent::class, + 'identifier' => 'cart-products--order--stock-check-stock', + ] + ) + ; +};