diff --git a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php index fe3329ec28169..b1c6ead630bd5 100644 --- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php +++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php @@ -1,6 +1,6 @@ layoutFactory = $layoutFactory ?: ObjectManager::getInstance()->get(LayoutFactory::class); $this->urlEncoder = $urlEncoder ?: ObjectManager::getInstance()->get(EncoderInterface::class); $this->categoryRepository = $categoryRepository ?? ObjectManager::getInstance() - ->get(CategoryRepositoryInterface::class); + ->get(CategoryRepositoryInterface::class); $this->optionsData = $optionsData ?: ObjectManager::getInstance()->get(OptionsData::class); $this->categoryConditionProcessor = $categoryConditionProcessor ?: ObjectManager::getInstance() - ->get(CategoryConditionProcessor::class); + ->get(CategoryConditionProcessor::class); parent::__construct( $context, $data @@ -255,8 +255,8 @@ public function getCacheKeyInfo() */ public function getProductPriceHtml( Product $product, - $priceType = null, - $renderZone = \Magento\Framework\Pricing\Render::ZONE_ITEM_LIST, + $priceType = null, + $renderZone = \Magento\Framework\Pricing\Render::ZONE_ITEM_LIST, array $arguments = [] ) { if (!isset($arguments['zone'])) { @@ -383,9 +383,7 @@ public function getBaseCollection(): Collection $collection = $this->_addProductAttributesAndPrices($collection) ->addStoreFilter() ->addAttributeToFilter(Product::STATUS, ProductStatus::STATUS_ENABLED) - ->addAttributeToSort('entity_id', 'desc') - ->setPageSize($this->getPageSize()) - ->setCurPage($this->getRequest()->getParam($this->getData('page_var_name'), 1)); + ->addAttributeToSort('entity_id', 'desc'); $conditions = $this->getConditions(); $conditions->collectValidatedAttributes($collection); @@ -397,6 +395,21 @@ public function getBaseCollection(): Collection */ $collection->distinct(true); + $currentPage = (int)$this->getRequest()->getParam($this->getData('page_var_name'), 1); + + // Apply pagination with total limit after all other operations + if ($this->showPager()) { + $pageSize = $this->getProductsPerPage(); + $totalLimit = $this->getProductsCount(); + + $offset = ($currentPage - 1) * $pageSize; + $limit = min($pageSize, max(0, $totalLimit - $offset)); + + $collection->getSelect()->limit($limit, $offset); + } else { + $collection->getSelect()->limit($this->getProductsCount()); + } + return $collection; } diff --git a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php index e37a95573176a..df3f80b8ee186 100644 --- a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php +++ b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php @@ -1,6 +1,6 @@ visibility->expects($this->once())->method('getVisibleInCatalogIds') ->willReturn([Visibility::VISIBILITY_IN_CATALOG, Visibility::VISIBILITY_BOTH]); + + $select = $this->createMock(\Magento\Framework\DB\Select::class); + $select->expects($this->once()) + ->method('limit') + ->with($expectedLimit, 0) + ->willReturnSelf(); + $collection = $this->createMock(Collection::class); $collection->expects($this->once())->method('setVisibility') ->with([Visibility::VISIBILITY_IN_CATALOG, Visibility::VISIBILITY_BOTH]) @@ -299,9 +306,8 @@ public function testCreateCollection($pagerEnable, $productsCount, $productsPerP ->with(Product::STATUS, ProductStatus::STATUS_ENABLED) ->willReturnSelf(); $collection->expects($this->once())->method('addAttributeToSort')->with('entity_id', 'desc')->willReturnSelf(); - $collection->expects($this->once())->method('setPageSize')->with($expectedPageSize)->willReturnSelf(); - $collection->expects($this->once())->method('setCurPage')->willReturnSelf(); $collection->expects($this->once())->method('distinct')->willReturnSelf(); + $collection->expects($this->once())->method('getSelect')->willReturn($select); $this->collectionFactory->expects($this->once())->method('create')->willReturn($collection); $this->productsList->setData('conditions_encoded', 'some_serialized_conditions'); @@ -323,6 +329,9 @@ public function testCreateCollection($pagerEnable, $productsCount, $productsPerP $this->productsList->setData('show_pager', $pagerEnable); $this->productsList->setData('products_count', $productsCount); + $this->productsList->setData('page_var_name', 'page'); + + $this->request->expects($this->any())->method('getParam')->with('page')->willReturn(1); $this->assertSame($collection, $this->productsList->createCollection()); } @@ -333,10 +342,10 @@ public function testCreateCollection($pagerEnable, $productsCount, $productsPerP public static function createCollectionDataProvider() { return [ - [true, 1, null, 5], + [true, 1, null, 1], [true, 5, null, 5], [true, 10, null, 5], - [true, 1, 2, 2], + [true, 1, 2, 1], [true, 5, 3, 3], [true, 10, 7, 7], [false, 1, null, 1], @@ -348,6 +357,88 @@ public static function createCollectionDataProvider() ]; } + /** + * Test that collection limit respects total products count on subsequent pages + * + * @param int $currentPage + * @param int $productsPerPage + * @param int $totalProducts + * @param int $expectedLimit + * @param int $expectedOffset + */ + #[DataProvider('createCollectionWithTotalLimitDataProvider')] + public function testCreateCollectionWithTotalLimit( + $currentPage, + $productsPerPage, + $totalProducts, + $expectedLimit, + $expectedOffset + ) { + $this->visibility->expects($this->once())->method('getVisibleInCatalogIds') + ->willReturn([Visibility::VISIBILITY_IN_CATALOG, Visibility::VISIBILITY_BOTH]); + + $select = $this->createMock(\Magento\Framework\DB\Select::class); + $select->expects($this->once()) + ->method('limit') + ->with($expectedLimit, $expectedOffset) + ->willReturnSelf(); + + $collection = $this->createMock(Collection::class); + $collection->expects($this->once())->method('setVisibility')->willReturnSelf(); + $collection->expects($this->once())->method('addMinimalPrice')->willReturnSelf(); + $collection->expects($this->once())->method('addFinalPrice')->willReturnSelf(); + $collection->expects($this->once())->method('addTaxPercents')->willReturnSelf(); + $collection->expects($this->once())->method('addAttributeToSelect')->willReturnSelf(); + $collection->expects($this->once())->method('addUrlRewrite')->willReturnSelf(); + $collection->expects($this->once())->method('addStoreFilter')->willReturnSelf(); + $collection->expects($this->once()) + ->method('addAttributeToFilter') + ->with(Product::STATUS, ProductStatus::STATUS_ENABLED) + ->willReturnSelf(); + $collection->expects($this->once())->method('addAttributeToSort')->with('entity_id', 'desc')->willReturnSelf(); + $collection->expects($this->once())->method('distinct')->willReturnSelf(); + $collection->expects($this->once())->method('getSelect')->willReturn($select); + + $this->collectionFactory->expects($this->once())->method('create')->willReturn($collection); + $this->productsList->setData('conditions_encoded', 'some_serialized_conditions'); + + $this->widgetConditionsHelper->expects($this->once()) + ->method('decode') + ->with('some_serialized_conditions') + ->willReturn([]); + + $this->builder->expects($this->once())->method('attachConditionToCollection') + ->with($collection, $this->getConditionsForCollection($collection)) + ->willReturnSelf(); + + $this->productsList->setData('products_per_page', $productsPerPage); + $this->productsList->setData('show_pager', true); + $this->productsList->setData('products_count', $totalProducts); + $this->productsList->setData('page_var_name', 'page'); + + $this->request->expects($this->once())->method('getParam')->with('page')->willReturn($currentPage); + + $this->assertSame($collection, $this->productsList->createCollection()); + } + + /** + * Data provider for testCreateCollectionWithTotalLimit + * + * @return array + */ + public static function createCollectionWithTotalLimitDataProvider() + { + return [ + // [currentPage, productsPerPage, totalProducts, expectedLimit, expectedOffset] + 'page 1 of 2 with 9 total' => [1, 5, 9, 5, 0], + 'page 2 of 2 with 9 total' => [2, 5, 9, 4, 5], + 'page 1 of 3 with 12 total' => [1, 5, 12, 5, 0], + 'page 2 of 3 with 12 total' => [2, 5, 12, 5, 5], + 'page 3 of 3 with 12 total' => [3, 5, 12, 2, 10], + 'page beyond limit' => [3, 5, 9, 0, 10], + ]; + } + public function testGetProductsCount() { $this->assertEquals(10, $this->productsList->getProductsCount());