From 1418aa86b6714314fcaa238c217de89ef9fa003e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Mike=C5=A1?= Date: Sat, 28 Mar 2026 15:26:19 +0100 Subject: [PATCH] Store puzzle image aspect ratio for CLS optimization Add imageRatio (nullable float) to Puzzle entity, calculated on upload from actual image dimensions (width/height). The ratio flows through all SQL queries, result DTOs, and templates to LazyImageTwigExtension which computes accurate width/height attributes instead of assuming square images. Includes backfill command (myspeedpuzzling:puzzle:backfill-image-ratio) to populate ratios for existing puzzles from S3 images. Values of null and 0 gracefully fall back to the previous square-assumption behavior. Closes #84 Co-Authored-By: Claude Opus 4.6 (1M context) --- migrations/Version20260328141426.php | 28 ++++ ...BackfillPuzzleImageRatioConsoleCommand.php | 120 ++++++++++++++++++ src/Entity/Puzzle.php | 2 + src/Entity/PuzzleChangeRequest.php | 3 + src/MessageHandler/AddPuzzleHandler.php | 3 + .../ApprovePuzzleChangeRequestHandler.php | 1 + .../ApprovePuzzleMergeRequestHandler.php | 1 + .../SubmitPuzzleChangeRequestHandler.php | 3 + src/Query/GetBorrowedPuzzles.php | 6 + src/Query/GetCollectionItems.php | 6 + src/Query/GetConversations.php | 9 ++ src/Query/GetFastestGroups.php | 2 + src/Query/GetFastestPairs.php | 2 + src/Query/GetFastestPlayers.php | 2 + src/Query/GetLastSolvedPuzzle.php | 6 + src/Query/GetLentPuzzles.php | 6 + src/Query/GetMarketplaceListings.php | 6 + src/Query/GetMostSolvedPuzzles.php | 4 + src/Query/GetNotifications.php | 6 + src/Query/GetPendingPuzzleProposals.php | 2 + src/Query/GetPlayerSolvedPuzzles.php | 14 ++ src/Query/GetPuzzleChangeRequests.php | 2 + src/Query/GetPuzzleMergeRequests.php | 4 + src/Query/GetPuzzleOverview.php | 6 + src/Query/GetPuzzleTracking.php | 2 + src/Query/GetPuzzlesOverview.php | 2 + src/Query/GetRanking.php | 4 + src/Query/GetRecentActivity.php | 6 + src/Query/GetSellSwapListItems.php | 6 + src/Query/GetSoldSwappedHistory.php | 3 + src/Query/GetTransactionRatings.php | 6 + src/Query/GetUnsolvedPuzzles.php | 6 + src/Query/GetWishListItems.php | 3 + src/Query/SearchPuzzle.php | 10 +- src/Results/AutocompletePuzzle.php | 3 + src/Results/BorrowedPuzzleOverview.php | 1 + src/Results/CollectionItemOverview.php | 1 + src/Results/ConversationOverview.php | 1 + src/Results/LentPuzzleOverview.php | 1 + src/Results/MarketplaceListingItem.php | 1 + src/Results/MergePuzzleInfo.php | 1 + src/Results/MostSolvedPuzzle.php | 3 + src/Results/PendingTransactionRating.php | 1 + src/Results/PlayerNotification.php | 2 + src/Results/PlayerRanking.php | 3 + src/Results/PuzzleChangeRequestOverview.php | 2 + src/Results/PuzzleOverview.php | 3 + src/Results/RecentActivityItem.php | 3 + src/Results/SellSwapListItemOverview.php | 1 + src/Results/SoldSwappedItemOverview.php | 1 + src/Results/SolvedPuzzle.php | 3 + src/Results/SolvedPuzzleDetail.php | 3 + src/Results/SolvedPuzzleOverview.php | 1 + src/Results/TrackedPuzzleDetail.php | 3 + src/Results/TransactionRatingView.php | 1 + src/Results/UnsolvedPuzzleItem.php | 1 + src/Results/WishListItemOverview.php | 1 + src/Services/ImageOptimizer.php | 20 +++ src/Services/PlayersComparison.php | 1 + src/Twig/LazyImageTwigExtension.php | 43 ++++++- src/Value/Comparison.php | 1 + templates/_player_solvings.html.twig | 3 +- templates/_puzzle_item.html.twig | 3 +- templates/_puzzle_library_item.html.twig | 4 +- templates/compare_players.html.twig | 4 +- templates/components/GlobalSearch.html.twig | 3 +- templates/components/LadderTable.html.twig | 3 +- .../components/MostSolvedPuzzles.html.twig | 4 +- templates/components/RecentActivity.html.twig | 4 +- templates/marketplace/_listing_card.html.twig | 3 +- templates/notifications.html.twig | 4 +- templates/sell-swap/_item_card.html.twig | 3 +- templates/sell-swap/edit_item.html.twig | 4 +- templates/sell-swap/edit_modal.html.twig | 4 +- templates/sell-swap/history.html.twig | 4 +- tests/Query/GetPuzzleOverviewTest.php | 8 ++ tests/Services/ImageOptimizerTest.php | 70 ++++++++++ tests/Twig/LazyImageTwigExtensionTest.php | 98 ++++++++++++++ 78 files changed, 606 insertions(+), 18 deletions(-) create mode 100644 migrations/Version20260328141426.php create mode 100644 src/ConsoleCommands/BackfillPuzzleImageRatioConsoleCommand.php create mode 100644 tests/Services/ImageOptimizerTest.php create mode 100644 tests/Twig/LazyImageTwigExtensionTest.php diff --git a/migrations/Version20260328141426.php b/migrations/Version20260328141426.php new file mode 100644 index 00000000..a1b7530d --- /dev/null +++ b/migrations/Version20260328141426.php @@ -0,0 +1,28 @@ +addSql('ALTER TABLE puzzle ADD image_ratio DOUBLE PRECISION DEFAULT NULL'); + $this->addSql('ALTER TABLE puzzle_change_request ADD proposed_image_ratio DOUBLE PRECISION DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE puzzle DROP image_ratio'); + $this->addSql('ALTER TABLE puzzle_change_request DROP proposed_image_ratio'); + } +} diff --git a/src/ConsoleCommands/BackfillPuzzleImageRatioConsoleCommand.php b/src/ConsoleCommands/BackfillPuzzleImageRatioConsoleCommand.php new file mode 100644 index 00000000..bbd18336 --- /dev/null +++ b/src/ConsoleCommands/BackfillPuzzleImageRatioConsoleCommand.php @@ -0,0 +1,120 @@ +addOption('batch-size', 'b', InputOption::VALUE_REQUIRED, 'Number of puzzles to process per batch', '50'); + $this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Only show what would be done without updating'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $batchSizeOption = $input->getOption('batch-size'); + assert(is_string($batchSizeOption)); + $batchSize = (int) $batchSizeOption; + $dryRun = $input->getOption('dry-run'); + + /** @var array $puzzles */ + $puzzles = $this->connection->fetchAllAssociative( + 'SELECT id, image FROM puzzle WHERE image IS NOT NULL AND (image_ratio IS NULL OR image_ratio = 0)', + ); + + $total = count($puzzles); + $io->info(sprintf('Found %d puzzles to backfill', $total)); + + if ($total === 0) { + $io->success('Nothing to backfill'); + return Command::SUCCESS; + } + + $progressBar = $io->createProgressBar($total); + $progressBar->start(); + + $successCount = 0; + $errorCount = 0; + $batched = array_chunk($puzzles, max(1, $batchSize)); + + foreach ($batched as $batch) { + foreach ($batch as $puzzle) { + try { + $ratio = $this->calculateRatio($puzzle['image']); + + if ($ratio !== null && !$dryRun) { + $this->connection->update('puzzle', ['image_ratio' => $ratio], ['id' => $puzzle['id']]); + } + + $successCount++; + } catch (\Throwable $e) { + $errorCount++; + $this->logger->warning('Failed to backfill image ratio for puzzle {id}: {error}', [ + 'id' => $puzzle['id'], + 'image' => $puzzle['image'], + 'error' => $e->getMessage(), + ]); + } + + $progressBar->advance(); + } + } + + $progressBar->finish(); + $io->newLine(2); + + $prefix = $dryRun ? '[DRY RUN] Would have updated' : 'Updated'; + $io->success(sprintf('%s %d puzzles, %d errors', $prefix, $successCount, $errorCount)); + + return Command::SUCCESS; + } + + private function calculateRatio(string $imagePath): null|float + { + $content = $this->filesystem->read($imagePath); + + $imagick = new Imagick(); + + try { + $imagick->readImageBlob($content); + + $width = $imagick->getImageWidth(); + $height = $imagick->getImageHeight(); + + if ($height === 0 || $width === 0) { + return null; + } + + return $width / $height; + } finally { + $imagick->clear(); + $imagick->destroy(); + } + } +} diff --git a/src/Entity/Puzzle.php b/src/Entity/Puzzle.php index a65c83e3..15232ba6 100644 --- a/src/Entity/Puzzle.php +++ b/src/Entity/Puzzle.php @@ -33,6 +33,8 @@ public function __construct( public bool $approved, #[Column(nullable: true)] public null|string $image = null, + #[Column(nullable: true)] + public null|float $imageRatio = null, #[ManyToOne] public null|Manufacturer $manufacturer = null, #[Column(nullable: true)] diff --git a/src/Entity/PuzzleChangeRequest.php b/src/Entity/PuzzleChangeRequest.php index f3320727..dcbf7067 100644 --- a/src/Entity/PuzzleChangeRequest.php +++ b/src/Entity/PuzzleChangeRequest.php @@ -71,6 +71,9 @@ public function __construct( #[Immutable] #[Column(nullable: true)] public null|string $proposedImage = null, + #[Immutable] + #[Column(nullable: true)] + public null|float $proposedImageRatio = null, // Original values (snapshot at time of request for audit trail) #[Immutable] #[Column] diff --git a/src/MessageHandler/AddPuzzleHandler.php b/src/MessageHandler/AddPuzzleHandler.php index bdb7aaa6..6af50558 100644 --- a/src/MessageHandler/AddPuzzleHandler.php +++ b/src/MessageHandler/AddPuzzleHandler.php @@ -54,12 +54,14 @@ public function __invoke(AddPuzzle $message): void } $puzzlePhotoPath = null; + $puzzleImageRatio = null; if ($message->puzzlePhoto !== null) { $extension = $message->puzzlePhoto->guessExtension(); $timestamp = $this->clock->now()->getTimestamp(); $puzzlePhotoPath = "$message->puzzleId-$timestamp.$extension"; $this->imageOptimizer->optimize($message->puzzlePhoto->getPathname()); + $puzzleImageRatio = $this->imageOptimizer->getImageRatio($message->puzzlePhoto->getPathname()); // Stream is better because it is memory safe $stream = fopen($message->puzzlePhoto->getPathname(), 'rb'); @@ -76,6 +78,7 @@ public function __invoke(AddPuzzle $message): void $message->puzzleName, approved: false, image: $puzzlePhotoPath, + imageRatio: $puzzleImageRatio, manufacturer: $manufacturer, addedByUser: $player, addedAt: $now, diff --git a/src/MessageHandler/ApprovePuzzleChangeRequestHandler.php b/src/MessageHandler/ApprovePuzzleChangeRequestHandler.php index 900e5549..22f2aa30 100644 --- a/src/MessageHandler/ApprovePuzzleChangeRequestHandler.php +++ b/src/MessageHandler/ApprovePuzzleChangeRequestHandler.php @@ -106,6 +106,7 @@ public function __invoke(ApprovePuzzleChangeRequest $message): void $this->filesystem->delete($changeRequest->proposedImage); $puzzle->image = $newImagePath; + $puzzle->imageRatio = $changeRequest->proposedImageRatio; } // Mark request as approved diff --git a/src/MessageHandler/ApprovePuzzleMergeRequestHandler.php b/src/MessageHandler/ApprovePuzzleMergeRequestHandler.php index 75db54dc..b1c80099 100644 --- a/src/MessageHandler/ApprovePuzzleMergeRequestHandler.php +++ b/src/MessageHandler/ApprovePuzzleMergeRequestHandler.php @@ -93,6 +93,7 @@ public function __invoke(ApprovePuzzleMergeRequest $message): void $imagePuzzle = $this->puzzleRepository->get($message->selectedImagePuzzleId); if ($imagePuzzle->image !== null) { $survivorPuzzle->image = $imagePuzzle->image; + $survivorPuzzle->imageRatio = $imagePuzzle->imageRatio; } } catch (PuzzleNotFound) { $this->logger->debug('Image puzzle {puzzleId} not found, keeping survivor image', [ diff --git a/src/MessageHandler/SubmitPuzzleChangeRequestHandler.php b/src/MessageHandler/SubmitPuzzleChangeRequestHandler.php index eb1893d1..dd902592 100644 --- a/src/MessageHandler/SubmitPuzzleChangeRequestHandler.php +++ b/src/MessageHandler/SubmitPuzzleChangeRequestHandler.php @@ -43,11 +43,13 @@ public function __invoke(SubmitPuzzleChangeRequest $message): void // Store proposed image with temporary name - proper SEO name is assigned on approval $proposedImagePath = null; + $proposedImageRatio = null; if ($message->proposedPhoto !== null) { $extension = $message->proposedPhoto->guessExtension() ?? 'jpg'; $proposedImagePath = "proposal-{$message->changeRequestId}.{$extension}"; $this->imageOptimizer->optimize($message->proposedPhoto->getPathname()); + $proposedImageRatio = $this->imageOptimizer->getImageRatio($message->proposedPhoto->getPathname()); $stream = fopen($message->proposedPhoto->getPathname(), 'rb'); $this->filesystem->writeStream($proposedImagePath, $stream); @@ -68,6 +70,7 @@ public function __invoke(SubmitPuzzleChangeRequest $message): void proposedEan: $message->proposedEan, proposedIdentificationNumber: $message->proposedIdentificationNumber, proposedImage: $proposedImagePath, + proposedImageRatio: $proposedImageRatio, originalName: $puzzle->name, originalManufacturerId: $puzzle->manufacturer?->id, originalPiecesCount: $puzzle->piecesCount, diff --git a/src/Query/GetBorrowedPuzzles.php b/src/Query/GetBorrowedPuzzles.php index 46fcb9d4..dc2b9608 100644 --- a/src/Query/GetBorrowedPuzzles.php +++ b/src/Query/GetBorrowedPuzzles.php @@ -32,6 +32,7 @@ public function byHolderId(string $holderId): array p.alternative_name as puzzle_alternative_name, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name, owner.id as owner_id, owner.name as owner_name, @@ -60,6 +61,7 @@ public function byHolderId(string $holderId): array * puzzle_alternative_name: string|null, * pieces_count: int, * image: string|null, + * image_ratio: string|null, * manufacturer_name: string|null, * owner_id: string|null, * owner_name: string|null, @@ -75,6 +77,7 @@ public function byHolderId(string $holderId): array piecesCount: $row['pieces_count'], manufacturerName: $row['manufacturer_name'], image: $row['image'], + imageRatio: $row['image_ratio'] !== null ? (float) $row['image_ratio'] : null, ownerId: $row['owner_id'], ownerName: $row['owner_name'] ?? $row['owner_text_name'] ?? '', ownerAvatar: $row['owner_avatar'], @@ -131,6 +134,7 @@ public function unsolvedByHolderId(string $holderId): array p.ean, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name, lp.lent_at as added_at, lp.owner_name as owner_text_name, @@ -163,6 +167,7 @@ public function unsolvedByHolderId(string $holderId): array * ean: string|null, * pieces_count: int, * image: string|null, + * image_ratio: string|null, * manufacturer_name: string|null, * added_at: string, * owner_text_name: string|null, @@ -180,6 +185,7 @@ public function unsolvedByHolderId(string $holderId): array piecesCount: $row['pieces_count'], manufacturerName: $row['manufacturer_name'], image: $row['image'], + imageRatio: $row['image_ratio'] !== null ? (float) $row['image_ratio'] : null, addedAt: new DateTimeImmutable($row['added_at']), isBorrowed: true, borrowedFromPlayerId: $row['owner_id'], diff --git a/src/Query/GetCollectionItems.php b/src/Query/GetCollectionItems.php index d4c3bf26..d222358e 100644 --- a/src/Query/GetCollectionItems.php +++ b/src/Query/GetCollectionItems.php @@ -41,6 +41,7 @@ public function byCollectionAndPlayer(null|string $collectionId, string $playerI p.ean, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name FROM collection_item ci JOIN puzzle p ON ci.puzzle_id = p.id @@ -65,6 +66,7 @@ public function byCollectionAndPlayer(null|string $collectionId, string $playerI * ean: string|null, * pieces_count: int, * image: string|null, + * image_ratio: string|null, * manufacturer_name: string|null, * } $row */ @@ -79,6 +81,7 @@ public function byCollectionAndPlayer(null|string $collectionId, string $playerI piecesCount: $row['pieces_count'], manufacturerName: $row['manufacturer_name'], image: $row['image'], + imageRatio: $row['image_ratio'] !== null ? (float) $row['image_ratio'] : null, comment: $row['comment'], addedAt: new DateTimeImmutable($row['added_at']), ); @@ -132,6 +135,7 @@ public function getByPuzzleIdAndPlayerId(string $puzzleId, string $playerId, nul p.ean, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name FROM collection_item ci JOIN puzzle p ON ci.puzzle_id = p.id @@ -150,6 +154,7 @@ public function getByPuzzleIdAndPlayerId(string $puzzleId, string $playerId, nul * ean: string|null, * pieces_count: int, * image: string|null, + * image_ratio: string|null, * manufacturer_name: string|null, * }|false $row */ @@ -171,6 +176,7 @@ public function getByPuzzleIdAndPlayerId(string $puzzleId, string $playerId, nul piecesCount: $row['pieces_count'], manufacturerName: $row['manufacturer_name'], image: $row['image'], + imageRatio: $row['image_ratio'] !== null ? (float) $row['image_ratio'] : null, comment: $row['comment'], addedAt: new DateTimeImmutable($row['added_at']), ); diff --git a/src/Query/GetConversations.php b/src/Query/GetConversations.php index 956b61c5..4bcc9093 100644 --- a/src/Query/GetConversations.php +++ b/src/Query/GetConversations.php @@ -86,6 +86,7 @@ public function forPlayer(string $playerId, null|ConversationStatus $status = nu p.id AS puzzle_id, p.name AS puzzle_name, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS puzzle_image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS puzzle_image_ratio, sli.listing_type AS listing_type, sli.price AS listing_price FROM conversation c @@ -125,6 +126,7 @@ public function forPlayer(string $playerId, null|ConversationStatus $status = nu * puzzle_id: null|string, * puzzle_name: null|string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * listing_type: null|string, * listing_price: null|string, * } $row @@ -145,6 +147,7 @@ public function forPlayer(string $playerId, null|ConversationStatus $status = nu puzzleId: $row['puzzle_id'], sellSwapListItemId: $row['sell_swap_list_item_id'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, listingType: $row['listing_type'], listingPrice: $row['listing_price'] !== null ? (float) $row['listing_price'] : null, lastMessageSentByMe: (bool) ($row['last_message_sent_by_me'] ?? false), @@ -173,6 +176,7 @@ public function pendingRequestsForPlayer(string $playerId): array p.id AS puzzle_id, p.name AS puzzle_name, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS puzzle_image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS puzzle_image_ratio, sli.listing_type AS listing_type, sli.price AS listing_price FROM conversation c @@ -211,6 +215,7 @@ public function pendingRequestsForPlayer(string $playerId): array * puzzle_id: null|string, * puzzle_name: null|string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * listing_type: null|string, * listing_price: null|string, * } $row @@ -231,6 +236,7 @@ public function pendingRequestsForPlayer(string $playerId): array puzzleId: $row['puzzle_id'], sellSwapListItemId: $row['sell_swap_list_item_id'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, listingType: $row['listing_type'], listingPrice: $row['listing_price'] !== null ? (float) $row['listing_price'] : null, ); @@ -278,6 +284,7 @@ public function ignoredForPlayer(string $playerId): array p.id AS puzzle_id, p.name AS puzzle_name, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS puzzle_image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS puzzle_image_ratio, sli.listing_type AS listing_type, sli.price AS listing_price FROM conversation c @@ -319,6 +326,7 @@ public function ignoredForPlayer(string $playerId): array * puzzle_id: null|string, * puzzle_name: null|string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * listing_type: null|string, * listing_price: null|string, * } $row @@ -339,6 +347,7 @@ public function ignoredForPlayer(string $playerId): array puzzleId: $row['puzzle_id'], sellSwapListItemId: $row['sell_swap_list_item_id'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, listingType: $row['listing_type'], listingPrice: $row['listing_price'] !== null ? (float) $row['listing_price'] : null, lastMessageSentByMe: (bool) ($row['last_message_sent_by_me'] ?? false), diff --git a/src/Query/GetFastestGroups.php b/src/Query/GetFastestGroups.php index 24cda521..beb59e3f 100644 --- a/src/Query/GetFastestGroups.php +++ b/src/Query/GetFastestGroups.php @@ -38,6 +38,7 @@ public function perPiecesCount(int $piecesCount, int $howManyPlayers, null|Count puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, pieces_count, comment, tracked_at, @@ -111,6 +112,7 @@ public function perPiecesCount(int $piecesCount, int $howManyPlayers, null|Count * puzzle_name: string, * puzzle_alternative_name: null|string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: int, * player_name: null|string, * player_code: string, diff --git a/src/Query/GetFastestPairs.php b/src/Query/GetFastestPairs.php index ab405db2..92db60bd 100644 --- a/src/Query/GetFastestPairs.php +++ b/src/Query/GetFastestPairs.php @@ -38,6 +38,7 @@ public function perPiecesCount(int $piecesCount, int $howManyPlayers, null|Count puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, pieces_count, comment, tracked_at, @@ -111,6 +112,7 @@ public function perPiecesCount(int $piecesCount, int $howManyPlayers, null|Count * puzzle_name: string, * puzzle_alternative_name: null|string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: int, * player_name: null|string, * player_code: string, diff --git a/src/Query/GetFastestPlayers.php b/src/Query/GetFastestPlayers.php index e57ecd7e..2ab983f5 100644 --- a/src/Query/GetFastestPlayers.php +++ b/src/Query/GetFastestPlayers.php @@ -54,6 +54,7 @@ public function perPiecesCount(int $piecesCount, int $limit, null|CountryCode $c puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle.pieces_count, puzzle_solving_time.comment, puzzle_solving_time.tracked_at, @@ -99,6 +100,7 @@ public function perPiecesCount(int $piecesCount, int $limit, null|CountryCode $c * puzzle_name: string, * puzzle_alternative_name: null|string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: int, * player_name: null|string, * player_code: string, diff --git a/src/Query/GetLastSolvedPuzzle.php b/src/Query/GetLastSolvedPuzzle.php index f4b1e741..f9c645c4 100644 --- a/src/Query/GetLastSolvedPuzzle.php +++ b/src/Query/GetLastSolvedPuzzle.php @@ -33,6 +33,7 @@ public function forPlayer(string $playerId, int $limit): array puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle_solving_time.seconds_to_solve AS time, puzzle_solving_time.player_id AS player_id, player.name AS player_name, @@ -95,6 +96,7 @@ public function forPlayer(string $playerId, int $limit): array * puzzle_alternative_name: null|string, * manufacturer_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: int, * pieces_count: int, * comment: null|string, @@ -130,6 +132,7 @@ public function limit(int $limit): array puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle_solving_time.seconds_to_solve AS time, puzzle_solving_time.player_id AS player_id, player.name AS player_name, @@ -190,6 +193,7 @@ public function limit(int $limit): array * puzzle_alternative_name: null|string, * manufacturer_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: int, * pieces_count: int, * comment: null|string, @@ -249,6 +253,7 @@ public function ofPlayerFavorites(int $limit, string $playerId): array puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, pst.seconds_to_solve AS time, pst.player_id AS player_id, player.name AS player_name, @@ -311,6 +316,7 @@ public function ofPlayerFavorites(int $limit, string $playerId): array * puzzle_alternative_name: null|string, * manufacturer_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: int, * pieces_count: int, * comment: null|string, diff --git a/src/Query/GetLentPuzzles.php b/src/Query/GetLentPuzzles.php index 78f587ed..00731715 100644 --- a/src/Query/GetLentPuzzles.php +++ b/src/Query/GetLentPuzzles.php @@ -31,6 +31,7 @@ public function byOwnerId(string $ownerId): array p.alternative_name as puzzle_alternative_name, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name, holder.id as current_holder_id, holder.name as current_holder_name, @@ -58,6 +59,7 @@ public function byOwnerId(string $ownerId): array * puzzle_alternative_name: string|null, * pieces_count: int, * image: string|null, + * image_ratio: string|null, * manufacturer_name: string|null, * current_holder_id: string|null, * current_holder_name: string|null, @@ -73,6 +75,7 @@ public function byOwnerId(string $ownerId): array piecesCount: $row['pieces_count'], manufacturerName: $row['manufacturer_name'], image: $row['image'], + imageRatio: $row['image_ratio'] !== null ? (float) $row['image_ratio'] : null, currentHolderId: $row['current_holder_id'], currentHolderName: $row['current_holder_name'] ?? $row['holder_text_name'] ?? '', currentHolderAvatar: $row['current_holder_avatar'], @@ -125,6 +128,7 @@ public function getByPuzzleIdAndOwnerId(string $puzzleId, string $ownerId): null p.alternative_name as puzzle_alternative_name, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name, holder.id as current_holder_id, holder.name as current_holder_name, @@ -146,6 +150,7 @@ public function getByPuzzleIdAndOwnerId(string $puzzleId, string $ownerId): null * puzzle_alternative_name: string|null, * pieces_count: int, * image: string|null, + * image_ratio: string|null, * manufacturer_name: string|null, * current_holder_id: string|null, * current_holder_name: string|null, @@ -168,6 +173,7 @@ public function getByPuzzleIdAndOwnerId(string $puzzleId, string $ownerId): null piecesCount: $row['pieces_count'], manufacturerName: $row['manufacturer_name'], image: $row['image'], + imageRatio: $row['image_ratio'] !== null ? (float) $row['image_ratio'] : null, currentHolderId: $row['current_holder_id'], currentHolderName: $row['current_holder_name'] ?? $row['holder_text_name'] ?? '', currentHolderAvatar: $row['current_holder_avatar'], diff --git a/src/Query/GetMarketplaceListings.php b/src/Query/GetMarketplaceListings.php index a46c1f53..c70e5dcc 100644 --- a/src/Query/GetMarketplaceListings.php +++ b/src/Query/GetMarketplaceListings.php @@ -46,6 +46,7 @@ public function search( p.alternative_name AS puzzle_alternative_name, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS puzzle_image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS puzzle_image_ratio, m.name AS manufacturer_name, ssli.listing_type, ssli.price, @@ -228,6 +229,7 @@ public function search( * puzzle_alternative_name: string|null, * pieces_count: int, * puzzle_image: string|null, + * puzzle_image_ratio: string|null, * manufacturer_name: string|null, * listing_type: string, * price: string|null, @@ -270,6 +272,7 @@ public function search( puzzleAlternativeName: $row['puzzle_alternative_name'], piecesCount: (int) $row['pieces_count'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, manufacturerName: $row['manufacturer_name'], listingType: $row['listing_type'], price: $row['price'] !== null ? (float) $row['price'] : null, @@ -303,6 +306,7 @@ public function byItemId(string $itemId): MarketplaceListingItem p.alternative_name AS puzzle_alternative_name, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS puzzle_image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS puzzle_image_ratio, m.name AS manufacturer_name, ssli.listing_type, ssli.price, @@ -344,6 +348,7 @@ public function byItemId(string $itemId): MarketplaceListingItem * puzzle_alternative_name: string|null, * pieces_count: int, * puzzle_image: string|null, + * puzzle_image_ratio: string|null, * manufacturer_name: string|null, * listing_type: string, * price: string|null, @@ -386,6 +391,7 @@ public function byItemId(string $itemId): MarketplaceListingItem puzzleAlternativeName: $row['puzzle_alternative_name'], piecesCount: (int) $row['pieces_count'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, manufacturerName: $row['manufacturer_name'], listingType: $row['listing_type'], price: $row['price'] !== null ? (float) $row['price'] : null, diff --git a/src/Query/GetMostSolvedPuzzles.php b/src/Query/GetMostSolvedPuzzles.php index 77744649..f4ef98fc 100644 --- a/src/Query/GetMostSolvedPuzzles.php +++ b/src/Query/GetMostSolvedPuzzles.php @@ -23,6 +23,7 @@ public function top(int $howManyPuzzles): array SELECT puzzle.id AS puzzle_id, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, puzzle_statistics.solved_times_count AS solved_times, @@ -51,6 +52,7 @@ public function top(int $howManyPuzzles): array * puzzle_name: string, * puzzle_alternative_name: null|string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * solved_times: int, * pieces_count: int, * average_time_solo: string, @@ -77,6 +79,7 @@ public function topInMonth(int $limit, int $month, int $year): array SELECT puzzle.id AS puzzle_id, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, count(puzzle_solving_time.puzzle_id) AS solved_times, @@ -109,6 +112,7 @@ public function topInMonth(int $limit, int $month, int $year): array * puzzle_name: string, * puzzle_alternative_name: null|string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * solved_times: int, * pieces_count: int, * average_time_solo: null|string, diff --git a/src/Query/GetNotifications.php b/src/Query/GetNotifications.php index b8485648..0b8fb234 100644 --- a/src/Query/GetNotifications.php +++ b/src/Query/GetNotifications.php @@ -80,6 +80,7 @@ public function forPlayer(string $playerId, int $limit, int $offset = 0): array puzzle.pieces_count, puzzle_solving_time.seconds_to_solve AS time, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle_solving_time.team ->> 'team_id' AS team_id, CASE WHEN puzzle_solving_time.team IS NOT NULL THEN JSON_AGG( @@ -164,6 +165,7 @@ public function forPlayer(string $playerId, int $limit, int $offset = 0): array NULL::int AS pieces_count, NULL::int AS time, NULL::varchar AS puzzle_image, + NULL::numeric AS puzzle_image_ratio, NULL::varchar AS team_id, NULL::json AS players, -- Lending fields @@ -238,6 +240,7 @@ public function forPlayer(string $playerId, int $limit, int $offset = 0): array NULL::int AS pieces_count, NULL::int AS time, NULL::varchar AS puzzle_image, + NULL::numeric AS puzzle_image_ratio, NULL::varchar AS team_id, NULL::json AS players, -- Lending fields (NULL) @@ -307,6 +310,7 @@ public function forPlayer(string $playerId, int $limit, int $offset = 0): array NULL::int AS pieces_count, NULL::int AS time, NULL::varchar AS puzzle_image, + NULL::numeric AS puzzle_image_ratio, NULL::varchar AS team_id, NULL::json AS players, -- Lending fields (NULL) @@ -376,6 +380,7 @@ public function forPlayer(string $playerId, int $limit, int $offset = 0): array NULL::int AS pieces_count, NULL::int AS time, NULL::varchar AS puzzle_image, + NULL::numeric AS puzzle_image_ratio, NULL::varchar AS team_id, NULL::json AS players, -- Lending fields (NULL) @@ -453,6 +458,7 @@ public function forPlayer(string $playerId, int $limit, int $offset = 0): array NULL::int AS pieces_count, NULL::int AS time, NULL::varchar AS puzzle_image, + NULL::numeric AS puzzle_image_ratio, NULL::varchar AS team_id, NULL::json AS players, -- Lending fields (NULL) diff --git a/src/Query/GetPendingPuzzleProposals.php b/src/Query/GetPendingPuzzleProposals.php index 849afd33..b9c655d3 100644 --- a/src/Query/GetPendingPuzzleProposals.php +++ b/src/Query/GetPendingPuzzleProposals.php @@ -134,6 +134,7 @@ private function fetchPuzzleDetails(array $puzzleIds): array p.name, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name, (SELECT COUNT(*) FROM puzzle_solving_time pst WHERE pst.puzzle_id = p.id) as times_count FROM puzzle p @@ -158,6 +159,7 @@ private function fetchPuzzleDetails(array $puzzleIds): array name: $name, piecesCount: is_int($row['pieces_count']) ? $row['pieces_count'] : null, image: is_string($row['image']) ? $row['image'] : null, + imageRatio: is_string($row['image_ratio']) ? (float) $row['image_ratio'] : null, manufacturerName: is_string($row['manufacturer_name']) ? $row['manufacturer_name'] : null, timesCount: (int) $timesCount, ); diff --git a/src/Query/GetPlayerSolvedPuzzles.php b/src/Query/GetPlayerSolvedPuzzles.php index b0dca066..b593a405 100644 --- a/src/Query/GetPlayerSolvedPuzzles.php +++ b/src/Query/GetPlayerSolvedPuzzles.php @@ -68,6 +68,7 @@ public function byTimeId(string $timeId): SolvedPuzzleDetail puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle_solving_time.seconds_to_solve AS time, puzzle_solving_time.player_id AS player_id, pieces_count, @@ -115,6 +116,7 @@ public function byTimeId(string $timeId): SolvedPuzzleDetail * manufacturer_name: string, * manufacturer_id: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: int, * pieces_count: int, * comment: null|string, @@ -171,6 +173,7 @@ public function soloByPlayerId( puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle_solving_time.seconds_to_solve AS time, puzzle_solving_time.player_id AS player_id, pieces_count, @@ -251,6 +254,7 @@ public function soloByPlayerId( * puzzle_alternative_name: null|string, * manufacturer_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: int, * pieces_count: int, * comment: null|string, @@ -330,6 +334,7 @@ public function duoByPlayerId( puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, pst.seconds_to_solve AS time, pst.player_id AS player_id, pieces_count, @@ -382,6 +387,7 @@ public function duoByPlayerId( * puzzle_alternative_name: null|string, * manufacturer_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: int, * pieces_count: int, * comment: null|string, @@ -467,6 +473,7 @@ public function teamByPlayerId( puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, pst.seconds_to_solve AS time, pst.player_id AS player_id, pieces_count, @@ -519,6 +526,7 @@ public function teamByPlayerId( * puzzle_alternative_name: null|string, * manufacturer_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: int, * pieces_count: int, * comment: null|string, @@ -595,6 +603,7 @@ public function byPuzzleIdAndPlayerId(string $puzzleId, string $playerId): null| puzzle.pieces_count, manufacturer.name AS manufacturer_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS image_ratio, puzzle_solving_time.finished_at FROM puzzle_solving_time INNER JOIN puzzle ON puzzle.id = puzzle_solving_time.puzzle_id @@ -629,6 +638,7 @@ public function byPuzzleIdAndPlayerId(string $puzzleId, string $playerId): null| * pieces_count: int, * manufacturer_name: null|string, * image: null|string, + * image_ratio: null|string, * finished_at: null|string, * } $data */ @@ -642,6 +652,7 @@ public function byPuzzleIdAndPlayerId(string $puzzleId, string $playerId): null| piecesCount: $data['pieces_count'], manufacturerName: $data['manufacturer_name'], image: $data['image'], + imageRatio: $data['image_ratio'] !== null ? (float) $data['image_ratio'] : null, finishedAt: $data['finished_at'] !== null ? new DateTimeImmutable($data['finished_at']) : null, ); } @@ -668,6 +679,7 @@ public function allByPlayerId(string $playerId): array puzzle.pieces_count, manufacturer.name AS manufacturer_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS image_ratio, puzzle_solving_time.finished_at FROM puzzle_solving_time INNER JOIN puzzle ON puzzle.id = puzzle_solving_time.puzzle_id @@ -695,6 +707,7 @@ public function allByPlayerId(string $playerId): array * pieces_count: int, * manufacturer_name: null|string, * image: null|string, + * image_ratio: null|string, * finished_at: null|string, * } $row */ @@ -708,6 +721,7 @@ public function allByPlayerId(string $playerId): array piecesCount: $row['pieces_count'], manufacturerName: $row['manufacturer_name'], image: $row['image'], + imageRatio: $row['image_ratio'] !== null ? (float) $row['image_ratio'] : null, finishedAt: $row['finished_at'] !== null ? new DateTimeImmutable($row['finished_at']) : null, ); }, $data); diff --git a/src/Query/GetPuzzleChangeRequests.php b/src/Query/GetPuzzleChangeRequests.php index 801ccc68..3642a007 100644 --- a/src/Query/GetPuzzleChangeRequests.php +++ b/src/Query/GetPuzzleChangeRequests.php @@ -95,6 +95,7 @@ public function byId(string $id): null|PuzzleChangeRequestOverview p.name as puzzle_name, p.pieces_count as puzzle_pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS puzzle_image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS puzzle_image_ratio, pm.name as puzzle_manufacturer_name, reporter.id as reporter_id, reporter.name as reporter_name, @@ -152,6 +153,7 @@ private function byStatus(PuzzleReportStatus $status, string $orderBy): array p.name as puzzle_name, p.pieces_count as puzzle_pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS puzzle_image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS puzzle_image_ratio, pm.name as puzzle_manufacturer_name, reporter.id as reporter_id, reporter.name as reporter_name, diff --git a/src/Query/GetPuzzleMergeRequests.php b/src/Query/GetPuzzleMergeRequests.php index 81e37f18..dfee9806 100644 --- a/src/Query/GetPuzzleMergeRequests.php +++ b/src/Query/GetPuzzleMergeRequests.php @@ -89,10 +89,12 @@ public function byId(string $id): null|PuzzleMergeRequestOverview source_p.name as source_puzzle_name, source_p.pieces_count as source_puzzle_pieces_count, CASE WHEN source_p.hide_image_until IS NOT NULL AND source_p.hide_image_until > NOW() THEN NULL ELSE source_p.image END AS source_puzzle_image, + CASE WHEN source_p.hide_image_until IS NOT NULL AND source_p.hide_image_until > NOW() THEN NULL ELSE source_p.image_ratio END AS source_puzzle_image_ratio, source_m.name as source_puzzle_manufacturer_name, survivor_p.name as survivor_puzzle_name, survivor_p.pieces_count as survivor_puzzle_pieces_count, CASE WHEN survivor_p.hide_image_until IS NOT NULL AND survivor_p.hide_image_until > NOW() THEN NULL ELSE survivor_p.image END AS survivor_puzzle_image, + CASE WHEN survivor_p.hide_image_until IS NOT NULL AND survivor_p.hide_image_until > NOW() THEN NULL ELSE survivor_p.image_ratio END AS survivor_puzzle_image_ratio, survivor_m.name as survivor_puzzle_manufacturer_name, reporter.id as reporter_id, reporter.name as reporter_name, @@ -140,10 +142,12 @@ private function byStatus(PuzzleReportStatus $status, string $orderBy): array source_p.name as source_puzzle_name, source_p.pieces_count as source_puzzle_pieces_count, CASE WHEN source_p.hide_image_until IS NOT NULL AND source_p.hide_image_until > NOW() THEN NULL ELSE source_p.image END AS source_puzzle_image, + CASE WHEN source_p.hide_image_until IS NOT NULL AND source_p.hide_image_until > NOW() THEN NULL ELSE source_p.image_ratio END AS source_puzzle_image_ratio, source_m.name as source_puzzle_manufacturer_name, survivor_p.name as survivor_puzzle_name, survivor_p.pieces_count as survivor_puzzle_pieces_count, CASE WHEN survivor_p.hide_image_until IS NOT NULL AND survivor_p.hide_image_until > NOW() THEN NULL ELSE survivor_p.image END AS survivor_puzzle_image, + CASE WHEN survivor_p.hide_image_until IS NOT NULL AND survivor_p.hide_image_until > NOW() THEN NULL ELSE survivor_p.image_ratio END AS survivor_puzzle_image_ratio, survivor_m.name as survivor_puzzle_manufacturer_name, reporter.id as reporter_id, reporter.name as reporter_name, diff --git a/src/Query/GetPuzzleOverview.php b/src/Query/GetPuzzleOverview.php index a3a92e37..c5cb0300 100644 --- a/src/Query/GetPuzzleOverview.php +++ b/src/Query/GetPuzzleOverview.php @@ -34,6 +34,7 @@ public function byEan(string $ean): PuzzleOverview puzzle.id AS puzzle_id, puzzle.name AS puzzle_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle.hide_image_until, puzzle.alternative_name AS puzzle_alternative_name, puzzle.pieces_count, @@ -61,6 +62,7 @@ public function byEan(string $ean): PuzzleOverview * puzzle_id: string, * puzzle_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * puzzle_alternative_name: null|string, * puzzle_approved: bool, * manufacturer_id: string, @@ -106,6 +108,7 @@ public function byId(string $puzzleId): PuzzleOverview puzzle.id AS puzzle_id, puzzle.name AS puzzle_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle.hide_image_until, puzzle.alternative_name AS puzzle_alternative_name, puzzle.pieces_count, @@ -133,6 +136,7 @@ public function byId(string $puzzleId): PuzzleOverview * puzzle_id: string, * puzzle_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * puzzle_alternative_name: null|string, * puzzle_approved: bool, * manufacturer_id: string, @@ -184,6 +188,7 @@ public function byTagId(string $tagId): array puzzle.id AS puzzle_id, puzzle.name AS puzzle_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle.hide_image_until, puzzle.alternative_name AS puzzle_alternative_name, puzzle.pieces_count, @@ -219,6 +224,7 @@ public function byTagId(string $tagId): array * puzzle_id: string, * puzzle_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * puzzle_alternative_name: null|string, * puzzle_approved: bool, * manufacturer_name: string, diff --git a/src/Query/GetPuzzleTracking.php b/src/Query/GetPuzzleTracking.php index 5e141755..8933e652 100644 --- a/src/Query/GetPuzzleTracking.php +++ b/src/Query/GetPuzzleTracking.php @@ -33,6 +33,7 @@ public function byId(string $trackingId): TrackedPuzzleDetail puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle_tracking.player_id AS player_id, pieces_count, player.name AS player_name, @@ -75,6 +76,7 @@ public function byId(string $trackingId): TrackedPuzzleDetail * manufacturer_name: string, * manufacturer_id: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * pieces_count: int, * comment: null|string, * players: null|string, diff --git a/src/Query/GetPuzzlesOverview.php b/src/Query/GetPuzzlesOverview.php index 95e4f1af..72c607cd 100644 --- a/src/Query/GetPuzzlesOverview.php +++ b/src/Query/GetPuzzlesOverview.php @@ -24,6 +24,7 @@ public function allApprovedOrAddedByPlayer(null|string $playerId): array puzzle.id AS puzzle_id, puzzle.name AS puzzle_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle.alternative_name AS puzzle_alternative_name, puzzle.pieces_count, puzzle.is_available, @@ -61,6 +62,7 @@ public function allApprovedOrAddedByPlayer(null|string $playerId): array * puzzle_id: string, * puzzle_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * puzzle_alternative_name: null|string, * puzzle_approved: bool, * manufacturer_name: string, diff --git a/src/Query/GetRanking.php b/src/Query/GetRanking.php index bc302106..2fd9bd70 100644 --- a/src/Query/GetRanking.php +++ b/src/Query/GetRanking.php @@ -83,6 +83,7 @@ public function allForPlayer(string $playerId): array p.alternative_name AS puzzle_alternative_name, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS puzzle_image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS puzzle_image_ratio, m.name AS manufacturer_name FROM RankedTimes rt @@ -114,6 +115,7 @@ public function allForPlayer(string $playerId): array * puzzle_alternative_name: null|string, * pieces_count: int, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * manufacturer_name: string, * } $row */ @@ -180,6 +182,7 @@ public function ofPuzzleForPlayer(string $puzzleId, string $playerId): null|Play puzzle.alternative_name AS puzzle_alternative_name, puzzle.pieces_count, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, manufacturer.name AS manufacturer_name FROM RankedTimes @@ -201,6 +204,7 @@ public function ofPuzzleForPlayer(string $puzzleId, string $playerId): null|Play * puzzle_alternative_name: null|string, * pieces_count: int, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * manufacturer_name: string, * } $row */ diff --git a/src/Query/GetRecentActivity.php b/src/Query/GetRecentActivity.php index f3d685c3..9b5b0ae7 100644 --- a/src/Query/GetRecentActivity.php +++ b/src/Query/GetRecentActivity.php @@ -33,6 +33,7 @@ public function forPlayer(string $playerId, int $limit): array puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle_solving_time.seconds_to_solve AS time, puzzle_solving_time.player_id AS player_id, player.name AS player_name, @@ -95,6 +96,7 @@ public function forPlayer(string $playerId, int $limit): array * puzzle_alternative_name: null|string, * manufacturer_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: null|int, * pieces_count: int, * comment: null|string, @@ -130,6 +132,7 @@ public function latest(int $limit): array puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle_solving_time.seconds_to_solve AS time, puzzle_solving_time.player_id AS player_id, player.name AS player_name, @@ -190,6 +193,7 @@ public function latest(int $limit): array * puzzle_alternative_name: null|string, * manufacturer_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: null|int, * pieces_count: int, * comment: null|string, @@ -249,6 +253,7 @@ public function ofPlayerFavorites(int $limit, string $playerId): array puzzle.name AS puzzle_name, puzzle.alternative_name AS puzzle_alternative_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, pst.seconds_to_solve AS time, pst.player_id AS player_id, player.name AS player_name, @@ -311,6 +316,7 @@ public function ofPlayerFavorites(int $limit, string $playerId): array * puzzle_alternative_name: null|string, * manufacturer_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: null|int, * pieces_count: int, * comment: null|string, diff --git a/src/Query/GetSellSwapListItems.php b/src/Query/GetSellSwapListItems.php index ad519203..ea8b451c 100644 --- a/src/Query/GetSellSwapListItems.php +++ b/src/Query/GetSellSwapListItems.php @@ -42,6 +42,7 @@ public function byPlayerId(string $playerId): array p.ean, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name FROM sell_swap_list_item ssli JOIN puzzle p ON ssli.puzzle_id = p.id @@ -74,6 +75,7 @@ public function byPlayerId(string $playerId): array * ean: string|null, * pieces_count: int, * image: string|null, + * image_ratio: string|null, * manufacturer_name: string|null, * } $row */ @@ -88,6 +90,7 @@ public function byPlayerId(string $playerId): array piecesCount: $row['pieces_count'], manufacturerName: $row['manufacturer_name'], image: $row['image'], + imageRatio: $row['image_ratio'] !== null ? (float) $row['image_ratio'] : null, listingType: ListingType::from($row['listing_type']), price: $row['price'] !== null ? (float) $row['price'] : null, condition: PuzzleCondition::from($row['condition']), @@ -122,6 +125,7 @@ public function byItemId(string $itemId): SellSwapListItemOverview p.ean, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name FROM sell_swap_list_item ssli JOIN puzzle p ON ssli.puzzle_id = p.id @@ -156,6 +160,7 @@ public function byItemId(string $itemId): SellSwapListItemOverview * ean: string|null, * pieces_count: int, * image: string|null, + * image_ratio: string|null, * manufacturer_name: string|null, * } $row */ @@ -170,6 +175,7 @@ public function byItemId(string $itemId): SellSwapListItemOverview piecesCount: $row['pieces_count'], manufacturerName: $row['manufacturer_name'], image: $row['image'], + imageRatio: $row['image_ratio'] !== null ? (float) $row['image_ratio'] : null, listingType: ListingType::from($row['listing_type']), price: $row['price'] !== null ? (float) $row['price'] : null, condition: PuzzleCondition::from($row['condition']), diff --git a/src/Query/GetSoldSwappedHistory.php b/src/Query/GetSoldSwappedHistory.php index d68d9c46..903997e4 100644 --- a/src/Query/GetSoldSwappedHistory.php +++ b/src/Query/GetSoldSwappedHistory.php @@ -34,6 +34,7 @@ public function byPlayerId(string $playerId): array p.identification_number as puzzle_identification_number, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name, bp.id as buyer_player_id, bp.name as buyer_player_name, @@ -63,6 +64,7 @@ public function byPlayerId(string $playerId): array * puzzle_identification_number: string|null, * pieces_count: int, * image: string|null, + * image_ratio: string|null, * manufacturer_name: string|null, * buyer_player_id: string|null, * buyer_player_name: string|null, @@ -79,6 +81,7 @@ public function byPlayerId(string $playerId): array piecesCount: $row['pieces_count'], manufacturerName: $row['manufacturer_name'], image: $row['image'], + imageRatio: $row['image_ratio'] !== null ? (float) $row['image_ratio'] : null, listingType: ListingType::from($row['listing_type']), price: $row['price'] !== null ? (float) $row['price'] : null, buyerPlayerId: $row['buyer_player_id'], diff --git a/src/Query/GetTransactionRatings.php b/src/Query/GetTransactionRatings.php index d767b03e..90a53af0 100644 --- a/src/Query/GetTransactionRatings.php +++ b/src/Query/GetTransactionRatings.php @@ -40,6 +40,7 @@ public function forPlayer(string $playerId, int $limit = 20, int $offset = 0): a p.pieces_count AS puzzle_pieces_count, ssi.listing_type AS transaction_type, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS puzzle_image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS puzzle_image_ratio, p.id AS puzzle_id FROM transaction_rating tr JOIN player reviewer ON tr.reviewer_id = reviewer.id @@ -74,6 +75,7 @@ public function forPlayer(string $playerId, int $limit = 20, int $offset = 0): a * puzzle_pieces_count: null|int|string, * transaction_type: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * puzzle_id: string, * } $row */ @@ -92,6 +94,7 @@ public function forPlayer(string $playerId, int $limit = 20, int $offset = 0): a puzzlePiecesCount: $row['puzzle_pieces_count'] !== null ? (int) $row['puzzle_pieces_count'] : null, transactionType: $row['transaction_type'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, puzzleId: $row['puzzle_id'], ); }, $data); @@ -159,6 +162,7 @@ public function pendingRatings(string $playerId): array ssi.id AS sold_swapped_item_id, p.name AS puzzle_name, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS puzzle_image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS puzzle_image_ratio, p.pieces_count, CASE WHEN ssi.seller_id = :playerId THEN COALESCE(buyer.name, buyer.code) @@ -201,6 +205,7 @@ public function pendingRatings(string $playerId): array * sold_swapped_item_id: string, * puzzle_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * pieces_count: null|int|string, * other_player_name: string, * other_player_code: string, @@ -214,6 +219,7 @@ public function pendingRatings(string $playerId): array soldSwappedItemId: $row['sold_swapped_item_id'], puzzleName: $row['puzzle_name'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, piecesCount: $row['pieces_count'] !== null ? (int) $row['pieces_count'] : null, otherPlayerName: $row['other_player_name'], otherPlayerCode: $row['other_player_code'], diff --git a/src/Query/GetUnsolvedPuzzles.php b/src/Query/GetUnsolvedPuzzles.php index 5fa7018a..d3a79d16 100644 --- a/src/Query/GetUnsolvedPuzzles.php +++ b/src/Query/GetUnsolvedPuzzles.php @@ -29,6 +29,7 @@ public function byPlayerId(string $playerId): array p.ean, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name, MIN(ci.added_at) as added_at FROM collection_item ci @@ -63,6 +64,7 @@ public function byPlayerId(string $playerId): array * ean: string|null, * pieces_count: int, * image: string|null, + * image_ratio: string|null, * manufacturer_name: string|null, * added_at: string, * } $row @@ -77,6 +79,7 @@ public function byPlayerId(string $playerId): array piecesCount: $row['pieces_count'], manufacturerName: $row['manufacturer_name'], image: $row['image'], + imageRatio: $row['image_ratio'] !== null ? (float) $row['image_ratio'] : null, addedAt: new DateTimeImmutable($row['added_at']), isBorrowed: false, borrowedFromPlayerId: null, @@ -123,6 +126,7 @@ public function byPuzzleIdAndPlayerId(string $puzzleId, string $playerId): null| p.ean, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name, MIN(ci.added_at) as added_at FROM collection_item ci @@ -160,6 +164,7 @@ public function byPuzzleIdAndPlayerId(string $puzzleId, string $playerId): null| * ean: string|null, * pieces_count: int, * image: string|null, + * image_ratio: string|null, * manufacturer_name: string|null, * added_at: string, * } $data @@ -174,6 +179,7 @@ public function byPuzzleIdAndPlayerId(string $puzzleId, string $playerId): null| piecesCount: $data['pieces_count'], manufacturerName: $data['manufacturer_name'], image: $data['image'], + imageRatio: $data['image_ratio'] !== null ? (float) $data['image_ratio'] : null, addedAt: new DateTimeImmutable($data['added_at']), isBorrowed: false, borrowedFromPlayerId: null, diff --git a/src/Query/GetWishListItems.php b/src/Query/GetWishListItems.php index f52747cc..7cac73ed 100644 --- a/src/Query/GetWishListItems.php +++ b/src/Query/GetWishListItems.php @@ -32,6 +32,7 @@ public function byPlayerId(string $playerId): array p.ean, p.pieces_count, CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image END AS image, + CASE WHEN p.hide_image_until IS NOT NULL AND p.hide_image_until > NOW() THEN NULL ELSE p.image_ratio END AS image_ratio, m.name as manufacturer_name FROM wish_list_item wli JOIN puzzle p ON wli.puzzle_id = p.id @@ -56,6 +57,7 @@ public function byPlayerId(string $playerId): array * ean: string|null, * pieces_count: int, * image: string|null, + * image_ratio: string|null, * manufacturer_name: string|null, * } $row */ @@ -70,6 +72,7 @@ public function byPlayerId(string $playerId): array piecesCount: $row['pieces_count'], manufacturerName: $row['manufacturer_name'], image: $row['image'], + imageRatio: $row['image_ratio'] !== null ? (float) $row['image_ratio'] : null, removeOnCollectionAdd: (bool) $row['remove_on_collection_add'], addedAt: new DateTimeImmutable($row['added_at']), ); diff --git a/src/Query/SearchPuzzle.php b/src/Query/SearchPuzzle.php index 7dbb5b13..7ae4afe2 100644 --- a/src/Query/SearchPuzzle.php +++ b/src/Query/SearchPuzzle.php @@ -100,6 +100,7 @@ public function byUserInput( puzzle.id AS puzzle_id, puzzle.name AS puzzle_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle.alternative_name AS puzzle_alternative_name, puzzle.pieces_count, puzzle.is_available, @@ -153,6 +154,7 @@ public function byUserInput( pb.puzzle_id, pb.puzzle_name, pb.puzzle_image, + pb.puzzle_image_ratio, pb.puzzle_alternative_name, pb.pieces_count, pb.is_available, @@ -221,6 +223,7 @@ public function byUserInput( * puzzle_id: string, * puzzle_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * puzzle_alternative_name: null|string, * puzzle_approved: bool, * manufacturer_name: string, @@ -248,7 +251,7 @@ public function byUserInput( * Search for all puzzles by EAN code. * Strips leading zeros for flexible matching. * - * @return array + * @return array */ public function allByEan(string $ean): array { @@ -264,6 +267,7 @@ public function allByEan(string $ean): array puzzle.id AS puzzle_id, puzzle.name AS puzzle_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle.ean AS puzzle_ean, puzzle.pieces_count, manufacturer.id AS manufacturer_id, @@ -273,7 +277,7 @@ public function allByEan(string $ean): array WHERE puzzle.ean LIKE :eanPattern SQL; - /** @var array $rows */ + /** @var array $rows */ $rows = $this->database ->executeQuery($query, [ 'eanPattern' => '%' . $eanSearch . '%', @@ -293,6 +297,7 @@ public function byBrandId(string $brandId): array puzzle.id AS puzzle_id, puzzle.name AS puzzle_name, CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image END AS puzzle_image, + CASE WHEN puzzle.hide_image_until IS NOT NULL AND puzzle.hide_image_until > NOW() THEN NULL ELSE puzzle.image_ratio END AS puzzle_image_ratio, puzzle.alternative_name AS puzzle_alternative_name, puzzle.pieces_count, puzzle.approved AS puzzle_approved, @@ -318,6 +323,7 @@ public function byBrandId(string $brandId): array * puzzle_id: string, * puzzle_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * puzzle_alternative_name: null|string, * manufacturer_name: string, * pieces_count: int, diff --git a/src/Results/AutocompletePuzzle.php b/src/Results/AutocompletePuzzle.php index 06e6728a..e2b98f89 100644 --- a/src/Results/AutocompletePuzzle.php +++ b/src/Results/AutocompletePuzzle.php @@ -14,6 +14,7 @@ public function __construct( public string $manufacturerName, public int $piecesCount, public null|string $puzzleImage, + public null|float $puzzleImageRatio, public null|string $puzzleEan, public null|string $puzzleIdentificationNumber, ) { @@ -24,6 +25,7 @@ public function __construct( * puzzle_id: string, * puzzle_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * puzzle_alternative_name: null|string, * puzzle_approved: bool, * manufacturer_name: string, @@ -42,6 +44,7 @@ public static function fromDatabaseRow(array $row): self manufacturerName: $row['manufacturer_name'], piecesCount: $row['pieces_count'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, puzzleEan: $row['puzzle_ean'], puzzleIdentificationNumber: $row['puzzle_identification_number'], ); diff --git a/src/Results/BorrowedPuzzleOverview.php b/src/Results/BorrowedPuzzleOverview.php index 16c515fe..0702ea0a 100644 --- a/src/Results/BorrowedPuzzleOverview.php +++ b/src/Results/BorrowedPuzzleOverview.php @@ -16,6 +16,7 @@ public function __construct( public int $piecesCount, public null|string $manufacturerName, public null|string $image, + public null|float $imageRatio, public null|string $ownerId, public string $ownerName, public null|string $ownerAvatar, diff --git a/src/Results/CollectionItemOverview.php b/src/Results/CollectionItemOverview.php index 0ac4d0d2..eced9492 100644 --- a/src/Results/CollectionItemOverview.php +++ b/src/Results/CollectionItemOverview.php @@ -18,6 +18,7 @@ public function __construct( public int $piecesCount, public null|string $manufacturerName, public null|string $image, + public null|float $imageRatio, public null|string $comment, public DateTimeImmutable $addedAt, ) { diff --git a/src/Results/ConversationOverview.php b/src/Results/ConversationOverview.php index bbe294a1..cbc8ec71 100644 --- a/src/Results/ConversationOverview.php +++ b/src/Results/ConversationOverview.php @@ -24,6 +24,7 @@ public function __construct( public null|string $puzzleId = null, public null|string $sellSwapListItemId = null, public null|string $puzzleImage = null, + public null|float $puzzleImageRatio = null, public null|string $listingType = null, public null|float $listingPrice = null, public bool $lastMessageSentByMe = false, diff --git a/src/Results/LentPuzzleOverview.php b/src/Results/LentPuzzleOverview.php index d0dbe758..ffd38de8 100644 --- a/src/Results/LentPuzzleOverview.php +++ b/src/Results/LentPuzzleOverview.php @@ -16,6 +16,7 @@ public function __construct( public int $piecesCount, public null|string $manufacturerName, public null|string $image, + public null|float $imageRatio, public null|string $currentHolderId, public string $currentHolderName, public null|string $currentHolderAvatar, diff --git a/src/Results/MarketplaceListingItem.php b/src/Results/MarketplaceListingItem.php index 8bd1eeeb..bf528118 100644 --- a/src/Results/MarketplaceListingItem.php +++ b/src/Results/MarketplaceListingItem.php @@ -13,6 +13,7 @@ public function __construct( public null|string $puzzleAlternativeName, public int $piecesCount, public null|string $puzzleImage, + public null|float $puzzleImageRatio, public null|string $manufacturerName, public string $listingType, public null|float $price, diff --git a/src/Results/MergePuzzleInfo.php b/src/Results/MergePuzzleInfo.php index f992d4f2..edf8106a 100644 --- a/src/Results/MergePuzzleInfo.php +++ b/src/Results/MergePuzzleInfo.php @@ -11,6 +11,7 @@ public function __construct( public string $name, public null|int $piecesCount, public null|string $image, + public null|float $imageRatio, public null|string $manufacturerName, public int $timesCount, ) { diff --git a/src/Results/MostSolvedPuzzle.php b/src/Results/MostSolvedPuzzle.php index 684de21e..3590c7b5 100644 --- a/src/Results/MostSolvedPuzzle.php +++ b/src/Results/MostSolvedPuzzle.php @@ -15,6 +15,7 @@ public function __construct( public int $averageTimeSolo, public int $fastestTimeSolo, public null|string $puzzleImage, + public null|float $puzzleImageRatio, public string $manufacturerName, ) { } @@ -25,6 +26,7 @@ public function __construct( * puzzle_name: string, * puzzle_alternative_name: null|string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * solved_times: int, * pieces_count: int, * average_time_solo: null|string, @@ -43,6 +45,7 @@ public static function fromDatabaseRow(array $row): self averageTimeSolo: (int) ($row['average_time_solo'] ?? 0), fastestTimeSolo: $row['fastest_time_solo'] ?? 0, puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, manufacturerName: $row['manufacturer_name'], ); } diff --git a/src/Results/PendingTransactionRating.php b/src/Results/PendingTransactionRating.php index 735ac387..c37701f4 100644 --- a/src/Results/PendingTransactionRating.php +++ b/src/Results/PendingTransactionRating.php @@ -12,6 +12,7 @@ public function __construct( public string $soldSwappedItemId, public string $puzzleName, public null|string $puzzleImage, + public null|float $puzzleImageRatio, public null|int $piecesCount, public string $otherPlayerName, public string $otherPlayerCode, diff --git a/src/Results/PlayerNotification.php b/src/Results/PlayerNotification.php index 9d59aad5..45e40030 100644 --- a/src/Results/PlayerNotification.php +++ b/src/Results/PlayerNotification.php @@ -28,6 +28,7 @@ public function __construct( public null|int $piecesCount, public null|int $time, public null|string $puzzleImage, + public null|float $puzzleImageRatio, public null|string $teamId, /** @var null|array */ public null|array $players, @@ -139,6 +140,7 @@ public static function fromDatabaseRow(array $row): self piecesCount: isset($row['pieces_count']) && is_numeric($row['pieces_count']) ? (int) $row['pieces_count'] : null, time: isset($row['time']) && is_numeric($row['time']) ? (int) $row['time'] : null, puzzleImage: is_string($puzzleImage) ? $puzzleImage : null, + puzzleImageRatio: is_string($row['puzzle_image_ratio'] ?? null) ? (float) $row['puzzle_image_ratio'] : null, teamId: is_string($teamId) ? $teamId : null, players: $players, // Lending fields diff --git a/src/Results/PlayerRanking.php b/src/Results/PlayerRanking.php index a1acc8fc..3dbe044b 100644 --- a/src/Results/PlayerRanking.php +++ b/src/Results/PlayerRanking.php @@ -17,6 +17,7 @@ public function __construct( public int $piecesCount, public int $time, public null|string $puzzleImage, + public null|float $puzzleImageRatio, ) { } @@ -31,6 +32,7 @@ public function __construct( * puzzle_alternative_name: null|string, * pieces_count: int, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * manufacturer_name: string, * } $row */ @@ -47,6 +49,7 @@ public static function fromDatabaseRow(array $row): self piecesCount: $row['pieces_count'], time: $row['time'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, ); } } diff --git a/src/Results/PuzzleChangeRequestOverview.php b/src/Results/PuzzleChangeRequestOverview.php index 5bc1030c..3bc0ab9d 100644 --- a/src/Results/PuzzleChangeRequestOverview.php +++ b/src/Results/PuzzleChangeRequestOverview.php @@ -19,6 +19,7 @@ public function __construct( public string $puzzleName, public int $puzzlePiecesCount, public null|string $puzzleImage, + public null|float $puzzleImageRatio, public null|string $puzzleManufacturerName, public string $reporterId, public null|string $reporterName, @@ -74,6 +75,7 @@ public static function fromDatabaseRow(array $row): self puzzleName: $puzzleName, puzzlePiecesCount: $puzzlePiecesCount, puzzleImage: is_string($row['puzzle_image']) ? $row['puzzle_image'] : null, + puzzleImageRatio: is_string($row['puzzle_image_ratio'] ?? null) ? (float) $row['puzzle_image_ratio'] : null, puzzleManufacturerName: is_string($row['puzzle_manufacturer_name']) ? $row['puzzle_manufacturer_name'] : null, reporterId: $reporterId, reporterName: is_string($row['reporter_name']) ? $row['reporter_name'] : null, diff --git a/src/Results/PuzzleOverview.php b/src/Results/PuzzleOverview.php index 0f32ce4f..0e026538 100644 --- a/src/Results/PuzzleOverview.php +++ b/src/Results/PuzzleOverview.php @@ -24,6 +24,7 @@ public function __construct( public int $fastestTimeTeam, public int $solvedTimes, public null|string $puzzleImage, + public null|float $puzzleImageRatio, public bool $isAvailable, public null|string $puzzleEan, public null|string $puzzleIdentificationNumber, @@ -36,6 +37,7 @@ public function __construct( * puzzle_id: string, * puzzle_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * puzzle_alternative_name: null|string, * puzzle_approved: bool, * manufacturer_id: string, @@ -72,6 +74,7 @@ public static function fromDatabaseRow(array $row): self fastestTimeTeam: (int) $row['fastest_time_team'], solvedTimes: $row['solved_times'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, isAvailable: $row['is_available'], puzzleEan: $row['puzzle_ean'], puzzleIdentificationNumber: $row['puzzle_identification_number'], diff --git a/src/Results/RecentActivityItem.php b/src/Results/RecentActivityItem.php index c09e6472..60b3dfb2 100644 --- a/src/Results/RecentActivityItem.php +++ b/src/Results/RecentActivityItem.php @@ -23,6 +23,7 @@ public function __construct( public int $piecesCount, public null|int $time, public null|string $puzzleImage, + public null|float $puzzleImageRatio, public null|string $comment, public DateTimeImmutable $trackedAt, public null|DateTimeImmutable $finishedAt, @@ -63,6 +64,7 @@ public function isSpeedMode(): bool * puzzle_alternative_name: null|string, * manufacturer_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: null|int, * pieces_count: int, * comment: null|string, @@ -106,6 +108,7 @@ public static function fromDatabaseRow(array $row): self piecesCount: $row['pieces_count'], time: $row['time'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, comment: $row['comment'], trackedAt: new DateTimeImmutable($row['tracked_at']), finishedAt: $row['finished_at'] !== null ? new DateTimeImmutable($row['finished_at']) : null, diff --git a/src/Results/SellSwapListItemOverview.php b/src/Results/SellSwapListItemOverview.php index 9b3b22be..c5d12b45 100644 --- a/src/Results/SellSwapListItemOverview.php +++ b/src/Results/SellSwapListItemOverview.php @@ -20,6 +20,7 @@ public function __construct( public int $piecesCount, public null|string $manufacturerName, public null|string $image, + public null|float $imageRatio, public ListingType $listingType, public null|float $price, public PuzzleCondition $condition, diff --git a/src/Results/SoldSwappedItemOverview.php b/src/Results/SoldSwappedItemOverview.php index 6345e4b5..30e04525 100644 --- a/src/Results/SoldSwappedItemOverview.php +++ b/src/Results/SoldSwappedItemOverview.php @@ -18,6 +18,7 @@ public function __construct( public int $piecesCount, public null|string $manufacturerName, public null|string $image, + public null|float $imageRatio, public ListingType $listingType, public null|float $price, public null|string $buyerPlayerId, diff --git a/src/Results/SolvedPuzzle.php b/src/Results/SolvedPuzzle.php index d94b5ddb..92930a47 100644 --- a/src/Results/SolvedPuzzle.php +++ b/src/Results/SolvedPuzzle.php @@ -23,6 +23,7 @@ public function __construct( public int $piecesCount, public null|int $time, public null|string $puzzleImage, + public null|float $puzzleImageRatio, public null|string $comment, public DateTimeImmutable $trackedAt, public null|string $finishedPuzzlePhoto, @@ -55,6 +56,7 @@ public function __construct( * puzzle_alternative_name: null|string, * manufacturer_name: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: null|int, * pieces_count: int, * comment: null|string, @@ -100,6 +102,7 @@ public static function fromDatabaseRow(array $row): self piecesCount: $row['pieces_count'], time: $row['time'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, comment: $row['comment'], trackedAt: new DateTimeImmutable($row['tracked_at']), finishedPuzzlePhoto: $row['finished_puzzle_photo'], diff --git a/src/Results/SolvedPuzzleDetail.php b/src/Results/SolvedPuzzleDetail.php index 4f6e589d..7ad23016 100644 --- a/src/Results/SolvedPuzzleDetail.php +++ b/src/Results/SolvedPuzzleDetail.php @@ -21,6 +21,7 @@ public function __construct( public int $piecesCount, public null|int $time, public null|string $puzzleImage, + public null|float $puzzleImageRatio, public null|string $comment, /** @var null|array */ public null|array $players, @@ -43,6 +44,7 @@ public function __construct( * manufacturer_name: string, * manufacturer_id: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * time: null|int, * pieces_count: int, * comment: null|string, @@ -73,6 +75,7 @@ public static function fromDatabaseRow(array $row): self piecesCount: $row['pieces_count'], time: $row['time'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, comment: $row['comment'], players: $players, finishedAt: $row['finished_at'] !== null ? new DateTimeImmutable($row['finished_at']) : null, diff --git a/src/Results/SolvedPuzzleOverview.php b/src/Results/SolvedPuzzleOverview.php index ce21e89f..3dc23f28 100644 --- a/src/Results/SolvedPuzzleOverview.php +++ b/src/Results/SolvedPuzzleOverview.php @@ -17,6 +17,7 @@ public function __construct( public int $piecesCount, public null|string $manufacturerName, public null|string $image, + public null|float $imageRatio, public null|DateTimeImmutable $finishedAt, ) { } diff --git a/src/Results/TrackedPuzzleDetail.php b/src/Results/TrackedPuzzleDetail.php index fcde04f5..2c2b1a6f 100644 --- a/src/Results/TrackedPuzzleDetail.php +++ b/src/Results/TrackedPuzzleDetail.php @@ -20,6 +20,7 @@ public function __construct( public string $manufacturerId, public int $piecesCount, public null|string $puzzleImage, + public null|float $puzzleImageRatio, public null|string $comment, /** @var null|array */ public null|array $players, @@ -39,6 +40,7 @@ public function __construct( * manufacturer_name: string, * manufacturer_id: string, * puzzle_image: null|string, + * puzzle_image_ratio: null|string, * pieces_count: int, * comment: null|string, * players: null|string, @@ -64,6 +66,7 @@ public static function fromDatabaseRow(array $row): self manufacturerId: $row['manufacturer_id'], piecesCount: $row['pieces_count'], puzzleImage: $row['puzzle_image'], + puzzleImageRatio: $row['puzzle_image_ratio'] !== null ? (float) $row['puzzle_image_ratio'] : null, comment: $row['comment'], players: $players, finishedAt: $row['finished_at'] !== null ? new DateTimeImmutable($row['finished_at']) : null, diff --git a/src/Results/TransactionRatingView.php b/src/Results/TransactionRatingView.php index a61c7ba5..47e4ebc4 100644 --- a/src/Results/TransactionRatingView.php +++ b/src/Results/TransactionRatingView.php @@ -22,6 +22,7 @@ public function __construct( public null|int $puzzlePiecesCount, public string $transactionType, public null|string $puzzleImage, + public null|float $puzzleImageRatio, public null|string $puzzleId, ) { } diff --git a/src/Results/UnsolvedPuzzleItem.php b/src/Results/UnsolvedPuzzleItem.php index a842d320..7f1ad09c 100644 --- a/src/Results/UnsolvedPuzzleItem.php +++ b/src/Results/UnsolvedPuzzleItem.php @@ -17,6 +17,7 @@ public function __construct( public int $piecesCount, public null|string $manufacturerName, public null|string $image, + public null|float $imageRatio, public DateTimeImmutable $addedAt, public bool $isBorrowed, public null|string $borrowedFromPlayerId, diff --git a/src/Results/WishListItemOverview.php b/src/Results/WishListItemOverview.php index 4ea9d7c4..5cc627da 100644 --- a/src/Results/WishListItemOverview.php +++ b/src/Results/WishListItemOverview.php @@ -18,6 +18,7 @@ public function __construct( public int $piecesCount, public null|string $manufacturerName, public null|string $image, + public null|float $imageRatio, public bool $removeOnCollectionAdd, public DateTimeImmutable $addedAt, ) { diff --git a/src/Services/ImageOptimizer.php b/src/Services/ImageOptimizer.php index b0a9a68c..29e584f9 100644 --- a/src/Services/ImageOptimizer.php +++ b/src/Services/ImageOptimizer.php @@ -17,6 +17,26 @@ public function __construct( ) { } + public function getImageRatio(string $filePath): float + { + $imagick = new Imagick(); + $imagick->readImage($filePath); + + try { + $width = $imagick->getImageWidth(); + $height = $imagick->getImageHeight(); + + if ($height === 0) { + return 1.0; + } + + return $width / $height; + } finally { + $imagick->clear(); + $imagick->destroy(); + } + } + public function optimize(string $filePath): void { // Use JPEG size hint for faster decoding of large JPEGs (skips unnecessary DCT detail) diff --git a/src/Services/PlayersComparison.php b/src/Services/PlayersComparison.php index 128f4c37..18f71f20 100644 --- a/src/Services/PlayersComparison.php +++ b/src/Services/PlayersComparison.php @@ -42,6 +42,7 @@ public function compare(array $playerRanking, array $opponentRanking): array manufacturerName: $playerRanking[$commonPuzzleId]->manufacturerName, piecesCount: $playerRanking[$commonPuzzleId]->piecesCount, puzzleImage: $playerRanking[$commonPuzzleId]->puzzleImage, + puzzleImageRatio: $playerRanking[$commonPuzzleId]->puzzleImageRatio, ); } diff --git a/src/Twig/LazyImageTwigExtension.php b/src/Twig/LazyImageTwigExtension.php index ace5b172..539df2ca 100644 --- a/src/Twig/LazyImageTwigExtension.php +++ b/src/Twig/LazyImageTwigExtension.php @@ -44,11 +44,15 @@ public function lazyPuzzleImage( int $size = 80, string $class = '', null|int $maxHeight = null, + null|float $imageRatio = null, ): string { $src = $this->getImageSrc($path, $filter); $sizeClass = $this->getSizeClass($size); $maxHeight ??= $size; + // Calculate accurate width/height from aspect ratio + [$imgWidth, $imgHeight] = $this->calculateDimensions($size, $maxHeight, $imageRatio); + // First 4 images are above-the-fold (eager loading) $isEager = $position <= 4; $loading = $isEager ? 'eager' : 'lazy'; @@ -75,13 +79,48 @@ public function lazyPuzzleImage( htmlspecialchars($alt ?? '', ENT_QUOTES, 'UTF-8'), $loading, $imgClasses, - $size, - $maxHeight, + $imgWidth, + $imgHeight, $maxHeight, $extraAttrs, ); } + /** + * Calculate accurate width and height from aspect ratio. + * + * @return array{int, int} [width, height] + */ + private function calculateDimensions(int $size, int $maxHeight, null|float $ratio): array + { + // No ratio available - fall back to container dimensions (square assumption) + if ($ratio === null || $ratio <= 0) { + return [$size, $maxHeight]; + } + + if ($ratio >= 1) { + // Landscape or square: width is constrained by $size + $width = $size; + $height = (int) round($size / $ratio); + + if ($height > $maxHeight) { + $height = $maxHeight; + $width = (int) round($maxHeight * $ratio); + } + } else { + // Portrait: height is constrained by $maxHeight + $height = $maxHeight; + $width = (int) round($maxHeight * $ratio); + + if ($width > $size) { + $width = $size; + $height = (int) round($size / $ratio); + } + } + + return [$width, $height]; + } + private function getImageSrc(null|string $path, string $filter): string { if ($path === null) { diff --git a/src/Value/Comparison.php b/src/Value/Comparison.php index 4a0b4bd4..a21bb31d 100644 --- a/src/Value/Comparison.php +++ b/src/Value/Comparison.php @@ -17,6 +17,7 @@ public function __construct( public string $manufacturerName, public int $piecesCount, public null|string $puzzleImage, + public null|float $puzzleImageRatio, ) { $this->diff = $this->playerTime - $this->opponentTime; } diff --git a/templates/_player_solvings.html.twig b/templates/_player_solvings.html.twig index 6e41aefd..b5b79e91 100644 --- a/templates/_player_solvings.html.twig +++ b/templates/_player_solvings.html.twig @@ -13,7 +13,8 @@ loop.index, 90, 'rounded-2', - 60 + 60, + solved_puzzle[0].puzzleImageRatio ) }} {% endif %} diff --git a/templates/_puzzle_item.html.twig b/templates/_puzzle_item.html.twig index e25caf6d..a330bbcd 100644 --- a/templates/_puzzle_item.html.twig +++ b/templates/_puzzle_item.html.twig @@ -22,7 +22,8 @@ 999, 90, 'rounded-2', - 60 + 60, + puzzle.puzzleImageRatio ) }} diff --git a/templates/_puzzle_library_item.html.twig b/templates/_puzzle_library_item.html.twig index 781fcacb..12c96b63 100644 --- a/templates/_puzzle_library_item.html.twig +++ b/templates/_puzzle_library_item.html.twig @@ -84,7 +84,9 @@ item.puzzleName, 999, 60, - 'rounded' + 'rounded', + null, + item.imageRatio ) }} diff --git a/templates/compare_players.html.twig b/templates/compare_players.html.twig index 7621e78d..200d15ad 100644 --- a/templates/compare_players.html.twig +++ b/templates/compare_players.html.twig @@ -66,7 +66,9 @@ 'puzzle_img_alt'|trans({'%puzzle%': comparison.manufacturerName ~ ' ' ~ comparison.puzzleName}), loop.index, 100, - 'rounded-2' + 'rounded-2', + null, + comparison.puzzleImageRatio ) }} {% endif %} diff --git a/templates/components/GlobalSearch.html.twig b/templates/components/GlobalSearch.html.twig index be81084e..c2ff21d6 100644 --- a/templates/components/GlobalSearch.html.twig +++ b/templates/components/GlobalSearch.html.twig @@ -76,7 +76,8 @@ loop.index, 90, 'rounded-2', - 88 + 88, + puzzle.puzzleImageRatio ) }} {% endif %} diff --git a/templates/components/LadderTable.html.twig b/templates/components/LadderTable.html.twig index 653fbe3a..ac1442a2 100644 --- a/templates/components/LadderTable.html.twig +++ b/templates/components/LadderTable.html.twig @@ -16,7 +16,8 @@ loop.index, 80, 'rounded-2 mt-1', - 65 + 65, + solving_time.puzzleImageRatio ) }} diff --git a/templates/components/MostSolvedPuzzles.html.twig b/templates/components/MostSolvedPuzzles.html.twig index e7db5b8a..b3d5a2bd 100644 --- a/templates/components/MostSolvedPuzzles.html.twig +++ b/templates/components/MostSolvedPuzzles.html.twig @@ -48,7 +48,9 @@ 'puzzle_img_alt'|trans({'%puzzle%': most_solved_puzzle.manufacturerName ~ ' ' ~ most_solved_puzzle.puzzleName}), loop.index, 80, - 'rounded-2' + 'rounded-2', + null, + most_solved_puzzle.puzzleImageRatio ) }} diff --git a/templates/components/RecentActivity.html.twig b/templates/components/RecentActivity.html.twig index ebe153f8..6aa0dcaf 100644 --- a/templates/components/RecentActivity.html.twig +++ b/templates/components/RecentActivity.html.twig @@ -13,7 +13,9 @@ 'puzzle_img_alt'|trans({'%puzzle%': item.manufacturerName ~ ' ' ~ item.puzzleName}), loop.index, 80, - 'rounded-2' + 'rounded-2', + null, + item.puzzleImageRatio ) }} diff --git a/templates/marketplace/_listing_card.html.twig b/templates/marketplace/_listing_card.html.twig index 65472901..5f702063 100644 --- a/templates/marketplace/_listing_card.html.twig +++ b/templates/marketplace/_listing_card.html.twig @@ -78,7 +78,8 @@ 999, 90, 'rounded lazy-img-marketplace' ~ (item.reserved ? ' grayscale' : ''), - 60 + 60, + item.puzzleImageRatio ) }} diff --git a/templates/notifications.html.twig b/templates/notifications.html.twig index 22c97caf..3b0ed228 100644 --- a/templates/notifications.html.twig +++ b/templates/notifications.html.twig @@ -231,7 +231,9 @@ 'puzzle_img_alt'|trans({'%puzzle%': notification.manufacturerName ~ ' ' ~ notification.puzzleName}), loop.index, 60, - 'rounded-2' + 'rounded-2', + null, + notification.puzzleImageRatio ) }} {% endif %} diff --git a/templates/sell-swap/_item_card.html.twig b/templates/sell-swap/_item_card.html.twig index 0bd0d3f7..defd6b89 100644 --- a/templates/sell-swap/_item_card.html.twig +++ b/templates/sell-swap/_item_card.html.twig @@ -81,7 +81,8 @@ 999, 90, 'rounded lazy-img-marketplace' ~ (item.reserved ? ' grayscale' : ''), - 60 + 60, + item.imageRatio ) }} diff --git a/templates/sell-swap/edit_item.html.twig b/templates/sell-swap/edit_item.html.twig index 77aa31d2..3407a147 100644 --- a/templates/sell-swap/edit_item.html.twig +++ b/templates/sell-swap/edit_item.html.twig @@ -20,7 +20,9 @@ item.puzzle.name, 1, 60, - 'rounded me-3' + 'rounded me-3', + null, + item.puzzle.imageRatio ) }} {% endif %}
diff --git a/templates/sell-swap/edit_modal.html.twig b/templates/sell-swap/edit_modal.html.twig index 0591a96a..83e6667f 100644 --- a/templates/sell-swap/edit_modal.html.twig +++ b/templates/sell-swap/edit_modal.html.twig @@ -13,7 +13,9 @@ item.puzzle.name, 1, 60, - 'rounded me-3' + 'rounded me-3', + null, + item.puzzle.imageRatio ) }} {% endif %}
diff --git a/templates/sell-swap/history.html.twig b/templates/sell-swap/history.html.twig index 34306096..31704dd3 100644 --- a/templates/sell-swap/history.html.twig +++ b/templates/sell-swap/history.html.twig @@ -45,7 +45,9 @@ item.puzzleName, loop.index, 40, - 'rounded me-2' + 'rounded me-2', + null, + item.imageRatio ) }} {% endif %}
diff --git a/tests/Query/GetPuzzleOverviewTest.php b/tests/Query/GetPuzzleOverviewTest.php index 02ff10b3..c33a42fd 100644 --- a/tests/Query/GetPuzzleOverviewTest.php +++ b/tests/Query/GetPuzzleOverviewTest.php @@ -38,6 +38,14 @@ public function testByIdReturnsPuzzleWithStatistics(): void self::assertGreaterThan(0, $overview->averageTimeSolo); } + public function testByIdReturnsNullImageRatioForPuzzleWithoutRatio(): void + { + // Puzzles from fixtures don't have image_ratio set + $overview = $this->query->byId(PuzzleFixture::PUZZLE_500_01); + + self::assertNull($overview->puzzleImageRatio); + } + public function testByIdReturnsZeroStatisticsForUnsolvedPuzzle(): void { // PUZZLE_9000 has no solving times in fixtures diff --git a/tests/Services/ImageOptimizerTest.php b/tests/Services/ImageOptimizerTest.php new file mode 100644 index 00000000..6166dac2 --- /dev/null +++ b/tests/Services/ImageOptimizerTest.php @@ -0,0 +1,70 @@ +optimizer = new ImageOptimizer(new NullLogger()); + } + + public function testGetImageRatioForSquareImage(): void + { + $path = $this->createTestImage(100, 100); + + try { + $ratio = $this->optimizer->getImageRatio($path); + self::assertEqualsWithDelta(1.0, $ratio, 0.001); + } finally { + unlink($path); + } + } + + public function testGetImageRatioForPortraitImage(): void + { + $path = $this->createTestImage(135, 200); + + try { + $ratio = $this->optimizer->getImageRatio($path); + self::assertEqualsWithDelta(0.675, $ratio, 0.001); + } finally { + unlink($path); + } + } + + public function testGetImageRatioForLandscapeImage(): void + { + $path = $this->createTestImage(200, 135); + + try { + $ratio = $this->optimizer->getImageRatio($path); + self::assertEqualsWithDelta(1.481, $ratio, 0.001); + } finally { + unlink($path); + } + } + + private function createTestImage(int $width, int $height): string + { + $path = tempnam(sys_get_temp_dir(), 'test_img_') . '.jpg'; + + $imagick = new Imagick(); + $imagick->newImage($width, $height, 'white'); + $imagick->setImageFormat('jpeg'); + $imagick->writeImage($path); + $imagick->clear(); + $imagick->destroy(); + + return $path; + } +} diff --git a/tests/Twig/LazyImageTwigExtensionTest.php b/tests/Twig/LazyImageTwigExtensionTest.php new file mode 100644 index 00000000..58f73b93 --- /dev/null +++ b/tests/Twig/LazyImageTwigExtensionTest.php @@ -0,0 +1,98 @@ +extension = new LazyImageTwigExtension($imageThumbnail); + } + + public function testSquareImageWithRatio(): void + { + $html = $this->extension->lazyPuzzleImage('/img.jpg', 'puzzle_small', 'alt', 999, 80, '', null, 1.0); + + self::assertStringContainsString('width="80"', $html); + self::assertStringContainsString('height="80"', $html); + } + + public function testPortraitImageWithRatio(): void + { + // 135x200 image → ratio 0.675 + $html = $this->extension->lazyPuzzleImage('/img.jpg', 'puzzle_small', 'alt', 999, 90, '', 60, 0.675); + + // Portrait: height=60, width=60*0.675=40.5 → 41 + self::assertStringContainsString('width="41"', $html); + self::assertStringContainsString('height="60"', $html); + } + + public function testLandscapeImageWithRatio(): void + { + // 200x135 image → ratio ~1.481 + $html = $this->extension->lazyPuzzleImage('/img.jpg', 'puzzle_small', 'alt', 999, 90, '', 60, 1.481); + + // Landscape: width=90, height=90/1.481=60.8 → capped at maxHeight=60 + // So: height=60, width=60*1.481=88.9 → 89 + self::assertStringContainsString('width="89"', $html); + self::assertStringContainsString('height="60"', $html); + } + + public function testNullRatioFallsBackToContainerDimensions(): void + { + $html = $this->extension->lazyPuzzleImage('/img.jpg', 'puzzle_small', 'alt', 999, 90, '', 60, null); + + // Falls back to size x maxHeight + self::assertStringContainsString('width="90"', $html); + self::assertStringContainsString('height="60"', $html); + } + + public function testZeroRatioTreatedAsNull(): void + { + $html = $this->extension->lazyPuzzleImage('/img.jpg', 'puzzle_small', 'alt', 999, 90, '', 60, 0.0); + + // Zero ratio is invalid, falls back to container dimensions + self::assertStringContainsString('width="90"', $html); + self::assertStringContainsString('height="60"', $html); + } + + public function testNegativeRatioTreatedAsNull(): void + { + $html = $this->extension->lazyPuzzleImage('/img.jpg', 'puzzle_small', 'alt', 999, 80, '', null, -1.0); + + self::assertStringContainsString('width="80"', $html); + self::assertStringContainsString('height="80"', $html); + } + + public function testNullPathReturnsPlaceholder(): void + { + $html = $this->extension->lazyPuzzleImage(null, 'puzzle_small', 'alt', 999, 80, '', null, 0.675); + + self::assertStringContainsString('/img/placeholder-puzzle.jpg', $html); + } + + public function testEagerLoadingForFirstFourPositions(): void + { + $html = $this->extension->lazyPuzzleImage('/img.jpg', 'puzzle_small', 'alt', 1, 80, '', null, 1.0); + + self::assertStringContainsString('loading="eager"', $html); + self::assertStringContainsString('lazy-img loaded', $html); + } + + public function testLazyLoadingForPositionBeyondFour(): void + { + $html = $this->extension->lazyPuzzleImage('/img.jpg', 'puzzle_small', 'alt', 5, 80, '', null, 1.0); + + self::assertStringContainsString('loading="lazy"', $html); + self::assertStringContainsString('onload=', $html); + } +}