From 24e0af3ff488d8c461d187d40e1d3e7ecee58f8c Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Tue, 19 May 2026 21:40:38 +0200 Subject: [PATCH 1/2] [BUGFIX] Add first product image file reference for new watchlist item --- .../Product/ProductRepository.php | 27 +++ Classes/Domain/Model/WatchlistItemFactory.php | 12 +- .../Fixtures/OneImageFileReference.php | 16 ++ .../Fixtures/TwoImageFileReference.php | 24 +++ .../Product/ProductRepositoryTest.php | 162 ++++++++++++++++++ 5 files changed, 231 insertions(+), 10 deletions(-) create mode 100644 Tests/Functional/Domain/DoctrineRepository/Product/Fixtures/OneImageFileReference.php create mode 100644 Tests/Functional/Domain/DoctrineRepository/Product/Fixtures/TwoImageFileReference.php create mode 100644 Tests/Functional/Domain/DoctrineRepository/Product/ProductRepositoryTest.php diff --git a/Classes/Domain/DoctrineRepository/Product/ProductRepository.php b/Classes/Domain/DoctrineRepository/Product/ProductRepository.php index 842c2e8d..b8153e2c 100644 --- a/Classes/Domain/DoctrineRepository/Product/ProductRepository.php +++ b/Classes/Domain/DoctrineRepository/Product/ProductRepository.php @@ -36,6 +36,33 @@ public function findProductByUid(int $uid): array|bool ->fetchAssociative(); } + public function findFirstProductImageUid(int $uid): int|bool + { + $queryBuilder = $this + ->connectionPool + ->getConnectionForTable('sys_file_reference') + ->createQueryBuilder() + ; + + $queryBuilder + ->select('uid') + ->from('sys_file_reference') + ->where( + $queryBuilder->expr()->and( + $queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter(self::TABLENAME)), + $queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('images')), + $queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($uid)), + ) + ) + ->orderBy('sorting_foreign') + ->setMaxResults(1); + + return $queryBuilder + ->executeQuery() + ->fetchOne() + ; + } + public function getStock(int $uid): int { $queryBuilder = $this->getQueryBuilder(); diff --git a/Classes/Domain/Model/WatchlistItemFactory.php b/Classes/Domain/Model/WatchlistItemFactory.php index b5850ed1..b8038831 100644 --- a/Classes/Domain/Model/WatchlistItemFactory.php +++ b/Classes/Domain/Model/WatchlistItemFactory.php @@ -40,18 +40,10 @@ public function createFromIdentifier( private function getFirstImageReference(array $product): ?int { - if (is_string($product['images'] ?? null) === false || $product['images'] === '') { + if ((int)$product['images'] === 0) { return null; } - $images = explode(',', $product['images']); - - $image = array_pop($images); - - if (is_numeric($image) === false) { - return null; - } - - return (int)$image; + return $this->productRepository->findFirstProductImageUid($product['uid']); } } diff --git a/Tests/Functional/Domain/DoctrineRepository/Product/Fixtures/OneImageFileReference.php b/Tests/Functional/Domain/DoctrineRepository/Product/Fixtures/OneImageFileReference.php new file mode 100644 index 00000000..bf5f2418 --- /dev/null +++ b/Tests/Functional/Domain/DoctrineRepository/Product/Fixtures/OneImageFileReference.php @@ -0,0 +1,16 @@ + [ + 0 => [ + 'uid' => 1, + 'pid' => 7, + 'uid_local' => 42, + 'uid_foreign' => 1, + 'tablenames' => 'tx_cartproducts_domain_model_product_product', + 'fieldname' => 'images', + ], + ], +]; diff --git a/Tests/Functional/Domain/DoctrineRepository/Product/Fixtures/TwoImageFileReference.php b/Tests/Functional/Domain/DoctrineRepository/Product/Fixtures/TwoImageFileReference.php new file mode 100644 index 00000000..568d4f53 --- /dev/null +++ b/Tests/Functional/Domain/DoctrineRepository/Product/Fixtures/TwoImageFileReference.php @@ -0,0 +1,24 @@ + [ + 0 => [ + 'uid' => 5, + 'pid' => 7, + 'uid_local' => 42, + 'uid_foreign' => 1, + 'tablenames' => 'tx_cartproducts_domain_model_product_product', + 'fieldname' => 'images', + ], + 1 => [ + 'uid' => 16, + 'pid' => 7, + 'uid_local' => 37, + 'uid_foreign' => 1, + 'tablenames' => 'tx_cartproducts_domain_model_product_product', + 'fieldname' => 'images', + ], + ], +]; diff --git a/Tests/Functional/Domain/DoctrineRepository/Product/ProductRepositoryTest.php b/Tests/Functional/Domain/DoctrineRepository/Product/ProductRepositoryTest.php new file mode 100644 index 00000000..fd27159d --- /dev/null +++ b/Tests/Functional/Domain/DoctrineRepository/Product/ProductRepositoryTest.php @@ -0,0 +1,162 @@ +testExtensionsToLoad[] = 'extcode/cart'; + $this->testExtensionsToLoad[] = 'extcode/cart-products'; + + $this->coreExtensionsToLoad[] = 'typo3/cms-reactions'; + + parent::setUp(); + + $GLOBALS['TYPO3_REQUEST'] = (new ServerRequest()) + ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE); + + $this->productRepository = GeneralUtility::makeInstance( + ProductRepository::class, + GeneralUtility::makeInstance( + ConnectionPool::class + ) + ); + + $this->importPHPDataSet(__DIR__ . '/../../../Fixtures/Pages.php'); + $this->importPHPDataSet(__DIR__ . '/../../../Fixtures/Products.php'); + } + + #[Test] + public function returnsFalseIfProductHasNoImage(): void + { + self::assertFalse( + $this->productRepository->findFirstProductImageUid(1) + ); + } + + #[Test] + public function returnsUidOfImageIfProductHasOnlyOneImage(): void + { + $this->importPhpDataSet(__DIR__ . '/Fixtures/OneImageFileReference.php'); + + self::assertSame( + 1, + $this->productRepository->findFirstProductImageUid(1) + ); + } + + #[Test] + public function returnsFirstUidOfImageIfProductHasMoreThanOneImage(): void + { + $this->importPhpDataSet(__DIR__ . '/Fixtures/TwoImageFileReference.php'); + + self::assertSame( + 5, + $this->productRepository->findFirstProductImageUid(1) + ); + } + + #[Test] + public function returnsSecondUidOfImageIfProductHasMoreThanOneImageAndRespectsSorting(): void + { + $this->importPhpDataSet(__DIR__ . '/Fixtures/TwoImageFileReference.php'); + + $this + ->getConnectionPool() + ->getConnectionForTable('sys_file_reference') + ->update( + 'sys_file_reference', + [ + 'sorting_foreign' => 128, + ], + [ + 'uid' => 5, + ] + ) + ; + $this + ->getConnectionPool() + ->getConnectionForTable('sys_file_reference') + ->update( + 'sys_file_reference', + [ + 'sorting_foreign' => 16, + ], + [ + 'uid' => 16, + ] + ) + ; + + self::assertSame( + 16, + $this->productRepository->findFirstProductImageUid(1) + ); + } + + #[Test] + public function returnsSecondUidOfImageIfProductHasMoreThanOneImageAndFirstIsDeleted(): void + { + $this->importPhpDataSet(__DIR__ . '/Fixtures/TwoImageFileReference.php'); + + $this + ->getConnectionPool() + ->getConnectionForTable('sys_file_reference') + ->update( + 'sys_file_reference', + [ + 'deleted' => 1, + ], + [ + 'uid' => 5, + ] + ) + ; + + self::assertSame( + 16, + $this->productRepository->findFirstProductImageUid(1) + ); + } + + #[Test] + public function returnsSecondUidOfImageIfProductHasMoreThanOneImageAndFirstIsHidden(): void + { + $this->importPhpDataSet(__DIR__ . '/Fixtures/TwoImageFileReference.php'); + + $this + ->getConnectionPool() + ->getConnectionForTable('sys_file_reference') + ->update( + 'sys_file_reference', + [ + 'hidden' => 1, + ], + [ + 'uid' => 5, + ] + ) + ; + + self::assertSame( + 16, + $this->productRepository->findFirstProductImageUid(1) + ); + } +} From cd4f256472f8c7fe13d9ee1a644706782f5b0ea7 Mon Sep 17 00:00:00 2001 From: Daniel Gohlke Date: Wed, 20 May 2026 20:12:12 +0200 Subject: [PATCH 2/2] [BUGFIX] Update CI configuration --- .github/workflows/ci.yaml | 8 +- Classes/Domain/Model/WatchlistItemFactory.php | 7 +- .../Product/ProductRepositoryTest.php | 24 ++++ .../Domain/Model/WatchlistItemFactoryTest.php | 125 ++++++++++++++++++ 4 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 Tests/Functional/Domain/Model/WatchlistItemFactoryTest.php diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f256ead6..e9e152e8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -103,9 +103,9 @@ jobs: - 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 @@ -138,9 +138,9 @@ jobs: needs: - test-php 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/Classes/Domain/Model/WatchlistItemFactory.php b/Classes/Domain/Model/WatchlistItemFactory.php index b8038831..33d5f047 100644 --- a/Classes/Domain/Model/WatchlistItemFactory.php +++ b/Classes/Domain/Model/WatchlistItemFactory.php @@ -44,6 +44,11 @@ private function getFirstImageReference(array $product): ?int return null; } - return $this->productRepository->findFirstProductImageUid($product['uid']); + $firstProductImageUid = $this->productRepository->findFirstProductImageUid($product['uid']); + if (is_int($firstProductImageUid)) { + return $firstProductImageUid; + } + + return null; } } diff --git a/Tests/Functional/Domain/DoctrineRepository/Product/ProductRepositoryTest.php b/Tests/Functional/Domain/DoctrineRepository/Product/ProductRepositoryTest.php index fd27159d..2dce1ff4 100644 --- a/Tests/Functional/Domain/DoctrineRepository/Product/ProductRepositoryTest.php +++ b/Tests/Functional/Domain/DoctrineRepository/Product/ProductRepositoryTest.php @@ -61,6 +61,30 @@ public function returnsUidOfImageIfProductHasOnlyOneImage(): void ); } + #[Test] + public function returnsFalseIfProductHasOnlyOneDeaktivatedImage(): void + { + $this->importPhpDataSet(__DIR__ . '/Fixtures/OneImageFileReference.php'); + + $this + ->getConnectionPool() + ->getConnectionForTable('sys_file_reference') + ->update( + 'sys_file_reference', + [ + 'deleted' => 1, + ], + [ + 'uid' => 1, + ] + ) + ; + + self::assertFalse( + $this->productRepository->findFirstProductImageUid(1) + ); + } + #[Test] public function returnsFirstUidOfImageIfProductHasMoreThanOneImage(): void { diff --git a/Tests/Functional/Domain/Model/WatchlistItemFactoryTest.php b/Tests/Functional/Domain/Model/WatchlistItemFactoryTest.php new file mode 100644 index 00000000..9300d3eb --- /dev/null +++ b/Tests/Functional/Domain/Model/WatchlistItemFactoryTest.php @@ -0,0 +1,125 @@ +testExtensionsToLoad[] = 'extcode/cart'; + $this->testExtensionsToLoad[] = 'extcode/cart-products'; + + $this->coreExtensionsToLoad[] = 'typo3/cms-reactions'; + + parent::setUp(); + + $GLOBALS['TYPO3_REQUEST'] = (new ServerRequest()) + ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE); + + $this->watchlistItemFactory = GeneralUtility::makeInstance( + WatchlistItemFactory::class, + GeneralUtility::makeInstance( + ProductRepository::class, + GeneralUtility::makeInstance( + ConnectionPool::class + ) + ) + ); + + $this->importPHPDataSet(__DIR__ . '/../../Fixtures/Pages.php'); + $this->importPHPDataSet(__DIR__ . '/../../Fixtures/Products.php'); + } + + #[Test] + public function returnsNullIfProductHasNoImage(): void + { + $watchlistItem = $this->watchlistItemFactory->createFromIdentifier('1-1'); + + self::assertNull( + $watchlistItem->getFileReference() + ); + } + + #[Test] + public function returnsUidOfFileReferenceIfProductHasOnlyOneImage(): void + { + $this->importPhpDataSet(__DIR__ . '/../DoctrineRepository/Product/Fixtures/OneImageFileReference.php'); + + $this + ->getConnectionPool() + ->getConnectionForTable('tx_cartproducts_domain_model_product_product') + ->update( + 'tx_cartproducts_domain_model_product_product', + [ + 'images' => 1, + ], + [ + 'uid' => 1, + ] + ) + ; + + $watchlistItem = $this->watchlistItemFactory->createFromIdentifier('1-1'); + + self::assertSame( + 1, + $watchlistItem->getFileReference() + ); + } + + #[Test] + public function returnsNullIfProductHasOnlyOneDeaktivatedImage(): void + { + $this->importPhpDataSet(__DIR__ . '/../DoctrineRepository/Product/Fixtures/OneImageFileReference.php'); + + $this + ->getConnectionPool() + ->getConnectionForTable('tx_cartproducts_domain_model_product_product') + ->update( + 'tx_cartproducts_domain_model_product_product', + [ + 'images' => 1, + ], + [ + 'uid' => 1, + ] + ) + ; + + $this + ->getConnectionPool() + ->getConnectionForTable('sys_file_reference') + ->update( + 'sys_file_reference', + [ + 'deleted' => 1, + ], + [ + 'uid' => 1, + ] + ) + ; + + $watchlistItem = $this->watchlistItemFactory->createFromIdentifier('1-1'); + + self::assertNull( + $watchlistItem->getFileReference() + ); + } +}