From 3e18833324eaef262f017f4d7049944f94b5283d Mon Sep 17 00:00:00 2001 From: jainroshan785-blip Date: Sun, 4 Jan 2026 15:43:16 +0530 Subject: [PATCH 01/19] Fix: PHP Fatal error when deleting product image by Webservice API #39111 --- classes/Image.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/classes/Image.php b/classes/Image.php index 1d791d61fd054..5cd42d6ace2c4 100644 --- a/classes/Image.php +++ b/classes/Image.php @@ -930,8 +930,6 @@ public function getPathForCreation() */ private function deleteAutoGeneratedImage(array $imageType, string $imageFormat, array $filesToDelete): array { - $configuration = SymfonyContainer::getInstance()->get('prestashop.adapter.legacy.configuration'); - // Regular thumbnail $filesToDelete[] = $this->image_dir . $this->getExistingImgPath() . '-' . $imageType['name'] . '.' . $imageFormat; @@ -939,8 +937,8 @@ private function deleteAutoGeneratedImage(array $imageType, string $imageFormat, $filesToDelete[] = $this->image_dir . $this->getExistingImgPath() . '-' . $imageType['name'] . '2x.' . $imageFormat; // Watermarked thumbnail, if present - if ($configuration->get('WATERMARK_HASH')) { - $filesToDelete[] = $this->image_dir . $this->getExistingImgPath() . '-' . $imageType['name'] . '-' . $configuration->get('WATERMARK_HASH') . '.' . $imageFormat; + if (Configuration::get('WATERMARK_HASH')) { + $filesToDelete[] = $this->image_dir . $this->getExistingImgPath() . '-' . $imageType['name'] . '-' . Configuration::get('WATERMARK_HASH') . '.' . $imageFormat; } return $filesToDelete; From e7764eb4db01c72b3769ded44f067150140ab60a Mon Sep 17 00:00:00 2001 From: Krystian Podemski Date: Fri, 9 Jan 2026 23:56:08 +0100 Subject: [PATCH 02/19] Fix legacy profiler in the back office --- tools/profiling/Profiler.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/profiling/Profiler.php b/tools/profiling/Profiler.php index 9e6ec900b0854..3f20a90d8088e 100644 --- a/tools/profiling/Profiler.php +++ b/tools/profiling/Profiler.php @@ -213,7 +213,9 @@ public function processData() { // Including a lot of files uses memory foreach (get_included_files() as $file) { - $this->totalFilesize += filesize($file); + if (file_exists($file)) { + $this->totalFilesize += filesize($file); + } } foreach ($GLOBALS as $key => $value) { From 0c05e4cffed2481d465151299ddee1c48cedb845 Mon Sep 17 00:00:00 2001 From: Krystian Podemski Date: Sat, 10 Jan 2026 00:32:34 +0100 Subject: [PATCH 03/19] Remove unused import of SymfonyContainer --- classes/Image.php | 1 - 1 file changed, 1 deletion(-) diff --git a/classes/Image.php b/classes/Image.php index 5cd42d6ace2c4..3fe710dccd011 100644 --- a/classes/Image.php +++ b/classes/Image.php @@ -24,7 +24,6 @@ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ -use PrestaShop\PrestaShop\Adapter\SymfonyContainer; use PrestaShop\PrestaShop\Core\Exception\InvalidArgumentException; use PrestaShop\PrestaShop\Core\Image\ImageFormatConfiguration; From dfa00e9106287484569bd58fd0e299b158b683ae Mon Sep 17 00:00:00 2001 From: Hlavtox Date: Sun, 18 Jan 2026 12:42:34 +0100 Subject: [PATCH 04/19] Fix wanted quantity --- controllers/front/ProductController.php | 61 +++++++++++++++++++------ 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/controllers/front/ProductController.php b/controllers/front/ProductController.php index c7cadb3cd2bc0..144f48bfc09ec 100644 --- a/controllers/front/ProductController.php +++ b/controllers/front/ProductController.php @@ -454,6 +454,8 @@ public function displayAjaxQuickview(): void public function displayAjaxRefresh(): void { $product = $this->getTemplateVarProduct(); + + // After refresh, we will show the customer a new quantity he has to use $minimalProductQuantity = $this->getProductMinimalQuantity($product); // If the product is already in the cart, we can set the minimal quantity to 1 @@ -1182,33 +1184,37 @@ private function getIdProductAttributeByGroup(): ?int public function getTemplateVarProduct(): ProductLazyArray { - $productSettings = $this->getProductPresentationSettings(); - // Hook displayProductExtraContent - $extraContentFinder = new ProductExtraContentFinder(); - + // Convert product object into array $product = $this->objectPresenter->present($this->product); + + // Assign several product properties to the array $product['description'] = $this->transformDescriptionWithImg($this->product->description); - $product['out_of_stock'] = (int) $this->product->out_of_stock; $product['id_product_attribute'] = $this->getIdProductAttributeByGroupOrRequestOrDefault(); + + // @todo These three properties should be migrated into the lazy array, so they are available also in listings $product['minimal_quantity'] = $this->getProductMinimalQuantity($product); $product['cart_quantity'] = $this->context->cart->getProductQuantity((int) $this->product->id, $product['id_product_attribute'])['quantity']; $product['quantity_wanted'] = $this->getRequiredQuantity($product); - $product['extraContent'] = $extraContentFinder->addParams(['product' => $this->product])->present(); + + // Render hook displayProductExtraContent + $product['extraContent'] = (new ProductExtraContentFinder())->addParams(['product' => $this->product])->present(); $product['ecotax_tax_inc'] = $this->product->getEcotax(null, true, true); $product['ecotax'] = Tools::convertPrice($this->getProductEcotax($product), $this->context->currency, true, $this->context); + // Enrich the product array $product_full = Product::getProductProperties($this->context->language->id, $product, $this->context); + // Add possible customizations $product_full = $this->addProductCustomizationData($product_full); $product_full['show_quantities'] = (bool) ( Configuration::get('PS_DISPLAY_QTIES') && Configuration::get('PS_STOCK_MANAGEMENT') - && $this->product->quantity > 0 + && $product_full['quantity'] > 0 && $this->product->available_for_order && !Configuration::isCatalogMode() ); - $product_full['quantity_label'] = ($this->product->quantity > 1) ? $this->trans('Items', [], 'Shop.Theme.Catalog') : $this->trans('Item', [], 'Shop.Theme.Catalog'); + $product_full['quantity_label'] = ($product_full['quantity'] > 1) ? $this->trans('Items', [], 'Shop.Theme.Catalog') : $this->trans('Item', [], 'Shop.Theme.Catalog'); $product_full['quantity_discounts'] = $this->quantity_discounts; $group_reduction = GroupReduction::getValueForProduct($this->product->id, (int) Group::getCurrent()->id); @@ -1218,14 +1224,19 @@ public function getTemplateVarProduct(): ProductLazyArray $product_full['customer_group_discount'] = $group_reduction; $product_full['title'] = $this->getProductPageTitle(); + // And finally, present it in the modern way return $this->getProductPresenter()->present( - $productSettings, + $this->getProductPresentationSettings(), $product_full, $this->context->language ); } /** + * Gets the minimal quantity allowed for the product or its combination. + * + * @todo This method should be migrated to ProductLazyArray, so it's available also in listings. + * * @param array $product * * @return int @@ -1294,19 +1305,41 @@ public function findProductCombinationById(int $combinationId) } /** + * Gets the minimal quantity the customer has to purchase. We cannot just let him buy 1 piece + * if the minimal quantity is higher. Also, we adjust it by the quantity already in cart. + * + * @todo This method should be migrated to ProductLazyArray, so it's available also in listings. + * * @param array $product * * @return int */ protected function getRequiredQuantity(array $product) { - $requiredQuantity = (int) Tools::getValue('quantity_wanted', $this->getProductMinimalQuantity($product)); - if ($requiredQuantity < $product['minimal_quantity']) { - $requiredQuantity = $product['minimal_quantity']; + // For the required quantity, we will need to limit it by the minimal quantity on the low side. + $minimalProductQuantity = $this->getProductMinimalQuantity($product); + + /* + * We reduce it by the quantity we already have in cart. If the user already has a sufficient + * quantity in the cart, we don't need to add more. Although it may seem that we can just reset + * the minimal quantity to one in that case, we must not do that, because the quantity in the cart + * may not be the correct one. + */ + if (!empty($product['cart_quantity'])) { + $minimalProductQuantity -= $product['cart_quantity']; + } + + // Get the quantity wanted from the request + $requiredQuantity = (int) Tools::getValue('quantity_wanted'); + + // Safety check to fall back on one, if some nonsense was passed + if (empty($requiredQuantity)) { + $requiredQuantity = 1; } - if ($product['cart_quantity'] >= $requiredQuantity) { - return 0; + // And adjust the lower boundary depending on the minimal quantity + if ($requiredQuantity < $minimalProductQuantity) { + $requiredQuantity = $minimalProductQuantity; } return $requiredQuantity; From d71df5d34d563754d3a51ee7917dedd7440fe39f Mon Sep 17 00:00:00 2001 From: Hlavtox Date: Sun, 18 Jan 2026 20:53:35 +0100 Subject: [PATCH 05/19] Fix minimal quantity input once for all --- controllers/front/ProductController.php | 58 ++++++++++++++++--------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/controllers/front/ProductController.php b/controllers/front/ProductController.php index 144f48bfc09ec..2662e3bf46c6a 100644 --- a/controllers/front/ProductController.php +++ b/controllers/front/ProductController.php @@ -456,11 +456,9 @@ public function displayAjaxRefresh(): void $product = $this->getTemplateVarProduct(); // After refresh, we will show the customer a new quantity he has to use - $minimalProductQuantity = $this->getProductMinimalQuantity($product); - - // If the product is already in the cart, we can set the minimal quantity to 1 - if ($product['cart_quantity'] >= $minimalProductQuantity) { - $minimalProductQuantity = 1; + $newMinimalQuantity = $product['quantity_required']; + if (empty($newMinimalQuantity)) { + $newMinimalQuantity = 1; } ob_end_clean(); @@ -498,7 +496,8 @@ public function displayAjaxRefresh(): void 'adtoken' => Tools::getValue('adtoken'), ] : [] ), - 'product_minimal_quantity' => $minimalProductQuantity, + // A new minimal quantity to use on the input after refresh + 'product_minimal_quantity' => $newMinimalQuantity, 'product_has_combinations' => !empty($this->combinations), 'id_product_attribute' => $product['id_product_attribute'], 'id_customization' => $product['id_customization'], @@ -1192,9 +1191,17 @@ public function getTemplateVarProduct(): ProductLazyArray $product['id_product_attribute'] = $this->getIdProductAttributeByGroupOrRequestOrDefault(); // @todo These three properties should be migrated into the lazy array, so they are available also in listings + // Minimal quantity setting of this product or combination $product['minimal_quantity'] = $this->getProductMinimalQuantity($product); + + // Quantity of this product in the current cart $product['cart_quantity'] = $this->context->cart->getProductQuantity((int) $this->product->id, $product['id_product_attribute'])['quantity']; - $product['quantity_wanted'] = $this->getRequiredQuantity($product); + + // Quantity requested by the customer by the quantity input on product page - may be force-altered by us + $product['quantity_wanted'] = $this->getWantedQuantity($product); + + // Required quantity to add to cart to reach minimal quantity + $product['quantity_required'] = $this->getRequiredQuantity($product); // Render hook displayProductExtraContent $product['extraContent'] = (new ProductExtraContentFinder())->addParams(['product' => $this->product])->present(); @@ -1233,7 +1240,8 @@ public function getTemplateVarProduct(): ProductLazyArray } /** - * Gets the minimal quantity allowed for the product or its combination. + * Gets the minimal quantity allowed for the product or its combination. With no adjustments + * by the current context. * * @todo This method should be migrated to ProductLazyArray, so it's available also in listings. * @@ -1314,10 +1322,10 @@ public function findProductCombinationById(int $combinationId) * * @return int */ - protected function getRequiredQuantity(array $product) + protected function getRequiredQuantity(ProductLazyArray|array $product) { // For the required quantity, we will need to limit it by the minimal quantity on the low side. - $minimalProductQuantity = $this->getProductMinimalQuantity($product); + $minimalRequiredQuantityForPurchase = $this->getProductMinimalQuantity($product); /* * We reduce it by the quantity we already have in cart. If the user already has a sufficient @@ -1326,23 +1334,33 @@ protected function getRequiredQuantity(array $product) * may not be the correct one. */ if (!empty($product['cart_quantity'])) { - $minimalProductQuantity -= $product['cart_quantity']; + $minimalRequiredQuantityForPurchase -= $product['cart_quantity']; } + return $minimalRequiredQuantityForPurchase; + } + + /** + * Gets the quantity wanted by the customer for the product. We will take his request, + * but we will adjust it if it's lower than the required quantity. + * + * @param array $product + * + * @return int + */ + public function getWantedQuantity(ProductLazyArray|array $product): int + { // Get the quantity wanted from the request - $requiredQuantity = (int) Tools::getValue('quantity_wanted'); + $quantityWantedByTheCustomer = (int) Tools::getValue('quantity_wanted', 1); - // Safety check to fall back on one, if some nonsense was passed - if (empty($requiredQuantity)) { - $requiredQuantity = 1; - } + // Get minimal required quantity for purchase + $minimalRequiredQuantityForPurchase = $this->getRequiredQuantity($product); - // And adjust the lower boundary depending on the minimal quantity - if ($requiredQuantity < $minimalProductQuantity) { - $requiredQuantity = $minimalProductQuantity; + if ($quantityWantedByTheCustomer < $minimalRequiredQuantityForPurchase) { + $quantityWantedByTheCustomer = $minimalRequiredQuantityForPurchase; } - return $requiredQuantity; + return $quantityWantedByTheCustomer; } /** From b8d31288d09625e6d3ce4c810d8449b7a8980b39 Mon Sep 17 00:00:00 2001 From: Hlavtox Date: Sun, 18 Jan 2026 23:00:38 +0100 Subject: [PATCH 06/19] Fix add to cart button, tests, add comments --- controllers/front/ProductController.php | 50 +++++++++---- .../Presenter/Product/ProductLazyArray.php | 74 +++++++++++++++---- .../Product/ProductLazyArrayTest.php | 1 + 3 files changed, 99 insertions(+), 26 deletions(-) diff --git a/controllers/front/ProductController.php b/controllers/front/ProductController.php index 2662e3bf46c6a..732d0b5f5cd83 100644 --- a/controllers/front/ProductController.php +++ b/controllers/front/ProductController.php @@ -1188,6 +1188,16 @@ public function getTemplateVarProduct(): ProductLazyArray // Assign several product properties to the array $product['description'] = $this->transformDescriptionWithImg($this->product->description); + + /* + * This property is not a product property, but value from stock_available table. It must be here because on this page, + * it's not initialized in any other way. On listings, it goes through ProductAssembler and the property is included. + * In cart, it's also in the selected fields. But not here. + * + * We could centralize it right now, but the call StockAvailable::outOfStock($this->id) would still be called + * when constructing a Product object, so, let's just migrate it all at once later. + */ + $product['out_of_stock'] = (int) $this->product->out_of_stock; $product['id_product_attribute'] = $this->getIdProductAttributeByGroupOrRequestOrDefault(); // @todo These three properties should be migrated into the lazy array, so they are available also in listings @@ -1198,9 +1208,11 @@ public function getTemplateVarProduct(): ProductLazyArray $product['cart_quantity'] = $this->context->cart->getProductQuantity((int) $this->product->id, $product['id_product_attribute'])['quantity']; // Quantity requested by the customer by the quantity input on product page - may be force-altered by us + // @todo - a centralized version of this method is implemented in ProductLazyArray - migrate to it when migrating this code $product['quantity_wanted'] = $this->getWantedQuantity($product); // Required quantity to add to cart to reach minimal quantity + // @todo - a centralized version of this method is implemented in ProductLazyArray - migrate to it when migrating this code $product['quantity_required'] = $this->getRequiredQuantity($product); // Render hook displayProductExtraContent @@ -1247,22 +1259,26 @@ public function getTemplateVarProduct(): ProductLazyArray * * @param array $product * - * @return int + * @return int Minimal quantity of product from it's settings, always a positive integer */ protected function getProductMinimalQuantity(ProductLazyArray|array $product) { - $minimal_quantity = 1; + $minimalQuantity = 1; if ($product['id_product_attribute']) { $combination = $this->findProductCombinationById($product['id_product_attribute']); if ($combination['minimal_quantity']) { - $minimal_quantity = $combination['minimal_quantity']; + $minimalQuantity = (int) $combination['minimal_quantity']; } } else { - $minimal_quantity = $this->product->minimal_quantity; + $minimalQuantity = (int) $this->product->minimal_quantity; + } + + if ($minimalQuantity < 1) { + $minimalQuantity = 1; } - return $minimal_quantity; + return $minimalQuantity; } /** @@ -1317,15 +1333,16 @@ public function findProductCombinationById(int $combinationId) * if the minimal quantity is higher. Also, we adjust it by the quantity already in cart. * * @todo This method should be migrated to ProductLazyArray, so it's available also in listings. + * It's already implemented there. * * @param array $product * - * @return int + * @return int Minimal quantity of product the customer buy right now, always a positive integer */ protected function getRequiredQuantity(ProductLazyArray|array $product) { // For the required quantity, we will need to limit it by the minimal quantity on the low side. - $minimalRequiredQuantityForPurchase = $this->getProductMinimalQuantity($product); + $requiredQuantityForPurchase = $this->getProductMinimalQuantity($product); /* * We reduce it by the quantity we already have in cart. If the user already has a sufficient @@ -1334,19 +1351,25 @@ protected function getRequiredQuantity(ProductLazyArray|array $product) * may not be the correct one. */ if (!empty($product['cart_quantity'])) { - $minimalRequiredQuantityForPurchase -= $product['cart_quantity']; + $requiredQuantityForPurchase -= $product['cart_quantity']; + if ($requiredQuantityForPurchase < 1) { + $requiredQuantityForPurchase = 1; + } } - return $minimalRequiredQuantityForPurchase; + return $requiredQuantityForPurchase; } /** * Gets the quantity wanted by the customer for the product. We will take his request, * but we will adjust it if it's lower than the required quantity. * + * @todo This method should be migrated to ProductLazyArray, so it's available also in listings. + * It's already implemented there. + * * @param array $product * - * @return int + * @return int Quantity of product requested by the customer, altered if needed, always a positive integer */ public function getWantedQuantity(ProductLazyArray|array $product): int { @@ -1354,10 +1377,11 @@ public function getWantedQuantity(ProductLazyArray|array $product): int $quantityWantedByTheCustomer = (int) Tools::getValue('quantity_wanted', 1); // Get minimal required quantity for purchase - $minimalRequiredQuantityForPurchase = $this->getRequiredQuantity($product); + $requiredQuantityForPurchase = $this->getRequiredQuantity($product); - if ($quantityWantedByTheCustomer < $minimalRequiredQuantityForPurchase) { - $quantityWantedByTheCustomer = $minimalRequiredQuantityForPurchase; + // If the wanted quantity is lower than the required, we adjust it + if ($quantityWantedByTheCustomer < $requiredQuantityForPurchase) { + $quantityWantedByTheCustomer = $requiredQuantityForPurchase; } return $quantityWantedByTheCustomer; diff --git a/src/Adapter/Presenter/Product/ProductLazyArray.php b/src/Adapter/Presenter/Product/ProductLazyArray.php index 384d8b210d493..1e6dabd4a63c3 100644 --- a/src/Adapter/Presenter/Product/ProductLazyArray.php +++ b/src/Adapter/Presenter/Product/ProductLazyArray.php @@ -1199,12 +1199,8 @@ protected function shouldEnableAddToCartButton( } // Disable because of stock management - if ( - $settings->stock_management_enabled - && !$product['allow_oosp'] - && ($product['quantity'] <= 0 - || $product['quantity'] - $this->getQuantityWanted() < 0 - || $product['quantity'] - $this->getMinimalQuantity() < 0) + if ($settings->stock_management_enabled && !$product['allow_oosp'] + && ($product['quantity'] <= 0 || $product['quantity'] - $this->getQuantityWanted() < 0) ) { $shouldEnable = false; } @@ -1213,22 +1209,74 @@ protected function shouldEnableAddToCartButton( } /** - * @return int Quantity of product requested by the customer + * Gets the quantity wanted by the customer for the product. We will take his request, + * but we will adjust it if it's lower than the required quantity. + * + * @return int Quantity of product requested by the customer, altered if needed, always a positive integer */ private function getQuantityWanted() { - return (int) Tools::getValue( - 'quantity_wanted', - $this->product['quantity_wanted'] ?? 1 - ); + if (empty($this->product['quantity_wanted'])) { + // Get the quantity wanted from the request + $quantityWantedByTheCustomer = (int) Tools::getValue('quantity_wanted', 1); + + // Get minimal required quantity for purchase + $requiredQuantityForPurchase = $this->getRequiredQuantity(); + + if ($quantityWantedByTheCustomer < $requiredQuantityForPurchase) { + $quantityWantedByTheCustomer = $requiredQuantityForPurchase; + } + + $this->product['quantity_wanted'] = $quantityWantedByTheCustomer; + } + + return $this->product['quantity_wanted']; + } + + /** + * Gets the minimal quantity the customer has to purchase. We cannot just let him buy 1 piece + * if the minimal quantity is higher. Also, we adjust it by the quantity already in cart. + * + * @return int Minimal quantity of product the customer buy right now, always a positive integer + */ + protected function getRequiredQuantity() + { + // For the required quantity, we will need to limit it by the minimal quantity on the low side. + $requiredQuantityForPurchase = $this->getMinimalQuantity(); + + /* + * We reduce it by the quantity we already have in cart. If the user already has a sufficient + * quantity in the cart, we don't need to add more. Although it may seem that we can just reset + * the minimal quantity to one in that case, we must not do that, because the quantity in the cart + * may not be the correct one. + */ + if (!empty($this->product['cart_quantity'])) { + $requiredQuantityForPurchase -= $this->product['cart_quantity']; + if ($requiredQuantityForPurchase < 1) { + $requiredQuantityForPurchase = 1; + } + } + + return $requiredQuantityForPurchase; } /** - * @return int Minimal quantity of product requested by the customer + * Gets the minimal quantity allowed for the product or its combination. With no adjustments + * by the current context. The builder of this object is responsible for passing the correct + * minimal quantity depending on the combination selected. + * + * @return int Minimal quantity of product from it's settings, always a positive integer */ private function getMinimalQuantity() { - return (int) $this->product['minimal_quantity']; + $minimalQuantity = (int) $this->product['minimal_quantity']; + + // If we received faulty data, we correct it to 1 + if ($minimalQuantity < 1) { + $minimalQuantity = 1; + } + + return $minimalQuantity; } /** diff --git a/tests/Integration/Adapter/Presenter/Product/ProductLazyArrayTest.php b/tests/Integration/Adapter/Presenter/Product/ProductLazyArrayTest.php index 9d6ae9592989e..eeea34c2d872e 100644 --- a/tests/Integration/Adapter/Presenter/Product/ProductLazyArrayTest.php +++ b/tests/Integration/Adapter/Presenter/Product/ProductLazyArrayTest.php @@ -94,6 +94,7 @@ class ProductLazyArrayTest extends TestCase 'out_of_stock' => OutOfStockType::OUT_OF_STOCK_DEFAULT, 'customizable' => 0, 'active' => 1, + 'minimal_quantity' => 1, ]; private const PRODUCT_DISCONTINUED = 'This product is no longer available for sale.'; From 890fc8bc3055cd9348a25bea3dcf1bf0c93b6cc0 Mon Sep 17 00:00:00 2001 From: Hlavtox Date: Tue, 20 Jan 2026 15:08:29 +0100 Subject: [PATCH 07/19] Make values available in lazy array for listing --- src/Adapter/Presenter/Product/ProductLazyArray.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Adapter/Presenter/Product/ProductLazyArray.php b/src/Adapter/Presenter/Product/ProductLazyArray.php index 1e6dabd4a63c3..286922a8090f1 100644 --- a/src/Adapter/Presenter/Product/ProductLazyArray.php +++ b/src/Adapter/Presenter/Product/ProductLazyArray.php @@ -1214,14 +1214,15 @@ protected function shouldEnableAddToCartButton( * * @return int Quantity of product requested by the customer, altered if needed, always a positive integer */ - private function getQuantityWanted() + #[LazyArrayAttribute(arrayAccess: true)] + public function getQuantityWanted() { if (empty($this->product['quantity_wanted'])) { // Get the quantity wanted from the request $quantityWantedByTheCustomer = (int) Tools::getValue('quantity_wanted', 1); // Get minimal required quantity for purchase - $requiredQuantityForPurchase = $this->getRequiredQuantity(); + $requiredQuantityForPurchase = $this->getQuantityRequired(); if ($quantityWantedByTheCustomer < $requiredQuantityForPurchase) { $quantityWantedByTheCustomer = $requiredQuantityForPurchase; @@ -1239,7 +1240,8 @@ private function getQuantityWanted() * * @return int Minimal quantity of product the customer buy right now, always a positive integer */ - protected function getRequiredQuantity() + #[LazyArrayAttribute(arrayAccess: true)] + public function getQuantityRequired() { // For the required quantity, we will need to limit it by the minimal quantity on the low side. $requiredQuantityForPurchase = $this->getMinimalQuantity(); @@ -1267,7 +1269,8 @@ protected function getRequiredQuantity() * * @return int Minimal quantity of product from it's settings, always a positive integer */ - private function getMinimalQuantity() + #[LazyArrayAttribute(arrayAccess: true)] + public function getMinimalQuantity() { $minimalQuantity = (int) $this->product['minimal_quantity']; From 72df87aef33c52b3a7d2e308a9ef6318f7247fa0 Mon Sep 17 00:00:00 2001 From: Codencode Date: Tue, 20 Jan 2026 18:01:26 +0100 Subject: [PATCH 08/19] Update VAT rates for Estonia and Romania --- localization/at.xml | 4 ++-- localization/be.xml | 4 ++-- localization/bg.xml | 4 ++-- localization/cy.xml | 4 ++-- localization/cz.xml | 4 ++-- localization/de.xml | 4 ++-- localization/dk.xml | 4 ++-- localization/ee.xml | 4 ++-- localization/es.xml | 4 ++-- localization/fi.xml | 4 ++-- localization/fr.xml | 4 ++-- localization/gb.xml | 4 ++-- localization/gr.xml | 4 ++-- localization/hr.xml | 4 ++-- localization/hu.xml | 4 ++-- localization/ie.xml | 4 ++-- localization/it.xml | 4 ++-- localization/lt.xml | 4 ++-- localization/lu.xml | 4 ++-- localization/lv.xml | 4 ++-- localization/mc.xml | 4 ++-- localization/mt.xml | 4 ++-- localization/nl.xml | 4 ++-- localization/pl.xml | 4 ++-- localization/pt.xml | 4 ++-- localization/ro.xml | 4 ++-- localization/se.xml | 4 ++-- localization/si.xml | 4 ++-- localization/sk.xml | 4 ++-- 29 files changed, 58 insertions(+), 58 deletions(-) diff --git a/localization/at.xml b/localization/at.xml index bddb55d99d1d0..08d288a829bfa 100644 --- a/localization/at.xml +++ b/localization/at.xml @@ -17,7 +17,7 @@ - + @@ -35,7 +35,7 @@ - + diff --git a/localization/be.xml b/localization/be.xml index af8eaf8eef586..0c8f73aeea99c 100644 --- a/localization/be.xml +++ b/localization/be.xml @@ -18,7 +18,7 @@ - + @@ -36,7 +36,7 @@ - + diff --git a/localization/bg.xml b/localization/bg.xml index f21a4792b7b9b..ba5eb11e6fcbb 100644 --- a/localization/bg.xml +++ b/localization/bg.xml @@ -16,7 +16,7 @@ - + @@ -34,7 +34,7 @@ - + diff --git a/localization/cy.xml b/localization/cy.xml index 6c47114287fb3..24229872e562d 100644 --- a/localization/cy.xml +++ b/localization/cy.xml @@ -17,7 +17,7 @@ - + @@ -35,7 +35,7 @@ - + diff --git a/localization/cz.xml b/localization/cz.xml index 013465de2d293..f3e2437a60407 100644 --- a/localization/cz.xml +++ b/localization/cz.xml @@ -15,7 +15,7 @@ - + @@ -33,7 +33,7 @@ - + diff --git a/localization/de.xml b/localization/de.xml index 79b0869791e83..99311233fba80 100644 --- a/localization/de.xml +++ b/localization/de.xml @@ -15,7 +15,7 @@ - + @@ -33,7 +33,7 @@ - + diff --git a/localization/dk.xml b/localization/dk.xml index 86eb42805bb2f..57e37c4878dbf 100644 --- a/localization/dk.xml +++ b/localization/dk.xml @@ -14,7 +14,7 @@ - + @@ -32,7 +32,7 @@ - + diff --git a/localization/ee.xml b/localization/ee.xml index dd6da169f1227..3f7ffa8f02d2b 100644 --- a/localization/ee.xml +++ b/localization/ee.xml @@ -7,7 +7,7 @@ - + @@ -33,7 +33,7 @@ - + diff --git a/localization/es.xml b/localization/es.xml index b92ed8b9b9c49..bbd1ab2120b11 100644 --- a/localization/es.xml +++ b/localization/es.xml @@ -17,7 +17,7 @@ - + @@ -34,7 +34,7 @@ - + diff --git a/localization/fi.xml b/localization/fi.xml index e773a9199fd71..38a7ce834dfce 100644 --- a/localization/fi.xml +++ b/localization/fi.xml @@ -17,7 +17,7 @@ - + @@ -34,7 +34,7 @@ - + diff --git a/localization/fr.xml b/localization/fr.xml index c6116bec6605b..1eb9498f49473 100644 --- a/localization/fr.xml +++ b/localization/fr.xml @@ -27,7 +27,7 @@ - + @@ -44,7 +44,7 @@ - + diff --git a/localization/gb.xml b/localization/gb.xml index d4a400197ac0b..306e7dfbe3609 100644 --- a/localization/gb.xml +++ b/localization/gb.xml @@ -16,7 +16,7 @@ - + @@ -33,7 +33,7 @@ - + diff --git a/localization/gr.xml b/localization/gr.xml index 95c4e7205fc8f..290d8edc30a7f 100644 --- a/localization/gr.xml +++ b/localization/gr.xml @@ -17,7 +17,7 @@ - + @@ -34,7 +34,7 @@ - + diff --git a/localization/hr.xml b/localization/hr.xml index 48bebe4e8257f..2ca3218f35f0b 100644 --- a/localization/hr.xml +++ b/localization/hr.xml @@ -18,7 +18,7 @@ - + @@ -35,7 +35,7 @@ - + diff --git a/localization/hu.xml b/localization/hu.xml index 289c8664eaafe..d7117a1f4a329 100644 --- a/localization/hu.xml +++ b/localization/hu.xml @@ -17,7 +17,7 @@ - + @@ -34,7 +34,7 @@ - + diff --git a/localization/ie.xml b/localization/ie.xml index 9a312508f6509..866f0ea137ea2 100644 --- a/localization/ie.xml +++ b/localization/ie.xml @@ -18,7 +18,7 @@ - + @@ -35,7 +35,7 @@ - + diff --git a/localization/it.xml b/localization/it.xml index 8aa930b098883..d6d6bb20593de 100644 --- a/localization/it.xml +++ b/localization/it.xml @@ -19,7 +19,7 @@ - + @@ -36,7 +36,7 @@ - + diff --git a/localization/lt.xml b/localization/lt.xml index d4a3093ac826f..1eac5bed69ff5 100644 --- a/localization/lt.xml +++ b/localization/lt.xml @@ -17,7 +17,7 @@ - + @@ -34,7 +34,7 @@ - + diff --git a/localization/lu.xml b/localization/lu.xml index 439865a7152dd..7067c5a8c7da4 100644 --- a/localization/lu.xml +++ b/localization/lu.xml @@ -19,7 +19,7 @@ - + @@ -36,7 +36,7 @@ - + diff --git a/localization/lv.xml b/localization/lv.xml index f31ce948c01ee..5d523284bf355 100644 --- a/localization/lv.xml +++ b/localization/lv.xml @@ -18,7 +18,7 @@ - + @@ -35,7 +35,7 @@ - + diff --git a/localization/mc.xml b/localization/mc.xml index 3cec58a64764d..e029d66825d43 100644 --- a/localization/mc.xml +++ b/localization/mc.xml @@ -18,7 +18,7 @@ - + @@ -35,7 +35,7 @@ - + diff --git a/localization/mt.xml b/localization/mt.xml index 020fe41a97f4a..25d91193fa1a5 100644 --- a/localization/mt.xml +++ b/localization/mt.xml @@ -18,7 +18,7 @@ - + @@ -35,7 +35,7 @@ - + diff --git a/localization/nl.xml b/localization/nl.xml index 048117e70846f..e2373b25a0416 100644 --- a/localization/nl.xml +++ b/localization/nl.xml @@ -16,7 +16,7 @@ - + @@ -33,7 +33,7 @@ - + diff --git a/localization/pl.xml b/localization/pl.xml index 39c6913b9b802..b6e2032308a79 100644 --- a/localization/pl.xml +++ b/localization/pl.xml @@ -18,7 +18,7 @@ - + @@ -35,7 +35,7 @@ - + diff --git a/localization/pt.xml b/localization/pt.xml index 1b23812edfa02..5e6bca8e92ac6 100644 --- a/localization/pt.xml +++ b/localization/pt.xml @@ -18,7 +18,7 @@ - + @@ -35,7 +35,7 @@ - + diff --git a/localization/ro.xml b/localization/ro.xml index 94c218288d0d6..ef4633b3730de 100644 --- a/localization/ro.xml +++ b/localization/ro.xml @@ -7,7 +7,7 @@ - + @@ -18,7 +18,7 @@ - + diff --git a/localization/se.xml b/localization/se.xml index 76c2be015b7f6..4c67f75be4662 100644 --- a/localization/se.xml +++ b/localization/se.xml @@ -18,7 +18,7 @@ - + @@ -36,7 +36,7 @@ - + diff --git a/localization/si.xml b/localization/si.xml index 61e76b6d83741..2131efc4c8d9e 100644 --- a/localization/si.xml +++ b/localization/si.xml @@ -16,7 +16,7 @@ - + @@ -34,7 +34,7 @@ - + diff --git a/localization/sk.xml b/localization/sk.xml index ef0f82f1d749d..0fbd32040d69a 100644 --- a/localization/sk.xml +++ b/localization/sk.xml @@ -17,7 +17,7 @@ - + @@ -35,7 +35,7 @@ - + From 6f4e814a010d90a26175d2cb181d17dfbcf7c1b2 Mon Sep 17 00:00:00 2001 From: Jonathan LELIEVRE Date: Wed, 21 Jan 2026 10:24:43 +0100 Subject: [PATCH 09/19] Limit classic and humming bird theme to patches only --- composer.json | 4 ++-- composer.lock | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 8266f6cbaf270..b94ebe321cbf6 100644 --- a/composer.json +++ b/composer.json @@ -59,7 +59,7 @@ "prestashop/blockreassurance": "^5", "prestashop/blockwishlist": "^3.0", "prestashop/circuit-breaker": "^4.0", - "prestashop/classic": "^3", + "prestashop/classic": "~3.0.0", "prestashop/contactform": "^4", "prestashop/dashactivity": "^2", "prestashop/dashgoals": "^2", @@ -69,7 +69,7 @@ "prestashop/graphnvd3": "^2", "prestashop/gridhtml": "^2", "prestashop/gsitemap": "^4", - "prestashop/hummingbird": "^1.0.1", + "prestashop/hummingbird": "~1.0.1", "prestashop/pagesnotfound": "^2", "prestashop/productcomments": "^7.0", "prestashop/ps_apiresources": "dev-add-combinations-endpoint", diff --git a/composer.lock b/composer.lock index e811d1bb58db4..6291d5661d971 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fe77dc0345ef7565c685e167c40e73bd", + "content-hash": "d152a8c9918d81f2d82e236bdea7177e", "packages": [ { "name": "api-platform/core", From d2f0e6f41aa94ac435347bdd087681eb096cfe90 Mon Sep 17 00:00:00 2001 From: Jonathan LELIEVRE Date: Thu, 22 Jan 2026 09:10:56 +0100 Subject: [PATCH 10/19] Add missing hooks in configuration --- install-dev/data/xml/hook.xml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/install-dev/data/xml/hook.xml b/install-dev/data/xml/hook.xml index f38bce769004b..f9b4bea8b1f75 100644 --- a/install-dev/data/xml/hook.xml +++ b/install-dev/data/xml/hook.xml @@ -5533,5 +5533,30 @@ Action after initializing context currency Allows modules to modify the context currency after it has been initialized. + + actionFacetedSearchSetSupportedControllers + + <description/> + </hook> + <hook id="actionFacetedSearchFilters"> + <name>actionFacetedSearchFilters</name> + <title/> + <description/> + </hook> + <hook id="actionMainMenuModifier"> + <name>actionMainMenuModifier</name> + <title/> + <description/> + </hook> + <hook id="actionFacetedSearchCacheKeyGeneration"> + <name>actionFacetedSearchCacheKeyGeneration</name> + <title/> + <description/> + </hook> + <hook id="gSitemapAppendUrls"> + <name>gSitemapAppendUrls</name> + <title/> + <description/> + </hook> </entities> </entity_hook> From 2e70a2bc40c78eb6c0ea48a706074e9d4d08255e Mon Sep 17 00:00:00 2001 From: Jonathan LELIEVRE <jonathan.lelievre@prestashop.com> Date: Thu, 22 Jan 2026 11:07:48 +0100 Subject: [PATCH 11/19] Update modules --- composer.json | 10 +++---- composer.lock | 72 +++++++++++++++++++++++++-------------------------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/composer.json b/composer.json index b94ebe321cbf6..782d125b58c67 100644 --- a/composer.json +++ b/composer.json @@ -68,11 +68,11 @@ "prestashop/decimal": "^1.4", "prestashop/graphnvd3": "^2", "prestashop/gridhtml": "^2", - "prestashop/gsitemap": "^4", + "prestashop/gsitemap": "^5", "prestashop/hummingbird": "~1.0.1", - "prestashop/pagesnotfound": "^2", - "prestashop/productcomments": "^7.0", - "prestashop/ps_apiresources": "dev-add-combinations-endpoint", + "prestashop/pagesnotfound": "^3", + "prestashop/productcomments": "^8.0", + "prestashop/ps_apiresources": "^0.3", "prestashop/ps_banner": "^2", "prestashop/ps_bestsellers": "^1.0", "prestashop/ps_brandlist": "^1.0", @@ -212,7 +212,7 @@ }, "ps_apiresources": { "type": "vcs", - "url": "https://github.com/sebajps/ps_apiresources" + "url": "https://github.com/PrestaShop/ps_apiresources" } }, "minimum-stability": "dev", diff --git a/composer.lock b/composer.lock index 6291d5661d971..0c759b5d4fbc8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d152a8c9918d81f2d82e236bdea7177e", + "content-hash": "c85c298d8edffc77ba8b90ae1f63d965", "packages": [ { "name": "api-platform/core", @@ -5091,16 +5091,16 @@ }, { "name": "prestashop/gsitemap", - "version": "v4.4.0", + "version": "v5.0.0", "source": { "type": "git", "url": "https://github.com/PrestaShop/gsitemap.git", - "reference": "2f9f69f9a06d864c7e7ed8a89cdee5d23b2c91ca" + "reference": "44ad0f5eafdabe641fb204ef1780c9ed586e39c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PrestaShop/gsitemap/zipball/2f9f69f9a06d864c7e7ed8a89cdee5d23b2c91ca", - "reference": "2f9f69f9a06d864c7e7ed8a89cdee5d23b2c91ca", + "url": "https://api.github.com/repos/PrestaShop/gsitemap/zipball/44ad0f5eafdabe641fb204ef1780c9ed586e39c0", + "reference": "44ad0f5eafdabe641fb204ef1780c9ed586e39c0", "shasum": "" }, "require": { @@ -5128,9 +5128,9 @@ "description": "PrestaShop module gsitemap", "homepage": "https://github.com/PrestaShop/gsitemap", "support": { - "source": "https://github.com/PrestaShop/gsitemap/tree/v4.4.0" + "source": "https://github.com/PrestaShop/gsitemap/tree/v5.0.0" }, - "time": "2024-01-10T17:10:01+00:00" + "time": "2025-11-24T07:24:20+00:00" }, { "name": "prestashop/hummingbird", @@ -5174,20 +5174,20 @@ }, { "name": "prestashop/pagesnotfound", - "version": "v2.0.3", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/PrestaShop/pagesnotfound.git", - "reference": "cf05fc7770f9beff2a5c2f17bdfe2da7652744db" + "reference": "3e6a5526d708c0f8b5914b8dd3213e1dd20ec0dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PrestaShop/pagesnotfound/zipball/cf05fc7770f9beff2a5c2f17bdfe2da7652744db", - "reference": "cf05fc7770f9beff2a5c2f17bdfe2da7652744db", + "url": "https://api.github.com/repos/PrestaShop/pagesnotfound/zipball/3e6a5526d708c0f8b5914b8dd3213e1dd20ec0dc", + "reference": "3e6a5526d708c0f8b5914b8dd3213e1dd20ec0dc", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": ">=7.1.3" }, "require-dev": { "prestashop/php-dev-tools": "^4.3" @@ -5206,22 +5206,22 @@ "description": "PrestaShop module pagesnotfound", "homepage": "https://github.com/PrestaShop/pagesnotfound", "support": { - "source": "https://github.com/PrestaShop/pagesnotfound/tree/v2.0.3" + "source": "https://github.com/PrestaShop/pagesnotfound/tree/v3.0.0" }, - "time": "2023-06-16T10:59:52+00:00" + "time": "2025-10-28T17:14:50+00:00" }, { "name": "prestashop/productcomments", - "version": "v7.0.0", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/PrestaShop/productcomments.git", - "reference": "7287476cb44969af58721cb9ad7b5cb89393b2c8" + "reference": "9e6a1f63b70cd2cfc416faea56aa000c3c220eec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PrestaShop/productcomments/zipball/7287476cb44969af58721cb9ad7b5cb89393b2c8", - "reference": "7287476cb44969af58721cb9ad7b5cb89393b2c8", + "url": "https://api.github.com/repos/PrestaShop/productcomments/zipball/9e6a1f63b70cd2cfc416faea56aa000c3c220eec", + "reference": "9e6a1f63b70cd2cfc416faea56aa000c3c220eec", "shasum": "" }, "require": { @@ -5253,22 +5253,22 @@ "description": "PrestaShop module productcomments", "homepage": "https://github.com/PrestaShop/productcomments", "support": { - "source": "https://github.com/PrestaShop/productcomments/tree/v7.0.0" + "source": "https://github.com/PrestaShop/productcomments/tree/v8.0.0" }, - "time": "2024-04-24T14:10:24+00:00" + "time": "2025-11-13T14:46:03+00:00" }, { "name": "prestashop/ps_apiresources", - "version": "dev-add-combinations-endpoint", + "version": "v0.3.0", "source": { "type": "git", - "url": "https://github.com/sebajps/ps_apiresources.git", - "reference": "86e47474aa9004dfeaf7b726bfb192e66ab15779" + "url": "https://github.com/PrestaShop/ps_apiresources.git", + "reference": "af1b194acca43dc48d932daee4b9e2f908177ad4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebajps/ps_apiresources/zipball/86e47474aa9004dfeaf7b726bfb192e66ab15779", - "reference": "86e47474aa9004dfeaf7b726bfb192e66ab15779", + "url": "https://api.github.com/repos/PrestaShop/ps_apiresources/zipball/af1b194acca43dc48d932daee4b9e2f908177ad4", + "reference": "af1b194acca43dc48d932daee4b9e2f908177ad4", "shasum": "" }, "require": { @@ -5332,9 +5332,10 @@ "description": "PrestaShop - API Resources", "homepage": "https://github.com/PrestaShop/ps_apiresources", "support": { - "source": "https://github.com/sebajps/ps_apiresources/tree/add-combinations-endpoint" + "source": "https://github.com/PrestaShop/ps_apiresources/tree/v0.3.0", + "issues": "https://github.com/PrestaShop/ps_apiresources/issues" }, - "time": "2026-01-21T07:13:47+00:00" + "time": "2026-01-22T10:05:01+00:00" }, { "name": "prestashop/ps_banner", @@ -6053,16 +6054,16 @@ }, { "name": "prestashop/ps_facetedsearch", - "version": "v4.0.2", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/PrestaShop/ps_facetedsearch.git", - "reference": "8ebc5f07be387e378baf907a02f53f1496e6347b" + "reference": "bd9f09e81d1d49a936b4c0c3fa6b7e846bc53e76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PrestaShop/ps_facetedsearch/zipball/8ebc5f07be387e378baf907a02f53f1496e6347b", - "reference": "8ebc5f07be387e378baf907a02f53f1496e6347b", + "url": "https://api.github.com/repos/PrestaShop/ps_facetedsearch/zipball/bd9f09e81d1d49a936b4c0c3fa6b7e846bc53e76", + "reference": "bd9f09e81d1d49a936b4c0c3fa6b7e846bc53e76", "shasum": "" }, "require": { @@ -6098,9 +6099,10 @@ "description": "PrestaShop module ps_facetedsearch", "homepage": "https://github.com/PrestaShop/ps_facetedsearch", "support": { - "source": "https://github.com/PrestaShop/ps_facetedsearch/tree/v4.0.2" + "issues": "https://github.com/PrestaShop/ps_facetedsearch/issues", + "source": "https://github.com/PrestaShop/ps_facetedsearch/tree/v4.0.3" }, - "time": "2025-11-21T13:36:43+00:00" + "time": "2025-12-31T13:27:28+00:00" }, { "name": "prestashop/ps_faviconnotificationbo", @@ -18359,9 +18361,7 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": { - "prestashop/ps_apiresources": 20 - }, + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { From e4e3d6aa4b819cf30380d9ee8b99a40b4b7c88b8 Mon Sep 17 00:00:00 2001 From: Jonathan LELIEVRE <jonathan.lelievre@prestashop.com> Date: Thu, 22 Jan 2026 15:33:41 +0100 Subject: [PATCH 12/19] Update Changelog 9.0.3 --- CONTRIBUTORS.md | 3 ++ docs/CHANGELOG.txt | 83 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 78d18e87e2179..893f39735bd02 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -344,6 +344,7 @@ GitHub contributors: - Georges Cubas - Gerdus van Zyl - ggedamed +- Giuseppe Tripiciano - Giant Leap Lab - Ginkosama - ginodev @@ -399,6 +400,7 @@ GitHub contributors: - indesign47 - Ines Sallemi - Inetbiz +- Inform-All - ironwo0d - Ish Gupta - Ishiki @@ -847,6 +849,7 @@ GitHub contributors: - Salim Benouamer - Sam Berry - sallemiines +- Salvo Passaro - Sam - Sam Sanchez - Samir Shah diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 6ed655690ec46..5399f8b6798d4 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -23,6 +23,89 @@ needs please refer to https://devdocs.prestashop.com/ for more information. Changelog for PrestaShop 9 +#################################### +# v9.0.3 - (2026-01-22) +#################################### + +- Back Office: + - Improvement: + - #40517: Add some help boxes to product page (by @Hlavtox) + - #39923: Improve wording of some settings, better explain the meaning of them (by @Hlavtox) + - #40230: Adds help text to product page fields (by @Hlavtox) + - Bug fix: + - #40563: Admin API improvements for combination endpoints (by @jolelievre) + - #40556: Fix: CsvFileReader service fails with "You have requested a non-existent service 'session'" (by @Codencode) + - #38775: Fix: Multishop - error loading CMS pages removed from the default shop (by @Codencode) + - #40499: Fix: When saving an Attribute is_color_group is not updated. (by @Codencode) + - #40554: Fix: always display taxes total in order summary (by @Codencode) + - #40532: BO Product page, fix feature value collection indexes (by @jolelievre) + - #40433: Fix: Module update problem (by @Codencode) + - #40054: Use URL when building urls to prevent subtle mistakes with &? (by @tswfi) + - #40036: BO - Product : Fixed feature display in multishop (by @Progi1984) + - #39854: Fix: Quick Access links redirect to root instead of subdirectory, causing 404 (by @Codencode) + - #40050: Fix: Incorrect redirect from HTTP to HTTPS on the admin login page when PrestaShop is in a subfolder (by @Codencode) + - #40475: Fix link for redirection on country BO page (by @jolelievre) + - #40066: Fix: [BO] Admin Countries page redirect issue with multishop after changing shop (by @Codencode) + - #40329: Prevent NoResultException when checking for existing translations (by @ChillCode) + - #39926: Fix: handle SELECT fields without no_quotes in getSensitiveAttributes (by @Codencode) + - #40001: Fix redirect after editing root category to use current categoryId in stead of PS_HOME_CATEGORY (by @Codencode) + - #39869: Update monologger to v3 (by @NKoonen) + - #40256: Fix: Issue retrieving product price when adding the first specific_price (by @Codencode) + - #40243: Admin API handle position update (by @jolelievre) + - #40257: Prevent saving ajax URL for future login redirection, or the redirect… (by @jolelievre) + - #40112: Bump prestakit to v2.0.5 (by @Quetzacoalt91) + - Refactoring: + - #37667: Apply backoffice optimizations (by @Hlavtox) +- Front Office: + - New feature: + - #40403: Allow easily hooking into country and currency selection logic (by @Hlavtox) + - Improvement: + - #40537: Remove unnecessary force refresh in checkout (by @Hlavtox) + - #40248: Prevent Exposure of Sensitive Product Attributes in Front Office (by @M0rgan01) + - Bug fix: + - #40246: Fix preview in multilang (by @tleon) + - #39582: Fix: Product customization text field bug with using symbol {} (by @Codencode) + - #40117: Fix cart rule validation in front office (by @Hlavtox) + - #40262: Allow access to customized files preview without multi-lang enabled (by @kpodemski) + - #40137: Updated zxcvbn to a maintained version for consistency with backend checks (by @tleon) + - Refactoring: + - #40406: Comment how language setting works in FO (by @Hlavtox) +- Core: + - Improvement: + - #40269: Prevent database inconsistencies by preventing faulty group delete calls (by @Hlavtox) + - #40133: Update ca-bundle from 1.3.7 to 1.5.9 (by @tswfi) + - #40350: Remove the composer config to ignore audit (by @jolelievre) + - #40332: Restore original repository for ps_apiresources (by @nicosomb) + - #40312: Bump to `9.0.3` (by @boherm) + - #40134: Docker : Fixed Install of xdebug (by @Progi1984) + - Bug fix: + - #40585: Preliminary tasks for patch version 9.0.3 (by @jolelievre) + - #40479: Fix legacy profiler in the back office (by @kpodemski) + - #40562: Update VAT rates for Estonia and Romania (by @Codencode) + - #40496: Fix some type issues related to carriers (by @Hlavtox) + - #40423: Fix empty extra vars / product list when using the new automatic text email option (by @matrixino) + - #40040: Always request a non cached result in Order::getIdByCartId (by @ilsalvopss) + - #40400: Simplify cart rule minimal value by avoiding subtracting values (by @Hlavtox) + - Refactoring: + - #40389: Comment logic related to addresses and their initialization (by @Hlavtox) +- Installer: + - Bug fix: + - #40574: Fix Makefile to prevent build assets twice (by @jolelievre) + - #40077: Install Console : Allow characters "<" & ">" in admin password (by @Progi1984) + - #40114: Chore(Makefile): fix Makefile shell detection issue (by @tyloo) +- Localization: + - Bug fix: + - #40521: Fix default fixtures translation (by @jolelievre) +- Tests: + - Improvement: + - #40512: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) + - #40458: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) + - #40434: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) + - #40363: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) + - #40138: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) + - Refactoring: + - #40298: Functional tests - Fix create account in FO classic theme test (by @nesrineabdmouleh) + #################################### # v9.0.2 - (2025-12-01) #################################### From 50bfc6525300200da41ad4700d245d2b06074b86 Mon Sep 17 00:00:00 2001 From: Krystian Podemski <podemski.krystian@gmail.com> Date: Fri, 23 Jan 2026 23:06:48 +0100 Subject: [PATCH 13/19] Test for discounts for guest creation fix --- .../Discount/Validate/DiscountValidator.php | 17 ++++++ .../Exception/DiscountConstraintException.php | 1 + .../Context/CustomerFeatureContext.php | 20 +++++++ .../Discount/DiscountFeatureContext.php | 8 +++ ..._discount_excludes_guest_customers.feature | 55 +++++++++++++++++++ 5 files changed, 101 insertions(+) create mode 100644 tests/Integration/Behaviour/Features/Scenario/Discount/BO/add_discount_excludes_guest_customers.feature diff --git a/src/Adapter/Discount/Validate/DiscountValidator.php b/src/Adapter/Discount/Validate/DiscountValidator.php index 6eb3fa63688dc..b47c54c357a38 100644 --- a/src/Adapter/Discount/Validate/DiscountValidator.php +++ b/src/Adapter/Discount/Validate/DiscountValidator.php @@ -28,6 +28,7 @@ namespace PrestaShop\PrestaShop\Adapter\Discount\Validate; use CartRule; +use Customer; use PrestaShop\Decimal\DecimalNumber; use PrestaShop\PrestaShop\Adapter\AbstractObjectModelValidator; use PrestaShop\PrestaShop\Adapter\Attribute\Repository\AttributeRepository; @@ -96,6 +97,7 @@ public function setDiscountRepository(DiscountRepository $discountRepository): v public function validate(CartRule $cartRule): void { $this->validateCartRuleProperty($cartRule, 'id_customer', DiscountConstraintException::INVALID_CUSTOMER_ID); + $this->assertCustomerIsNotGuest($cartRule); $this->validateCartRuleProperty($cartRule, 'date_from', DiscountConstraintException::INVALID_DATE_FROM); $this->validateCartRuleProperty($cartRule, 'date_to', DiscountConstraintException::INVALID_DATE_TO); $this->validateCartRuleProperty($cartRule, 'description', DiscountConstraintException::INVALID_DESCRIPTION); @@ -318,4 +320,19 @@ private function assertDateRangeIsCorrect(CartRule $cartrule): void throw new DiscountConstraintException('Date from cannot be greater than date to.', DiscountConstraintException::DATE_FROM_GREATER_THAN_DATE_TO); } } + + private function assertCustomerIsNotGuest(CartRule $cartRule): void + { + if (empty($cartRule->id_customer)) { + return; + } + + $customer = new Customer((int) $cartRule->id_customer); + if ($customer->isGuest()) { + throw new DiscountConstraintException( + sprintf('Cannot assign discount to guest customer with ID %d', $cartRule->id_customer), + DiscountConstraintException::INVALID_GUEST_CUSTOMER + ); + } + } } diff --git a/src/Core/Domain/Discount/Exception/DiscountConstraintException.php b/src/Core/Domain/Discount/Exception/DiscountConstraintException.php index 0ba9f77a85bda..cd862a948f532 100644 --- a/src/Core/Domain/Discount/Exception/DiscountConstraintException.php +++ b/src/Core/Domain/Discount/Exception/DiscountConstraintException.php @@ -78,4 +78,5 @@ class DiscountConstraintException extends DiscountException public const INVALID_PRODUCT_DISCOUNT_MISSING_TARGET = 48; public const INVALID_PRODUCT_DISCOUNT_INCOMPATIBLE_TARGETS = 49; public const INVALID_PRODUCT_DISCOUNT_INCOMPATIBLE_REDUCTIONS = 50; + public const INVALID_GUEST_CUSTOMER = 51; } diff --git a/tests/Integration/Behaviour/Features/Context/CustomerFeatureContext.php b/tests/Integration/Behaviour/Features/Context/CustomerFeatureContext.php index fdf79d4d05264..ef997c08d0a00 100644 --- a/tests/Integration/Behaviour/Features/Context/CustomerFeatureContext.php +++ b/tests/Integration/Behaviour/Features/Context/CustomerFeatureContext.php @@ -65,6 +65,26 @@ public function createCustomer($customerName, $customerEmail) SharedStorage::getStorage()->set($customerName, $customer->id); } + /** + * @Given /^there is a guest customer named "(.+)" whose email is "(.+)"$/ + */ + public function createGuestCustomer($customerName, $customerEmail) + { + /** @var Hashing $crypto */ + $crypto = ServiceLocator::get(Hashing::class); + + $customer = new Customer(); + $customer->firstname = 'fake'; + $customer->lastname = 'fake'; + $customer->passwd = $crypto->hash('Correct Horse Battery Staple'); + $customer->email = $customerEmail; + $customer->is_guest = true; + $customer->id_shop = Context::getContext()->shop->id; + $customer->add(); + $this->customers[$customerName] = $customer; + SharedStorage::getStorage()->set($customerName, $customer->id); + } + /** * @Given /^customer "(.+)" belongs to group "(.+)"$/ */ diff --git a/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountFeatureContext.php b/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountFeatureContext.php index 2e4aa6235c7ee..3c89d3cc8e64f 100644 --- a/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountFeatureContext.php +++ b/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountFeatureContext.php @@ -136,6 +136,14 @@ public function assertDiscountIncompatibleReductions(): void $this->assertLastErrorIs(DiscountConstraintException::class, DiscountConstraintException::INVALID_PRODUCT_DISCOUNT_INCOMPATIBLE_REDUCTIONS); } + /** + * @Then I should get an error that discount cannot be assigned to guest customers + */ + public function assertDiscountCannotBeAssignedToGuestCustomers(): void + { + $this->assertLastErrorIs(DiscountConstraintException::class, DiscountConstraintException::INVALID_GUEST_CUSTOMER); + } + /** * @Then discount :discountReference should have the following properties: * diff --git a/tests/Integration/Behaviour/Features/Scenario/Discount/BO/add_discount_excludes_guest_customers.feature b/tests/Integration/Behaviour/Features/Scenario/Discount/BO/add_discount_excludes_guest_customers.feature new file mode 100644 index 0000000000000..7b009a8d1317a --- /dev/null +++ b/tests/Integration/Behaviour/Features/Scenario/Discount/BO/add_discount_excludes_guest_customers.feature @@ -0,0 +1,55 @@ +# ./vendor/bin/behat -c tests/Integration/Behaviour/behat.yml -s discount --tags add-discount-excludes-guests +@restore-all-tables-before-feature +@restore-languages-after-feature +@add-discount-excludes-guests +Feature: Add discount with single customer eligibility excludes guest customers + PrestaShop should prevent BO users from creating discounts for guest customers + As a BO user + I should not be able to create discounts that are assigned to guest customers + Guest customers should be excluded from the customer search when creating discounts + + Background: + Given shop "shop1" with name "test_shop" exists + Given there is a currency named "usd" with iso code "USD" and exchange rate of 0.92 + Given currency "usd" is the default one + And language with iso code "en" is the default one + And language "french" with locale "fr-FR" exists + Given there is a customer named "john_doe" whose email is "john.doe@example.com" + And there is a guest customer named "guest_customer" whose email is "guest@example.com" + + Scenario: Cannot create a discount limited to a guest customer + When I create a "cart_level" discount "discount_for_guest" with following properties: + | name[en-US] | Guest Discount | + | name[fr-FR] | Réduction Invité | + | active | true | + | valid_from | 2025-01-01 00:00:00 | + | valid_to | 2025-12-31 23:59:59 | + | code | GUEST_2025 | + | reduction_percent | 20.0 | + | customer | guest_customer | + | total_quantity | 100 | + | quantity_per_user | 1 | + | minimum_product_quantity | 0 | + Then I should get an error that discount cannot be assigned to guest customers + + Scenario: Can create a discount for a regular customer but not for a guest + When I create a "cart_level" discount "discount_for_regular" with following properties: + | name[en-US] | Regular Customer Discount | + | name[fr-FR] | Réduction Client | + | active | true | + | valid_from | 2025-01-01 00:00:00 | + | valid_to | 2025-12-31 23:59:59 | + | code | REGULAR_2025 | + | reduction_percent | 15.0 | + | customer | john_doe | + | total_quantity | 100 | + | quantity_per_user | 1 | + | minimum_product_quantity | 0 | + Then discount "discount_for_regular" should have the following properties: + | name[en-US] | Regular Customer Discount | + | name[fr-FR] | Réduction Client | + | customer | john_doe | + # This should fail if we try to update to guest customer + When I update discount "discount_for_regular" with the following properties: + | customer | guest_customer | + Then I should get an error that discount cannot be assigned to guest customers From dfb235c66b32823cdc97662448a0233ec041ee28 Mon Sep 17 00:00:00 2001 From: Hlavtox <daniel.hlavacek@hotmail.cz> Date: Mon, 26 Jan 2026 10:06:26 +0100 Subject: [PATCH 14/19] Update classic to 3.0.6 --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 0c759b5d4fbc8..2a84141657922 100644 --- a/composer.lock +++ b/composer.lock @@ -4713,16 +4713,16 @@ }, { "name": "prestashop/classic", - "version": "3.0.5", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/PrestaShop/classic-theme.git", - "reference": "09a8934bcb5e8f0cc0a3e234d5e9b18a61c27ec2" + "reference": "d446bb110e2c874bbc090a075120ec271b51bb47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PrestaShop/classic-theme/zipball/09a8934bcb5e8f0cc0a3e234d5e9b18a61c27ec2", - "reference": "09a8934bcb5e8f0cc0a3e234d5e9b18a61c27ec2", + "url": "https://api.github.com/repos/PrestaShop/classic-theme/zipball/d446bb110e2c874bbc090a075120ec271b51bb47", + "reference": "d446bb110e2c874bbc090a075120ec271b51bb47", "shasum": "" }, "require-dev": { @@ -4747,9 +4747,9 @@ "description": "Classic theme for develop PrestaShop version", "support": { "issues": "https://github.com/PrestaShop/classic-theme/issues", - "source": "https://github.com/PrestaShop/classic-theme/tree/3.0.5" + "source": "https://github.com/PrestaShop/classic-theme/tree/3.0.6" }, - "time": "2025-12-15T16:49:50+00:00" + "time": "2026-01-23T21:31:11+00:00" }, { "name": "prestashop/contactform", From 216280c3c60501b8e33bbacf1ded4a7f57625ec1 Mon Sep 17 00:00:00 2001 From: Progi1984 <franck.lefevre@prestashop.com> Date: Wed, 21 Jan 2026 09:11:57 +0100 Subject: [PATCH 15/19] Functional Tests : Fixed campaigns (Part 4) --- composer.lock | 16 ++-- .../03_catalog/01_products/14_detailsTab.ts | 5 +- .../03_catalog/01_products/17_pricingTab.ts | 3 +- .../22_bulkActionsEnableDisable3DotsButton.ts | 1 - .../02_categories/02_CRUDCategoryInBO.ts | 6 +- .../02_categories/08_editHomeCategory.ts | 2 +- .../08_design/05_positions/02_filterModule.ts | 2 + .../14_checkCategoryImageFormat.ts | 3 +- .../04_separatorOfAttributeAnchor.ts | 17 ++--- .../06_labelOfInStockProducts.ts | 9 ++- .../03_productsStock/07_labelOutOfStock.ts | 75 ++++++++++--------- .../03_userAccount/05_getGDPRDataInCSV.ts | 14 ++-- .../03_userAccount/05_getGDPRDataInCSV.ts | 11 ++- .../01_quickView/06_changeCombination.ts | 4 +- .../02_productPage/02_changeQuantity.ts | 4 +- .../01_installation/04_resetModule.ts | 8 +- .../03_frontOffice/01_lists/03_shareList.ts | 34 ++++----- .../02_products/01_addProductToList.ts | 46 +++++++----- .../01_uninstallAndInstallModule.ts | 10 +-- .../03_uninstallAndDeleteModule.ts | 5 +- .../01_installation/05_disableEnableModule.ts | 5 +- .../01_enableDisableProductAvailability.ts | 5 +- .../01_installation/05_resetModule.ts | 4 +- .../03_consentCheckboxCustomization.ts | 23 +++--- tests/UI/package-lock.json | 4 +- 25 files changed, 160 insertions(+), 156 deletions(-) diff --git a/composer.lock b/composer.lock index cf75a70b67569..c1bccb4f8b3d6 100644 --- a/composer.lock +++ b/composer.lock @@ -5083,16 +5083,16 @@ }, { "name": "prestashop/hummingbird", - "version": "v2.0.0-beta.2", + "version": "v2.0.0-beta.3", "source": { "type": "git", "url": "https://github.com/PrestaShop/hummingbird.git", - "reference": "d2576caa72ca08468ea372ce6f133f8ac52dacb2" + "reference": "9732762a0d7b88b6b40a4c9d36b299e52deedaff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PrestaShop/hummingbird/zipball/d2576caa72ca08468ea372ce6f133f8ac52dacb2", - "reference": "d2576caa72ca08468ea372ce6f133f8ac52dacb2", + "url": "https://api.github.com/repos/PrestaShop/hummingbird/zipball/9732762a0d7b88b6b40a4c9d36b299e52deedaff", + "reference": "9732762a0d7b88b6b40a4c9d36b299e52deedaff", "shasum": "" }, "require-dev": { @@ -5117,9 +5117,9 @@ "description": "Hummingbird development theme for PrestaShop", "support": { "issues": "https://github.com/PrestaShop/hummingbird/issues", - "source": "https://github.com/PrestaShop/hummingbird/tree/v2.0.0-beta.2" + "source": "https://github.com/PrestaShop/hummingbird/tree/v2.0.0-beta.3" }, - "time": "2025-12-15T17:19:33+00:00" + "time": "2026-01-21T14:00:19+00:00" }, { "name": "prestashop/pagesnotfound", @@ -18267,9 +18267,9 @@ "ext-simplexml": "*", "ext-zip": "*" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.2.0" } diff --git a/tests/UI/campaigns/functional/BO/03_catalog/01_products/14_detailsTab.ts b/tests/UI/campaigns/functional/BO/03_catalog/01_products/14_detailsTab.ts index ebe27ec07ee79..22806b0cf22be 100644 --- a/tests/UI/campaigns/functional/BO/03_catalog/01_products/14_detailsTab.ts +++ b/tests/UI/campaigns/functional/BO/03_catalog/01_products/14_detailsTab.ts @@ -432,12 +432,11 @@ describe('BO - Catalog - Products : Details tab', async () => { expect(pageTitle).to.contains(newProductData.name); }); - // @todo : https://github.com/PrestaShop/hummingbird/pull/898 - it.skip('should check the product condition', async function () { + it('should check the product condition', async function () { await testContext.addContextItem(this, 'testIdentifier', 'checkProductCondition', baseContext); const productCondition = await foHummingbirdProductPage.getProductCondition(page); - expect(productCondition).to.eq(`Condition ${editProductData.condition}`); + expect(productCondition).to.eq(editProductData.condition); }); it('should go back to BO', async function () { diff --git a/tests/UI/campaigns/functional/BO/03_catalog/01_products/17_pricingTab.ts b/tests/UI/campaigns/functional/BO/03_catalog/01_products/17_pricingTab.ts index 946e09dae8606..4667986e028f1 100644 --- a/tests/UI/campaigns/functional/BO/03_catalog/01_products/17_pricingTab.ts +++ b/tests/UI/campaigns/functional/BO/03_catalog/01_products/17_pricingTab.ts @@ -304,8 +304,7 @@ describe('BO - Catalog - Products : Pricing tab', async () => { expect(pageTitle).to.contains(newProductData.name); }); - // @todo : https://github.com/PrestaShop/hummingbird/issues/879 - it.skip('should check the on sale flag', async function () { + it('should check the on sale flag', async function () { await testContext.addContextItem(this, 'testIdentifier', 'checkOnSaleFlag', baseContext); const flagText = await foHummingbirdProductPage.getProductTag(page); diff --git a/tests/UI/campaigns/functional/BO/03_catalog/01_products/22_bulkActionsEnableDisable3DotsButton.ts b/tests/UI/campaigns/functional/BO/03_catalog/01_products/22_bulkActionsEnableDisable3DotsButton.ts index 6b27e38181751..85128a313ec76 100644 --- a/tests/UI/campaigns/functional/BO/03_catalog/01_products/22_bulkActionsEnableDisable3DotsButton.ts +++ b/tests/UI/campaigns/functional/BO/03_catalog/01_products/22_bulkActionsEnableDisable3DotsButton.ts @@ -20,7 +20,6 @@ describe('BO - Catalog - Products list : Bulk actions, Enable/Disable, 3-dot but let numberOfProducts: number = 0; let productName: string = ''; - // before and after functions before(async function () { browserContext = await utilsPlaywright.createBrowserContext(this.browser); page = await utilsPlaywright.newTab(browserContext); diff --git a/tests/UI/campaigns/functional/BO/03_catalog/02_categories/02_CRUDCategoryInBO.ts b/tests/UI/campaigns/functional/BO/03_catalog/02_categories/02_CRUDCategoryInBO.ts index 17e04c46b073e..eaa24fd3d2b17 100644 --- a/tests/UI/campaigns/functional/BO/03_catalog/02_categories/02_CRUDCategoryInBO.ts +++ b/tests/UI/campaigns/functional/BO/03_catalog/02_categories/02_CRUDCategoryInBO.ts @@ -173,7 +173,7 @@ describe('BO - Catalog - Categories : CRUD Category in BO', async () => { // Check category name const pageTitle = await foHummingbirdCategoryPage.getHeaderPageName(page); - expect(pageTitle).to.contains(createCategoryData.name.toUpperCase()); + expect(pageTitle).to.contains(createCategoryData.name); // Check category description const categoryDescription = await foHummingbirdCategoryPage.getCategoryDescription(page); @@ -258,7 +258,7 @@ describe('BO - Catalog - Categories : CRUD Category in BO', async () => { // Check subcategory name const pageTitle = await foHummingbirdCategoryPage.getHeaderPageName(page); - expect(pageTitle).to.contains(createSubCategoryData.name.toUpperCase()); + expect(pageTitle).to.contains(createSubCategoryData.name); // Check subcategory description const subcategoryDescription = await foHummingbirdCategoryPage.getCategoryDescription(page); @@ -431,7 +431,7 @@ describe('BO - Catalog - Categories : CRUD Category in BO', async () => { // Check if it is redirected to the category const categoryName = await foHummingbirdCategoryPage.getHeaderPageName(page); - expect(categoryName).to.contains(arg.category.redirectedCategory!.name.toUpperCase()); + expect(categoryName).to.contains(arg.category.redirectedCategory!.name); }); it('should go back to BO', async function () { diff --git a/tests/UI/campaigns/functional/BO/03_catalog/02_categories/08_editHomeCategory.ts b/tests/UI/campaigns/functional/BO/03_catalog/02_categories/08_editHomeCategory.ts index 3d1af7b91e0d8..1a4e3aead6cfe 100644 --- a/tests/UI/campaigns/functional/BO/03_catalog/02_categories/08_editHomeCategory.ts +++ b/tests/UI/campaigns/functional/BO/03_catalog/02_categories/08_editHomeCategory.ts @@ -109,7 +109,7 @@ describe('BO - Catalog - Categories : Edit home category', async () => { // Check category name const pageTitle = await foHummingbirdCategoryPage.getHeaderPageName(page); - expect(pageTitle).to.contains(editCategoryData.name.toUpperCase()); + expect(pageTitle).to.contains(editCategoryData.name); // Check category description const categoryDescription = await foHummingbirdCategoryPage.getCategoryDescription(page); diff --git a/tests/UI/campaigns/functional/BO/08_design/05_positions/02_filterModule.ts b/tests/UI/campaigns/functional/BO/08_design/05_positions/02_filterModule.ts index 565df8a86278f..80e4b00aabb87 100644 --- a/tests/UI/campaigns/functional/BO/08_design/05_positions/02_filterModule.ts +++ b/tests/UI/campaigns/functional/BO/08_design/05_positions/02_filterModule.ts @@ -20,6 +20,8 @@ describe('BO - Design - Positions : Filter module', async () => { const hooks: string[] = [ 'displayAdminCustomers', + 'displayCustomerAccount', + 'displayFooter', 'displayMyAccountBlock', 'displayProductActions', ]; diff --git a/tests/UI/campaigns/functional/BO/08_design/06_imageSettings/14_checkCategoryImageFormat.ts b/tests/UI/campaigns/functional/BO/08_design/06_imageSettings/14_checkCategoryImageFormat.ts index def0c665a0a72..3f0571a00bcf5 100644 --- a/tests/UI/campaigns/functional/BO/08_design/06_imageSettings/14_checkCategoryImageFormat.ts +++ b/tests/UI/campaigns/functional/BO/08_design/06_imageSettings/14_checkCategoryImageFormat.ts @@ -282,8 +282,7 @@ describe('BO - Design - Image Settings : Check category image format', async () expect(isCategoryPageVisible, 'Home category page was not opened').to.eq(true); }); - // @todo : https://github.com/PrestaShop/hummingbird/issues/874 - it.skip('should check that the main image of the quick view is a WebP', async function () { + it('should check that the main image of the quick view is a WebP', async function () { await testContext.addContextItem(this, 'testIdentifier', `checkMainImageQuickView${argExtension}`, baseContext); const categoryImage = await foHummingbirdCategoryPage.getCategoryImageMain(page, arg.category.name); diff --git a/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/02_productPage/04_separatorOfAttributeAnchor.ts b/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/02_productPage/04_separatorOfAttributeAnchor.ts index 91473749e9c7a..723694c43b44f 100644 --- a/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/02_productPage/04_separatorOfAttributeAnchor.ts +++ b/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/02_productPage/04_separatorOfAttributeAnchor.ts @@ -24,7 +24,6 @@ describe('BO - Shop Parameters - Product Settings : Update separator of attribut const productAttributes: string[] = ['1', 'size', 's/8', 'color', 'white']; - // before and after functions before(async function () { browserContext = await utilsPlaywright.createBrowserContext(this.browser); page = await utilsPlaywright.newTab(browserContext); @@ -57,18 +56,16 @@ describe('BO - Shop Parameters - Product Settings : Update separator of attribut expect(pageTitle).to.contains(boProductSettingsPage.pageTitle); }); - const tests = [ - {args: {option: ',', attributesInProductLink: productAttributes.join(',')}}, - {args: {option: '-', attributesInProductLink: productAttributes.join('-')}}, - ]; - - tests.forEach((test, index: number) => { - it(`should choose the separator option '${test.args.option}'`, async function () { + [ + {option: ',', attributesInProductLink: productAttributes.join(',')}, + {option: '-', attributesInProductLink: productAttributes.join('-')}, + ].forEach((arg, index: number) => { + it(`should choose the separator option '${arg.option}'`, async function () { await testContext.addContextItem(this, 'testIdentifier', `chooseOption_${index}`, baseContext); const result = await boProductSettingsPage.setSeparatorOfAttributeOnProductLink( page, - test.args.option, + arg.option, ); expect(result).to.contains(boProductSettingsPage.successfulUpdateMessage); }); @@ -97,7 +94,7 @@ describe('BO - Shop Parameters - Product Settings : Update separator of attribut await testContext.addContextItem(this, 'testIdentifier', `checkAttributeSeparator_${index}`, baseContext); const currentURL = await foHummingbirdProductPage.getProductPageURL(page); - expect(currentURL).to.contains(test.args.attributesInProductLink); + expect(currentURL).to.contains(arg.attributesInProductLink); }); it('should close the page and go back to BO', async function () { diff --git a/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/06_labelOfInStockProducts.ts b/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/06_labelOfInStockProducts.ts index 1184d2a11bd23..55917e2f243cf 100644 --- a/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/06_labelOfInStockProducts.ts +++ b/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/06_labelOfInStockProducts.ts @@ -20,7 +20,6 @@ describe('BO - Shop Parameters - Product Settings : Update label of in-stock pro let browserContext: BrowserContext; let page: Page; - // before and after functions before(async function () { browserContext = await utilsPlaywright.createBrowserContext(this.browser); page = await utilsPlaywright.newTab(browserContext); @@ -87,11 +86,13 @@ describe('BO - Shop Parameters - Product Settings : Update label of in-stock pro it('should check the label of in-stock product in FO product page', async function () { await testContext.addContextItem(this, 'testIdentifier', `checkLabelInStock_${index}`, baseContext); - const isVisible = await foHummingbirdProductPage.isAvailabilityQuantityDisplayed(page); + const isVisible = await foHummingbirdProductPage.hasProductAvailabilityLabel(page); expect(isVisible).to.be.equal(test.args.exist); - const availabilityLabel = await foHummingbirdProductPage.getProductAvailabilityLabel(page); - expect(availabilityLabel).to.contains(test.args.labelToCheck); + if (test.args.exist) { + const availabilityLabel = await foHummingbirdProductPage.getProductAvailabilityLabel(page); + expect(availabilityLabel).to.contains(test.args.labelToCheck); + } }); it('should go back to BO', async function () { diff --git a/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/07_labelOutOfStock.ts b/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/07_labelOutOfStock.ts index b1d818611e69d..6ee9878e4b117 100644 --- a/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/07_labelOutOfStock.ts +++ b/tests/UI/campaigns/functional/BO/13_shopParameters/03_productSettings/03_productsStock/07_labelOutOfStock.ts @@ -18,8 +18,7 @@ import { const baseContext: string = 'functional_BO_shopParameters_productSettings_productsStock_labelOutOfStock'; -describe('BO - Shop Parameters - product Settings : Set label out-of-stock with ' - + 'allowed/denied backorders', async () => { +describe('BO - Shop Parameters - product Settings : Set label out-of-stock with allowed/denied backorders', async () => { let browserContext: BrowserContext; let page: Page; @@ -30,7 +29,6 @@ describe('BO - Shop Parameters - product Settings : Set label out-of-stock with labelWhenInStock: ' ', }); - // before and after functions before(async function () { browserContext = await utilsPlaywright.createBrowserContext(this.browser); page = await utilsPlaywright.newTab(browserContext); @@ -101,49 +99,45 @@ describe('BO - Shop Parameters - product Settings : Set label out-of-stock with const tests = [ { - args: { - action: 'enable', - enable: true, - backordersAction: 'allowed', - label: 'You can order', - labelToCheck: 'You can order', - }, + action: 'enable', + enable: true, + backordersAction: 'allowed', + label: 'You can order', }, { - args: { - action: 'enable', enable: true, backordersAction: 'allowed', label: ' ', labelToCheck: '', - }, + action: 'enable', + enable: true, + backordersAction: 'allowed', + label: ' ', }, { - args: { - action: 'disable', enable: false, backordersAction: 'denied', label: ' ', labelToCheck: '', - }, + action: 'disable', + enable: false, + backordersAction: 'denied', + label: ' ', }, { - args: { - action: 'disable', - enable: false, - backordersAction: 'denied', - label: 'Out-of-Stock', - labelToCheck: 'Out-of-Stock', - }, + action: 'disable', + enable: false, + backordersAction: 'denied', + label: 'Out-of-Stock', }, ]; - tests.forEach((test, index: number) => { - it(`should ${test.args.action} allow ordering of out-of-stock products`, async function () { + tests.forEach((arg, index: number) => { + it(`should ${arg.action} allow ordering of out-of-stock products`, async function () { await testContext.addContextItem( this, 'testIdentifier', - `${test.args.action}AllowOrderingOutOfStock${index}`, + `${arg.action}AllowOrderingOutOfStock${index}`, baseContext, ); - const result = await boProductSettingsPage.setAllowOrderingOutOfStockStatus(page, test.args.enable); + const result = await boProductSettingsPage.setAllowOrderingOutOfStockStatus(page, arg.enable); expect(result).to.contains(boProductSettingsPage.successfulUpdateMessage); }); - it(`should set Label of out-of-stock products with ${test.args.backordersAction} backorders`, async function () { + it(`should set Label of out-of-stock products with ${arg.backordersAction} backorders`, async function () { await testContext.addContextItem( this, 'testIdentifier', @@ -153,10 +147,10 @@ describe('BO - Shop Parameters - product Settings : Set label out-of-stock with let result; - if (test.args.enable) { - result = await boProductSettingsPage.setLabelOosAllowedBackorders(page, test.args.label); + if (arg.enable) { + result = await boProductSettingsPage.setLabelOosAllowedBackorders(page, arg.label); } else { - result = await boProductSettingsPage.setLabelOosDeniedBackorders(page, test.args.label); + result = await boProductSettingsPage.setLabelOosDeniedBackorders(page, arg.label); } expect(result).to.contains(boProductSettingsPage.successfulUpdateMessage); @@ -166,7 +160,7 @@ describe('BO - Shop Parameters - product Settings : Set label out-of-stock with await testContext.addContextItem( this, 'testIdentifier', - `viewMyShop${test.args.action}${index}`, + `viewMyShop${arg.action}${index}`, baseContext, ); @@ -180,7 +174,7 @@ describe('BO - Shop Parameters - product Settings : Set label out-of-stock with await testContext.addContextItem( this, 'testIdentifier', - `goToProductPage${test.args.action}${index}`, + `goToProductPage${arg.action}${index}`, baseContext, ); @@ -196,16 +190,23 @@ describe('BO - Shop Parameters - product Settings : Set label out-of-stock with await testContext.addContextItem( this, 'testIdentifier', - `checkOrderingOutOfStock${test.args.action}${index}`, + `checkOrderingOutOfStock${arg.action}${index}`, baseContext, ); + const hasLabel: boolean = arg.label.trim() !== ''; + // Check quantity and availability label const lastQuantityIsVisible = await foHummingbirdProductPage.isAddToCartButtonEnabled(page); - expect(lastQuantityIsVisible).to.be.equal(test.args.enable); + expect(lastQuantityIsVisible).to.be.equal(arg.enable); + + const hasProductAvailabilityLabel = await foHummingbirdProductPage.hasProductAvailabilityLabel(page); + expect(hasProductAvailabilityLabel).to.equals(hasLabel); - const availabilityLabel = await foHummingbirdProductPage.getProductAvailabilityLabel(page); - expect(availabilityLabel).to.contains(test.args.labelToCheck); + if (hasLabel) { + const availabilityLabel = await foHummingbirdProductPage.getProductAvailabilityLabel(page); + expect(availabilityLabel).to.contains(arg.label); + } }); it('should go back to BO', async function () { diff --git a/tests/UI/campaigns/functional/FO/classic/03_userAccount/05_getGDPRDataInCSV.ts b/tests/UI/campaigns/functional/FO/classic/03_userAccount/05_getGDPRDataInCSV.ts index 108d681b32f6c..6447c6ec69a91 100644 --- a/tests/UI/campaigns/functional/FO/classic/03_userAccount/05_getGDPRDataInCSV.ts +++ b/tests/UI/campaigns/functional/FO/classic/03_userAccount/05_getGDPRDataInCSV.ts @@ -252,7 +252,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => { true, 'utf16le', ); - expect(isVisible, 'General info is not correct!').to.eq(true); + expect(isVisible).to.eq(true); }); it('should check that Addresses table is empty', async function () { @@ -265,7 +265,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => { true, 'utf16le', ); - expect(isVisible, 'Addresses table is not empty!').to.eq(true); + expect(isVisible).to.eq(true); }); it('should check that Orders table is empty', async function () { @@ -278,7 +278,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => { true, 'utf16le', ); - expect(isVisible, 'Orders table is not empty!').to.eq(true); + expect(isVisible).to.eq(true); }); it('should check that Carts table is empty', async function () { @@ -291,7 +291,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => { true, 'utf16le', ); - expect(isVisible, 'Carts table is not empty!').to.eq(true); + expect(isVisible).to.eq(true); }); it('should check that Messages table is empty', async function () { @@ -326,7 +326,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => { const isVisible = await utilsFile.isTextInFile( filePath, - '"MODULE:NEWSLETTERSUBSCRIPTION""Newslettersubscription:noemailtoexport,thiscustomerhasnotregistered.""', + '"MODULE:NEWSLETTERSUBSCRIPTION""Newslettersubscription:noemailtoexport,thiscustomerhasnotregistered."', true, true, 'utf16le', @@ -339,7 +339,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => { const isVisible = await utilsFile.isTextInFile( filePath, - '""MODULE:PRODUCTCOMMENTS""MODULE:MAILALERTS"', + '""MODULE:PRODUCTCOMMENTS""MODULE:NEWSLETTERSUBSCRIPTION"', true, true, 'utf16le', @@ -352,7 +352,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => { const isVisible = await utilsFile.isTextInFile( filePath, - 'MODULE:MAILALERTS""Mailalert:Unabletoexportcustomerusingemail."', + '"MODULE:MAILALERTS""Mailalert:Unabletoexportcustomerusingemail."', true, true, 'utf16le', diff --git a/tests/UI/campaigns/functional/FO/hummingbird/03_userAccount/05_getGDPRDataInCSV.ts b/tests/UI/campaigns/functional/FO/hummingbird/03_userAccount/05_getGDPRDataInCSV.ts index 4d3c26aadfad3..fc56ee9ebb28a 100644 --- a/tests/UI/campaigns/functional/FO/hummingbird/03_userAccount/05_getGDPRDataInCSV.ts +++ b/tests/UI/campaigns/functional/FO/hummingbird/03_userAccount/05_getGDPRDataInCSV.ts @@ -87,7 +87,6 @@ describe('FO - Account : Get GDPR data in CSV', async () => { const createCustomerName: string = `${customerData.firstName[0]}. ${customerData.lastName}`; - // before and after functions before(async function () { browserContext = await utilsPlaywright.createBrowserContext(this.browser); page = await utilsPlaywright.newTab(browserContext); @@ -319,7 +318,7 @@ describe('FO - Account : Get GDPR data in CSV', async () => { const isVisible = await utilsFile.isTextInFile( filePath, - '"MODULE:NEWSLETTERSUBSCRIPTION""Newslettersubscription:noemailtoexport,thiscustomerhasnotregistered.""', + '"MODULE:NEWSLETTERSUBSCRIPTION""Newslettersubscription:noemailtoexport,thiscustomerhasnotregistered."', true, true, 'utf16le', @@ -332,12 +331,12 @@ describe('FO - Account : Get GDPR data in CSV', async () => { const isVisible = await utilsFile.isTextInFile( filePath, - '""MODULE:PRODUCTCOMMENTS""MODULE:MAILALERTS"', + '""MODULE:PRODUCTCOMMENTS""MODULE:NEWSLETTERSUBSCRIPTION"', true, true, 'utf16le', ); - expect(isVisible, 'Products comments is not empty!').to.eq(true); + expect(isVisible).to.eq(true); }); it('should check that mail alerts table is empty', async function () { @@ -345,12 +344,12 @@ describe('FO - Account : Get GDPR data in CSV', async () => { const isVisible = await utilsFile.isTextInFile( filePath, - 'MODULE:MAILALERTS""Mailalert:Unabletoexportcustomerusingemail."', + '"MODULE:MAILALERTS""Mailalert:Unabletoexportcustomerusingemail."', true, true, 'utf16le', ); - expect(isVisible, 'Mail alert table is not empty!').to.eq(true); + expect(isVisible).to.eq(true); }); }); }); diff --git a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/01_quickView/06_changeCombination.ts b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/01_quickView/06_changeCombination.ts index 82d4dfcca7e89..dcafebe02fd9c 100644 --- a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/01_quickView/06_changeCombination.ts +++ b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/01_quickView/06_changeCombination.ts @@ -104,7 +104,7 @@ describe('FO - Product page - Quick view : Change combination', async () => { await foHummingbirdModalQuickViewPage.setAttribute(page, secondAttributes[1]); const quickViewImageMain = await foHummingbirdModalQuickViewPage.getQuickViewImageMain(page); - expect(quickViewImageMain).to.contains('1-home_default'); + expect(quickViewImageMain).to.contains('1-default_xl'); }); it('should select the color white and check the cover image', async function () { @@ -113,7 +113,7 @@ describe('FO - Product page - Quick view : Change combination', async () => { await foHummingbirdModalQuickViewPage.setAttribute(page, firstAttributes[1]); const quickViewImageMain = await foHummingbirdModalQuickViewPage.getQuickViewImageMain(page); - expect(quickViewImageMain).to.contains('2-home_default'); + expect(quickViewImageMain).to.contains('2-default_xl'); }); it('should close the quick view modal', async function () { diff --git a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts index fc14c5ee7fcdd..2add99348973c 100644 --- a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts +++ b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts @@ -57,13 +57,13 @@ describe('FO - Product page : Change quantity', async () => { it('should change the quantity by using the arrow \'Down\' button', async function () { await testContext.addContextItem(this, 'testIdentifier', 'decrement', baseContext); - await foHummingbirdProductPage.setQuantityByArrowUpDown(page, 1, 'down'); + await foHummingbirdProductPage.setQuantityByArrowUpDown(page, 1, 'decrement'); const productQuantity = await foHummingbirdProductPage.getProductQuantity(page); expect(productQuantity).to.equal(1); }); - it('should change the quantity by using the arrow \'UP\' button', async function () { + it('should change the quantity by using the arrow \'Up\' button', async function () { await testContext.addContextItem(this, 'testIdentifier', 'incrementQuantity', baseContext); await foHummingbirdProductPage.setQuantityByArrowUpDown(page, 2, 'increment'); diff --git a/tests/UI/campaigns/modules/02_blockwishlist/01_installation/04_resetModule.ts b/tests/UI/campaigns/modules/02_blockwishlist/01_installation/04_resetModule.ts index 07a556aee7ec2..3deb31d8d493a 100644 --- a/tests/UI/campaigns/modules/02_blockwishlist/01_installation/04_resetModule.ts +++ b/tests/UI/campaigns/modules/02_blockwishlist/01_installation/04_resetModule.ts @@ -14,7 +14,7 @@ import { dataProducts, foHummingbirdHomePage, foHummingbirdLoginPage, - foClassicModalWishlistPage, + foHummingbirdModalWishlistPage, foHummingbirdMyWishlistsViewPage, foHummingbirdProductPage, foHummingbirdSearchResultsPage, @@ -161,14 +161,14 @@ describe('Wishlist module - Reset module', async () => { await foHummingbirdProductPage.clickAddToWishlistButton(page); - const textResult = await foClassicModalWishlistPage.getModalAddToCreateWislistLabel(page); + const textResult = await foHummingbirdModalWishlistPage.getModalAddToCreateWislistLabel(page); expect(textResult).to.contains(labelButton); }); it('should go to \'Modules > Module Manager\' page', async function () { await testContext.addContextItem(this, 'testIdentifier', 'goToModuleManagerPageForReset', baseContext); - page = await foClassicModalWishlistPage.changePage(browserContext, 0); + page = await foHummingbirdModalWishlistPage.changePage(browserContext, 0); await boDashboardPage.goToSubMenu( page, boDashboardPage.modulesParentLink, @@ -201,7 +201,7 @@ describe('Wishlist module - Reset module', async () => { await foHummingbirdProductPage.reloadPage(page); await foHummingbirdProductPage.clickAddToWishlistButton(page); - const textResult = await foClassicModalWishlistPage.getModalAddToCreateWislistLabel(page); + const textResult = await foHummingbirdModalWishlistPage.getModalAddToCreateWislistLabel(page); expect(textResult).to.contains(modBlockwishlistBoMain.defaultValueCreateButtonLabel); }); }); diff --git a/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/01_lists/03_shareList.ts b/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/01_lists/03_shareList.ts index 15d8b0734be5b..acea9ecea4753 100644 --- a/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/01_lists/03_shareList.ts +++ b/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/01_lists/03_shareList.ts @@ -7,7 +7,7 @@ import { dataModules, foHummingbirdHomePage, foHummingbirdLoginPage, - foClassicModalWishlistPage, + foHummingbirdModalWishlistPage, foHummingbirdMyAccountPage, foHummingbirdMyWishlistsPage, foHummingbirdMyWishlistsViewPage, @@ -90,10 +90,10 @@ describe('Wishlist module - Share a list', async () => { await foHummingbirdMyWishlistsPage.clickShareWishlistButton(page, 1); - const hasModalShare = await foClassicModalWishlistPage.hasModalShare(page); + const hasModalShare = await foHummingbirdModalWishlistPage.hasModalShare(page); expect(hasModalShare).to.equal(true); - const isModalVisible = await foClassicModalWishlistPage.clickCancelOnModalShare(page); + const isModalVisible = await foHummingbirdModalWishlistPage.clickCancelOnModalShare(page); expect(isModalVisible).to.equal(false); }); @@ -102,11 +102,11 @@ describe('Wishlist module - Share a list', async () => { await foHummingbirdMyWishlistsPage.clickShareWishlistButton(page, 1); - const hasModalLogin = await foClassicModalWishlistPage.hasModalShare(page); + const hasModalLogin = await foHummingbirdModalWishlistPage.hasModalShare(page); expect(hasModalLogin).to.equal(true); - const textToast = await foClassicModalWishlistPage.clickShareOnModalShare(page); - expect(textToast).to.equal(foClassicModalWishlistPage.messageLinkSharedWishlist); + const textToast = await foHummingbirdModalWishlistPage.clickShareOnModalShare(page); + expect(textToast).to.equal(foHummingbirdModalWishlistPage.messageLinkSharedWishlist); }); it('should click on the Create new list link and cancel', async function () { @@ -114,10 +114,10 @@ describe('Wishlist module - Share a list', async () => { await foHummingbirdMyWishlistsPage.clickCreateWishlistButton(page); - const hasModalCreate = await foClassicModalWishlistPage.hasModalCreate(page); + const hasModalCreate = await foHummingbirdModalWishlistPage.hasModalCreate(page); expect(hasModalCreate).to.equal(true); - const isModalVisible = await foClassicModalWishlistPage.clickCancelOnModalCreate(page); + const isModalVisible = await foHummingbirdModalWishlistPage.clickCancelOnModalCreate(page); expect(isModalVisible).to.equal(false); }); @@ -126,13 +126,13 @@ describe('Wishlist module - Share a list', async () => { await foHummingbirdMyWishlistsPage.clickCreateWishlistButton(page); - const hasModalCreate = await foClassicModalWishlistPage.hasModalCreate(page); + const hasModalCreate = await foHummingbirdModalWishlistPage.hasModalCreate(page); expect(hasModalCreate).to.equal(true); - await foClassicModalWishlistPage.setNameOnModalCreate(page, wishlistName); + await foHummingbirdModalWishlistPage.setNameOnModalCreate(page, wishlistName); - const textToast = await foClassicModalWishlistPage.clickCreateOnModalCreate(page); - expect(textToast).to.equal(foClassicModalWishlistPage.messageWishlistCreated); + const textToast = await foHummingbirdModalWishlistPage.clickCreateOnModalCreate(page); + expect(textToast).to.equal(foHummingbirdModalWishlistPage.messageWishlistCreated); }); it('should click on the share icon (in dropdown) and cancel the modal', async function () { @@ -140,10 +140,10 @@ describe('Wishlist module - Share a list', async () => { await foHummingbirdMyWishlistsPage.clickShareWishlistButton(page, 2); - const hasModalShare = await foClassicModalWishlistPage.hasModalShare(page); + const hasModalShare = await foHummingbirdModalWishlistPage.hasModalShare(page); expect(hasModalShare).to.equal(true); - const isModalVisible = await foClassicModalWishlistPage.clickCancelOnModalShare(page); + const isModalVisible = await foHummingbirdModalWishlistPage.clickCancelOnModalShare(page); expect(isModalVisible).to.equal(false); }); @@ -152,11 +152,11 @@ describe('Wishlist module - Share a list', async () => { await foHummingbirdMyWishlistsPage.clickShareWishlistButton(page, 2); - const hasModalLogin = await foClassicModalWishlistPage.hasModalShare(page); + const hasModalLogin = await foHummingbirdModalWishlistPage.hasModalShare(page); expect(hasModalLogin).to.equal(true); - const textToast = await foClassicModalWishlistPage.clickShareOnModalShare(page); - expect(textToast).to.equal(foClassicModalWishlistPage.messageLinkSharedWishlist); + const textToast = await foHummingbirdModalWishlistPage.clickShareOnModalShare(page); + expect(textToast).to.equal(foHummingbirdModalWishlistPage.messageLinkSharedWishlist); wishlistUrl = await foHummingbirdMyWishlistsPage.getClipboardText(page); expect(wishlistUrl).to.be.a('string'); diff --git a/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/02_products/01_addProductToList.ts b/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/02_products/01_addProductToList.ts index 108698fca8f27..edf813fd9633f 100644 --- a/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/02_products/01_addProductToList.ts +++ b/tests/UI/campaigns/modules/02_blockwishlist/03_frontOffice/02_products/01_addProductToList.ts @@ -12,7 +12,7 @@ import { FakerProduct, foHummingbirdHomePage, foHummingbirdLoginPage, - foClassicModalWishlistPage, + foHummingbirdModalWishlistPage, foHummingbirdMyAccountPage, foHummingbirdMyWishlistsPage, foHummingbirdMyWishlistsViewPage, @@ -90,10 +90,10 @@ describe('Wishlist module - Add a product to a list', async () => { await foHummingbirdProductPage.clickAddToWishlistButton(page); - const hasModalLogin = await foClassicModalWishlistPage.hasModalLogin(page); + const hasModalLogin = await foHummingbirdModalWishlistPage.hasModalLogin(page); expect(hasModalLogin).to.equal(true); - const isModalVisible = await foClassicModalWishlistPage.clickCancelOnModalLogin(page); + const isModalVisible = await foHummingbirdModalWishlistPage.clickCancelOnModalLogin(page); expect(isModalVisible).to.equal(false); }); @@ -102,10 +102,10 @@ describe('Wishlist module - Add a product to a list', async () => { await foHummingbirdProductPage.clickAddToWishlistButton(page); - const hasModalLogin = await foClassicModalWishlistPage.hasModalLogin(page); + const hasModalLogin = await foHummingbirdModalWishlistPage.hasModalLogin(page); expect(hasModalLogin).to.equal(true); - await foClassicModalWishlistPage.clickLoginOnModalLogin(page); + await foHummingbirdModalWishlistPage.clickLoginOnModalLogin(page); const pageTitle = await foHummingbirdLoginPage.getPageTitle(page); expect(pageTitle).to.contains(foHummingbirdLoginPage.pageTitle); @@ -165,7 +165,7 @@ describe('Wishlist module - Add a product to a list', async () => { const pageTitle = await foHummingbirdProductPage.getPageTitle(page); expect(pageTitle).to.equal(dataProducts.demo_3.name); - await foHummingbirdProductPage.setQuantityByArrowUpDown(page, 5, 'up'); + await foHummingbirdProductPage.setQuantityByArrowUpDown(page, 5, 'increment'); }); it('should add to the wishlist and select the first wishlist', async function () { @@ -173,8 +173,8 @@ describe('Wishlist module - Add a product to a list', async () => { await foHummingbirdProductPage.clickAddToWishlistButton(page); - const textResult = await foClassicModalWishlistPage.addWishlist(page, 1); - expect(textResult).to.equal(foClassicModalWishlistPage.messageAddedToWishlist); + const textResult = await foHummingbirdModalWishlistPage.addWishlist(page, 1); + expect(textResult).to.equal(foHummingbirdModalWishlistPage.messageAddedToWishlist); }); it('should go to "My Account" page', async function () { @@ -213,8 +213,9 @@ describe('Wishlist module - Add a product to a list', async () => { const nameProduct = await foHummingbirdMyWishlistsViewPage.getProductName(page, 1); expect(nameProduct).to.equal(dataProducts.demo_3.name); - const qtyProduct = await foHummingbirdMyWishlistsViewPage.getProductQuantity(page, 1); - expect(qtyProduct).to.equal(5); + // @todo : https://github.com/PrestaShop/hummingbird/issues/908 + //const qtyProduct = await foHummingbirdMyWishlistsViewPage.getProductQuantity(page, 1); + //expect(qtyProduct).to.equal(5); const sizeProduct = await foHummingbirdMyWishlistsViewPage.getProductAttribute(page, 1, 'Size'); expect(sizeProduct).to.equal('S'); @@ -235,8 +236,8 @@ describe('Wishlist module - Add a product to a list', async () => { await foHummingbirdProductPage.clickAddToWishlistButton(page); - const textResult = await foClassicModalWishlistPage.addWishlist(page, 1); - expect(textResult).to.equal(foClassicModalWishlistPage.messageAddedToWishlist); + const textResult = await foHummingbirdModalWishlistPage.addWishlist(page, 1); + expect(textResult).to.equal(foHummingbirdModalWishlistPage.messageAddedToWishlist); }); it('should go to "My Account" page', async function () { @@ -300,8 +301,8 @@ describe('Wishlist module - Add a product to a list', async () => { await foHummingbirdProductPage.clickAddToWishlistButton(page); - const textResult = await foClassicModalWishlistPage.addWishlist(page, 1); - expect(textResult).to.equal(foClassicModalWishlistPage.messageAddedToWishlist); + const textResult = await foHummingbirdModalWishlistPage.addWishlist(page, 1); + expect(textResult).to.equal(foHummingbirdModalWishlistPage.messageAddedToWishlist); }); it('should go to "My Account" page', async function () { @@ -357,17 +358,22 @@ describe('Wishlist module - Add a product to a list', async () => { expect(pageTitle).to.equal(dataProducts.demo_1.name); }); - it('should select the size \'M\' / color "Black" and check it', async function () { - await testContext.addContextItem(this, 'testIdentifier', 'selectSizeColor', baseContext); + it('should select the size \'M\' and check it', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'selectSize', baseContext); await foHummingbirdProductPage.selectAttributes(page, 'select', [{name: 'size', value: 'M'}]); - await foHummingbirdProductPage.selectAttributes(page, 'radio', [{name: 'Color', value: 'Black'}], 2); const selectedAttributeSize = await foHummingbirdProductPage.getSelectedAttribute(page, 1, 'select'); expect(selectedAttributeSize).to.equal('M'); + }); + + it('should select the color "Black" and check it', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'selectColor', baseContext); + + await foHummingbirdProductPage.selectAttributes(page, 'radio', [{name: 'Color', value: 'Black'}], 2); const selectedAttributeColor = await foHummingbirdProductPage.getSelectedAttribute(page, 2, 'radio'); - expect(selectedAttributeColor).to.equal('Black'); + expect(selectedAttributeColor).to.equal('Color - Black'); }); it('should add to the wishlist and select the first wishlist', async function () { @@ -375,8 +381,8 @@ describe('Wishlist module - Add a product to a list', async () => { await foHummingbirdProductPage.clickAddToWishlistButton(page); - const textResult = await foClassicModalWishlistPage.addWishlist(page, 1); - expect(textResult).to.equal(foClassicModalWishlistPage.messageAddedToWishlist); + const textResult = await foHummingbirdModalWishlistPage.addWishlist(page, 1); + expect(textResult).to.equal(foHummingbirdModalWishlistPage.messageAddedToWishlist); }); it('should go to "My Account" page', async function () { diff --git a/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/01_uninstallAndInstallModule.ts b/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/01_uninstallAndInstallModule.ts index 21b877b34e5a1..f2b1cf48d810d 100644 --- a/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/01_uninstallAndInstallModule.ts +++ b/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/01_uninstallAndInstallModule.ts @@ -174,9 +174,8 @@ describe('Mail alerts module - Uninstall and install module', async () => { const pageTitle = await foHummingbirdProductPage.getPageTitle(page); expect(pageTitle.toUpperCase()).to.contains(productOutOfStockNotAllowed.name.toUpperCase()); - // @todo : https://github.com/PrestaShop/hummingbird/issues/879 - // const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock'); - // expect(hasFlagOutOfStock).to.be.equal(true); + const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock'); + expect(hasFlagOutOfStock).to.be.equal(true); const hasBlockMailAlert = await foHummingbirdProductPage.hasBlockMailAlert(page); expect(hasBlockMailAlert).to.be.equal(false); @@ -235,9 +234,8 @@ describe('Mail alerts module - Uninstall and install module', async () => { const pageTitle = await foHummingbirdProductPage.getPageTitle(page); expect(pageTitle.toUpperCase()).to.contains(productOutOfStockNotAllowed.name.toUpperCase()); - // @todo : https://github.com/PrestaShop/hummingbird/issues/879 - // const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock'); - // expect(hasFlagOutOfStock).to.be.equal(true); + const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock'); + expect(hasFlagOutOfStock).to.be.equal(true); const hasBlockMailAlert = await foHummingbirdProductPage.hasBlockMailAlert(page); expect(hasBlockMailAlert).to.be.equal(true); diff --git a/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/03_uninstallAndDeleteModule.ts b/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/03_uninstallAndDeleteModule.ts index ba6880916e4c5..b18dd28ee789d 100644 --- a/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/03_uninstallAndDeleteModule.ts +++ b/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/03_uninstallAndDeleteModule.ts @@ -179,9 +179,8 @@ describe('Mail alerts module - Uninstall and delete module', async () => { const pageTitle = await foHummingbirdProductPage.getPageTitle(page); expect(pageTitle.toUpperCase()).to.contains(productOutOfStockNotAllowed.name.toUpperCase()); - // @todo : https://github.com/PrestaShop/hummingbird/issues/879 - // const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock'); - // expect(hasFlagOutOfStock).to.be.equal(true); + const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock'); + expect(hasFlagOutOfStock).to.be.equal(true); const hasBlockMailAlert = await foHummingbirdProductPage.hasBlockMailAlert(page); expect(hasBlockMailAlert).to.be.equal(false); diff --git a/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/05_disableEnableModule.ts b/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/05_disableEnableModule.ts index c67f9846e03c8..270e5de844aaa 100644 --- a/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/05_disableEnableModule.ts +++ b/tests/UI/campaigns/modules/20_ps_emailalerts/01_installation/05_disableEnableModule.ts @@ -166,9 +166,8 @@ describe('Mail alerts module - Disable/Enable module', async () => { const pageTitle = await foHummingbirdProductPage.getPageTitle(page); expect(pageTitle.toUpperCase()).to.contains(productOutOfStockNotAllowed.name.toUpperCase()); - // @todo : https://github.com/PrestaShop/hummingbird/issues/879 - // const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock'); - // expect(hasFlagOutOfStock).to.be.equal(true); + const hasFlagOutOfStock = await foHummingbirdProductPage.hasProductFlag(page, 'out_of_stock'); + expect(hasFlagOutOfStock).to.be.equal(true); const hasBlockMailAlert = await foHummingbirdProductPage.hasBlockMailAlert(page); expect(hasBlockMailAlert).to.be.equal(test.state); diff --git a/tests/UI/campaigns/modules/20_ps_emailalerts/02_configuration/01_customerNotifications/01_enableDisableProductAvailability.ts b/tests/UI/campaigns/modules/20_ps_emailalerts/02_configuration/01_customerNotifications/01_enableDisableProductAvailability.ts index 37d278d1ed384..f78e84227f1a4 100644 --- a/tests/UI/campaigns/modules/20_ps_emailalerts/02_configuration/01_customerNotifications/01_enableDisableProductAvailability.ts +++ b/tests/UI/campaigns/modules/20_ps_emailalerts/02_configuration/01_customerNotifications/01_enableDisableProductAvailability.ts @@ -321,8 +321,9 @@ describe('Mail alerts module - Customer notifications - Enable/Disable product a await page.reload(); - const textMessage = await foHummingbirdProductPage.getBlockMailAlertNotification(page); - expect(textMessage).to.be.equal(foHummingbirdProductPage.messageAlertNotificationAlreadyRegistered); + // @todo : https://github.com/PrestaShop/hummingbird/issues/910 + //const textMessage = await foHummingbirdProductPage.getBlockMailAlertNotification(page); + //expect(textMessage).to.be.equal(foHummingbirdProductPage.messageAlertNotificationAlreadyRegistered); }); }); diff --git a/tests/UI/campaigns/modules/22_ps_facetedsearch/01_installation/05_resetModule.ts b/tests/UI/campaigns/modules/22_ps_facetedsearch/01_installation/05_resetModule.ts index e7d712ea6fd05..e263e6fc72c33 100644 --- a/tests/UI/campaigns/modules/22_ps_facetedsearch/01_installation/05_resetModule.ts +++ b/tests/UI/campaigns/modules/22_ps_facetedsearch/01_installation/05_resetModule.ts @@ -114,7 +114,7 @@ describe('Faceted search module - Reset module', async () => { it('should check the "All products" page', async function () { await testContext.addContextItem(this, 'testIdentifier', 'goToAllProductsPage', baseContext); - await foHummingbirdHomePage.goToAllProductsBlockPage(page, 1); + await foHummingbirdHomePage.goToAllProductsPage(page); const isCategoryPageVisible = await foHummingbirdCategoryPage.isCategoryPage(page); expect(isCategoryPageVisible).to.be.eq(true); @@ -203,7 +203,7 @@ describe('Faceted search module - Reset module', async () => { it('should check the "All products" page', async function () { await testContext.addContextItem(this, 'testIdentifier', 'goToAllProductsPage1', baseContext); - await foHummingbirdHomePage.goToAllProductsBlockPage(page, 1); + await foHummingbirdHomePage.goToAllProductsPage(page); const isCategoryPageVisible = await foHummingbirdCategoryPage.isCategoryPage(page); expect(isCategoryPageVisible).to.be.eq(true); diff --git a/tests/UI/campaigns/modules/37_psgdpr/02_configuration/03_consentCheckboxCustomization.ts b/tests/UI/campaigns/modules/37_psgdpr/02_configuration/03_consentCheckboxCustomization.ts index f283adb335809..ff5bed55e7244 100644 --- a/tests/UI/campaigns/modules/37_psgdpr/02_configuration/03_consentCheckboxCustomization.ts +++ b/tests/UI/campaigns/modules/37_psgdpr/02_configuration/03_consentCheckboxCustomization.ts @@ -1,6 +1,6 @@ import testContext from '@utils/testContext'; import {expect} from 'chai'; -import {faker, fakerFR} from '@faker-js/faker'; +import {faker} from '@faker-js/faker'; // Import commonTests import {createProductTest, deleteProductTest} from '@commonTests/BO/catalog/product'; @@ -37,13 +37,14 @@ const baseContext: string = 'modules_psgdpr_configuration_consentCheckboxCustomi describe('GDPR : Consent checkbox customization', async () => { let browserContext: BrowserContext; let page: Page; - const messageAccountCreation: string = faker.lorem.sentence(); - const messageCustomerAccount: string = faker.lorem.sentence(); - const messageNewsletter: string = faker.lorem.sentence(); - const messageContactForm: string = faker.lorem.sentence(); - const messageProductComments: string = faker.lorem.sentence(); - const messageMailAlerts: string = faker.lorem.sentence(); - const messageMailAlertsFR: string = fakerFR.lorem.sentence(); + const messageBase: string = faker.lorem.sentence(); + const messageAccountCreation: string = `Account Creation - ${messageBase}`; + const messageCustomerAccount: string = `Customer Account - ${messageBase}`; + const messageNewsletter: string = `Newsletter - ${messageBase}`; + const messageContactForm: string = `Contact Form - ${messageBase}`; + const messageProductComments: string = `Product Comments - ${messageBase}`; + const messageMailAlerts: string = `Mail Alerts EN - ${messageBase}`; + const messageMailAlertsFR: string = `Mail Alerts FR - ${messageBase}`; const customerData: FakerCustomer = new FakerCustomer(); const productOutOfStock: FakerProduct = new FakerProduct({ quantity: 0, @@ -52,7 +53,6 @@ describe('GDPR : Consent checkbox customization', async () => { createProductTest(productOutOfStock, `${baseContext}_preTest_0`); describe('Consent checkbox customization', async () => { - // before and after functions before(async function () { browserContext = await utilsPlaywright.createBrowserContext(this.browser); page = await utilsPlaywright.newTab(browserContext); @@ -452,6 +452,11 @@ describe('GDPR : Consent checkbox customization', async () => { const successMessage = await modPsGdprBoTabDataConsent.saveForm(page); expect(successMessage).to.be.contains(modPsGdprBoTabDataConsent.saveFormMessage); + + await page.screenshot({ + path: `${global.SCREENSHOT.FOLDER}/gdpr_00.png`, + fullPage: true, + }); }); it('should check on Contact Form the GDPR Label', async function () { diff --git a/tests/UI/package-lock.json b/tests/UI/package-lock.json index 5fb0ef2ef3269..28c19221b6d29 100644 --- a/tests/UI/package-lock.json +++ b/tests/UI/package-lock.json @@ -534,7 +534,7 @@ }, "node_modules/@prestashop-core/ui-testing": { "version": "0.0.12", - "resolved": "git+ssh://git@github.com/PrestaShop/ui-testing-library.git#e0c93bbbdee39d0415b96ff4ea0c1cc9b6cbf2cc", + "resolved": "git+ssh://git@github.com/PrestaShop/ui-testing-library.git#8e86150dfb5ec99980384e33ccd0ad5217ce123b", "license": "MIT", "dependencies": { "@faker-js/faker": "^10.1.0", @@ -8704,7 +8704,7 @@ } }, "@prestashop-core/ui-testing": { - "version": "git+ssh://git@github.com/PrestaShop/ui-testing-library.git#e0c93bbbdee39d0415b96ff4ea0c1cc9b6cbf2cc", + "version": "git+ssh://git@github.com/PrestaShop/ui-testing-library.git#8e86150dfb5ec99980384e33ccd0ad5217ce123b", "from": "@prestashop-core/ui-testing@https://github.com/PrestaShop/ui-testing-library#main", "requires": { "@faker-js/faker": "^10.1.0", From 5bab1e867ddc0cd79ef41eb71f71c4ee82aed927 Mon Sep 17 00:00:00 2001 From: Progi1984 <franck.lefevre@prestashop.com> Date: Mon, 26 Jan 2026 15:50:13 +0100 Subject: [PATCH 16/19] Functional Tests : Bump @prestashop-core/ui-testing --- .../02_sortAndFilter/02_filterProducts.ts | 1 - tests/UI/package-lock.json | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/UI/campaigns/functional/FO/hummingbird/08_menuAndNavigation/02_sortAndFilter/02_filterProducts.ts b/tests/UI/campaigns/functional/FO/hummingbird/08_menuAndNavigation/02_sortAndFilter/02_filterProducts.ts index 7344535b897c2..3e6946e89a2c8 100644 --- a/tests/UI/campaigns/functional/FO/hummingbird/08_menuAndNavigation/02_sortAndFilter/02_filterProducts.ts +++ b/tests/UI/campaigns/functional/FO/hummingbird/08_menuAndNavigation/02_sortAndFilter/02_filterProducts.ts @@ -39,7 +39,6 @@ describe('FO - Menu and Navigation - Sort and filter : Filter products', async ( // Pre-condition : Install Hummingbird enableHummingbird(`${baseContext}_preTest`); - // before and after functions before(async function () { browserContext = await utilsPlaywright.createBrowserContext(this.browser); page = await utilsPlaywright.newTab(browserContext); diff --git a/tests/UI/package-lock.json b/tests/UI/package-lock.json index 49bb26b77cc41..630fb6ca2d683 100644 --- a/tests/UI/package-lock.json +++ b/tests/UI/package-lock.json @@ -418,7 +418,7 @@ }, "node_modules/@prestashop-core/ui-testing": { "version": "0.0.12", - "resolved": "git+ssh://git@github.com/PrestaShop/ui-testing-library.git#e0c93bbbdee39d0415b96ff4ea0c1cc9b6cbf2cc", + "resolved": "git+ssh://git@github.com/PrestaShop/ui-testing-library.git#8e86150dfb5ec99980384e33ccd0ad5217ce123b", "license": "MIT", "dependencies": { "@faker-js/faker": "^10.1.0", @@ -7769,7 +7769,7 @@ } }, "@prestashop-core/ui-testing": { - "version": "git+ssh://git@github.com/PrestaShop/ui-testing-library.git#e0c93bbbdee39d0415b96ff4ea0c1cc9b6cbf2cc", + "version": "git+ssh://git@github.com/PrestaShop/ui-testing-library.git#8e86150dfb5ec99980384e33ccd0ad5217ce123b", "from": "@prestashop-core/ui-testing@https://github.com/PrestaShop/ui-testing-library#main", "requires": { "@faker-js/faker": "^10.1.0", From 55087c1e7a418cd31ef4c19bbf1f34d0fb97e8f6 Mon Sep 17 00:00:00 2001 From: Jonathan LELIEVRE <jonathan.lelievre@prestashop.com> Date: Thu, 29 Jan 2026 16:08:18 +0100 Subject: [PATCH 17/19] Fix changelog order --- docs/CHANGELOG.txt | 166 ++++++++++++++++++++++----------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index f51b8f0d7f90c..a4b0604eda890 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -23,89 +23,6 @@ needs please refer to https://devdocs.prestashop.com/ for more information. Changelog for PrestaShop 9 -#################################### -# v9.0.3 - (2026-01-22) -#################################### - -- Back Office: - - Improvement: - - #40517: Add some help boxes to product page (by @Hlavtox) - - #39923: Improve wording of some settings, better explain the meaning of them (by @Hlavtox) - - #40230: Adds help text to product page fields (by @Hlavtox) - - Bug fix: - - #40563: Admin API improvements for combination endpoints (by @jolelievre) - - #40556: Fix: CsvFileReader service fails with "You have requested a non-existent service 'session'" (by @Codencode) - - #38775: Fix: Multishop - error loading CMS pages removed from the default shop (by @Codencode) - - #40499: Fix: When saving an Attribute is_color_group is not updated. (by @Codencode) - - #40554: Fix: always display taxes total in order summary (by @Codencode) - - #40532: BO Product page, fix feature value collection indexes (by @jolelievre) - - #40433: Fix: Module update problem (by @Codencode) - - #40054: Use URL when building urls to prevent subtle mistakes with &? (by @tswfi) - - #40036: BO - Product : Fixed feature display in multishop (by @Progi1984) - - #39854: Fix: Quick Access links redirect to root instead of subdirectory, causing 404 (by @Codencode) - - #40050: Fix: Incorrect redirect from HTTP to HTTPS on the admin login page when PrestaShop is in a subfolder (by @Codencode) - - #40475: Fix link for redirection on country BO page (by @jolelievre) - - #40066: Fix: [BO] Admin Countries page redirect issue with multishop after changing shop (by @Codencode) - - #40329: Prevent NoResultException when checking for existing translations (by @ChillCode) - - #39926: Fix: handle SELECT fields without no_quotes in getSensitiveAttributes (by @Codencode) - - #40001: Fix redirect after editing root category to use current categoryId in stead of PS_HOME_CATEGORY (by @Codencode) - - #39869: Update monologger to v3 (by @NKoonen) - - #40256: Fix: Issue retrieving product price when adding the first specific_price (by @Codencode) - - #40243: Admin API handle position update (by @jolelievre) - - #40257: Prevent saving ajax URL for future login redirection, or the redirect… (by @jolelievre) - - #40112: Bump prestakit to v2.0.5 (by @Quetzacoalt91) - - Refactoring: - - #37667: Apply backoffice optimizations (by @Hlavtox) -- Front Office: - - New feature: - - #40403: Allow easily hooking into country and currency selection logic (by @Hlavtox) - - Improvement: - - #40537: Remove unnecessary force refresh in checkout (by @Hlavtox) - - #40248: Prevent Exposure of Sensitive Product Attributes in Front Office (by @M0rgan01) - - Bug fix: - - #40246: Fix preview in multilang (by @tleon) - - #39582: Fix: Product customization text field bug with using symbol {} (by @Codencode) - - #40117: Fix cart rule validation in front office (by @Hlavtox) - - #40262: Allow access to customized files preview without multi-lang enabled (by @kpodemski) - - #40137: Updated zxcvbn to a maintained version for consistency with backend checks (by @tleon) - - Refactoring: - - #40406: Comment how language setting works in FO (by @Hlavtox) -- Core: - - Improvement: - - #40269: Prevent database inconsistencies by preventing faulty group delete calls (by @Hlavtox) - - #40133: Update ca-bundle from 1.3.7 to 1.5.9 (by @tswfi) - - #40350: Remove the composer config to ignore audit (by @jolelievre) - - #40332: Restore original repository for ps_apiresources (by @nicosomb) - - #40312: Bump to `9.0.3` (by @boherm) - - #40134: Docker : Fixed Install of xdebug (by @Progi1984) - - Bug fix: - - #40585: Preliminary tasks for patch version 9.0.3 (by @jolelievre) - - #40479: Fix legacy profiler in the back office (by @kpodemski) - - #40562: Update VAT rates for Estonia and Romania (by @Codencode) - - #40496: Fix some type issues related to carriers (by @Hlavtox) - - #40423: Fix empty extra vars / product list when using the new automatic text email option (by @matrixino) - - #40040: Always request a non cached result in Order::getIdByCartId (by @ilsalvopss) - - #40400: Simplify cart rule minimal value by avoiding subtracting values (by @Hlavtox) - - Refactoring: - - #40389: Comment logic related to addresses and their initialization (by @Hlavtox) -- Installer: - - Bug fix: - - #40574: Fix Makefile to prevent build assets twice (by @jolelievre) - - #40077: Install Console : Allow characters "<" & ">" in admin password (by @Progi1984) - - #40114: Chore(Makefile): fix Makefile shell detection issue (by @tyloo) -- Localization: - - Bug fix: - - #40521: Fix default fixtures translation (by @jolelievre) -- Tests: - - Improvement: - - #40512: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) - - #40458: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) - - #40434: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) - - #40363: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) - - #40138: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) - - Refactoring: - - #40298: Functional tests - Fix create account in FO classic theme test (by @nesrineabdmouleh) - #################################### # v9.1.0 Beta 1 - (2025-12-18) #################################### @@ -593,6 +510,89 @@ Changelog for PrestaShop 9 - #40298: Functional tests - Fix create account in FO classic theme test (by @nesrineabdmouleh) - #39166: Refactor manufacturer behat tests (by @jolelievre) +#################################### +# v9.0.3 - (2026-01-22) +#################################### + +- Back Office: + - Improvement: + - #40517: Add some help boxes to product page (by @Hlavtox) + - #39923: Improve wording of some settings, better explain the meaning of them (by @Hlavtox) + - #40230: Adds help text to product page fields (by @Hlavtox) + - Bug fix: + - #40563: Admin API improvements for combination endpoints (by @jolelievre) + - #40556: Fix: CsvFileReader service fails with "You have requested a non-existent service 'session'" (by @Codencode) + - #38775: Fix: Multishop - error loading CMS pages removed from the default shop (by @Codencode) + - #40499: Fix: When saving an Attribute is_color_group is not updated. (by @Codencode) + - #40554: Fix: always display taxes total in order summary (by @Codencode) + - #40532: BO Product page, fix feature value collection indexes (by @jolelievre) + - #40433: Fix: Module update problem (by @Codencode) + - #40054: Use URL when building urls to prevent subtle mistakes with &? (by @tswfi) + - #40036: BO - Product : Fixed feature display in multishop (by @Progi1984) + - #39854: Fix: Quick Access links redirect to root instead of subdirectory, causing 404 (by @Codencode) + - #40050: Fix: Incorrect redirect from HTTP to HTTPS on the admin login page when PrestaShop is in a subfolder (by @Codencode) + - #40475: Fix link for redirection on country BO page (by @jolelievre) + - #40066: Fix: [BO] Admin Countries page redirect issue with multishop after changing shop (by @Codencode) + - #40329: Prevent NoResultException when checking for existing translations (by @ChillCode) + - #39926: Fix: handle SELECT fields without no_quotes in getSensitiveAttributes (by @Codencode) + - #40001: Fix redirect after editing root category to use current categoryId in stead of PS_HOME_CATEGORY (by @Codencode) + - #39869: Update monologger to v3 (by @NKoonen) + - #40256: Fix: Issue retrieving product price when adding the first specific_price (by @Codencode) + - #40243: Admin API handle position update (by @jolelievre) + - #40257: Prevent saving ajax URL for future login redirection, or the redirect… (by @jolelievre) + - #40112: Bump prestakit to v2.0.5 (by @Quetzacoalt91) + - Refactoring: + - #37667: Apply backoffice optimizations (by @Hlavtox) +- Front Office: + - New feature: + - #40403: Allow easily hooking into country and currency selection logic (by @Hlavtox) + - Improvement: + - #40537: Remove unnecessary force refresh in checkout (by @Hlavtox) + - #40248: Prevent Exposure of Sensitive Product Attributes in Front Office (by @M0rgan01) + - Bug fix: + - #40246: Fix preview in multilang (by @tleon) + - #39582: Fix: Product customization text field bug with using symbol {} (by @Codencode) + - #40117: Fix cart rule validation in front office (by @Hlavtox) + - #40262: Allow access to customized files preview without multi-lang enabled (by @kpodemski) + - #40137: Updated zxcvbn to a maintained version for consistency with backend checks (by @tleon) + - Refactoring: + - #40406: Comment how language setting works in FO (by @Hlavtox) +- Core: + - Improvement: + - #40269: Prevent database inconsistencies by preventing faulty group delete calls (by @Hlavtox) + - #40133: Update ca-bundle from 1.3.7 to 1.5.9 (by @tswfi) + - #40350: Remove the composer config to ignore audit (by @jolelievre) + - #40332: Restore original repository for ps_apiresources (by @nicosomb) + - #40312: Bump to `9.0.3` (by @boherm) + - #40134: Docker : Fixed Install of xdebug (by @Progi1984) + - Bug fix: + - #40585: Preliminary tasks for patch version 9.0.3 (by @jolelievre) + - #40479: Fix legacy profiler in the back office (by @kpodemski) + - #40562: Update VAT rates for Estonia and Romania (by @Codencode) + - #40496: Fix some type issues related to carriers (by @Hlavtox) + - #40423: Fix empty extra vars / product list when using the new automatic text email option (by @matrixino) + - #40040: Always request a non cached result in Order::getIdByCartId (by @ilsalvopss) + - #40400: Simplify cart rule minimal value by avoiding subtracting values (by @Hlavtox) + - Refactoring: + - #40389: Comment logic related to addresses and their initialization (by @Hlavtox) +- Installer: + - Bug fix: + - #40574: Fix Makefile to prevent build assets twice (by @jolelievre) + - #40077: Install Console : Allow characters "<" & ">" in admin password (by @Progi1984) + - #40114: Chore(Makefile): fix Makefile shell detection issue (by @tyloo) +- Localization: + - Bug fix: + - #40521: Fix default fixtures translation (by @jolelievre) +- Tests: + - Improvement: + - #40512: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) + - #40458: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) + - #40434: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) + - #40363: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) + - #40138: Functional Tests : Bump @prestashop-core/ui-testing (by @Progi1984) + - Refactoring: + - #40298: Functional tests - Fix create account in FO classic theme test (by @nesrineabdmouleh) + #################################### # v9.0.2 - (2025-12-01) #################################### From bf032f2e5b3ebae81870e2f767221c2cccfa187c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= <nicolas.loeuillet@prestashop.com> Date: Thu, 29 Jan 2026 10:16:03 +0100 Subject: [PATCH 18/19] Add discountType endpoint --- composer.json | 2 +- composer.lock | 20 +-- .../QueryHandler/GetDiscountTypesHandler.php | 62 +++++++++ .../Repository/DiscountTypeRepository.php | 46 +++++++ .../Discount/Query/GetDiscountTypes.php | 31 +++++ .../GetDiscountTypesHandlerInterface.php | 38 ++++++ .../Discount/QueryResult/DiscountType.php | 78 +++++++++++ .../config/services/adapter/discount.yml | 3 + .../Discount/DiscountTypesFeatureContext.php | 121 ++++++++++++++++++ .../Discount/BO/discount_types.feature | 18 +++ tests/Integration/Behaviour/behat.yml | 1 + .../functional/API/02_checkEndpoints.ts | 2 + 12 files changed, 412 insertions(+), 10 deletions(-) create mode 100644 src/Adapter/Discount/QueryHandler/GetDiscountTypesHandler.php create mode 100644 src/Core/Domain/Discount/Query/GetDiscountTypes.php create mode 100644 src/Core/Domain/Discount/QueryHandler/GetDiscountTypesHandlerInterface.php create mode 100644 src/Core/Domain/Discount/QueryResult/DiscountType.php create mode 100644 tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountTypesFeatureContext.php create mode 100644 tests/Integration/Behaviour/Features/Scenario/Discount/BO/discount_types.feature diff --git a/composer.json b/composer.json index 515df971035b7..4279736c730f5 100644 --- a/composer.json +++ b/composer.json @@ -71,7 +71,7 @@ "prestashop/hummingbird": "^2.0", "prestashop/pagesnotfound": "^3", "prestashop/productcomments": "^8.0", - "prestashop/ps_apiresources": "^0.3", + "prestashop/ps_apiresources": "dev-add-discountType-resource", "prestashop/ps_banner": "^2", "prestashop/ps_bestsellers": "^1.0", "prestashop/ps_brandlist": "^1.0", diff --git a/composer.lock b/composer.lock index 49ab22c7d83fd..bf1d03bf5b622 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "90d647f23adfcfd08405108bfe8913fd", + "content-hash": "a432fafce6e80f362185427cad5f4b8d", "packages": [ { "name": "api-platform/core", @@ -5208,16 +5208,16 @@ }, { "name": "prestashop/ps_apiresources", - "version": "v0.3.0", + "version": "dev-add-discountType-resource", "source": { "type": "git", "url": "https://github.com/PrestaShop/ps_apiresources.git", - "reference": "af1b194acca43dc48d932daee4b9e2f908177ad4" + "reference": "f7d72022404d9c03691e3d63bde7ccae2f26a04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PrestaShop/ps_apiresources/zipball/af1b194acca43dc48d932daee4b9e2f908177ad4", - "reference": "af1b194acca43dc48d932daee4b9e2f908177ad4", + "url": "https://api.github.com/repos/PrestaShop/ps_apiresources/zipball/f7d72022404d9c03691e3d63bde7ccae2f26a04a", + "reference": "f7d72022404d9c03691e3d63bde7ccae2f26a04a", "shasum": "" }, "require": { @@ -5281,10 +5281,10 @@ "description": "PrestaShop - API Resources", "homepage": "https://github.com/PrestaShop/ps_apiresources", "support": { - "source": "https://github.com/PrestaShop/ps_apiresources/tree/v0.3.0", + "source": "https://github.com/PrestaShop/ps_apiresources/tree/add-discountType-resource", "issues": "https://github.com/PrestaShop/ps_apiresources/issues" }, - "time": "2026-01-22T10:05:01+00:00" + "time": "2026-01-28T06:41:30+00:00" }, { "name": "prestashop/ps_banner", @@ -18250,7 +18250,9 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": {}, + "stability-flags": { + "prestashop/ps_apiresources": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -18271,5 +18273,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/src/Adapter/Discount/QueryHandler/GetDiscountTypesHandler.php b/src/Adapter/Discount/QueryHandler/GetDiscountTypesHandler.php new file mode 100644 index 0000000000000..f2c184e4df092 --- /dev/null +++ b/src/Adapter/Discount/QueryHandler/GetDiscountTypesHandler.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors <contact@prestashop.com> + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Adapter\Discount\QueryHandler; + +use PrestaShop\PrestaShop\Adapter\Discount\Repository\DiscountTypeRepository; +use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsQueryHandler; +use PrestaShop\PrestaShop\Core\Domain\Discount\Query\GetDiscountTypes; +use PrestaShop\PrestaShop\Core\Domain\Discount\QueryHandler\GetDiscountTypesHandlerInterface; +use PrestaShop\PrestaShop\Core\Domain\Discount\QueryResult\DiscountType; + +#[AsQueryHandler] +class GetDiscountTypesHandler implements GetDiscountTypesHandlerInterface +{ + public function __construct( + private readonly DiscountTypeRepository $discountTypeRepository, + ) { + } + + /** + * @return DiscountType[] + */ + public function handle(GetDiscountTypes $query): array + { + $groupedTypes = $this->discountTypeRepository->getAllTypes(); + + return array_map( + fn (array $type) => new DiscountType( + $type['id_cart_rule_type'], + $type['discount_type'], + $type['names'], + $type['descriptions'], + $type['is_core'], + $type['enabled'] + ), + array_values($groupedTypes) + ); + } +} diff --git a/src/Adapter/Discount/Repository/DiscountTypeRepository.php b/src/Adapter/Discount/Repository/DiscountTypeRepository.php index fc7856007e852..63b32eaec22a3 100644 --- a/src/Adapter/Discount/Repository/DiscountTypeRepository.php +++ b/src/Adapter/Discount/Repository/DiscountTypeRepository.php @@ -40,6 +40,52 @@ public function __construct( ) { } + /** + * Get all discount types grouped by discount type ID with translations + * + * @return array<int, array{id_cart_rule_type: int, discount_type: string, is_core: bool, enabled: bool, names: array<int, string>, descriptions: array<int, string>}> + */ + public function getAllTypes(): array + { + $qb = $this->connection->createQueryBuilder(); + $qb + ->select('crt.id_cart_rule_type', 'crt.discount_type', 'crt.is_core', 'crt.active', 'crtl.name', 'crtl.description', 'crtl.id_lang') + ->from($this->dbPrefix . 'cart_rule_type', 'crt') + ->leftJoin('crt', $this->dbPrefix . 'cart_rule_type_lang', 'crtl', 'crt.id_cart_rule_type = crtl.id_cart_rule_type') + ->orderBy('crt.id_cart_rule_type', 'ASC') + ->addOrderBy('crtl.id_lang', 'ASC') + ; + + $allTypes = $qb->executeQuery()->fetchAllAssociative(); + $groupedTypes = []; + + foreach ($allTypes as $type) { + $discountTypeId = (int) $type['id_cart_rule_type']; + $langId = !empty($type['id_lang']) ? (int) $type['id_lang'] : null; + + if (!isset($groupedTypes[$discountTypeId])) { + $groupedTypes[$discountTypeId] = [ + 'id_cart_rule_type' => $discountTypeId, + 'discount_type' => $type['discount_type'], + 'is_core' => (bool) $type['is_core'], + 'enabled' => (bool) $type['active'], + 'names' => [], + 'descriptions' => [], + ]; + } + + // Add translations if available + if ($langId !== null && !empty($type['name'])) { + $groupedTypes[$discountTypeId]['names'][$langId] = $type['name']; + } + if ($langId !== null && !empty($type['description'])) { + $groupedTypes[$discountTypeId]['descriptions'][$langId] = $type['description']; + } + } + + return $groupedTypes; + } + /** * Get all active discount types * diff --git a/src/Core/Domain/Discount/Query/GetDiscountTypes.php b/src/Core/Domain/Discount/Query/GetDiscountTypes.php new file mode 100644 index 0000000000000..472e4a8528f9b --- /dev/null +++ b/src/Core/Domain/Discount/Query/GetDiscountTypes.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors <contact@prestashop.com> + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\Query; + +class GetDiscountTypes +{ +} diff --git a/src/Core/Domain/Discount/QueryHandler/GetDiscountTypesHandlerInterface.php b/src/Core/Domain/Discount/QueryHandler/GetDiscountTypesHandlerInterface.php new file mode 100644 index 0000000000000..e2cca3a665c57 --- /dev/null +++ b/src/Core/Domain/Discount/QueryHandler/GetDiscountTypesHandlerInterface.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors <contact@prestashop.com> + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\QueryHandler; + +use PrestaShop\PrestaShop\Core\Domain\Discount\Query\GetDiscountTypes; +use PrestaShop\PrestaShop\Core\Domain\Discount\QueryResult\DiscountType; + +interface GetDiscountTypesHandlerInterface +{ + /** + * @return DiscountType[] + */ + public function handle(GetDiscountTypes $query): array; +} diff --git a/src/Core/Domain/Discount/QueryResult/DiscountType.php b/src/Core/Domain/Discount/QueryResult/DiscountType.php new file mode 100644 index 0000000000000..df4470f224660 --- /dev/null +++ b/src/Core/Domain/Discount/QueryResult/DiscountType.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors <contact@prestashop.com> + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\QueryResult; + +class DiscountType +{ + /** + * @param int $discountTypeId + * @param string $type + * @param array<int, string> $localizedNames indexed by language ID + * @param array<int, string> $localizedDescriptions indexed by language ID + * @param bool $isCore + * @param bool $enabled + */ + public function __construct( + private readonly int $discountTypeId, + private readonly string $type, + private readonly array $localizedNames, + private readonly array $localizedDescriptions, + private readonly bool $isCore = false, + private readonly bool $enabled = true + ) { + } + + public function getDiscountTypeId(): int + { + return $this->discountTypeId; + } + + public function getType(): string + { + return $this->type; + } + + public function getLocalizedNames(): array + { + return $this->localizedNames; + } + + public function getLocalizedDescriptions(): array + { + return $this->localizedDescriptions; + } + + public function isCore(): bool + { + return $this->isCore; + } + + public function isEnabled(): bool + { + return $this->enabled; + } +} diff --git a/src/PrestaShopBundle/Resources/config/services/adapter/discount.yml b/src/PrestaShopBundle/Resources/config/services/adapter/discount.yml index 913d739603617..3b5233fbae96c 100644 --- a/src/PrestaShopBundle/Resources/config/services/adapter/discount.yml +++ b/src/PrestaShopBundle/Resources/config/services/adapter/discount.yml @@ -40,6 +40,9 @@ services: PrestaShop\PrestaShop\Adapter\Discount\QueryHandler\GetDiscountForEditingHandler: autoconfigure: true + PrestaShop\PrestaShop\Adapter\Discount\QueryHandler\GetDiscountTypesHandler: + autoconfigure: true + PrestaShop\PrestaShop\Adapter\Discount\Validate\DiscountValidator: calls: - setDiscountRepository: [ '@PrestaShop\PrestaShop\Adapter\Discount\Repository\DiscountRepository' ] diff --git a/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountTypesFeatureContext.php b/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountTypesFeatureContext.php new file mode 100644 index 0000000000000..366f4267b3313 --- /dev/null +++ b/tests/Integration/Behaviour/Features/Context/Domain/Discount/DiscountTypesFeatureContext.php @@ -0,0 +1,121 @@ +<?php +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors <contact@prestashop.com> + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace Tests\Integration\Behaviour\Features\Context\Domain\Discount; + +use Behat\Gherkin\Node\TableNode; +use PHPUnit\Framework\Assert; +use PrestaShop\PrestaShop\Core\Domain\Discount\Query\GetDiscountTypes; +use RuntimeException; +use Tests\Integration\Behaviour\Features\Context\Domain\AbstractDomainFeatureContext; + +class DiscountTypesFeatureContext extends AbstractDomainFeatureContext +{ + /** + * @Then I should receive the following discount types: + * + * @param TableNode $table + */ + public function assertDiscountTypes(TableNode $table): void + { + $queryBus = $this->getQueryBus(); + $query = new GetDiscountTypes(); + $discountTypes = $queryBus->handle($query); + + Assert::assertNotNull($discountTypes, 'No discount types received'); + Assert::assertIsArray($discountTypes, 'Discount types should be an array'); + + $expectedDiscountTypes = $this->localizeByColumns($table); + + Assert::assertGreaterThanOrEqual( + count($expectedDiscountTypes), + count($discountTypes), + sprintf('Expected %d discount types but got %d', count($expectedDiscountTypes), count($discountTypes)) + ); + + foreach ($expectedDiscountTypes as $expectedDiscountType) { + $foundDiscountType = null; + foreach ($discountTypes as $discountType) { + if ($discountType->getType() === $expectedDiscountType['type']) { + $foundDiscountType = $discountType; + break; + } + } + + if (null === $foundDiscountType) { + throw new RuntimeException(sprintf('Couldnt find discount type "%s"', $expectedDiscountType['type'])); + } + + if (isset($expectedDiscountType['discountTypeId'])) { + Assert::assertEquals( + (int) $expectedDiscountType['discountTypeId'], + $foundDiscountType->getDiscountTypeId(), + sprintf('Unexpected discountTypeId for type "%s"', $expectedDiscountType['type']) + ); + } + + if (isset($expectedDiscountType['core'])) { + $expectedCore = filter_var($expectedDiscountType['core'], FILTER_VALIDATE_BOOLEAN); + Assert::assertEquals( + $expectedCore, + $foundDiscountType->isCore(), + sprintf('Unexpected core value for type "%s"', $expectedDiscountType['type']) + ); + } + + if (isset($expectedDiscountType['enabled'])) { + $expectedEnabled = filter_var($expectedDiscountType['enabled'], FILTER_VALIDATE_BOOLEAN); + Assert::assertEquals( + $expectedEnabled, + $foundDiscountType->isEnabled(), + sprintf('Unexpected enabled value for type "%s"', $expectedDiscountType['type']) + ); + } + + if (isset($expectedDiscountType['names'])) { + $actualNames = $foundDiscountType->getLocalizedNames(); + foreach ($expectedDiscountType['names'] as $langId => $expectedName) { + Assert::assertEquals( + $expectedName, + $actualNames[$langId], + sprintf('Unexpected name for language ID %d in type "%s"', $langId, $expectedDiscountType['type']) + ); + } + } + + if (isset($expectedDiscountType['descriptions'])) { + $actualDescriptions = $foundDiscountType->getLocalizedDescriptions(); + foreach ($expectedDiscountType['descriptions'] as $langId => $expectedDescription) { + Assert::assertEquals( + $expectedDescription, + $actualDescriptions[$langId], + sprintf('Unexpected description for language ID %d in type "%s"', $langId, $expectedDiscountType['type']) + ); + } + } + } + } +} diff --git a/tests/Integration/Behaviour/Features/Scenario/Discount/BO/discount_types.feature b/tests/Integration/Behaviour/Features/Scenario/Discount/BO/discount_types.feature new file mode 100644 index 0000000000000..845d5ee59a35f --- /dev/null +++ b/tests/Integration/Behaviour/Features/Scenario/Discount/BO/discount_types.feature @@ -0,0 +1,18 @@ +# ./vendor/bin/behat -c tests/Integration/Behaviour/behat.yml -s discount --tags discount-types +@discount-types +Feature: Discount Types + PrestaShop provides discount types + As a developer + I must be able to retrieve the list of available discount types + + Background: + Given I enable feature flag "discount" + + Scenario: List all discount types + Then I should receive the following discount types: + | type | core | enabled | names[en-US] | descriptions[en-US] | + | cart_level | true | true | On cart amount | Discount applied to cart | + | product_level | true | true | On catalog products | Discount applied to specific products | + | free_shipping | true | true | On free shipping | Discount that provides free shipping to the order | + | free_gift | true | true | On free gift | Discount that provides a free gift product | + | order_level | true | true | On total order | Discount applied to the order | diff --git a/tests/Integration/Behaviour/behat.yml b/tests/Integration/Behaviour/behat.yml index d3673cef112e8..86ae0b80742dd 100644 --- a/tests/Integration/Behaviour/behat.yml +++ b/tests/Integration/Behaviour/behat.yml @@ -322,6 +322,7 @@ default: - Tests\Integration\Behaviour\Features\Context\Domain\CustomerFeatureContext - Tests\Integration\Behaviour\Features\Context\Domain\CustomerGroupFeatureContext - Tests\Integration\Behaviour\Features\Context\Domain\Discount\DiscountFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\Discount\DiscountTypesFeatureContext - Tests\Integration\Behaviour\Features\Context\Domain\ProductFeatureContext - Tests\Integration\Behaviour\Features\Context\Domain\Product\AddProductFeatureContext - Tests\Integration\Behaviour\Features\Context\Domain\Product\Combination\CombinationAssertionFeatureContext diff --git a/tests/UI/campaigns/functional/API/02_checkEndpoints.ts b/tests/UI/campaigns/functional/API/02_checkEndpoints.ts index 8f226ef627dd3..5834997582ddb 100644 --- a/tests/UI/campaigns/functional/API/02_checkEndpoints.ts +++ b/tests/UI/campaigns/functional/API/02_checkEndpoints.ts @@ -204,6 +204,8 @@ describe('API : Check endpoints', async () => { // @todo: add tests '/customers: POST', // @todo: add tests + '/discount-types: GET', + // @todo: add tests '/discounts/bulk-delete: DELETE', // @todo: add tests '/discounts/bulk-update-status: PATCH', From 620118a61dc472aa119fd709f90ff7d611cb2cfb Mon Sep 17 00:00:00 2001 From: Krystian Podemski <podemski.krystian@gmail.com> Date: Sat, 31 Jan 2026 16:00:58 +0100 Subject: [PATCH 19/19] Fix configuration of Dev mode and Debug profiling in the back office --- src/Adapter/Debug/DebugMode.php | 6 +++++- src/Adapter/Debug/DebugProfiling.php | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Adapter/Debug/DebugMode.php b/src/Adapter/Debug/DebugMode.php index f199043a0dc4a..8f574c3814f77 100644 --- a/src/Adapter/Debug/DebugMode.php +++ b/src/Adapter/Debug/DebugMode.php @@ -221,7 +221,11 @@ public function changePsModeDevValue($value) { // Check custom defines file first if ($this->isCustomDefinesReadable()) { - return $this->updateDebugModeValueInCustomFile($value); + $result = $this->updateDebugModeValueInCustomFile($value); + // If the constant is not found in custom file, fallback to main file + if ($result !== self::DEBUG_MODE_ERROR_NO_DEFINITION_FOUND) { + return $result; + } } if ($this->isMainDefinesReadable()) { diff --git a/src/Adapter/Debug/DebugProfiling.php b/src/Adapter/Debug/DebugProfiling.php index cea5254a9f795..a74e84fa0dbc1 100644 --- a/src/Adapter/Debug/DebugProfiling.php +++ b/src/Adapter/Debug/DebugProfiling.php @@ -175,7 +175,11 @@ private function changeProfilingValue(string $value): int { // Check custom defines file first if ($this->isCustomDefinesReadable()) { - return $this->updateProfilingValueInCustomFile($value); + $result = $this->updateProfilingValueInCustomFile($value); + // If the constant is not found in custom file, fallback to main file + if ($result !== self::DEBUG_PROFILING_ERROR_NO_DEFINITION_FOUND) { + return $result; + } } if ($this->isMainDefinesReadable()) {