From fab97fcde951d67324b73ffe885f917e29c0fe67 Mon Sep 17 00:00:00 2001 From: aplapana Date: Tue, 28 Mar 2023 16:36:33 +0300 Subject: [PATCH 01/41] ACP2E-1783: Unable to create 301/302 redirect for product with a category path when Generate "category/product" URL Rewrites set to Yes - implemented solution and MFTF --- .../Controller/Adminhtml/Url/Rewrite/Save.php | 27 +++++-- .../AdminUrlRewriteProductCategoryPage.xml | 14 ++++ ...AdminDeleteCreateProductUrlRewriteTest.xml | 73 +++++++++++++++++++ 3 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteProductCategoryPage.xml create mode 100644 app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCreateProductUrlRewriteTest.xml diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php index c508e85d87c3..200ab748fc26 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php @@ -7,6 +7,7 @@ namespace Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Exception\LocalizedException; use Magento\UrlRewrite\Model\UrlFinderInterface; @@ -34,6 +35,11 @@ class Save extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite implemen */ protected $urlFinder; + /** + * @var ProductRepositoryInterface + */ + protected ProductRepositoryInterface $productRepository; + /** * @param \Magento\Backend\App\Action\Context $context * @param \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator @@ -46,13 +52,16 @@ public function __construct( \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator, \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator, \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $cmsPageUrlPathGenerator, - UrlFinderInterface $urlFinder + UrlFinderInterface $urlFinder, + ProductRepositoryInterface $productRepository = null ) { parent::__construct($context); $this->productUrlPathGenerator = $productUrlPathGenerator; $this->categoryUrlPathGenerator = $categoryUrlPathGenerator; $this->cmsPageUrlPathGenerator = $cmsPageUrlPathGenerator; $this->urlFinder = $urlFinder; + $this->productRepository = $productRepository ?: + \Magento\Framework\App\ObjectManager::getInstance()->create(ProductRepositoryInterface::class); } /** @@ -97,12 +106,18 @@ protected function getTargetPath($model) ]; $rewrite = $this->urlFinder->findOneByData($data); if (!$rewrite) { - $message = $model->getEntityType() === self::ENTITY_TYPE_PRODUCT - ? __("The selected product isn't associated with the selected store or category.") - : __("The selected category isn't associated with the selected store."); - throw new LocalizedException($message); + $check = $model->getEntityType() === self::ENTITY_TYPE_PRODUCT ? + $this->_getProduct()->canBeShowInCategory($this->_getCategory()->getId()) : + $this->_getCategory()->getStoreId() == $model->getStoreId(); + if (false === $check) { + $message = $model->getEntityType() === self::ENTITY_TYPE_PRODUCT + ? __("The selected product isn't associated with the selected store or category.") + : __("The selected category isn't associated with the selected store."); + throw new LocalizedException($message); + } + } else { + $targetPath = $rewrite->getRequestPath(); } - $targetPath = $rewrite->getRequestPath(); } return $targetPath; } diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteProductCategoryPage.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteProductCategoryPage.xml new file mode 100644 index 000000000000..33db1aff8f55 --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteProductCategoryPage.xml @@ -0,0 +1,14 @@ + + + + + +
+ + diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCreateProductUrlRewriteTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCreateProductUrlRewriteTest.xml new file mode 100644 index 000000000000..c741d7cb1cf7 --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCreateProductUrlRewriteTest.xml @@ -0,0 +1,73 @@ + + + + + + + + <description value="Delete automated URL rewrites and then create one"/> + <testCaseId value="AC-8380"/> + <severity value="MAJOR"/> + <group value="url_rewrite"/> + </annotations> + <before> + <magentoCLI command="config:set catalog/seo/generate_category_product_rewrites 1" stepKey="enableCategoryProductRewrites"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + <!-- Create the category to put the product in --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="performReindex"/> + <magentoCLI command="cache:flush" stepKey="cleanCache"/> + </before> + <after> + <!-- Delete the category and product --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <magentoCLI command="config:set catalog/seo/generate_category_product_rewrites 0" stepKey="disableGenerateUrlRewrite"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + <magentoCLI command="indexer:reindex" stepKey="performReindex"/> + </after> + + <!--Delete created product url rewrite and verify AssertUrlRewriteDeletedMessage--> + <actionGroup ref="AdminDeleteUrlRewriteActionGroup" stepKey="deleteProductUrlRewrite"> + <argument name="requestPath" value="$$createSimpleProduct.custom_attributes[url_key]$$.html"/> + </actionGroup> + <actionGroup ref="AdminDeleteUrlRewriteActionGroup" stepKey="deleteProductCategoryUrlRewrite"> + <argument name="requestPath" value="$$createSimpleProduct.custom_attributes[url_key]$$.html"/> + </actionGroup> + + <!--Search and verify AssertUrlRewriteNotInGrid--> + <actionGroup ref="AdminSearchDeletedUrlRewriteActionGroup" stepKey="searchDeletedUrlRewriteInGrid"> + <argument name="requestPath" value="$$createSimpleProduct.custom_attributes[url_key]$$.html"/> + </actionGroup> + + <!--Filter Product in product page and get the Product ID --> + <actionGroup ref="FilterAndSelectProductActionGroup" stepKey="filterProduct"> + <argument name="productSku" value="$$createSimpleProduct.sku$$"/> + </actionGroup> + <grabFromCurrentUrl stepKey="productId" regex="#\/([0-9]*)?\/$#"/> + + <!-- Open Category Page and Get Category ID --> + <actionGroup ref="OpenCategoryFromCategoryTreeActionGroup" stepKey="getCategoryId"> + <argument name="category" value="$$createCategory.name$$"/> + </actionGroup> + <grabFromCurrentUrl stepKey="categoryId" regex="#\/([0-9]*)?\/$#"/> + + <!-- Create product redirect --> + <amOnPage url="{{AdminUrlRewriteProductCategoryPage.url({$productId}, {$categoryId})}}" stepKey="openProductRedirectWithCategory"/> + <click selector="{{AdminUrlRewriteEditSection.redirectTypeValue('Temporary (302)')}}" stepKey="clickOnRedirectTypeValue"/> + <click selector="{{AdminUrlRewriteEditSection.saveButton}}" stepKey="clickOnSaveButton"/> + + <!-- Assert Url Rewrite Save Message --> + <actionGroup ref="AssertMessageInAdminPanelActionGroup" stepKey="assertSuccessMessage"> + <argument name="message" value="The URL Rewrite has been saved."/> + </actionGroup> + </test> +</tests> From 03eda34318171fa0ba9d2210339ff3b7bfba9679 Mon Sep 17 00:00:00 2001 From: aplapana <aplapana@adobe.com> Date: Wed, 29 Mar 2023 09:18:55 +0300 Subject: [PATCH 02/41] ACP2E-1783: Unable to create 301/302 redirect for product with a category path when Generate "category/product" URL Rewrites set to Yes - fixed static --- .../Controller/Adminhtml/Url/Rewrite/Save.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php index 200ab748fc26..519a7eeb24b2 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php @@ -7,7 +7,11 @@ namespace Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite; +use Magento\Backend\App\Action\Context; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; +use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; +use Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Exception\LocalizedException; use Magento\UrlRewrite\Model\UrlFinderInterface; @@ -41,11 +45,12 @@ class Save extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite implemen protected ProductRepositoryInterface $productRepository; /** - * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator - * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator - * @param \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $cmsPageUrlPathGenerator + * @param Context $context + * @param ProductUrlPathGenerator $productUrlPathGenerator + * @param CategoryUrlPathGenerator $categoryUrlPathGenerator + * @param CmsPageUrlPathGenerator $cmsPageUrlPathGenerator * @param UrlFinderInterface $urlFinder + * @param ProductRepositoryInterface|null $productRepository */ public function __construct( \Magento\Backend\App\Action\Context $context, @@ -123,6 +128,8 @@ protected function getTargetPath($model) } /** + * Generate canonical product / category path + * * @return string */ protected function getCanonicalTargetPath() @@ -157,6 +164,8 @@ private function _handleCmsPageUrlRewrite($model) } /** + * Process save URL rewrite request + * * @return void */ public function execute() From a34978482b7a8ee830a28ee1e1167cad3387a0bb Mon Sep 17 00:00:00 2001 From: aplapana <aplapana@adobe.com> Date: Fri, 31 Mar 2023 11:11:16 +0300 Subject: [PATCH 03/41] ACP2E-1783: Unable to create 301/302 redirect for product with a category path when Generate "category/product" URL Rewrites set to Yes - improved mftf test --- .../Test/Mftf/Section/AdminUrlRewriteIndexSection.xml | 1 + .../Mftf/Test/AdminDeleteCreateProductUrlRewriteTest.xml | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml index b6a155c9db6f..03cc49f95e63 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml @@ -15,5 +15,6 @@ <element name="gridCellByColumnValue" type="text" selector="//*[@data-role='grid']//tbody//td[count(//*[@data-role='grid']//th[contains(., '{{column}}')]/preceding-sibling::th)+1][normalize-space(.)='{{columnValue}}']" parameterized="true"/> <element name="select" type="button" selector="//*[@data-role='grid']//tbody//tr[{{row}}+1]//button[@class='action-select']" timeout="30" parameterized="true"/> <element name="activeEdit" type="button" selector="//*[@data-role='grid']//tbody//ul[@class='action-menu _active']//a[@data-action='item-edit']" timeout="30"/> + <element name="clearFiltersButton" type="button" selector="//div[@class='admin__data-grid-header']//button[@class='action-tertiary action-clear']" timeout="10"/> </section> </sections> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCreateProductUrlRewriteTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCreateProductUrlRewriteTest.xml index c741d7cb1cf7..eacf142f66a2 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCreateProductUrlRewriteTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCreateProductUrlRewriteTest.xml @@ -27,12 +27,16 @@ <magentoCLI command="cache:flush" stepKey="cleanCache"/> </before> <after> + <!-- Clear filters: URL re-write, product --> + <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="openUrlRewriteEditPage"/> + <conditionalClick selector="{{AdminUrlRewriteIndexSection.clearFiltersButton}}" dependentSelector="{{AdminUrlRewriteIndexSection.clearFiltersButton}}" visible="true" stepKey="cleanFiltersIfTheySet"/> + <actionGroup ref="ClearProductsFilterActionGroup" stepKey="clearProductsFilter"/> <!-- Delete the category and product --> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> <magentoCLI command="config:set catalog/seo/generate_category_product_rewrites 0" stepKey="disableGenerateUrlRewrite"/> + <magentoCLI command="indexer:reindex" stepKey="performExitReindex"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> - <magentoCLI command="indexer:reindex" stepKey="performReindex"/> </after> <!--Delete created product url rewrite and verify AssertUrlRewriteDeletedMessage--> From b056fc70f2ed59fd1d848cd837f48098171b2612 Mon Sep 17 00:00:00 2001 From: aplapana <aplapana@adobe.com> Date: Fri, 21 Apr 2023 13:59:42 +0300 Subject: [PATCH 04/41] ACP2E-1783: Unable to create 301/302 redirect for product with a category path when Generate "category/product" URL Rewrites set to Yes - missing redirects are restored --- .../Controller/Adminhtml/Url/Rewrite/Save.php | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php index 519a7eeb24b2..428101e9c75a 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php @@ -8,8 +8,8 @@ namespace Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite; use Magento\Backend\App\Action\Context; -use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; +use Magento\CatalogUrlRewrite\Model\Products\AppendUrlRewritesToProducts; use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; use Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; @@ -17,6 +17,9 @@ use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class Save extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite implements HttpPostActionInterface { /** @@ -40,9 +43,9 @@ class Save extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite implemen protected $urlFinder; /** - * @var ProductRepositoryInterface + * @var AppendUrlRewritesToProducts */ - protected ProductRepositoryInterface $productRepository; + protected AppendUrlRewritesToProducts $productAppendRewrites; /** * @param Context $context @@ -50,23 +53,23 @@ class Save extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite implemen * @param CategoryUrlPathGenerator $categoryUrlPathGenerator * @param CmsPageUrlPathGenerator $cmsPageUrlPathGenerator * @param UrlFinderInterface $urlFinder - * @param ProductRepositoryInterface|null $productRepository + * @param AppendUrlRewritesToProducts|null $productAppendRewrites */ public function __construct( - \Magento\Backend\App\Action\Context $context, - \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator, + \Magento\Backend\App\Action\Context $context, + \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator, \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator, - \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $cmsPageUrlPathGenerator, - UrlFinderInterface $urlFinder, - ProductRepositoryInterface $productRepository = null + \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $cmsPageUrlPathGenerator, + UrlFinderInterface $urlFinder, + AppendUrlRewritesToProducts $productAppendRewrites = null ) { parent::__construct($context); $this->productUrlPathGenerator = $productUrlPathGenerator; $this->categoryUrlPathGenerator = $categoryUrlPathGenerator; $this->cmsPageUrlPathGenerator = $cmsPageUrlPathGenerator; $this->urlFinder = $urlFinder; - $this->productRepository = $productRepository ?: - \Magento\Framework\App\ObjectManager::getInstance()->create(ProductRepositoryInterface::class); + $this->productAppendRewrites = $productAppendRewrites ?: + \Magento\Framework\App\ObjectManager::getInstance()->create(AppendUrlRewritesToProducts::class); } /** @@ -88,18 +91,18 @@ protected function _handleCatalogUrlRewrite($model) $model->setMetadata(['category_id' => $categoryId]); } } - $model->setTargetPath($this->getTargetPath($model)); + $model->setTargetPath($this->generateTargetPath($model)); } } /** - * Get Target Path + * Generate Target Path * * @param \Magento\UrlRewrite\Model\UrlRewrite $model * @return string * @throws \Magento\Framework\Exception\LocalizedException */ - protected function getTargetPath($model) + protected function generateTargetPath($model) { $targetPath = $this->getCanonicalTargetPath(); if ($model->getRedirectType() && !$model->getIsAutogenerated()) { @@ -111,14 +114,29 @@ protected function getTargetPath($model) ]; $rewrite = $this->urlFinder->findOneByData($data); if (!$rewrite) { - $check = $model->getEntityType() === self::ENTITY_TYPE_PRODUCT ? - $this->_getProduct()->canBeShowInCategory($this->_getCategory()->getId()) : - $this->_getCategory()->getStoreId() == $model->getStoreId(); - if (false === $check) { - $message = $model->getEntityType() === self::ENTITY_TYPE_PRODUCT - ? __("The selected product isn't associated with the selected store or category.") - : __("The selected category isn't associated with the selected store."); - throw new LocalizedException($message); + if ($model->getEntityType() === self::ENTITY_TYPE_PRODUCT) { + $this->productAppendRewrites->execute( + [$this->_getProduct()], + [$this->getRequest()->getParam('store_id', 0)] + ); + $rewrite = $this->urlFinder->findOneByData($data); + if (!$rewrite) { + throw new LocalizedException( + __( + "The selected product isn't associated with the selected store or category." + ) + ); + } + $targetPath = $rewrite->getRequestPath(); + if ($rewrite->getRequestPath() == $model->getRequestPath() && + $rewrite->getStoreId() == $model->getStoreId()) { + $obsoleteRewrite = $this->_objectManager->create(\Magento\UrlRewrite\Model\UrlRewrite::class); + $obsoleteRewrite->load($rewrite->getUrlRewriteId()); + $obsoleteRewrite->delete(); + } + } else { + throw new + LocalizedException(__("The selected category isn't associated with the selected store.")); } } else { $targetPath = $rewrite->getRequestPath(); From a87d4ecd90957082e10b1e8fac761eaadbad57ca Mon Sep 17 00:00:00 2001 From: aplapana <aplapana@adobe.com> Date: Wed, 26 Apr 2023 16:42:49 +0300 Subject: [PATCH 05/41] ACP2E-1783: Unable to create 301/302 redirect for product with a category path when Generate "category/product" URL Rewrites set to Yes - implemented new solution --- .../Products/AppendUrlRewritesToProducts.php | 63 +++++++++++++------ .../Controller/Adminhtml/Url/Rewrite/Save.php | 54 +++++++++------- 2 files changed, 75 insertions(+), 42 deletions(-) diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Products/AppendUrlRewritesToProducts.php b/app/code/Magento/CatalogUrlRewrite/Model/Products/AppendUrlRewritesToProducts.php index 15d4aabf4246..d328365db733 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Products/AppendUrlRewritesToProducts.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Products/AppendUrlRewritesToProducts.php @@ -79,23 +79,7 @@ public function __construct( public function execute(array $products, array $storesToAdd): void { foreach ($products as $product) { - $forceGenerateDefault = false; - foreach ($storesToAdd as $storeId) { - if ($this->needGenerateUrlForStore($product, (int)$storeId)) { - $urls[] = $this->generateUrls($product, (int)$storeId); - } elseif ((int)$product->getStoreId() !== Store::DEFAULT_STORE_ID) { - $forceGenerateDefault = true; - } - } - if ($product->getStoreId() === Store::DEFAULT_STORE_ID - || $this->isProductAssignedToStore($product)) { - $product->unsUrlPath(); - $product->setUrlPath($this->productUrlPathGenerator->getUrlPath($product)); - $urls[] = $this->productUrlRewriteGenerator->generate($product); - } - if ($forceGenerateDefault && $product->getStoreId() !== Store::DEFAULT_STORE_ID) { - $urls[] = $this->generateUrls($product, Store::DEFAULT_STORE_ID); - } + $urls = $this->getProductUrlRewrites($product, $storesToAdd); $this->getDataByStore->clearProductUrlRewriteDataCache($product); } if (!empty($urls)) { @@ -103,6 +87,49 @@ public function execute(array $products, array $storesToAdd): void } } + /** + * Generate store product URLs + * + * @param ProductInterface $product + * @param array $stores + * @return array + */ + public function getProductUrlRewrites(ProductInterface $product, array $stores): array + { + $urls = []; + $forceGenerateDefault = false; + foreach ($stores as $storeId) { + if ($this->needGenerateUrlForStore($product, (int)$storeId)) { + $urls[] = $this->generateProductStoreUrls($product, (int)$storeId); + } elseif ((int)$product->getStoreId() !== Store::DEFAULT_STORE_ID) { + $forceGenerateDefault = true; + } + } + if ($product->getStoreId() === Store::DEFAULT_STORE_ID + || $this->isProductAssignedToStore($product)) { + $product->unsUrlPath(); + $product->setUrlPath($this->productUrlPathGenerator->getUrlPath($product)); + $urls[] = $this->productUrlRewriteGenerator->generate($product); + } + if ($forceGenerateDefault && $product->getStoreId() !== Store::DEFAULT_STORE_ID) { + $urls[] = $this->generateProductStoreUrls($product, Store::DEFAULT_STORE_ID); + } + + return $urls; + } + + /** + * Replaces given product URL rewrites + * + * @param array $rewrites + * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] + * @throws UrlAlreadyExistsException + */ + public function saveProductUrlRewrites(array $rewrites) + { + return $this->urlPersist->replace($rewrites); + } + /** * Generate urls for specific store * @@ -110,7 +137,7 @@ public function execute(array $products, array $storesToAdd): void * @param int $storeId * @return array */ - private function generateUrls(ProductInterface $product, int $storeId): array + private function generateProductStoreUrls(ProductInterface $product, int $storeId): array { $storeData = $this->getDataByStore->execute($product, $storeId); $origStoreId = $product->getStoreId(); diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php index 428101e9c75a..7301c4d9fe8e 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php @@ -47,6 +47,11 @@ class Save extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite implemen */ protected AppendUrlRewritesToProducts $productAppendRewrites; + /** + * @var array + */ + protected array $missingRewrites = []; + /** * @param Context $context * @param ProductUrlPathGenerator $productUrlPathGenerator @@ -61,7 +66,7 @@ public function __construct( \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator, \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $cmsPageUrlPathGenerator, UrlFinderInterface $urlFinder, - AppendUrlRewritesToProducts $productAppendRewrites = null + ?AppendUrlRewritesToProducts $productAppendRewrites = null ) { parent::__construct($context); $this->productUrlPathGenerator = $productUrlPathGenerator; @@ -91,7 +96,7 @@ protected function _handleCatalogUrlRewrite($model) $model->setMetadata(['category_id' => $categoryId]); } } - $model->setTargetPath($this->generateTargetPath($model)); + $this->generateTargetPath($model); } } @@ -99,50 +104,49 @@ protected function _handleCatalogUrlRewrite($model) * Generate Target Path * * @param \Magento\UrlRewrite\Model\UrlRewrite $model - * @return string * @throws \Magento\Framework\Exception\LocalizedException */ protected function generateTargetPath($model) { $targetPath = $this->getCanonicalTargetPath(); if ($model->getRedirectType() && !$model->getIsAutogenerated()) { - $data = [ + if ($rewrite = $this->urlFinder->findOneByData([ UrlRewrite::ENTITY_ID => $model->getEntityId(), UrlRewrite::TARGET_PATH => $targetPath, UrlRewrite::ENTITY_TYPE => $model->getEntityType(), - UrlRewrite::STORE_ID => $model->getStoreId(), - ]; - $rewrite = $this->urlFinder->findOneByData($data); - if (!$rewrite) { + UrlRewrite::STORE_ID => $model->getStoreId() + ])) { + $targetPath = $rewrite->getRequestPath(); + } else { if ($model->getEntityType() === self::ENTITY_TYPE_PRODUCT) { - $this->productAppendRewrites->execute( - [$this->_getProduct()], + $productRewrites = $this->productAppendRewrites->getProductUrlRewrites( + $this->_getProduct(), [$this->getRequest()->getParam('store_id', 0)] ); - $rewrite = $this->urlFinder->findOneByData($data); - if (!$rewrite) { + $productRewrites = array_merge(...$productRewrites); + /** @var UrlRewrite $rewrite */ + foreach ($productRewrites as $rewrite) { + if ($rewrite->getTargetPath() == $model->getTargetPath()) { + $targetPath = $rewrite->getRequestPath(); + } else { + $this->missingRewrites[] = $rewrite; + } + } + if (!$targetPath) { throw new LocalizedException( __( "The selected product isn't associated with the selected store or category." ) ); } - $targetPath = $rewrite->getRequestPath(); - if ($rewrite->getRequestPath() == $model->getRequestPath() && - $rewrite->getStoreId() == $model->getStoreId()) { - $obsoleteRewrite = $this->_objectManager->create(\Magento\UrlRewrite\Model\UrlRewrite::class); - $obsoleteRewrite->load($rewrite->getUrlRewriteId()); - $obsoleteRewrite->delete(); - } } else { throw new LocalizedException(__("The selected category isn't associated with the selected store.")); } - } else { - $targetPath = $rewrite->getRequestPath(); } } - return $targetPath; + + $model->setTargetPath($targetPath); } /** @@ -207,10 +211,12 @@ public function execute() ->setStoreId($this->getRequest()->getParam('store_id', 0)) ->setDescription($this->getRequest()->getParam('description')); - $this->_handleCatalogUrlRewrite($model); $this->_handleCmsPageUrlRewrite($model); + $this->_handleCatalogUrlRewrite($model); $model->save(); - + if (!empty($this->missingRewrites)) { + $this->productAppendRewrites->saveProductUrlRewrites($this->missingRewrites); + } $this->messageManager->addSuccess(__('The URL Rewrite has been saved.')); $this->_redirect('adminhtml/*/'); return; From 70848f58fc0c886cec6d896c21e0ad4e6a432940 Mon Sep 17 00:00:00 2001 From: aplapana <aplapana@adobe.com> Date: Thu, 27 Apr 2023 11:01:18 +0300 Subject: [PATCH 06/41] ACP2E-1783: Unable to create 301/302 redirect for product with a category path when Generate "category/product" URL Rewrites set to Yes - added unit tests --- .../AppendUrlRewritesToProductsTest.php | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Products/AppendUrlRewritesToProductsTest.php diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Products/AppendUrlRewritesToProductsTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Products/AppendUrlRewritesToProductsTest.php new file mode 100644 index 000000000000..ec4e008ef126 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Products/AppendUrlRewritesToProductsTest.php @@ -0,0 +1,111 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Products; + +use Magento\Catalog\Model\Product; +use Magento\CatalogUrlRewrite\Model\Product\GetProductUrlRewriteDataByStore; +use Magento\CatalogUrlRewrite\Model\Products\AppendUrlRewritesToProducts; +use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; +use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; +use Magento\CatalogUrlRewrite\Service\V1\StoreViewService; +use Magento\UrlRewrite\Model\UrlPersistInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class AppendUrlRewritesToProductsTest extends TestCase +{ + /** + * @var ProductUrlRewriteGenerator|MockObject + */ + private ProductUrlRewriteGenerator $productUrlRewriteGenerator; + + /** + * @var StoreViewService|MockObject + */ + private StoreViewService $storeViewService; + + /** + * @var ProductUrlPathGenerator|MockObject + */ + private ProductUrlPathGenerator $productUrlPathGenerator; + + /** + * @var UrlPersistInterface|MockObject + */ + private UrlPersistInterface $urlPersist; + + /** + * @var GetProductUrlRewriteDataByStore|MockObject + */ + private GetProductUrlRewriteDataByStore $getDataByStore; + + /** + * @var AppendUrlRewritesToProducts + */ + private AppendUrlRewritesToProducts $append; + + protected function setUp(): void + { + $this->productUrlRewriteGenerator = $this->createMock(ProductUrlRewriteGenerator::class); + $this->storeViewService = $this->createMock(StoreViewService::class); + $this->productUrlPathGenerator = $this->createMock(ProductUrlPathGenerator::class); + $this->urlPersist = $this->createMock(UrlPersistInterface::class); + $this->getDataByStore = $this->createMock(GetProductUrlRewriteDataByStore::class); + + $this->append = new AppendUrlRewritesToProducts( + $this->productUrlRewriteGenerator, + $this->storeViewService, + $this->productUrlPathGenerator, + $this->urlPersist, + $this->getDataByStore + ); + parent::setUp(); + } + + /** + * @return void + * @throws \Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException + */ + public function testSaveProductUrlRewrites(): void + { + $rewrites = ['test']; + $this->urlPersist->expects($this->once())->method('replace')->with($rewrites); + $this->append->saveProductUrlRewrites($rewrites); + } + + /** + * @return void + * @throws \Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException + */ + public function testGetProductUrlRewrites(): void + { + $storeId = $productId = 1; + $product = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->addMethods(['unsUrlPath', 'setUrlPath']) + ->onlyMethods(['getStoreId', 'getId', 'getStoreIds']) + ->getMock(); + $product->expects($this->any())->method('getStoreId')->willReturn(0); + $product->expects($this->any())->method('getId')->willReturn($productId); + $product->expects($this->any())->method('getStoreIds')->willReturn([$storeId]); + $product->expects($this->once())->method('unsUrlPath'); + $product->expects($this->once())->method('setUrlPath'); + + $this->productUrlPathGenerator->expects($this->once())->method('getUrlPath'); + $this->productUrlRewriteGenerator->expects($this->once())->method('generate')->willReturn([]); + $this->getDataByStore->expects($this->once())->method('clearProductUrlRewriteDataCache'); + $this->urlPersist->expects($this->once())->method('replace'); + + $this->storeViewService->expects($this->once()) + ->method('doesEntityHaveOverriddenUrlKeyForStore') + ->with($storeId, $productId, Product::ENTITY) + ->willReturn(false); + + $this->append->execute([$product], [$storeId]); + } +} From 593190152d177495b726fbaf9646439c19aa8a97 Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Thu, 27 Apr 2023 13:34:49 +0300 Subject: [PATCH 07/41] ACP2E-1880: send numeric values for product qty and price, total, shipping and tax. --- app/code/Magento/GoogleAnalytics/Block/Ga.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/GoogleAnalytics/Block/Ga.php b/app/code/Magento/GoogleAnalytics/Block/Ga.php index 0370174c0b7f..a88b9feca1be 100644 --- a/app/code/Magento/GoogleAnalytics/Block/Ga.php +++ b/app/code/Magento/GoogleAnalytics/Block/Ga.php @@ -124,8 +124,8 @@ public function getOrdersTrackingCode() "ga('ec:addProduct', { 'id': '%s', 'name': '%s', - 'price': '%s', - 'quantity': %s + 'price': '%f', + 'quantity': %d });", $this->escapeJsQuote($item->getSku()), $this->escapeJsQuote($item->getName()), @@ -138,9 +138,9 @@ public function getOrdersTrackingCode() "ga('ec:setAction', 'purchase', { 'id': '%s', 'affiliation': '%s', - 'revenue': '%s', - 'tax': '%s', - 'shipping': '%s' + 'revenue': '%f', + 'tax': '%f', + 'shipping': '%f' });", $order->getIncrementId(), $this->escapeJsQuote($this->_storeManager->getStore()->getFrontendName()), @@ -235,16 +235,16 @@ public function getOrdersTrackingData() $result['products'][] = [ 'id' => $this->escapeJsQuote($item->getSku()), 'name' => $this->escapeJsQuote($item->getName()), - 'price' => $item->getPrice(), - 'quantity' => $item->getQtyOrdered(), + 'price' => (float)$item->getPrice(), + 'quantity' => (int)$item->getQtyOrdered(), ]; } $result['orders'][] = [ 'id' => $order->getIncrementId(), 'affiliation' => $this->escapeJsQuote($this->_storeManager->getStore()->getFrontendName()), - 'revenue' => $order->getGrandTotal(), - 'tax' => $order->getTaxAmount(), - 'shipping' => $order->getShippingAmount(), + 'revenue' => (float)$order->getGrandTotal(), + 'tax' => (float)$order->getTaxAmount(), + 'shipping' => (float)$order->getShippingAmount(), ]; $result['currency'] = $order->getOrderCurrencyCode(); } From bf69016a084f4098a65e2ee6e6b03a34a90c3b9f Mon Sep 17 00:00:00 2001 From: aplapana <aplapana@adobe.com> Date: Thu, 27 Apr 2023 14:40:13 +0300 Subject: [PATCH 08/41] ACP2E-1783: Unable to create 301/302 redirect for product with a category path when Generate "category/product" URL Rewrites set to Yes - improved solution --- .../Products/AppendUrlRewritesToProducts.php | 12 ----- .../AppendUrlRewritesToProductsTest.php | 11 ----- .../Controller/Adminhtml/Url/Rewrite/Save.php | 44 ++++++++++++------- 3 files changed, 29 insertions(+), 38 deletions(-) diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Products/AppendUrlRewritesToProducts.php b/app/code/Magento/CatalogUrlRewrite/Model/Products/AppendUrlRewritesToProducts.php index d328365db733..ce842b667e50 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Products/AppendUrlRewritesToProducts.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Products/AppendUrlRewritesToProducts.php @@ -118,18 +118,6 @@ public function getProductUrlRewrites(ProductInterface $product, array $stores): return $urls; } - /** - * Replaces given product URL rewrites - * - * @param array $rewrites - * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] - * @throws UrlAlreadyExistsException - */ - public function saveProductUrlRewrites(array $rewrites) - { - return $this->urlPersist->replace($rewrites); - } - /** * Generate urls for specific store * diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Products/AppendUrlRewritesToProductsTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Products/AppendUrlRewritesToProductsTest.php index ec4e008ef126..48568be00dde 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Products/AppendUrlRewritesToProductsTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Products/AppendUrlRewritesToProductsTest.php @@ -67,17 +67,6 @@ protected function setUp(): void parent::setUp(); } - /** - * @return void - * @throws \Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException - */ - public function testSaveProductUrlRewrites(): void - { - $rewrites = ['test']; - $this->urlPersist->expects($this->once())->method('replace')->with($rewrites); - $this->append->saveProductUrlRewrites($rewrites); - } - /** * @return void * @throws \Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php index 7301c4d9fe8e..d8d5dc9dac75 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php @@ -118,6 +118,18 @@ protected function generateTargetPath($model) ])) { $targetPath = $rewrite->getRequestPath(); } else { + $check = $model->getEntityType() === self::ENTITY_TYPE_PRODUCT ? + $this->_getProduct()->canBeShowInCategory($this->_getCategory()->getId()) && + in_array($model->getStoreId(), $this->_getProduct()->getStoreIds()) : + $this->_getCategory()->getStoreId() == $model->getStoreId(); + if (false === $check) { + throw new LocalizedException( + $model->getEntityType() === self::ENTITY_TYPE_PRODUCT + ? __("The selected product isn't associated with the selected store or category.") + : __("The selected category isn't associated with the selected store.") + ); + } + if ($model->getEntityType() === self::ENTITY_TYPE_PRODUCT) { $productRewrites = $this->productAppendRewrites->getProductUrlRewrites( $this->_getProduct(), @@ -126,22 +138,21 @@ protected function generateTargetPath($model) $productRewrites = array_merge(...$productRewrites); /** @var UrlRewrite $rewrite */ foreach ($productRewrites as $rewrite) { - if ($rewrite->getTargetPath() == $model->getTargetPath()) { - $targetPath = $rewrite->getRequestPath(); - } else { - $this->missingRewrites[] = $rewrite; + if ($rewrite->getRequestPath() != $model->getRequestPath()) { + $missingRewrite = $this->_objectManager->create(\Magento\UrlRewrite\Model\UrlRewrite::class); + $missingRewrite->setEntityType(self::ENTITY_TYPE_PRODUCT) + ->setRequestPath($rewrite->getRequestPath()) + ->setTargetPath($rewrite->getTargetPath()) + ->setRedirectType($rewrite->getRedirectType()) + ->setStoreId($rewrite->getStoreId()) + ->setDescription($rewrite->getDescription()) + ->setMetadata($rewrite->getMetadata()); + $this->missingRewrites[] = $missingRewrite; + if ($rewrite->getTargetPath() == $targetPath) { + $targetPath = $rewrite->getRequestPath(); + } } } - if (!$targetPath) { - throw new LocalizedException( - __( - "The selected product isn't associated with the selected store or category." - ) - ); - } - } else { - throw new - LocalizedException(__("The selected category isn't associated with the selected store.")); } } } @@ -215,8 +226,11 @@ public function execute() $this->_handleCatalogUrlRewrite($model); $model->save(); if (!empty($this->missingRewrites)) { - $this->productAppendRewrites->saveProductUrlRewrites($this->missingRewrites); + foreach ($this->missingRewrites as $missingRewrite) { + $missingRewrite->save(); + } } + $this->messageManager->addSuccess(__('The URL Rewrite has been saved.')); $this->_redirect('adminhtml/*/'); return; From 8e482deb5b7144bb766ac2af7c75370dc69e10db Mon Sep 17 00:00:00 2001 From: aplapana <aplapana@adobe.com> Date: Thu, 27 Apr 2023 15:42:26 +0300 Subject: [PATCH 09/41] ACP2E-1783: Unable to create 301/302 redirect for product with a category path when Generate "category/product" URL Rewrites set to Yes - addressed static issues --- .../Controller/Adminhtml/Url/Rewrite/Save.php | 133 +++++++++++++----- 1 file changed, 98 insertions(+), 35 deletions(-) diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php index d8d5dc9dac75..aec7b76b0119 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php @@ -66,7 +66,7 @@ public function __construct( \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator, \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $cmsPageUrlPathGenerator, UrlFinderInterface $urlFinder, - ?AppendUrlRewritesToProducts $productAppendRewrites = null + ?AppendUrlRewritesToProducts $productAppendRewrites = null ) { parent::__construct($context); $this->productUrlPathGenerator = $productUrlPathGenerator; @@ -82,7 +82,7 @@ public function __construct( * * @param \Magento\UrlRewrite\Model\UrlRewrite $model * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ protected function _handleCatalogUrlRewrite($model) { @@ -96,7 +96,7 @@ protected function _handleCatalogUrlRewrite($model) $model->setMetadata(['category_id' => $categoryId]); } } - $this->generateTargetPath($model); + $model->setTargetPath($this->generateTargetPath($model)); } } @@ -104,9 +104,9 @@ protected function _handleCatalogUrlRewrite($model) * Generate Target Path * * @param \Magento\UrlRewrite\Model\UrlRewrite $model - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ - protected function generateTargetPath($model) + protected function generateTargetPath($model): ?string { $targetPath = $this->getCanonicalTargetPath(); if ($model->getRedirectType() && !$model->getIsAutogenerated()) { @@ -118,46 +118,109 @@ protected function generateTargetPath($model) ])) { $targetPath = $rewrite->getRequestPath(); } else { - $check = $model->getEntityType() === self::ENTITY_TYPE_PRODUCT ? - $this->_getProduct()->canBeShowInCategory($this->_getCategory()->getId()) && - in_array($model->getStoreId(), $this->_getProduct()->getStoreIds()) : - $this->_getCategory()->getStoreId() == $model->getStoreId(); - if (false === $check) { - throw new LocalizedException( - $model->getEntityType() === self::ENTITY_TYPE_PRODUCT - ? __("The selected product isn't associated with the selected store or category.") - : __("The selected category isn't associated with the selected store.") - ); - } - + $this->checkEntityAssociations($model); if ($model->getEntityType() === self::ENTITY_TYPE_PRODUCT) { $productRewrites = $this->productAppendRewrites->getProductUrlRewrites( $this->_getProduct(), [$this->getRequest()->getParam('store_id', 0)] ); $productRewrites = array_merge(...$productRewrites); - /** @var UrlRewrite $rewrite */ - foreach ($productRewrites as $rewrite) { - if ($rewrite->getRequestPath() != $model->getRequestPath()) { - $missingRewrite = $this->_objectManager->create(\Magento\UrlRewrite\Model\UrlRewrite::class); - $missingRewrite->setEntityType(self::ENTITY_TYPE_PRODUCT) - ->setRequestPath($rewrite->getRequestPath()) - ->setTargetPath($rewrite->getTargetPath()) - ->setRedirectType($rewrite->getRedirectType()) - ->setStoreId($rewrite->getStoreId()) - ->setDescription($rewrite->getDescription()) - ->setMetadata($rewrite->getMetadata()); - $this->missingRewrites[] = $missingRewrite; - if ($rewrite->getTargetPath() == $targetPath) { - $targetPath = $rewrite->getRequestPath(); - } - } - } + $targetPath = $this->getAdjustedTargetPath($model, $productRewrites, $targetPath); + $this->setMissingRewrites($model, $productRewrites); + } + } + } + + return $targetPath; + } + + /** + * Checks for missing product rewrites + * + * @param \Magento\UrlRewrite\Model\UrlRewrite $model + * @param array $rewrites + * @return void + */ + private function setMissingRewrites(\Magento\UrlRewrite\Model\UrlRewrite $model, array $rewrites): void + { + if (!empty($rewrites)) { + foreach ($rewrites as $rewrite) { + if ($rewrite->getRequestPath() != $model->getRequestPath()) { + $this->missingRewrites[] = $this->generateRewriteModel($rewrite); } } } + } - $model->setTargetPath($targetPath); + /** + * Checks for potential target path adjustments + * + * @param \Magento\UrlRewrite\Model\UrlRewrite $model + * @param array $productRewrites + * @param string $canonicalTargetPath + * @return string|null + */ + private function getAdjustedTargetPath( + \Magento\UrlRewrite\Model\UrlRewrite $model, + array $productRewrites, + string $canonicalTargetPath + ): ?string { + if (empty($productRewrites)) { + return null; + } + + foreach ($productRewrites as $rewrite) { + if ($rewrite->getRequestPath() != $model->getRequestPath()) { + $this->missingRewrites[] = $this->generateRewriteModel($rewrite); + if ($rewrite->getTargetPath() == $canonicalTargetPath) { + return $rewrite->getRequestPath(); + } + } + } + + return $canonicalTargetPath; + } + + /** + * Generates rewrite model + * + * @param UrlRewrite $rewrite + * @return \Magento\UrlRewrite\Model\UrlRewrite + */ + private function generateRewriteModel(UrlRewrite $rewrite): \Magento\UrlRewrite\Model\UrlRewrite + { + $rewriteModel = $this->_objectManager->create(\Magento\UrlRewrite\Model\UrlRewrite::class); + $rewriteModel->setEntityType(self::ENTITY_TYPE_PRODUCT) + ->setRequestPath($rewrite->getRequestPath()) + ->setTargetPath($rewrite->getTargetPath()) + ->setRedirectType($rewrite->getRedirectType()) + ->setStoreId($rewrite->getStoreId()) + ->setDescription($rewrite->getDescription()) + ->setMetadata($rewrite->getMetadata()); + + return $rewriteModel; + } + + /** + * Checks if rewrite can be created for product or category + * + * @param \Magento\UrlRewrite\Model\UrlRewrite $model + * @return void + * @throws LocalizedException + */ + private function checkEntityAssociations(\Magento\UrlRewrite\Model\UrlRewrite $model): void + { + $check = $model->getEntityType() === self::ENTITY_TYPE_PRODUCT ? + $this->_getProduct()->canBeShowInCategory($this->_getCategory()->getId()) && + in_array($model->getStoreId(), $this->_getProduct()->getStoreIds()) : + $this->_getCategory()->getStoreId() == $model->getStoreId(); + if (false === $check) { + throw new LocalizedException( + $model->getEntityType() === self::ENTITY_TYPE_PRODUCT + ? __("The selected product isn't associated with the selected store or category.") + : __("The selected category isn't associated with the selected store.") + ); + } } /** From 1f23f4a46a247e6fbe2a8799790c167a6ab7b726 Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Thu, 27 Apr 2023 17:37:25 +0300 Subject: [PATCH 10/41] ACP2E-1880: fix static and unit tests errors --- app/code/Magento/GoogleAnalytics/Block/Ga.php | 26 ++++++++++--------- .../Test/Unit/Block/GaTest.php | 20 +++++++------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/app/code/Magento/GoogleAnalytics/Block/Ga.php b/app/code/Magento/GoogleAnalytics/Block/Ga.php index a88b9feca1be..3c5345d153f7 100644 --- a/app/code/Magento/GoogleAnalytics/Block/Ga.php +++ b/app/code/Magento/GoogleAnalytics/Block/Ga.php @@ -82,6 +82,7 @@ public function getPageName() * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/method-reference#set * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/method-reference#gaObjectMethods * @deprecated 100.2.0 please use getPageTrackingData method + * @see getPageTrackingData method */ public function getPageTrackingCode($accountId) { @@ -103,6 +104,7 @@ public function getPageTrackingCode($accountId) * * @return string|void * @deprecated 100.2.0 please use getOrdersTrackingData method + * @see getOrdersTrackingData method */ public function getOrdersTrackingCode() { @@ -124,12 +126,12 @@ public function getOrdersTrackingCode() "ga('ec:addProduct', { 'id': '%s', 'name': '%s', - 'price': '%f', + 'price': %.2f, 'quantity': %d });", $this->escapeJsQuote($item->getSku()), $this->escapeJsQuote($item->getName()), - $item->getPrice(), + round($item->getPrice(), 2), $item->getQtyOrdered() ); } @@ -138,15 +140,15 @@ public function getOrdersTrackingCode() "ga('ec:setAction', 'purchase', { 'id': '%s', 'affiliation': '%s', - 'revenue': '%f', - 'tax': '%f', - 'shipping': '%f' + 'revenue': %.2f, + 'tax': %.2f, + 'shipping': %.2f });", $order->getIncrementId(), $this->escapeJsQuote($this->_storeManager->getStore()->getFrontendName()), - $order->getGrandTotal(), - $order->getTaxAmount(), - $order->getShippingAmount() + round($order->getGrandTotal(), 2), + round($order->getTaxAmount(), 2), + round($order->getShippingAmount(), 2) ); $result[] = "ga('send', 'pageview');"; @@ -235,16 +237,16 @@ public function getOrdersTrackingData() $result['products'][] = [ 'id' => $this->escapeJsQuote($item->getSku()), 'name' => $this->escapeJsQuote($item->getName()), - 'price' => (float)$item->getPrice(), + 'price' => round((float)$item->getPrice(), 2), 'quantity' => (int)$item->getQtyOrdered(), ]; } $result['orders'][] = [ 'id' => $order->getIncrementId(), 'affiliation' => $this->escapeJsQuote($this->_storeManager->getStore()->getFrontendName()), - 'revenue' => (float)$order->getGrandTotal(), - 'tax' => (float)$order->getTaxAmount(), - 'shipping' => (float)$order->getShippingAmount(), + 'revenue' => round((float)$order->getGrandTotal(), 2), + 'tax' => round((float)$order->getTaxAmount(), 2), + 'shipping' => round((float)$order->getShippingAmount(), 2) ]; $result['currency'] = $order->getOrderCurrencyCode(); } diff --git a/app/code/Magento/GoogleAnalytics/Test/Unit/Block/GaTest.php b/app/code/Magento/GoogleAnalytics/Test/Unit/Block/GaTest.php index a367a938d45b..2fa45a4567e4 100644 --- a/app/code/Magento/GoogleAnalytics/Test/Unit/Block/GaTest.php +++ b/app/code/Magento/GoogleAnalytics/Test/Unit/Block/GaTest.php @@ -115,15 +115,15 @@ public function testOrderTrackingCode() ga('ec:addProduct', { 'id': 'sku0', 'name': 'testName0', - 'price': '0.00', + 'price': 0.00, 'quantity': 1 }); ga('ec:setAction', 'purchase', { 'id': '100', 'affiliation': 'test', - 'revenue': '10', - 'tax': '2', - 'shipping': '1' + 'revenue': 10.00, + 'tax': 2.00, + 'shipping': 1.00 }); ga('send', 'pageview');"; @@ -163,8 +163,8 @@ public function testOrderTrackingData() [ 'id' => 100, 'affiliation' => 'test', - 'revenue' => 10, - 'tax' => 2, + 'revenue' => 10.00, + 'tax' => 2.00, 'shipping' => 1 ] ], @@ -213,7 +213,7 @@ protected function createOrderMock($orderItemCount = 1) ->getMockForAbstractClass(); $orderItemMock->expects($this->once())->method('getSku')->willReturn('sku' . $i); $orderItemMock->expects($this->once())->method('getName')->willReturn('testName' . $i); - $orderItemMock->expects($this->once())->method('getPrice')->willReturn($i . '.00'); + $orderItemMock->expects($this->once())->method('getPrice')->willReturn(round((float)($i . '.0000'), 2)); $orderItemMock->expects($this->once())->method('getQtyOrdered')->willReturn($i + 1); $orderItems[] = $orderItemMock; } @@ -223,9 +223,9 @@ protected function createOrderMock($orderItemCount = 1) ->getMock(); $orderMock->expects($this->once())->method('getIncrementId')->willReturn(100); $orderMock->expects($this->once())->method('getAllVisibleItems')->willReturn($orderItems); - $orderMock->expects($this->once())->method('getGrandTotal')->willReturn(10); - $orderMock->expects($this->once())->method('getTaxAmount')->willReturn(2); - $orderMock->expects($this->once())->method('getShippingAmount')->willReturn($orderItemCount); + $orderMock->expects($this->once())->method('getGrandTotal')->willReturn(10.00); + $orderMock->expects($this->once())->method('getTaxAmount')->willReturn(2.00); + $orderMock->expects($this->once())->method('getShippingAmount')->willReturn(round((float)$orderItemCount, 2)); $orderMock->expects($this->once())->method('getOrderCurrencyCode')->willReturn('USD'); return $orderMock; } From 13fe3de9c3529a5653c5a16841bf0eb2717f13c6 Mon Sep 17 00:00:00 2001 From: Viktor Tymchynskyi <tymchyns@adobe.com> Date: Wed, 26 Apr 2023 16:35:24 -0500 Subject: [PATCH 11/41] ACP2E-1882: Database restoration fails due a delimiter error --- .../Magento/Backup/Model/ResourceModel/Db.php | 2 +- .../Magento/Framework/Backup/DbTest.php | 14 ++++- .../Backup/Filesystem/Iterator/File.php | 55 ++++++++++--------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/app/code/Magento/Backup/Model/ResourceModel/Db.php b/app/code/Magento/Backup/Model/ResourceModel/Db.php index c38a7b3005e2..cf39406d54ae 100644 --- a/app/code/Magento/Backup/Model/ResourceModel/Db.php +++ b/app/code/Magento/Backup/Model/ResourceModel/Db.php @@ -301,7 +301,7 @@ public function rollBackTransaction() */ public function runCommand($command) { - $this->connection->query($command); + $this->connection->multiQuery($command); return $this; } } diff --git a/dev/tests/integration/testsuite/Magento/Framework/Backup/DbTest.php b/dev/tests/integration/testsuite/Magento/Framework/Backup/DbTest.php index f25880e10c81..9d48de03c736 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Backup/DbTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Backup/DbTest.php @@ -7,11 +7,12 @@ namespace Magento\Framework\Backup; use Magento\Backup\Helper\Data; +use Magento\Backup\Model\ResourceModel\Db; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem; use Magento\Framework\Module\Setup; use Magento\TestFramework\Helper\Bootstrap; -use PHPUnit\Framework\TestCase; +use Magento\Framework\Backup\BackupInterface; /** * Provide tests for \Magento\Framework\Backup\Db. @@ -32,16 +33,17 @@ public static function setUpBeforeClass(): void } /** - * Test db backup includes triggers. + * Test db backup and rollback including triggers. * * @magentoConfigFixture default/system/backup/functionality_enabled 1 * @magentoDataFixture Magento/Framework/Backup/_files/trigger.php * @magentoDbIsolation disabled */ - public function testBackupIncludesCustomTriggers() + public function testBackupAndRollbackIncludesCustomTriggers() { $helper = Bootstrap::getObjectManager()->get(Data::class); $time = time(); + /** BackupInterface $backupManager */ $backupManager = Bootstrap::getObjectManager()->get(Factory::class)->create( Factory::TYPE_DB )->setBackupExtension( @@ -60,6 +62,12 @@ public function testBackupIncludesCustomTriggers() '/CREATE TRIGGER `?test_custom_trigger`? AFTER INSERT ON `?'. $tableName . '`? FOR EACH ROW/', $content ); + + // Test rollback + $backupResourceModel = Bootstrap::getObjectManager()->get(Db::class); + $backupManager->setResourceModel($backupResourceModel); + $backupManager->rollback(); + //Clean up. $write->delete('/backups/' . $time . '_db_testbackup.sql'); } diff --git a/lib/internal/Magento/Framework/Backup/Filesystem/Iterator/File.php b/lib/internal/Magento/Framework/Backup/Filesystem/Iterator/File.php index b58ad53dd139..1409fba14c5f 100644 --- a/lib/internal/Magento/Framework/Backup/Filesystem/Iterator/File.php +++ b/lib/internal/Magento/Framework/Backup/Filesystem/Iterator/File.php @@ -19,6 +19,13 @@ class File extends \SplFileObject */ protected $_currentStatement = ''; + /** + * Store current statement delimiter. + * + * @var string + */ + private string $statementDelimiter = ';'; + /** * Return current sql statement * @@ -41,15 +48,35 @@ public function next() $this->_currentStatement = ''; while (!$this->eof()) { $line = $this->fgets(); - if (strlen(trim($line))) { - $this->_currentStatement .= $line; - if ($this->_isLineLastInCommand($line)) { + $trimmedLine = trim($line); + if (!empty($trimmedLine) && !$this->isDelimiterChanged($trimmedLine)) { + $statementFinalLine = '/(?<statement>.*)' . preg_quote($this->statementDelimiter, '/') . '$/'; + if (preg_match($statementFinalLine, $trimmedLine, $matches)) { + $this->_currentStatement .= $matches['statement']; break; + } else { + $this->_currentStatement .= $line; } } } } + /** + * Check whether statement delimiter has been changed. + * + * @param string $line + * @return bool + */ + private function isDelimiterChanged(string $line): bool + { + if (preg_match('/^delimiter\s+(?<delimiter>.+)$/i', $line, $matches)) { + $this->statementDelimiter = $matches['delimiter']; + return true; + } + + return false; + } + /** * Return to first statement * @@ -72,26 +99,4 @@ protected function _isComment($line) { return $line[0] == '#' || ($line && substr($line, 0, 2) == '--'); } - - /** - * Check is line a last in sql command - * - * @param string $line - * @return bool - */ - protected function _isLineLastInCommand($line) - { - $cleanLine = trim($line); - $lineLength = strlen($cleanLine); - - $returnResult = false; - if ($lineLength > 0) { - $lastSymbolIndex = $lineLength - 1; - if ($cleanLine[$lastSymbolIndex] == ';') { - $returnResult = true; - } - } - - return $returnResult; - } } From 8fc013d02343c97c225e5ea4190525203dc17086 Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Wed, 3 May 2023 17:24:38 +0300 Subject: [PATCH 12/41] ACP2E-1852: get and set image size for already loaded images in content. --- .../base/web/js/form/element/file-uploader.js | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js index e7dc245d47d6..e978584d9a88 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js @@ -75,7 +75,14 @@ define([ * @returns {FileUploader} Chainable. */ setInitialValue: function () { - var value = this.getInitialValue(); + var value = this.getInitialValue(), + imageSize = this.setImageSize; + + _.each(value, function (value) { + if (value.type.indexOf('image') >= 0) { + imageSize(value); + } + }, this); value = value.map(this.processFile, this); @@ -88,6 +95,19 @@ define([ return this; }, + /** + * Set image size for already loaded image + * + * @param value + * @returns {Promise<void>} + */ + async setImageSize(value) { + let response = await fetch(value.url), + blob = await response.blob(); + + value.size = blob.size; + }, + /** * Empties files list. * From 5cab607ccdad47a4a37065e6e63c49a61e0ad79a Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Thu, 4 May 2023 11:37:56 +0300 Subject: [PATCH 13/41] ACP2E-1753: fix merge conflicts --- .../Test/Mftf/Test/AddNewProductAttributeInProductPageTest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddNewProductAttributeInProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddNewProductAttributeInProductPageTest.xml index 45e2724ecf6c..9248c6bea610 100755 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AddNewProductAttributeInProductPageTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddNewProductAttributeInProductPageTest.xml @@ -21,6 +21,7 @@ <before> <!-- Login as admin --> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="openProductIndexPageBefore"/> <!-- remove the Filter From the page--> <actionGroup ref="ClearFiltersAdminProductGridActionGroup" stepKey="clearFilterFromProductIndex"/> <!--Create Category--> From 042d199c60bcb45733af959596a36cfb931d4a8a Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Thu, 4 May 2023 16:03:24 +0300 Subject: [PATCH 14/41] ACP2E-1880: adapt for decimal quantity and allow several decimals on price --- app/code/Magento/GoogleAnalytics/Block/Ga.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/GoogleAnalytics/Block/Ga.php b/app/code/Magento/GoogleAnalytics/Block/Ga.php index 3c5345d153f7..160793de5a8f 100644 --- a/app/code/Magento/GoogleAnalytics/Block/Ga.php +++ b/app/code/Magento/GoogleAnalytics/Block/Ga.php @@ -122,17 +122,20 @@ public function getOrdersTrackingCode() foreach ($collection as $order) { $result[] = "ga('set', 'currencyCode', '" . $order->getOrderCurrencyCode() . "');"; foreach ($order->getAllVisibleItems() as $item) { + $quantity = (float)$item->getQtyOrdered(); + $quantity = fmod($quantity, 1) !== 0.00 ? $quantity : (int)$quantity; + $format = fmod($quantity, 1) !== 0.00 ? '%.2f' : '%d'; $result[] = sprintf( "ga('ec:addProduct', { 'id': '%s', 'name': '%s', 'price': %.2f, - 'quantity': %d + 'quantity': $format });", $this->escapeJsQuote($item->getSku()), $this->escapeJsQuote($item->getName()), - round($item->getPrice(), 2), - $item->getQtyOrdered() + (float)$item->getPrice(), + $quantity ); } @@ -146,9 +149,9 @@ public function getOrdersTrackingCode() });", $order->getIncrementId(), $this->escapeJsQuote($this->_storeManager->getStore()->getFrontendName()), - round($order->getGrandTotal(), 2), - round($order->getTaxAmount(), 2), - round($order->getShippingAmount(), 2) + (float)$order->getGrandTotal(), + (float)$order->getTaxAmount(), + (float)$order->getShippingAmount(), ); $result[] = "ga('send', 'pageview');"; From 382ab5b83e65042fe1bd98f07215d888728b1fb4 Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Thu, 4 May 2023 16:06:08 +0300 Subject: [PATCH 15/41] ACP2E-1880: adapt for decimal quantity and allow several decimals on price --- app/code/Magento/GoogleAnalytics/Block/Ga.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/GoogleAnalytics/Block/Ga.php b/app/code/Magento/GoogleAnalytics/Block/Ga.php index 160793de5a8f..9b2635569a5b 100644 --- a/app/code/Magento/GoogleAnalytics/Block/Ga.php +++ b/app/code/Magento/GoogleAnalytics/Block/Ga.php @@ -237,19 +237,21 @@ public function getOrdersTrackingData() foreach ($collection as $order) { foreach ($order->getAllVisibleItems() as $item) { + $quantity = (float)$item->getQtyOrdered(); + $quantity = fmod($quantity, 1) !== 0.00 ? $quantity : (int)$quantity; $result['products'][] = [ 'id' => $this->escapeJsQuote($item->getSku()), 'name' => $this->escapeJsQuote($item->getName()), - 'price' => round((float)$item->getPrice(), 2), - 'quantity' => (int)$item->getQtyOrdered(), + 'price' => (float)$item->getPrice(), + 'quantity' => $quantity, ]; } $result['orders'][] = [ 'id' => $order->getIncrementId(), 'affiliation' => $this->escapeJsQuote($this->_storeManager->getStore()->getFrontendName()), - 'revenue' => round((float)$order->getGrandTotal(), 2), - 'tax' => round((float)$order->getTaxAmount(), 2), - 'shipping' => round((float)$order->getShippingAmount(), 2) + 'revenue' => (float)$order->getGrandTotal(), + 'tax' => (float)$order->getTaxAmount(), + 'shipping' => (float)$order->getShippingAmount(), ]; $result['currency'] = $order->getOrderCurrencyCode(); } From f24a329f289119a5f45f420cf99fe818143f9e86 Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Thu, 4 May 2023 16:19:12 +0300 Subject: [PATCH 16/41] ACP2E-1880: adapt for decimal quantity and allow several decimals on price --- .../Test/Unit/Block/GaTest.php | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/GoogleAnalytics/Test/Unit/Block/GaTest.php b/app/code/Magento/GoogleAnalytics/Test/Unit/Block/GaTest.php index 2fa45a4567e4..8088b03707b2 100644 --- a/app/code/Magento/GoogleAnalytics/Test/Unit/Block/GaTest.php +++ b/app/code/Magento/GoogleAnalytics/Test/Unit/Block/GaTest.php @@ -118,12 +118,18 @@ public function testOrderTrackingCode() 'price': 0.00, 'quantity': 1 }); + ga('ec:addProduct', { + 'id': 'sku1', + 'name': 'testName1', + 'price': 1.00, + 'quantity': 1.11 + }); ga('ec:setAction', 'purchase', { 'id': '100', 'affiliation': 'test', 'revenue': 10.00, 'tax': 2.00, - 'shipping': 1.00 + 'shipping': 2.00 }); ga('send', 'pageview');"; @@ -165,7 +171,7 @@ public function testOrderTrackingData() 'affiliation' => 'test', 'revenue' => 10.00, 'tax' => 2.00, - 'shipping' => 1 + 'shipping' => 2.0 ] ], 'products' => [ @@ -174,6 +180,12 @@ public function testOrderTrackingData() 'name' => 'testName0', 'price' => 0.00, 'quantity' => 1 + ], + [ + 'id' => 'sku1', + 'name' => 'testName1', + 'price' => 1.00, + 'quantity' => 1.11 ] ], 'currency' => 'USD' @@ -204,7 +216,7 @@ public function testGetPageTrackingData() * @param int $orderItemCount * @return Order|MockObject */ - protected function createOrderMock($orderItemCount = 1) + protected function createOrderMock($orderItemCount = 2) { $orderItems = []; for ($i = 0; $i < $orderItemCount; $i++) { @@ -213,8 +225,8 @@ protected function createOrderMock($orderItemCount = 1) ->getMockForAbstractClass(); $orderItemMock->expects($this->once())->method('getSku')->willReturn('sku' . $i); $orderItemMock->expects($this->once())->method('getName')->willReturn('testName' . $i); - $orderItemMock->expects($this->once())->method('getPrice')->willReturn(round((float)($i . '.0000'), 2)); - $orderItemMock->expects($this->once())->method('getQtyOrdered')->willReturn($i + 1); + $orderItemMock->expects($this->once())->method('getPrice')->willReturn((float)($i . '.0000')); + $orderItemMock->expects($this->once())->method('getQtyOrdered')->willReturn($i == 1 ? 1.11 : $i + 1); $orderItems[] = $orderItemMock; } @@ -241,7 +253,7 @@ protected function createCollectionMock() $collectionMock->expects($this->any()) ->method('getIterator') - ->willReturn(new \ArrayIterator([$this->createOrderMock(1)])); + ->willReturn(new \ArrayIterator([$this->createOrderMock(2)])); return $collectionMock; } From 4f6514c943354ffba51b8adecbcf8e43e7e11b86 Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Thu, 4 May 2023 21:13:20 +0300 Subject: [PATCH 17/41] ACP2E-1880: adapt for decimal quantity and allow several decimals on price --- app/code/Magento/GoogleAnalytics/Block/Ga.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/GoogleAnalytics/Block/Ga.php b/app/code/Magento/GoogleAnalytics/Block/Ga.php index 9b2635569a5b..3be62a588efa 100644 --- a/app/code/Magento/GoogleAnalytics/Block/Ga.php +++ b/app/code/Magento/GoogleAnalytics/Block/Ga.php @@ -122,8 +122,7 @@ public function getOrdersTrackingCode() foreach ($collection as $order) { $result[] = "ga('set', 'currencyCode', '" . $order->getOrderCurrencyCode() . "');"; foreach ($order->getAllVisibleItems() as $item) { - $quantity = (float)$item->getQtyOrdered(); - $quantity = fmod($quantity, 1) !== 0.00 ? $quantity : (int)$quantity; + $quantity = $item->getQtyOrdered() * 1; $format = fmod($quantity, 1) !== 0.00 ? '%.2f' : '%d'; $result[] = sprintf( "ga('ec:addProduct', { @@ -237,8 +236,7 @@ public function getOrdersTrackingData() foreach ($collection as $order) { foreach ($order->getAllVisibleItems() as $item) { - $quantity = (float)$item->getQtyOrdered(); - $quantity = fmod($quantity, 1) !== 0.00 ? $quantity : (int)$quantity; + $quantity = $item->getQtyOrdered() * 1; $result['products'][] = [ 'id' => $this->escapeJsQuote($item->getSku()), 'name' => $this->escapeJsQuote($item->getName()), From a980dcb412579e9ada28ec1257ebc57b0a70f695 Mon Sep 17 00:00:00 2001 From: npuchko <npuchko@adobe.com> Date: Thu, 4 May 2023 12:59:35 -0700 Subject: [PATCH 18/41] ACP2E-1886: Image not marked as used on page if uploaded through Page Builder --- .../Magento/MediaGalleryRenditions/etc/media_content.xml | 2 +- .../MediaContent/Model/ExtractAssetsFromContentTest.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/MediaGalleryRenditions/etc/media_content.xml b/app/code/Magento/MediaGalleryRenditions/etc/media_content.xml index 7596de07b892..05d2c3006651 100644 --- a/app/code/Magento/MediaGalleryRenditions/etc/media_content.xml +++ b/app/code/Magento/MediaGalleryRenditions/etc/media_content.xml @@ -9,7 +9,7 @@ <search> <patterns> <pattern name="media_gallery_renditions">/{{media url=(?:"|&quot;)(?:.renditions)?(.*?)(?:"|&quot;)}}/</pattern> - <pattern name="media_gallery">/{{media url="?(?:.*?\.renditions\/)(.*?)"?}}/</pattern> + <pattern name="media_gallery">/{{media url="?(?:.*?\.renditions\/)?(.*?)"?}}/</pattern> <pattern name="wysiwyg">/src=".*\/media\/(?:.renditions\/)*(.*?)"/</pattern> <pattern name="catalog_image">/^\/?media\/(?:.renditions\/)?(.*)/</pattern> <pattern name="catalog_image_with_pub">/^\/pub\/?media\/(?:.renditions\/)?(.*)/</pattern> diff --git a/dev/tests/integration/testsuite/Magento/MediaContent/Model/ExtractAssetsFromContentTest.php b/dev/tests/integration/testsuite/Magento/MediaContent/Model/ExtractAssetsFromContentTest.php index e561311fc4e7..85b0b53e6426 100644 --- a/dev/tests/integration/testsuite/Magento/MediaContent/Model/ExtractAssetsFromContentTest.php +++ b/dev/tests/integration/testsuite/Magento/MediaContent/Model/ExtractAssetsFromContentTest.php @@ -77,6 +77,12 @@ public function contentProvider() 2020 ] ], + 'Relevant paths in content without quotes' => [ + 'content {{media url=testDirectory/path.jpg}} content', + [ + 2020 + ] + ], 'Relevant wysiwyg paths in content' => [ 'content <img src="https://domain.com/media/testDirectory/path.jpg"}} content', [ From a73b032f1681b57ba158c3deab5f2eba9d20f5e9 Mon Sep 17 00:00:00 2001 From: aplapana <aplapana@adobe.com> Date: Fri, 5 May 2023 12:45:41 +0300 Subject: [PATCH 19/41] ACP2E-1783: Unable to create 301/302 redirect for product with a category path when Generate "category/product" URL Rewrites set to Yes - refactored solution --- .../Products/AppendUrlRewritesToProducts.php | 51 ++---- .../AppendUrlRewritesToProductsTest.php | 100 ----------- .../Controller/Adminhtml/Url/Rewrite/Save.php | 162 ++++-------------- 3 files changed, 54 insertions(+), 259 deletions(-) delete mode 100644 app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Products/AppendUrlRewritesToProductsTest.php diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Products/AppendUrlRewritesToProducts.php b/app/code/Magento/CatalogUrlRewrite/Model/Products/AppendUrlRewritesToProducts.php index ce842b667e50..15d4aabf4246 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Products/AppendUrlRewritesToProducts.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Products/AppendUrlRewritesToProducts.php @@ -79,7 +79,23 @@ public function __construct( public function execute(array $products, array $storesToAdd): void { foreach ($products as $product) { - $urls = $this->getProductUrlRewrites($product, $storesToAdd); + $forceGenerateDefault = false; + foreach ($storesToAdd as $storeId) { + if ($this->needGenerateUrlForStore($product, (int)$storeId)) { + $urls[] = $this->generateUrls($product, (int)$storeId); + } elseif ((int)$product->getStoreId() !== Store::DEFAULT_STORE_ID) { + $forceGenerateDefault = true; + } + } + if ($product->getStoreId() === Store::DEFAULT_STORE_ID + || $this->isProductAssignedToStore($product)) { + $product->unsUrlPath(); + $product->setUrlPath($this->productUrlPathGenerator->getUrlPath($product)); + $urls[] = $this->productUrlRewriteGenerator->generate($product); + } + if ($forceGenerateDefault && $product->getStoreId() !== Store::DEFAULT_STORE_ID) { + $urls[] = $this->generateUrls($product, Store::DEFAULT_STORE_ID); + } $this->getDataByStore->clearProductUrlRewriteDataCache($product); } if (!empty($urls)) { @@ -87,37 +103,6 @@ public function execute(array $products, array $storesToAdd): void } } - /** - * Generate store product URLs - * - * @param ProductInterface $product - * @param array $stores - * @return array - */ - public function getProductUrlRewrites(ProductInterface $product, array $stores): array - { - $urls = []; - $forceGenerateDefault = false; - foreach ($stores as $storeId) { - if ($this->needGenerateUrlForStore($product, (int)$storeId)) { - $urls[] = $this->generateProductStoreUrls($product, (int)$storeId); - } elseif ((int)$product->getStoreId() !== Store::DEFAULT_STORE_ID) { - $forceGenerateDefault = true; - } - } - if ($product->getStoreId() === Store::DEFAULT_STORE_ID - || $this->isProductAssignedToStore($product)) { - $product->unsUrlPath(); - $product->setUrlPath($this->productUrlPathGenerator->getUrlPath($product)); - $urls[] = $this->productUrlRewriteGenerator->generate($product); - } - if ($forceGenerateDefault && $product->getStoreId() !== Store::DEFAULT_STORE_ID) { - $urls[] = $this->generateProductStoreUrls($product, Store::DEFAULT_STORE_ID); - } - - return $urls; - } - /** * Generate urls for specific store * @@ -125,7 +110,7 @@ public function getProductUrlRewrites(ProductInterface $product, array $stores): * @param int $storeId * @return array */ - private function generateProductStoreUrls(ProductInterface $product, int $storeId): array + private function generateUrls(ProductInterface $product, int $storeId): array { $storeData = $this->getDataByStore->execute($product, $storeId); $origStoreId = $product->getStoreId(); diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Products/AppendUrlRewritesToProductsTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Products/AppendUrlRewritesToProductsTest.php deleted file mode 100644 index 48568be00dde..000000000000 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Products/AppendUrlRewritesToProductsTest.php +++ /dev/null @@ -1,100 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Products; - -use Magento\Catalog\Model\Product; -use Magento\CatalogUrlRewrite\Model\Product\GetProductUrlRewriteDataByStore; -use Magento\CatalogUrlRewrite\Model\Products\AppendUrlRewritesToProducts; -use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; -use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; -use Magento\CatalogUrlRewrite\Service\V1\StoreViewService; -use Magento\UrlRewrite\Model\UrlPersistInterface; -use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; - -class AppendUrlRewritesToProductsTest extends TestCase -{ - /** - * @var ProductUrlRewriteGenerator|MockObject - */ - private ProductUrlRewriteGenerator $productUrlRewriteGenerator; - - /** - * @var StoreViewService|MockObject - */ - private StoreViewService $storeViewService; - - /** - * @var ProductUrlPathGenerator|MockObject - */ - private ProductUrlPathGenerator $productUrlPathGenerator; - - /** - * @var UrlPersistInterface|MockObject - */ - private UrlPersistInterface $urlPersist; - - /** - * @var GetProductUrlRewriteDataByStore|MockObject - */ - private GetProductUrlRewriteDataByStore $getDataByStore; - - /** - * @var AppendUrlRewritesToProducts - */ - private AppendUrlRewritesToProducts $append; - - protected function setUp(): void - { - $this->productUrlRewriteGenerator = $this->createMock(ProductUrlRewriteGenerator::class); - $this->storeViewService = $this->createMock(StoreViewService::class); - $this->productUrlPathGenerator = $this->createMock(ProductUrlPathGenerator::class); - $this->urlPersist = $this->createMock(UrlPersistInterface::class); - $this->getDataByStore = $this->createMock(GetProductUrlRewriteDataByStore::class); - - $this->append = new AppendUrlRewritesToProducts( - $this->productUrlRewriteGenerator, - $this->storeViewService, - $this->productUrlPathGenerator, - $this->urlPersist, - $this->getDataByStore - ); - parent::setUp(); - } - - /** - * @return void - * @throws \Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException - */ - public function testGetProductUrlRewrites(): void - { - $storeId = $productId = 1; - $product = $this->getMockBuilder(Product::class) - ->disableOriginalConstructor() - ->addMethods(['unsUrlPath', 'setUrlPath']) - ->onlyMethods(['getStoreId', 'getId', 'getStoreIds']) - ->getMock(); - $product->expects($this->any())->method('getStoreId')->willReturn(0); - $product->expects($this->any())->method('getId')->willReturn($productId); - $product->expects($this->any())->method('getStoreIds')->willReturn([$storeId]); - $product->expects($this->once())->method('unsUrlPath'); - $product->expects($this->once())->method('setUrlPath'); - - $this->productUrlPathGenerator->expects($this->once())->method('getUrlPath'); - $this->productUrlRewriteGenerator->expects($this->once())->method('generate')->willReturn([]); - $this->getDataByStore->expects($this->once())->method('clearProductUrlRewriteDataCache'); - $this->urlPersist->expects($this->once())->method('replace'); - - $this->storeViewService->expects($this->once()) - ->method('doesEntityHaveOverriddenUrlKeyForStore') - ->with($storeId, $productId, Product::ENTITY) - ->willReturn(false); - - $this->append->execute([$product], [$storeId]); - } -} diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php index aec7b76b0119..151833a00b58 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php @@ -7,19 +7,11 @@ namespace Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite; -use Magento\Backend\App\Action\Context; -use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; -use Magento\CatalogUrlRewrite\Model\Products\AppendUrlRewritesToProducts; -use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; -use Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Exception\LocalizedException; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ class Save extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite implements HttpPostActionInterface { /** @@ -43,38 +35,24 @@ class Save extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite implemen protected $urlFinder; /** - * @var AppendUrlRewritesToProducts - */ - protected AppendUrlRewritesToProducts $productAppendRewrites; - - /** - * @var array - */ - protected array $missingRewrites = []; - - /** - * @param Context $context - * @param ProductUrlPathGenerator $productUrlPathGenerator - * @param CategoryUrlPathGenerator $categoryUrlPathGenerator - * @param CmsPageUrlPathGenerator $cmsPageUrlPathGenerator + * @param \Magento\Backend\App\Action\Context $context + * @param \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator + * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator + * @param \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $cmsPageUrlPathGenerator * @param UrlFinderInterface $urlFinder - * @param AppendUrlRewritesToProducts|null $productAppendRewrites */ public function __construct( - \Magento\Backend\App\Action\Context $context, - \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator, + \Magento\Backend\App\Action\Context $context, + \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator, \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator, - \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $cmsPageUrlPathGenerator, - UrlFinderInterface $urlFinder, - ?AppendUrlRewritesToProducts $productAppendRewrites = null + \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $cmsPageUrlPathGenerator, + UrlFinderInterface $urlFinder ) { parent::__construct($context); $this->productUrlPathGenerator = $productUrlPathGenerator; $this->categoryUrlPathGenerator = $categoryUrlPathGenerator; $this->cmsPageUrlPathGenerator = $cmsPageUrlPathGenerator; $this->urlFinder = $urlFinder; - $this->productAppendRewrites = $productAppendRewrites ?: - \Magento\Framework\App\ObjectManager::getInstance()->create(AppendUrlRewritesToProducts::class); } /** @@ -82,7 +60,7 @@ public function __construct( * * @param \Magento\UrlRewrite\Model\UrlRewrite $model * @return void - * @throws LocalizedException + * @throws \Magento\Framework\Exception\LocalizedException */ protected function _handleCatalogUrlRewrite($model) { @@ -96,135 +74,72 @@ protected function _handleCatalogUrlRewrite($model) $model->setMetadata(['category_id' => $categoryId]); } } - $model->setTargetPath($this->generateTargetPath($model)); + $model->setTargetPath($this->getTargetPath($model)); } } /** - * Generate Target Path + * Get Target Path * * @param \Magento\UrlRewrite\Model\UrlRewrite $model - * @throws LocalizedException + * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ - protected function generateTargetPath($model): ?string + protected function getTargetPath($model) { $targetPath = $this->getCanonicalTargetPath(); if ($model->getRedirectType() && !$model->getIsAutogenerated()) { - if ($rewrite = $this->urlFinder->findOneByData([ + $data = [ UrlRewrite::ENTITY_ID => $model->getEntityId(), UrlRewrite::TARGET_PATH => $targetPath, UrlRewrite::ENTITY_TYPE => $model->getEntityType(), - UrlRewrite::STORE_ID => $model->getStoreId() - ])) { - $targetPath = $rewrite->getRequestPath(); - } else { - $this->checkEntityAssociations($model); - if ($model->getEntityType() === self::ENTITY_TYPE_PRODUCT) { - $productRewrites = $this->productAppendRewrites->getProductUrlRewrites( - $this->_getProduct(), - [$this->getRequest()->getParam('store_id', 0)] - ); - $productRewrites = array_merge(...$productRewrites); - $targetPath = $this->getAdjustedTargetPath($model, $productRewrites, $targetPath); - $this->setMissingRewrites($model, $productRewrites); - } + UrlRewrite::STORE_ID => $model->getStoreId(), + ]; + $rewrite = $this->urlFinder->findOneByData($data); + if (!$rewrite) { + $model->getEntityType() === self::ENTITY_TYPE_PRODUCT ? $this->checkProductCorrelation($model) : + $this->checkCategoryCorrelation($model); } + $targetPath = $rewrite->getRequestPath(); } - return $targetPath; } /** - * Checks for missing product rewrites + * Checks if rewrite details match category properties * * @param \Magento\UrlRewrite\Model\UrlRewrite $model - * @param array $rewrites * @return void + * @throws LocalizedException */ - private function setMissingRewrites(\Magento\UrlRewrite\Model\UrlRewrite $model, array $rewrites): void + private function checkCategoryCorrelation(\Magento\UrlRewrite\Model\UrlRewrite $model): void { - if (!empty($rewrites)) { - foreach ($rewrites as $rewrite) { - if ($rewrite->getRequestPath() != $model->getRequestPath()) { - $this->missingRewrites[] = $this->generateRewriteModel($rewrite); - } - } - } - } - - /** - * Checks for potential target path adjustments - * - * @param \Magento\UrlRewrite\Model\UrlRewrite $model - * @param array $productRewrites - * @param string $canonicalTargetPath - * @return string|null - */ - private function getAdjustedTargetPath( - \Magento\UrlRewrite\Model\UrlRewrite $model, - array $productRewrites, - string $canonicalTargetPath - ): ?string { - if (empty($productRewrites)) { - return null; - } - - foreach ($productRewrites as $rewrite) { - if ($rewrite->getRequestPath() != $model->getRequestPath()) { - $this->missingRewrites[] = $this->generateRewriteModel($rewrite); - if ($rewrite->getTargetPath() == $canonicalTargetPath) { - return $rewrite->getRequestPath(); - } - } + if (false === ($this->_getCategory()->getStoreId() == $model->getStoreId())) { + throw new LocalizedException( + __("The selected category isn't associated with the selected store.") + ); } - - return $canonicalTargetPath; } /** - * Generates rewrite model - * - * @param UrlRewrite $rewrite - * @return \Magento\UrlRewrite\Model\UrlRewrite - */ - private function generateRewriteModel(UrlRewrite $rewrite): \Magento\UrlRewrite\Model\UrlRewrite - { - $rewriteModel = $this->_objectManager->create(\Magento\UrlRewrite\Model\UrlRewrite::class); - $rewriteModel->setEntityType(self::ENTITY_TYPE_PRODUCT) - ->setRequestPath($rewrite->getRequestPath()) - ->setTargetPath($rewrite->getTargetPath()) - ->setRedirectType($rewrite->getRedirectType()) - ->setStoreId($rewrite->getStoreId()) - ->setDescription($rewrite->getDescription()) - ->setMetadata($rewrite->getMetadata()); - - return $rewriteModel; - } - - /** - * Checks if rewrite can be created for product or category + * Checks if rewrite details match product properties * * @param \Magento\UrlRewrite\Model\UrlRewrite $model * @return void * @throws LocalizedException */ - private function checkEntityAssociations(\Magento\UrlRewrite\Model\UrlRewrite $model): void + private function checkProductCorrelation(\Magento\UrlRewrite\Model\UrlRewrite $model): void { - $check = $model->getEntityType() === self::ENTITY_TYPE_PRODUCT ? - $this->_getProduct()->canBeShowInCategory($this->_getCategory()->getId()) && - in_array($model->getStoreId(), $this->_getProduct()->getStoreIds()) : - $this->_getCategory()->getStoreId() == $model->getStoreId(); - if (false === $check) { + if (false === ($this->_getProduct()->canBeShowInCategory($this->_getCategory()->getId())) && + in_array($model->getStoreId(), $this->_getProduct()->getStoreIds())) { throw new LocalizedException( - $model->getEntityType() === self::ENTITY_TYPE_PRODUCT - ? __("The selected product isn't associated with the selected store or category.") - : __("The selected category isn't associated with the selected store.") + __("The selected product isn't associated with the selected store or category.") ); } } /** - * Generate canonical product / category path + * Get rewrite canonical target path * * @return string */ @@ -285,14 +200,9 @@ public function execute() ->setStoreId($this->getRequest()->getParam('store_id', 0)) ->setDescription($this->getRequest()->getParam('description')); - $this->_handleCmsPageUrlRewrite($model); $this->_handleCatalogUrlRewrite($model); + $this->_handleCmsPageUrlRewrite($model); $model->save(); - if (!empty($this->missingRewrites)) { - foreach ($this->missingRewrites as $missingRewrite) { - $missingRewrite->save(); - } - } $this->messageManager->addSuccess(__('The URL Rewrite has been saved.')); $this->_redirect('adminhtml/*/'); From 03a1500ffa09c3aa7d8169968c974ba49218f0e6 Mon Sep 17 00:00:00 2001 From: aplapana <aplapana@adobe.com> Date: Fri, 5 May 2023 13:46:37 +0300 Subject: [PATCH 20/41] ACP2E-1783: Unable to create 301/302 redirect for product with a category path when Generate "category/product" URL Rewrites set to Yes - refactored solution --- .../UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php index 151833a00b58..4ef7ea4b2062 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php @@ -99,8 +99,9 @@ protected function getTargetPath($model) if (!$rewrite) { $model->getEntityType() === self::ENTITY_TYPE_PRODUCT ? $this->checkProductCorrelation($model) : $this->checkCategoryCorrelation($model); + } else { + $targetPath = $rewrite->getRequestPath(); } - $targetPath = $rewrite->getRequestPath(); } return $targetPath; } @@ -114,7 +115,7 @@ protected function getTargetPath($model) */ private function checkCategoryCorrelation(\Magento\UrlRewrite\Model\UrlRewrite $model): void { - if (false === ($this->_getCategory()->getStoreId() == $model->getStoreId())) { + if (false === in_array($model->getStoreId(), $this->_getCategory()->getStoreIds())) { throw new LocalizedException( __("The selected category isn't associated with the selected store.") ); From 3225de7a6629fd589c05978fea29da0e07ec060f Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Tue, 9 May 2023 13:46:29 +0300 Subject: [PATCH 21/41] ACP2E-1852: get and set image size for already loaded images in content. --- .../Ui/view/base/web/js/form/element/file-uploader.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js index e978584d9a88..aed50cb7d5c9 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js @@ -78,9 +78,9 @@ define([ var value = this.getInitialValue(), imageSize = this.setImageSize; - _.each(value, function (value) { - if (value.type.indexOf('image') >= 0) { - imageSize(value); + _.each(value, function (val) { + if (val.type.indexOf('image') >= 0) { + imageSize(val); } }, this); From d32ed46c61a53548448b86c60f25f12dec4b692a Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Tue, 9 May 2023 16:58:43 +0300 Subject: [PATCH 22/41] ACP2E-1852: added unit test --- .../js/form/element/file-uploader.test.js | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/file-uploader.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/file-uploader.test.js index ba5ad61cfe31..d7516c64fedc 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/file-uploader.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/file-uploader.test.js @@ -33,7 +33,13 @@ define([ }, component, dataScope = 'dataScope', - originalJQuery = jQuery.fn; + originalJQuery = jQuery.fn, + params = { + provider: 'provName', + name: '', + index: '', + dataScope: dataScope + }; beforeEach(function (done) { injector.mock(mocks); @@ -41,12 +47,7 @@ define([ 'Magento_Ui/js/form/element/file-uploader', 'knockoutjs/knockout-es5' ], function (Constr) { - component = new Constr({ - provider: 'provName', - name: '', - index: '', - dataScope: dataScope - }); + component = new Constr(params); done(); }); @@ -69,6 +70,40 @@ define([ }); }); + describe('setInitialValue method', function () { + + it('check for chainable', function () { + expect(component.setInitialValue()).toEqual(component); + }); + it('check for set value', function () { + var initialValue = [ + { + 'name': 'test.png', + 'size': 0, + 'type': 'image/png', + 'url': 'http://localhost:8000/media/wysiwyg/test.png' + } + ], expectedValue = [ + { + 'name': 'test.png', + 'size': 2000, + 'type': 'image/png', + 'url': 'http://localhost:8000/media/wysiwyg/test.png' + } + ]; + + spyOn(component, 'setImageSize').and.callFake(function () { + component.value().size = 2000; + }); + spyOn(component, 'getInitialValue').and.returnValue(initialValue); + component.service = true; + expect(component.setInitialValue()).toEqual(component); + expect(component.getInitialValue).toHaveBeenCalled(); + component.setImageSize(initialValue); + expect(component.value().size).toEqual(expectedValue[0].size); + }); + }); + describe('isFileAllowed method', function () { var invalidFile, validFile; From e53b688bf80153b470872b7377681b73321fcdb7 Mon Sep 17 00:00:00 2001 From: npuchko <npuchko@adobe.com> Date: Tue, 9 May 2023 12:03:40 -0700 Subject: [PATCH 23/41] ACP2E-1870: Advanced search price filter is not working along with SKU --- .../CatalogSearch/etc/search_request.xml | 2 +- .../Controller/Advanced/ResultTest.php | 103 ++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogSearch/etc/search_request.xml b/app/code/Magento/CatalogSearch/etc/search_request.xml index 376e4ced4d5a..9a84bf4c458d 100644 --- a/app/code/Magento/CatalogSearch/etc/search_request.xml +++ b/app/code/Magento/CatalogSearch/etc/search_request.xml @@ -67,7 +67,7 @@ <queries> <query xsi:type="boolQuery" name="advanced_search_container" boost="1"> <queryReference clause="should" ref="sku_query"/> - <queryReference clause="should" ref="price_query"/> + <queryReference clause="must" ref="price_query"/> <queryReference clause="should" ref="category_query"/> <queryReference clause="must" ref="visibility_query"/> </query> diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php index c032f47d8834..8f4b804de157 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php @@ -165,6 +165,42 @@ public function testExecuteWithArrayInParam(array $searchParams): void ); } + /** + * Advanced search test by difference product attributes. + * + * @magentoAppArea frontend + * @magentoDataFixture Magento/CatalogSearch/_files/product_for_search.php + * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php + * @dataProvider testDataForAttributesCombination + * + * @param array $searchParams + * @param bool $isProductShown + * @return void + */ + public function testExecuteForAttributesCombination(array $searchParams, bool $isProductShown): void + { + $this->getRequest()->setQuery( + $this->_objectManager->create( + Parameters::class, + [ + 'values' => $searchParams + ] + ) + ); + $this->dispatch('catalogsearch/advanced/result'); + $responseBody = $this->getResponse()->getBody(); + + if ($isProductShown) { + $this->assertStringContainsString('Simple product name', $responseBody); + } else { + $this->assertStringContainsString( + 'We can't find any items matching these search criteria.', + $responseBody + ); + } + $this->assertStringNotContainsString('Not visible simple product', $responseBody); + } + /** * Data provider with array in params values * @@ -339,4 +375,71 @@ private function getAttributeOptionValueByOptionLabel(string $attributeCode, str return $attribute->getSource()->getOptionId($optionLabel); } + + /** + * Data provider with strings for quick search. + * + * @return array + */ + public function testDataForAttributesCombination(): array + { + return [ + 'search_product_by_name_and_price' => [ + [ + 'name' => 'Simple product name', + 'sku' => '', + 'description' => '', + 'short_description' => '', + 'price' => [ + 'from' => 99, + 'to' => 101, + ], + 'test_searchable_attribute' => '', + ], + true + ], + 'search_product_by_name_and_price_not_shown' => [ + [ + 'name' => 'Simple product name', + 'sku' => '', + 'description' => '', + 'short_description' => '', + 'price' => [ + 'from' => 101, + 'to' => 102, + ], + 'test_searchable_attribute' => '', + ], + false + ], + 'search_product_by_sku' => [ + [ + 'name' => '', + 'sku' => 'simple_for_search', + 'description' => '', + 'short_description' => '', + 'price' => [ + 'from' => 99, + 'to' => 101, + ], + 'test_searchable_attribute' => '', + ], + true + ], + 'search_product_by_sku_not_shown' => [ + [ + 'name' => '', + 'sku' => 'simple_for_search', + 'description' => '', + 'short_description' => '', + 'price' => [ + 'from' => 990, + 'to' => 1010, + ], + 'test_searchable_attribute' => '', + ], + false + ], + ]; + } } From 89d499d1ebd56f40ccbbdbfd2f039e61fd648d2a Mon Sep 17 00:00:00 2001 From: npuchko <npuchko@adobe.com> Date: Mon, 8 May 2023 18:31:18 -0700 Subject: [PATCH 24/41] ACP2E-1932: Cannot save cart price rule with module Magento_OfflineShipping disabled --- .../Controller/Adminhtml/Promo/Quote/Save.php | 2 +- .../Adminhtml/Promo/Quote/SaveTest.php | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/SaveTest.php diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php index a0d3977ba83d..1c49b16161a5 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php @@ -64,7 +64,7 @@ public function execute() { $data = $this->getRequest()->getPostValue(); if ($data) { - $data['simple_free_shipping'] = ($data['simple_free_shipping'] === '') + $data['simple_free_shipping'] = (($data['simple_free_shipping'] ?? '') === '') ? null : $data['simple_free_shipping']; try { diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/SaveTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/SaveTest.php new file mode 100644 index 000000000000..a840e9680935 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/SaveTest.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; + +use Magento\Framework\App\Request\Http; +use Magento\Framework\Message\MessageInterface; +use Magento\SalesRule\Model\ResourceModel\Rule\Collection; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Test class for \Magento\SalesRule\Controller\Adminhtml\Promo\Quote\Save + * + * @magentoAppArea adminhtml + */ +class SaveTest extends AbstractBackendController +{ + public function testCreateRuleWithOnlyFormkey(): void + { + $requestData = []; + $this->getRequest()->setMethod(Http::METHOD_POST); + $this->getRequest()->setPostValue($requestData); + + $this->dispatch('backend/sales_rule/promo_quote/save'); + $this->assertSessionMessages( + self::equalTo(['You saved the rule.']), + MessageInterface::TYPE_SUCCESS + ); + } + + public function testCreateRuleWithFreeShipping(): void + { + $ruleCollection = Bootstrap::getObjectManager()->create(Collection::class); + $resource = $ruleCollection->getResource(); + $select = $resource->getConnection()->select(); + $select->from($resource->getTable('salesrule'), [new \Zend_Db_Expr('MAX(rule_id)')]); + $maxId = (int)$resource->getConnection()->fetchOne($select); + + $requestData = [ + 'simple_free_shipping' => 1, + ]; + $this->getRequest()->setMethod(Http::METHOD_POST); + $this->getRequest()->setPostValue($requestData); + + $this->dispatch('backend/sales_rule/promo_quote/save'); + $this->assertSessionMessages( + self::equalTo(['You saved the rule.']), + MessageInterface::TYPE_SUCCESS + ); + + $select = $resource->getConnection()->select(); + $select + ->from($resource->getTable('salesrule'), ['simple_free_shipping']) + ->where('rule_id > ?', $maxId); + $simpleFreeShipping = (int)$resource->getConnection()->fetchOne($select); + + $this->assertEquals(1, $simpleFreeShipping); + } + + public function testCreateRuleWithWrongDates(): void + { + $requestData = [ + 'from_date' => '2023-02-02', + 'to_date' => '2023-01-01', + ]; + $this->getRequest()->setMethod(Http::METHOD_POST); + $this->getRequest()->setPostValue($requestData); + + $this->dispatch('backend/sales_rule/promo_quote/save'); + $this->assertSessionMessages( + self::equalTo(['End Date must follow Start Date.']), + MessageInterface::TYPE_ERROR + ); + } +} From 76094c49c5bfb0a637a9a4ee007e45bbef8ef1db Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Wed, 10 May 2023 16:06:49 +0300 Subject: [PATCH 25/41] ACP2E-1852: fix for failing mftf test --- .../Magento/Ui/view/base/web/js/form/element/file-uploader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js index aed50cb7d5c9..988ff2ffe76f 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js @@ -79,7 +79,7 @@ define([ imageSize = this.setImageSize; _.each(value, function (val) { - if (val.type.indexOf('image') >= 0) { + if (val.type !== undefined && val.type.indexOf('image') >= 0) { imageSize(val); } }, this); From 5e9c1dbf42d7cad2b83587786df9871ec5f4e2a2 Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Thu, 11 May 2023 17:15:37 +0300 Subject: [PATCH 26/41] ACP2E-1753: cleanup on MC-6641 --- ...gularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml index 0aa6f8827e40..ce8a247b9b2e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml @@ -31,6 +31,7 @@ <after> <deleteData stepKey="deleteSimpleSubCategory" createDataKey="initialCategoryEntity"/> <deleteData stepKey="deleteSimpleSubCategory2" createDataKey="categoryEntity"/> + <deleteData stepKey="deleteInitialVirtualProduct" createDataKey="initialVirtualProduct"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> From 66d55ab470c341705693084fab6d219c9387da48 Mon Sep 17 00:00:00 2001 From: Olga Moyseyenko <moyseyen@adobe.com> Date: Thu, 11 May 2023 20:16:32 -0500 Subject: [PATCH 27/41] ACP2E-1799: 'Qty Uses Decimal' cannot be used for a Bundle product option --- .../Section/AdminProductFormBundleSection.xml | 2 + ...dminAddDecimalDefaultToBundleItemsTest.xml | 102 ++++++++++++++++++ .../Product/BundleDataProviderTest.php | 15 ++- .../Product/BundleDataProvider.php | 43 +++++++- .../AddSelectionQtyTypeToProductsData.php | 75 +++++++++++++ .../Product/Form/Modifier/BundlePanel.php | 1 + app/code/Magento/Bundle/etc/adminhtml/di.xml | 17 +++ 7 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDecimalDefaultToBundleItemsTest.xml create mode 100644 app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/AddSelectionQtyTypeToProductsData.php diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml index 8b78ac7b5fe6..295243f8a81d 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml @@ -125,5 +125,7 @@ <element name="priceType" type="select" selector="[name='product[options][0][price_type]']" /> <element name="priceTypeSelectPercent" type="select" selector="//*[@name='product[options][0][price_type]']/option[2]" /> <element name="weightFieldLabel" type="input" selector="//div[@data-index='weight']/div/label/span"/> + <!--Errors--> + <element name="fieldError" type="text" selector=".admin__field-error[data-bind='attr: {for: {{field}}}, text: error']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDecimalDefaultToBundleItemsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDecimalDefaultToBundleItemsTest.xml new file mode 100644 index 000000000000..25c94f015a10 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDecimalDefaultToBundleItemsTest.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminAddDecimalDefaultToBundleItemsTest"> + <annotations> + <features value="Bundle"/> + <stories value="Create/Edit bundle product in Admin"/> + <title value="Admin should be able to set decimal default to bundle item when item allows it"/> + <description value="Admin should be able to set decimal default value to new bundle option"/> + <severity value="AVERAGE"/> + <testCaseId value="AC-8646"/> + <useCaseId value="ACP2E-1799"/> + <group value="Bundle"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + <actionGroup stepKey="loginToAdminPanel" ref="AdminLoginActionGroup"/> + </before> + <after> + <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="clearFilters"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <!-- Open simpleProduct1 in Admin --> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="filterSimpleProduct1"> + <argument name="product" value="SimpleProduct2"/> + </actionGroup> + <click selector="{{AdminProductGridSection.productGridNameProduct('$$simpleProduct1.name$$')}}" stepKey="clickOpenProductForEdit"/> + <waitForPageLoad time="30" stepKey="waitForProductEditOpen"/> + <!-- Open *Advanced Inventory* pop-up (Click on *Advanced Inventory* link). Set *Qty Uses Decimals* to *Yes*. Click on button *Done* --> + <actionGroup ref="AdminClickOnAdvancedInventoryLinkActionGroup" stepKey="clickOnAdvancedInventoryLink"/> + <actionGroup ref="AdminSetQtyUsesDecimalsConfigActionGroup" stepKey="setQtyUsesDecimalsConfig"> + <argument name="value" value="Yes"/> + </actionGroup> + <actionGroup ref="AdminSubmitAdvancedInventoryFormActionGroup" stepKey="clickOnDoneButton"/> + <actionGroup ref="AdminProductFormSaveActionGroup" stepKey="clickOnSaveButton"/> + + <!-- Create new Bundle product --> + <actionGroup ref="AdminOpenCreateBundleProductPageActionGroup" stepKey="goToBundleProductCreationPage"/> + <actionGroup ref="AdminClickAddOptionOnBundleProductEditPageActionGroup" stepKey="clickAddOption1"/> + <actionGroup ref="AdminFillBundleOptionTitleActionGroup" stepKey="fillOptionTitle"> + <argument name="optionTitle" value="{{BundleProduct.optionTitle1}}"/> + </actionGroup> + <actionGroup ref="AdminFillBundleOptionTypeActionGroup" stepKey="selectInputType"/> + + <actionGroup ref="AdminClickAddProductToOptionByOptionIndexActionGroup" stepKey="clickAddProductsToOption"/> + <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterBundleProductOptions"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <actionGroup ref="AdminCheckFirstCheckboxInAddProductsToOptionPanelGridActionGroup" stepKey="selectFirstGridRow"/> + <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterBundleProductOptions2"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + <actionGroup ref="AdminCheckFirstCheckboxInAddProductsToOptionPanelGridActionGroup" stepKey="selectFirstGridRow2"/> + <actionGroup ref="AdminClickAddSelectedProductsOnAddProductsToOptionPanelActionGroup" stepKey="clickAddSelectedBundleProducts"/> + + <grabValueFrom selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '0')}}" stepKey="grabbedFirstBundleOptionQuantity"/> + <assertEquals stepKey="assertFirstBundleOptionDefaultQuantity"> + <expectedResult type="string">1</expectedResult> + <actualResult type="string">$grabbedFirstBundleOptionQuantity</actualResult> + </assertEquals> + <grabValueFrom selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '1')}}" stepKey="grabbedSecondBundleOptionQuantity"/> + <assertEquals stepKey="assertSecondBundleOptionDefaultQuantity"> + <expectedResult type="string">1</expectedResult> + <actualResult type="string">$grabbedSecondBundleOptionQuantity</actualResult> + </assertEquals> + + <!-- Fill first selection with decimal value --> + <actionGroup ref="AdminFillBundleItemQtyActionGroup" stepKey="fillProduct1DefaultQty"> + <argument name="optionIndex" value="0"/> + <argument name="productIndex" value="0"/> + <argument name="qty" value="2.56"/> + </actionGroup> + + <!-- Check there is no error message for the slection with allowed decimal value --> + <dontSee selector="{{AdminProductFormBundleSection.fieldError('uid')}}" userInput="Please enter a valid number in this field." stepKey="doNotSeeErrorMessageForProduct1"/> + + <!-- Fill second selection with decimal value --> + <actionGroup ref="AdminFillBundleItemQtyActionGroup" stepKey="fillProduct2DefaultQty"> + <argument name="optionIndex" value="0"/> + <argument name="productIndex" value="1"/> + <argument name="qty" value="2.56"/> + </actionGroup> + + <!-- Check there is an error message for the slection with not allowed decimal value --> + <see selector="{{AdminProductFormBundleSection.fieldError('uid')}}" userInput="Please enter a valid number in this field." stepKey="seeErrorMessageForProduct2"/> + + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/BundleDataProviderTest.php b/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/BundleDataProviderTest.php index 9eb0e7aa8946..2cf0c201f120 100644 --- a/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/BundleDataProviderTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/BundleDataProviderTest.php @@ -14,12 +14,13 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Model\Store; +use Magento\Ui\DataProvider\Modifier\PoolInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class BundleDataProviderTest extends TestCase { - const ALLOWED_TYPE = 'simple'; + private const ALLOWED_TYPE = 'simple'; /** * @var ObjectManager @@ -46,6 +47,11 @@ class BundleDataProviderTest extends TestCase */ protected $dataHelperMock; + /** + * @var PoolInterface|MockObject + */ + private $modifierPool; + /** * @return void */ @@ -53,6 +59,9 @@ protected function setUp(): void { $this->objectManager = new ObjectManager($this); + $this->modifierPool = $this->getMockBuilder(PoolInterface::class) + ->getMockForAbstractClass(); + $this->requestMock = $this->getMockBuilder(RequestInterface::class) ->getMockForAbstractClass(); $this->collectionMock = $this->getMockBuilder(Collection::class) @@ -97,6 +106,7 @@ protected function getModel() 'addFilterStrategies' => [], 'meta' => [], 'data' => [], + 'modifiersPool' => $this->modifierPool, ]); } @@ -128,6 +138,9 @@ public function testGetData() $this->collectionMock->expects($this->once()) ->method('getSize') ->willReturn(count($items)); + $this->modifierPool->expects($this->once()) + ->method('getModifiersInstances') + ->willReturn([]); $this->assertEquals($expectedData, $this->getModel()->getData()); } diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/BundleDataProvider.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/BundleDataProvider.php index 5f1ffc3c2682..acc484b12ce3 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/BundleDataProvider.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/BundleDataProvider.php @@ -8,6 +8,9 @@ use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\Catalog\Ui\DataProvider\Product\ProductDataProvider; use Magento\Bundle\Helper\Data; +use Magento\Framework\App\ObjectManager; +use Magento\Ui\DataProvider\Modifier\ModifierInterface; +use Magento\Ui\DataProvider\Modifier\PoolInterface; class BundleDataProvider extends ProductDataProvider { @@ -16,6 +19,11 @@ class BundleDataProvider extends ProductDataProvider */ protected $dataHelper; + /** + * @var PoolInterface + */ + private $modifiersPool; + /** * Construct * @@ -24,10 +32,12 @@ class BundleDataProvider extends ProductDataProvider * @param string $requestFieldName * @param CollectionFactory $collectionFactory * @param Data $dataHelper - * @param \Magento\Ui\DataProvider\AddFieldToCollectionInterface[] $addFieldStrategies - * @param \Magento\Ui\DataProvider\AddFilterToCollectionInterface[] $addFilterStrategies * @param array $meta * @param array $data + * @param \Magento\Ui\DataProvider\AddFieldToCollectionInterface[] $addFieldStrategies + * @param \Magento\Ui\DataProvider\AddFilterToCollectionInterface[] $addFilterStrategies + * @param PoolInterface|null $modifiersPool + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( $name, @@ -38,7 +48,8 @@ public function __construct( array $meta = [], array $data = [], array $addFieldStrategies = [], - array $addFilterStrategies = [] + array $addFilterStrategies = [], + PoolInterface $modifiersPool = null ) { parent::__construct( $name, @@ -52,6 +63,7 @@ public function __construct( ); $this->dataHelper = $dataHelper; + $this->modifiersPool = $modifiersPool ?: ObjectManager::getInstance()->get(PoolInterface::class); } /** @@ -72,11 +84,34 @@ public function getData() ); $this->getCollection()->load(); } + $items = $this->getCollection()->toArray(); - return [ + $data = [ 'totalRecords' => $this->getCollection()->getSize(), 'items' => array_values($items), ]; + + /** @var ModifierInterface $modifier */ + foreach ($this->modifiersPool->getModifiersInstances() as $modifier) { + $data = $modifier->modifyData($data); + } + + return $data; + } + + /** + * {@inheritdoc} + */ + public function getMeta() + { + $meta = parent::getMeta(); + + /** @var ModifierInterface $modifier */ + foreach ($this->modifiersPool->getModifiersInstances() as $modifier) { + $meta = $modifier->modifyMeta($meta); + } + + return $meta; } } diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/AddSelectionQtyTypeToProductsData.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/AddSelectionQtyTypeToProductsData.php new file mode 100644 index 000000000000..27dbd5606c31 --- /dev/null +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/AddSelectionQtyTypeToProductsData.php @@ -0,0 +1,75 @@ +<?php + +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Ui\DataProvider\Product\Form\Modifier; + +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Ui\DataProvider\Modifier\ModifierInterface; +use Magento\CatalogInventory\Model\StockRegistryPreloader; + +/** + * Affects Qty field for newly added selection + */ +class AddSelectionQtyTypeToProductsData implements ModifierInterface +{ + /** + * @var StockRegistryPreloader + */ + private StockRegistryPreloader $stockRegistryPreloader; + + /** + * Construct + * + */ + public function __construct() + { + $this->stockRegistryPreloader = ObjectManager::getInstance()->get(StockRegistryPreloader::class); + } + + /** + * Modify Meta + * + * @param array $meta + * @return array + */ + public function modifyMeta(array $meta) + { + return $meta; + } + + /** + * Modify Data - checks if new selection can have decimal quantity + * + * @param array $data + * @return array + * @throws NoSuchEntityException + */ + public function modifyData(array $data): array + { + $productIds = array_column($data['items'], 'entity_id'); + + $stockItems = []; + if ($productIds) { + $stockItems = $this->stockRegistryPreloader->preloadStockItems($productIds); + } + + $isQtyDecimals = []; + foreach ($stockItems as $stockItem) { + $isQtyDecimals[$stockItem->getProductId()] = $stockItem->getIsQtyDecimal(); + } + + foreach ($data['items'] as &$item) { + if ($isQtyDecimals[$item['entity_id']]) { + $item['selection_qty_is_integer'] = false; + } + } + + return $data; + } +} diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php index 4e2f17fa46d4..7b1c254eae6f 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php @@ -403,6 +403,7 @@ protected function getBundleOptions() 'selection_price_type' => '', 'selection_price_value' => '', 'selection_qty' => '', + 'selection_qty_is_integer'=> 'selection_qty_is_integer', ], 'links' => [ 'insertData' => '${ $.provider }:${ $.dataProvider }', diff --git a/app/code/Magento/Bundle/etc/adminhtml/di.xml b/app/code/Magento/Bundle/etc/adminhtml/di.xml index f173bb26fcc3..4f3069dee65b 100644 --- a/app/code/Magento/Bundle/etc/adminhtml/di.xml +++ b/app/code/Magento/Bundle/etc/adminhtml/di.xml @@ -76,4 +76,21 @@ </argument> </arguments> </type> + <virtualType name="Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\ModifiersPool" type="Magento\Ui\DataProvider\Modifier\Pool"> + <arguments> + <argument name="modifiers" xsi:type="array"> + <item name="add_selection_qty_type_to_products_data" xsi:type="array"> + <item name="class" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\AddSelectionQtyTypeToProductsData</item> + <item name="sortOrder" xsi:type="number">200</item> + </item> + </argument> + </arguments> + </virtualType> + <type name="Magento\Bundle\Ui\DataProvider\Product\BundleDataProvider"> + <arguments> + <argument name="modifiersPool" xsi:type="object"> + Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\ModifiersPool + </argument> + </arguments> + </type> </config> From fb172b38c3afd27681b9c96e0e9eee2845efaa1a Mon Sep 17 00:00:00 2001 From: Olga Moyseyenko <moyseyen@adobe.com> Date: Thu, 11 May 2023 22:45:17 -0500 Subject: [PATCH 28/41] ACP2E-1799: 'Qty Uses Decimal' cannot be used for a Bundle product option --- .../Bundle/Ui/DataProvider/Product/BundleDataProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/BundleDataProvider.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/BundleDataProvider.php index acc484b12ce3..827082dc7744 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/BundleDataProvider.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/BundleDataProvider.php @@ -101,7 +101,7 @@ public function getData() } /** - * {@inheritdoc} + * @inheritdoc */ public function getMeta() { From 24735fcb3c36cda355cab3efd324d5b43eb5bc0d Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Fri, 12 May 2023 12:35:05 +0300 Subject: [PATCH 29/41] ACP2E-1753: cleanup on MC-6641 --- ...arPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml index ce8a247b9b2e..7c066343ee7f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml @@ -29,9 +29,11 @@ <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> </before> <after> + <actionGroup ref="AdminProductCatalogPageOpenActionGroup" stepKey="openProductCatalogPage1"/> + <actionGroup ref="AdminDeleteAllProductsFromGridActionGroup" stepKey="selectAndDeleteProducts"/> + <actionGroup ref="ClearFiltersAdminProductGridActionGroup" stepKey="clearFilterFromProductIndex"/> <deleteData stepKey="deleteSimpleSubCategory" createDataKey="initialCategoryEntity"/> <deleteData stepKey="deleteSimpleSubCategory2" createDataKey="categoryEntity"/> - <deleteData stepKey="deleteInitialVirtualProduct" createDataKey="initialVirtualProduct"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> From b538a51b22d4abf2af7068b965ce98541f3b3dcb Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Fri, 12 May 2023 13:54:55 +0300 Subject: [PATCH 30/41] ACP2E-1753: cleanup on MC-14271 --- .../Test/Mftf/ActionGroup/AdminDeleteNewUserActionGroup.xml | 2 +- .../Test/Mftf/Test/AdminDeleteOwnAdminUserAccountTest.xml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteNewUserActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteNewUserActionGroup.xml index a4e5492f8e3e..a1410213daa1 100644 --- a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteNewUserActionGroup.xml +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteNewUserActionGroup.xml @@ -16,7 +16,7 @@ <argument name="userName" type="string" defaultValue="John"/> </arguments> <click stepKey="clickOnUser" selector="{{AdminDeleteUserSection.theUser(userName)}}"/> - <fillField stepKey="typeCurrentPassword" selector="{{AdminDeleteUserSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + <fillField stepKey="typeCurrentPassword" selector="{{AdminDeleteUserSection.password}}" userInput="{{_CREDS.magento/MAGENTO_ADMIN_PASSWORD}}"/> <scrollToTopOfPage stepKey="scrollToTop"/> <click stepKey="clickToDeleteUser" selector="{{AdminDeleteUserSection.delete}}"/> <waitForPageLoad stepKey="waitForDeletePopupOpen" time="5"/> diff --git a/app/code/Magento/User/Test/Mftf/Test/AdminDeleteOwnAdminUserAccountTest.xml b/app/code/Magento/User/Test/Mftf/Test/AdminDeleteOwnAdminUserAccountTest.xml index 13f85157ead0..294a6e337f31 100644 --- a/app/code/Magento/User/Test/Mftf/Test/AdminDeleteOwnAdminUserAccountTest.xml +++ b/app/code/Magento/User/Test/Mftf/Test/AdminDeleteOwnAdminUserAccountTest.xml @@ -35,8 +35,9 @@ <after> <!-- Delete New Admin User --> <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAdmin"/> - <actionGroup ref="AdminDeleteUserViaCurlActionGroup" stepKey="deleteUser"> - <argument name="user" value="$$user$$" /> + <actionGroup ref="AdminOpenAdminUsersPageActionGroup" stepKey="goToAllUsersPage"/> + <actionGroup ref="AdminDeleteNewUserActionGroup" stepKey="deleteUser"> + <argument name="userName" value="$$user.username$$"/> </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logOut"/> </after> From f889095afcd7b5ec64a39bee7731f2c57fcd6457 Mon Sep 17 00:00:00 2001 From: Olga Moyseyenko <moyseyen@adobe.com> Date: Fri, 12 May 2023 12:08:25 -0500 Subject: [PATCH 31/41] ACP2E-1799: 'Qty Uses Decimal' cannot be used for a Bundle product option --- .../Modifier/AddSelectionQtyTypeToProductsData.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/AddSelectionQtyTypeToProductsData.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/AddSelectionQtyTypeToProductsData.php index 27dbd5606c31..a2170aa30f8d 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/AddSelectionQtyTypeToProductsData.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/AddSelectionQtyTypeToProductsData.php @@ -24,12 +24,13 @@ class AddSelectionQtyTypeToProductsData implements ModifierInterface private StockRegistryPreloader $stockRegistryPreloader; /** - * Construct + * Initializes dependencies * + * @param StockRegistryPreloader $stockRegistryPreloader */ - public function __construct() + public function __construct(StockRegistryPreloader $stockRegistryPreloader) { - $this->stockRegistryPreloader = ObjectManager::getInstance()->get(StockRegistryPreloader::class); + $this->stockRegistryPreloader = $stockRegistryPreloader; } /** @@ -63,10 +64,10 @@ public function modifyData(array $data): array foreach ($stockItems as $stockItem) { $isQtyDecimals[$stockItem->getProductId()] = $stockItem->getIsQtyDecimal(); } - + foreach ($data['items'] as &$item) { - if ($isQtyDecimals[$item['entity_id']]) { - $item['selection_qty_is_integer'] = false; + if (isset($isQtyDecimals[$item['entity_id']])) { + $item['selection_qty_is_integer'] = !$isQtyDecimals[$item['entity_id']]; } } From de39d222b86fc52c03127e67856e46ca580df3b4 Mon Sep 17 00:00:00 2001 From: Arnob Saha <arnobsh@gmail.com> Date: Tue, 9 May 2023 17:01:22 -0500 Subject: [PATCH 32/41] ACP2E-1916: Unable to sort catalog by Custom Attribute of type Dropdown - with test --- .../BatchDataMapper/ProductDataMapper.php | 2 + .../BatchDataMapper/ProductDataMapperTest.php | 51 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php index a9fb67f209aa..c1772086d7ba 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php @@ -250,6 +250,7 @@ private function convertAttribute(Attribute $attribute, array $attributeValues, * - "Visible in Advanced Search" (is_visible_in_advanced_search) * - "Use in Layered Navigation" (is_filterable) * - "Use in Search Results Layered Navigation" (is_filterable_in_search) + * - "Use in Sorting in Product Listing" (used_for_sort_by) * * @param Attribute $attribute * @return bool @@ -261,6 +262,7 @@ private function isAttributeLabelsShouldBeMapped(Attribute $attribute): bool || $attribute->getIsVisibleInAdvancedSearch() || $attribute->getIsFilterable() || $attribute->getIsFilterableInSearch() + || $attribute->getUsedForSortBy() ); } diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php index 9f1b59b1bfc8..354ad01f14a6 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php @@ -373,6 +373,57 @@ public static function mapProvider(): array [10 => '44', 11 => '45'], ['color' => [44, 45], 'color_value' => ['red', 'black']], ], + 'select with options with sort by and filterable' => [ + 10, + [ + 'attribute_code' => 'color', + 'backend_type' => 'text', + 'frontend_input' => 'select', + 'is_searchable' => true, + 'used_for_sort_by' => true, + 'is_filterable_in_grid' => true, + 'options' => [ + ['value' => '44', 'label' => 'red'], + ['value' => '45', 'label' => 'black'], + ], + ], + [10 => '44', 11 => '45'], + ['color' => [44, 45], 'color_value' => ['red', 'black']], + ], + 'unsearchable select with options with sort by and filterable' => [ + 10, + [ + 'attribute_code' => 'color', + 'backend_type' => 'text', + 'frontend_input' => 'select', + 'is_searchable' => false, + 'used_for_sort_by' => false, + 'is_filterable_in_grid' => false, + 'options' => [ + ['value' => '44', 'label' => 'red'], + ['value' => '45', 'label' => 'black'], + ], + ], + '44', + ['color' => 44], + ], + 'select with options with sort by only' => [ + 10, + [ + 'attribute_code' => 'color', + 'backend_type' => 'text', + 'frontend_input' => 'select', + 'is_searchable' => false, + 'used_for_sort_by' => true, + 'is_filterable_in_grid' => false, + 'options' => [ + ['value' => '44', 'label' => 'red'], + ['value' => '45', 'label' => 'black'], + ], + ], + [10 => '44', 11 => '45'], + ['color' => [44, 45], 'color_value' => ['red', 'black']], + ], 'multiselect without options' => [ 10, [ From 4cd9d138b311ed5b9e4c72cd48fde1f6b0f22a35 Mon Sep 17 00:00:00 2001 From: npuchko <npuchko@adobe.com> Date: Fri, 28 Apr 2023 11:45:31 -0700 Subject: [PATCH 33/41] ACP2E-1862: File uploaded during a company registration is not displayed later --- app/code/Magento/Customer/Model/Metadata/Form/File.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Customer/Model/Metadata/Form/File.php b/app/code/Magento/Customer/Model/Metadata/Form/File.php index 54b8b75c9ca3..05788dcaf763 100644 --- a/app/code/Magento/Customer/Model/Metadata/Form/File.php +++ b/app/code/Magento/Customer/Model/Metadata/Form/File.php @@ -23,6 +23,8 @@ */ class File extends AbstractData { + public const UPLOADED_FILE_SUFFIX = '_uploaded'; + /** * Validator for check not protected extensions * @@ -59,7 +61,8 @@ class File extends AbstractData /** * @var FileProcessorFactory - * @deprecated 101.0.0 + * @deprecated 101.0.0 Call fileProcessor directly from code + * @see $this->fileProcessor */ protected $fileProcessorFactory; @@ -126,7 +129,7 @@ public function extractValue(\Magento\Framework\App\RequestInterface $request) $attrCode = $this->getAttribute()->getAttributeCode(); // phpcs:disable Magento2.Security.Superglobal - $uploadedFile = $request->getParam($attrCode . '_uploaded'); + $uploadedFile = $request->getParam($attrCode . static::UPLOADED_FILE_SUFFIX); if ($uploadedFile) { $value = $uploadedFile; } elseif ($this->_requestScope || !isset($_FILES[$attrCode])) { @@ -424,7 +427,8 @@ public function outputValue($format = \Magento\Customer\Model\Metadata\ElementFa * Get file processor * * @return FileProcessor - * @deprecated 100.1.3 + * @deprecated 100.1.3 we don’t use such approach anymore. Call fileProcessor directly + * @see $this->fileProcessor */ protected function getFileProcessor() { From d09224aba48e16d4c97c85bb66c1264d48be1a8e Mon Sep 17 00:00:00 2001 From: Anna Bukatar <abukatar@magento.com> Date: Fri, 12 May 2023 19:16:07 -0700 Subject: [PATCH 34/41] ACP2E-1834: Some catalog rules are not correctly indexed when those are enabled by schedule update --- app/code/Magento/Indexer/Model/Indexer.php | 6 ++++-- app/code/Magento/Indexer/Model/Processor.php | 10 ++++++---- .../Indexer/Test/Unit/Model/IndexerTest.php | 17 ++++++++++++----- .../Indexer/Test/Unit/Model/ProcessorTest.php | 17 ++++++++--------- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/app/code/Magento/Indexer/Model/Indexer.php b/app/code/Magento/Indexer/Model/Indexer.php index ac8b9590e58f..7be1d5a3a9e2 100644 --- a/app/code/Magento/Indexer/Model/Indexer.php +++ b/app/code/Magento/Indexer/Model/Indexer.php @@ -441,8 +441,10 @@ public function reindexAll() } try { $this->getActionInstance()->executeFull(); - $state->setStatus(StateInterface::STATUS_VALID); - $state->save(); + if ($this->workingStateProvider->isWorking($this->getId())) { + $state->setStatus(StateInterface::STATUS_VALID); + $state->save(); + } if (!empty($sharedIndexers)) { $this->resumeSharedViews($sharedIndexers); } diff --git a/app/code/Magento/Indexer/Model/Processor.php b/app/code/Magento/Indexer/Model/Processor.php index 78b8fa070b15..7846421daa70 100644 --- a/app/code/Magento/Indexer/Model/Processor.php +++ b/app/code/Magento/Indexer/Model/Processor.php @@ -59,7 +59,7 @@ public function __construct( IndexerInterfaceFactory $indexerFactory, Indexer\CollectionFactory $indexersFactory, ProcessorInterface $mviewProcessor, - MakeSharedIndexValid $makeSharedValid = null + ?MakeSharedIndexValid $makeSharedValid = null ) { $this->config = $config; $this->indexerFactory = $indexerFactory; @@ -86,9 +86,11 @@ public function reindexAllInvalid() $sharedIndex = $indexerConfig['shared_index'] ?? null; if (!in_array($sharedIndex, $this->sharedIndexesComplete)) { $indexer->reindexAll(); - - if (!empty($sharedIndex) && $this->makeSharedValid->execute($sharedIndex)) { - $this->sharedIndexesComplete[] = $sharedIndex; + $indexer->load($indexer->getId()); + if ($indexer->isValid()) { + if (!empty($sharedIndex) && $this->makeSharedValid->execute($sharedIndex)) { + $this->sharedIndexesComplete[] = $sharedIndex; + } } } } diff --git a/app/code/Magento/Indexer/Test/Unit/Model/IndexerTest.php b/app/code/Magento/Indexer/Test/Unit/Model/IndexerTest.php index bcdfbea78b0b..451d4c211ead 100644 --- a/app/code/Magento/Indexer/Test/Unit/Model/IndexerTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Model/IndexerTest.php @@ -144,7 +144,8 @@ public function testLoadWithException() public function testGetView() { $indexId = 'indexer_internal_name'; - $this->viewMock->expects($this->once())->method('load')->with('view_test')->willReturnSelf(); + $this->viewMock->expects($this->once()) + ->method('load')->with('view_test')->willReturnSelf(); $this->loadIndexer($indexId); $this->assertEquals($this->viewMock, $this->model->getView()); @@ -224,11 +225,14 @@ public function testReindexAll() $indexId = 'indexer_internal_name'; $this->loadIndexer($indexId); + $this->workingStateProvider->method('isWorking')->willReturnOnConsecutiveCalls(false, true); + $stateMock = $this->createPartialMock( State::class, ['load', 'getId', 'setIndexerId', '__wakeup', 'getStatus', 'setStatus', 'save'] ); - $stateMock->expects($this->once())->method('load')->with($indexId, 'indexer_id')->willReturnSelf(); + $stateMock->expects($this->once()) + ->method('load')->with($indexId, 'indexer_id')->willReturnSelf(); $stateMock->expects($this->never())->method('setIndexerId'); $stateMock->expects($this->once())->method('getId')->willReturn(1); $stateMock->expects($this->exactly(2))->method('setStatus')->willReturnSelf(); @@ -268,7 +272,8 @@ public function testReindexAllWithException() State::class, ['load', 'getId', 'setIndexerId', '__wakeup', 'getStatus', 'setStatus', 'save'] ); - $stateMock->expects($this->once())->method('load')->with($indexId, 'indexer_id')->willReturnSelf(); + $stateMock->expects($this->once()) + ->method('load')->with($indexId, 'indexer_id')->willReturnSelf(); $stateMock->expects($this->never())->method('setIndexerId'); $stateMock->expects($this->once())->method('getId')->willReturn(1); $stateMock->expects($this->exactly(2))->method('setStatus')->willReturnSelf(); @@ -313,7 +318,8 @@ public function testReindexAllWithError() State::class, ['load', 'getId', 'setIndexerId', '__wakeup', 'getStatus', 'setStatus', 'save'] ); - $stateMock->expects($this->once())->method('load')->with($indexId, 'indexer_id')->willReturnSelf(); + $stateMock->expects($this->once()) + ->method('load')->with($indexId, 'indexer_id')->willReturnSelf(); $stateMock->expects($this->never())->method('setIndexerId'); $stateMock->expects($this->once())->method('getId')->willReturn(1); $stateMock->expects($this->exactly(2))->method('setStatus')->willReturnSelf(); @@ -483,7 +489,8 @@ public function testInvalidate() ); $this->stateFactoryMock->expects($this->once())->method('create')->willReturn($stateMock); - $stateMock->expects($this->once())->method('setStatus')->with(StateInterface::STATUS_INVALID)->willReturnSelf(); + $stateMock->expects($this->once()) + ->method('setStatus')->with(StateInterface::STATUS_INVALID)->willReturnSelf(); $stateMock->expects($this->once())->method('save')->willReturnSelf(); $this->model->invalidate(); } diff --git a/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php b/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php index b0a339551955..ba6216f37f7d 100644 --- a/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php @@ -102,18 +102,14 @@ public function testReindexAllInvalid(): void $this->configMock->expects($this->once())->method('getIndexers')->willReturn($indexers); $state1Mock = $this->createPartialMock(State::class, ['getStatus', '__wakeup']); - $state1Mock->expects( - $this->once() - )->method( - 'getStatus' - )->willReturn( - StateInterface::STATUS_INVALID - ); + $state1Mock->expects($this->exactly(2)) + ->method('getStatus') + ->willReturnOnConsecutiveCalls(StateInterface::STATUS_INVALID, StateInterface::STATUS_VALID); $indexer1Mock = $this->createPartialMock( Indexer::class, ['load', 'getState', 'reindexAll'] ); - $indexer1Mock->expects($this->once())->method('getState')->willReturn($state1Mock); + $indexer1Mock->expects($this->exactly(2))->method('getState')->willReturn($state1Mock); $indexer1Mock->expects($this->once())->method('reindexAll'); $state2Mock = $this->createPartialMock(State::class, ['getStatus', '__wakeup']); @@ -169,7 +165,10 @@ function ($elem) { $stateMock = $this->createPartialMock(State::class, ['getStatus', '__wakeup']); $stateMock->expects($this->any()) ->method('getStatus') - ->willReturn($indexerStates[$indexerData['indexer_id']]); + ->willReturnOnConsecutiveCalls( + $indexerStates[$indexerData['indexer_id']], + StateInterface::STATUS_VALID + ); $indexerMock = $this->createPartialMock(Indexer::class, ['load', 'getState', 'reindexAll']); $indexerMock->expects($this->any())->method('getState')->willReturn($stateMock); $indexerMock->expects($expectedReindexAllCalls[$indexerData['indexer_id']])->method('reindexAll'); From ea20244f2860187377295178118466abc76554de Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Thu, 18 May 2023 15:04:03 +0300 Subject: [PATCH 35/41] ACP2E-1964: fix CVV config validation; unit test --- .../Payflow/Service/Response/Validator/CVV2Match.php | 2 +- .../Payflow/Service/Response/Validator/CVV2MatchTest.php | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Response/Validator/CVV2Match.php b/app/code/Magento/Paypal/Model/Payflow/Service/Response/Validator/CVV2Match.php index 705b667ab2f6..0505c04bed97 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Service/Response/Validator/CVV2Match.php +++ b/app/code/Magento/Paypal/Model/Payflow/Service/Response/Validator/CVV2Match.php @@ -55,7 +55,7 @@ class CVV2Match implements ValidatorInterface */ public function validate(DataObject $response, Transparent $transparentModel) { - if ($transparentModel->getConfig()->getValue(static::CONFIG_NAME) === static::CONFIG_OFF) { + if ((int)$transparentModel->getConfig()->getValue(static::CONFIG_NAME) === static::CONFIG_OFF) { return true; } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/Response/Validator/CVV2MatchTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/Response/Validator/CVV2MatchTest.php index affb335491c5..b2179fb32fe5 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/Response/Validator/CVV2MatchTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/Response/Validator/CVV2MatchTest.php @@ -137,6 +137,15 @@ public function validationDataProvider() 'response' => new DataObject(), 'configValue' => '1', ], + [ + 'expectedResult' => true, + 'response' => new DataObject( + [ + 'cvv2match' => 'N', + ] + ), + 'configValue' => '0', + ], ]; } } From c1771062c1c9ccab8be23331b524616cc9ed7abc Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Thu, 18 May 2023 17:16:03 +0300 Subject: [PATCH 36/41] ACP2E-1964: fix static errors --- .../Service/Response/Validator/CVV2Match.php | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Response/Validator/CVV2Match.php b/app/code/Magento/Paypal/Model/Payflow/Service/Response/Validator/CVV2Match.php index 0505c04bed97..53c4a4e08318 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Service/Response/Validator/CVV2Match.php +++ b/app/code/Magento/Paypal/Model/Payflow/Service/Response/Validator/CVV2Match.php @@ -9,41 +9,38 @@ use Magento\Paypal\Model\Payflow\Service\Response\ValidatorInterface; use Magento\Paypal\Model\Payflow\Transparent; -/** - * Class CVV2Match - */ class CVV2Match implements ValidatorInterface { /** * Result of the card security code (CVV2) check */ - const CVV2MATCH = 'cvv2match'; + public const CVV2MATCH = 'cvv2match'; /** * This field returns the transaction amount, or if performing a partial authorization, * the amount approved for the partial authorization. */ - const AMT = 'amt'; + public const AMT = 'amt'; /** * Message if validation fail */ - const ERROR_MESSAGE = 'Card security code does not match.'; + public const ERROR_MESSAGE = 'Card security code does not match.'; /**#@+ Values of the response */ - const RESPONSE_YES = 'y'; + public const RESPONSE_YES = 'y'; - const RESPONSE_NO = 'n'; + public const RESPONSE_NO = 'n'; - const RESPONSE_NOT_SUPPORTED = 'x'; + public const RESPONSE_NOT_SUPPORTED = 'x'; /**#@-*/ /**#@+ Validation settings payments */ - const CONFIG_ON = 1; + public const CONFIG_ON = 1; - const CONFIG_OFF = 0; + public const CONFIG_OFF = 0; - const CONFIG_NAME = 'avs_security_code'; + public const CONFIG_NAME = 'avs_security_code'; /**#@-*/ /** From 1ba3de36598280e9add6bd163b5be4fd1bbd8a4b Mon Sep 17 00:00:00 2001 From: Anna Bukatar <abukatar@magento.com> Date: Thu, 18 May 2023 16:28:54 -0700 Subject: [PATCH 37/41] ACP2E-1943: Order item status set to "Backordered" instead of "Ordered" --- .../QuantityValidator/Initializer/StockItem.php | 7 ++----- .../Initializer/StockItemTest.php | 14 ++++---------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/StockItem.php b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/StockItem.php index f104552b4e0f..ea4c35de053b 100644 --- a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/StockItem.php +++ b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/StockItem.php @@ -35,6 +35,8 @@ class StockItem /** * @var StockStateProviderInterface + * @deprecated + * @see was overriding ItemBackorders value with the Default Scope value; caused discrepancy in multistock config */ private $stockStateProvider; @@ -122,11 +124,6 @@ public function initialize( $quoteItem->setHasError(true); } - /* We need to ensure that any possible plugin will not erase the data */ - $backOrdersQty = $this->stockStateProvider->checkQuoteItemQty($stockItem, $rowQty, $qtyForCheck, $qty) - ->getItemBackorders(); - $result->setItemBackorders($backOrdersQty); - if ($stockItem->hasIsChildItem()) { $stockItem->unsIsChildItem(); } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Quote/Item/QuantityValidator/Initializer/StockItemTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Quote/Item/QuantityValidator/Initializer/StockItemTest.php index 24f46c2414f3..9591b84b4c8d 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Quote/Item/QuantityValidator/Initializer/StockItemTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Quote/Item/QuantityValidator/Initializer/StockItemTest.php @@ -173,10 +173,7 @@ public function testInitializeWithSubitem() ->method('checkQuoteItemQty') ->withAnyParameters() ->willReturn($result); - $this->stockStateProviderMock->expects($this->once()) - ->method('checkQuoteItemQty') - ->withAnyParameters() - ->willReturn($result); + $this->stockStateProviderMock->expects($this->never())->method('checkQuoteItemQty'); $product->expects($this->once()) ->method('getCustomOption') ->with('product_type') @@ -213,7 +210,7 @@ public function testInitializeWithSubitem() $quoteItem->expects($this->once())->method('setUseOldQty')->with('item')->willReturnSelf(); $result->expects($this->exactly(2))->method('getMessage')->willReturn('message'); $quoteItem->expects($this->once())->method('setMessage')->with('message')->willReturnSelf(); - $result->expects($this->exactly(3))->method('getItemBackorders')->willReturn('backorders'); + $result->expects($this->exactly(2))->method('getItemBackorders')->willReturn('backorders'); $quoteItem->expects($this->once())->method('setBackorders')->with('backorders')->willReturnSelf(); $quoteItem->expects($this->once())->method('setStockStateResult')->with($result)->willReturnSelf(); @@ -276,10 +273,7 @@ public function testInitializeWithoutSubitem() ->method('checkQuoteItemQty') ->withAnyParameters() ->willReturn($result); - $this->stockStateProviderMock->expects($this->once()) - ->method('checkQuoteItemQty') - ->withAnyParameters() - ->willReturn($result); + $this->stockStateProviderMock->expects($this->never())->method('checkQuoteItemQty'); $product->expects($this->once()) ->method('getCustomOption') ->with('product_type') @@ -299,7 +293,7 @@ public function testInitializeWithoutSubitem() $result->expects($this->once())->method('getHasQtyOptionUpdate')->willReturn(false); $result->expects($this->once())->method('getItemUseOldQty')->willReturn(null); $result->expects($this->once())->method('getMessage')->willReturn(null); - $result->expects($this->exactly(2))->method('getItemBackorders')->willReturn(null); + $result->expects($this->exactly(1))->method('getItemBackorders')->willReturn(null); $this->model->initialize($stockItem, $quoteItem, $qty); } From 645050142df4f29a5bb6024daf2b1af10021e963 Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Fri, 19 May 2023 14:24:06 +0300 Subject: [PATCH 38/41] ACP2E-1965: load product attributes related to active sales rules only --- app/code/Magento/SalesRule/Model/ResourceModel/Rule.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php b/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php index d65021ed82a2..14506c349faa 100644 --- a/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php +++ b/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php @@ -312,6 +312,10 @@ public function getActiveAttributes() ['ea' => $this->getTable('eav_attribute')], 'ea.attribute_id = a.attribute_id', [] + )->joinInner( + ['sr' => $this->getTable('salesrule')], + 'a.' . $this->getLinkField() . ' = sr.rule_id AND sr.is_active = 1', + [] ); return $connection->fetchAll($select); } From 210a86dee8c0b1e2f0dfdee0fe45457854868fb2 Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Fri, 19 May 2023 19:19:16 +0300 Subject: [PATCH 39/41] ACP2E-1965: added integration test --- .../Model/ResourceModel/RuleTest.php | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/ResourceModel/RuleTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/ResourceModel/RuleTest.php index ccab0fc9f154..1299c5fca09f 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/ResourceModel/RuleTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/ResourceModel/RuleTest.php @@ -5,22 +5,78 @@ */ namespace Magento\SalesRule\Model\ResourceModel; +use Magento\SalesRule\Test\Fixture\ProductCondition as ProductConditionFixture; +use Magento\SalesRule\Test\Fixture\ProductFoundInCartConditions as ProductFoundInCartConditionsFixture; +use Magento\SalesRule\Test\Fixture\Rule as RuleFixture; +use Magento\TestFramework\Fixture\DataFixture; +use Magento\TestFramework\Fixture\DataFixtureStorage; +use Magento\TestFramework\Fixture\DataFixtureStorageManager; +use Magento\TestFramework\Helper\Bootstrap; + /** * @magentoDbIsolation enabled * @magentoAppIsolation enabled */ class RuleTest extends \PHPUnit\Framework\TestCase { + /** + * @var DataFixtureStorage + */ + private $fixtures; + + /** + * @var Rule + */ + private $resource; + + /** + * @inheirtDoc + */ + protected function setUp(): void + { + $this->fixtures = Bootstrap::getObjectManager()->get( + DataFixtureStorageManager::class + )->getStorage(); + $this->resource = Bootstrap::getObjectManager()->create( + Rule::class + ); + } + /** * @magentoDataFixture Magento/SalesRule/_files/rule_custom_product_attribute.php */ public function testAfterSave() { - $resource = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\SalesRule\Model\ResourceModel\Rule::class - ); - $items = $resource->getActiveAttributes(); + $items = $this->resource->getActiveAttributes(); $this->assertEquals([['attribute_code' => 'attribute_for_sales_rule_1']], $items); } + + #[ + DataFixture( + ProductConditionFixture::class, + ['attribute' => 'category_ids', 'value' => '2'], + 'cond11' + ), + DataFixture( + ProductFoundInCartConditionsFixture::class, + ['conditions' => ['$cond11$']], + 'cond1' + ), + DataFixture( + RuleFixture::class, + ['discount_amount' => 50, 'conditions' => ['$cond1$'], 'is_active' => 0], + 'rule1' + ) + ] + public function testGetActiveAttributes() + { + $rule = $this->fixtures->get('rule1'); + $items = $this->resource->getActiveAttributes(); + $this->assertEquals([], $items); + $rule->setIsActive(1); + $rule->save(); + $items = $this->resource->getActiveAttributes(); + $this->assertEquals([['attribute_code' => 'category_ids']], $items); + } } From 5a5419842873122e659fdc8fe4be389b39a71f5f Mon Sep 17 00:00:00 2001 From: Alexandra Zota <zota@adobe.com> Date: Mon, 22 May 2023 20:03:34 +0300 Subject: [PATCH 40/41] ACP2E-1965: address CR comment --- app/code/Magento/SalesRule/Model/ResourceModel/Rule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php b/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php index 14506c349faa..125e2194b45e 100644 --- a/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php +++ b/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php @@ -314,7 +314,7 @@ public function getActiveAttributes() [] )->joinInner( ['sr' => $this->getTable('salesrule')], - 'a.' . $this->getLinkField() . ' = sr.rule_id AND sr.is_active = 1', + 'a.' . $this->getLinkField() . ' = sr.' . $this->getLinkField() . ' AND sr.is_active = 1', [] ); return $connection->fetchAll($select); From 070287141a108f64e5ed749fcca3df87d8baab11 Mon Sep 17 00:00:00 2001 From: Arnob Saha <arnobsh@gmail.com> Date: Mon, 22 May 2023 13:37:08 -0500 Subject: [PATCH 41/41] ACP2E-1948: Error when accessing downloadable product in admin page. - with test --- .../Product/Form/Modifier/Data/LinksTest.php | 191 +++++++++++++++++- .../Product/Form/Modifier/Data/Links.php | 2 +- 2 files changed, 190 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Downloadable/Test/Unit/Ui/DataProvider/Product/Form/Modifier/Data/LinksTest.php b/app/code/Magento/Downloadable/Test/Unit/Ui/DataProvider/Product/Form/Modifier/Data/LinksTest.php index 8ded865057dc..10e7ddbb86c2 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Ui/DataProvider/Product/Form/Modifier/Data/LinksTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Ui/DataProvider/Product/Form/Modifier/Data/LinksTest.php @@ -9,11 +9,14 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Locator\LocatorInterface; +use Magento\Catalog\Model\Product\Type as ProductType; +use Magento\Downloadable\Api\Data\LinkInterface; use Magento\Downloadable\Helper\File as DownloadableFile; use Magento\Downloadable\Model\Link as LinkModel; use Magento\Downloadable\Model\Product\Type; use Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Data\Links; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\DataObject; use Magento\Framework\Escaper; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Framework\UrlInterface; @@ -78,11 +81,14 @@ protected function setUp(): void { $this->objectManagerHelper = new ObjectManagerHelper($this); $this->productMock = $this->getMockBuilder(ProductInterface::class) - ->setMethods(['getLinksTitle', 'getId', 'getTypeId']) + ->onlyMethods(['getId', 'getTypeId']) + ->addMethods(['getLinksTitle', 'getTypeInstance', 'getStoreId']) ->getMockForAbstractClass(); $this->locatorMock = $this->getMockForAbstractClass(LocatorInterface::class); $this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); - $this->escaperMock = $this->createMock(Escaper::class); + $this->escaperMock = $this->getMockBuilder(Escaper::class) + ->onlyMethods(['escapeHtml']) + ->getMockForAbstractClass(); $this->downloadableFileMock = $this->createMock(DownloadableFile::class); $this->urlBuilderMock = $this->getMockForAbstractClass(UrlInterface::class); $this->linkModelMock = $this->createMock(LinkModel::class); @@ -100,6 +106,8 @@ protected function setUp(): void } /** + * Test case for getLinksTitle + * * @param int|null $id * @param string $typeId * @param InvokedCount $expectedGetTitle @@ -161,4 +169,183 @@ public function getLinksTitleDataProvider() ], ]; } + + /** + * Test case for getLinksData + * + * @param $productTypeMock + * @param string $typeId + * @param int $storeId + * @param array $links + * @param array $expectedLinksData + * @return void + * @dataProvider getLinksDataProvider + */ + public function testGetLinksData( + $productTypeMock, + string $typeId, + int $storeId, + array $links, + array $expectedLinksData + ): void { + $this->locatorMock->expects($this->any()) + ->method('getProduct') + ->willReturn($this->productMock); + if (!empty($expectedLinksData)) { + $this->escaperMock->expects($this->any()) + ->method('escapeHtml') + ->willReturn($expectedLinksData['title']); + } + $this->productMock->expects($this->any()) + ->method('getTypeId') + ->willReturn($typeId); + $this->productMock->expects($this->any()) + ->method('getTypeInstance') + ->willReturn($productTypeMock); + $this->productMock->expects($this->any()) + ->method('getStoreId') + ->willReturn($storeId); + $productTypeMock->expects($this->any()) + ->method('getLinks') + ->willReturn($links); + $getLinksData = $this->links->getLinksData(); + if (!empty($getLinksData)) { + $actualResult = current($getLinksData); + } else { + $actualResult = $getLinksData; + } + $this->assertEquals($expectedLinksData, $actualResult); + } + + /** + * Get Links data provider + * + * @return array + */ + public function getLinksDataProvider() + { + $productData1 = [ + 'link_id' => '1', + 'title' => 'test', + 'price' => '0.00', + 'number_of_downloads' => '0', + 'is_shareable' => '1', + 'link_url' => 'http://cdn.sourcebooks.com/test', + 'type' => 'url', + 'sample' => + [ + 'url' => null, + 'type' => null, + ], + 'sort_order' => '1', + 'is_unlimited' => '1', + 'use_default_price' => '0', + 'use_default_title' => '0', + + ]; + $productData2 = $productData1; + unset($productData2['use_default_price']); + unset($productData2['use_default_title']); + $productData3 = [ + 'link_id' => '1', + 'title' => 'simple', + 'price' => '10.00', + 'number_of_downloads' => '0', + 'is_shareable' => '0', + 'link_url' => '', + 'type' => 'simple', + 'sample' => + [ + 'url' => null, + 'type' => null, + ], + 'sort_order' => '1', + 'is_unlimited' => '1', + 'use_default_price' => '0', + 'use_default_title' => '0', + + ]; + $linkMock1 = $this->getLinkMockObject($productData1, '1', '1'); + $linkMock2 = $this->getLinkMockObject($productData1, '0', '0'); + $linkMock3 = $this->getLinkMockObject($productData3, '0', '0'); + return [ + 'test case for downloadable product for default store' => [ + 'type' => $this->createMock(Type::class), + 'type_id' => Type::TYPE_DOWNLOADABLE, + 'store_id' => 1, + 'links' => [$linkMock1], + 'expectedLinksData' => $productData1 + ], + 'test case for downloadable product for all store' => [ + 'type' => $this->createMock(Type::class), + 'type_id' => Type::TYPE_DOWNLOADABLE, + 'store_id' => 0, + 'links' => [$linkMock2], + 'expectedLinksData' => $productData2 + ], + 'test case for simple product for default store' => [ + 'type' => $this->createMock(Type::class), + 'type_id' => ProductType::TYPE_SIMPLE, + 'store_id' => 1, + 'links' => [$linkMock3], + 'expectedLinksData' => [] + ], + ]; + } + + /** + * Data provider for getLinks + * + * @param array $productData + * @param string $useDefaultPrice + * @param string $useDefaultTitle + * @return MockObject + */ + private function getLinkMockObject( + array $productData, + string $useDefaultPrice, + string $useDefaultTitle + ): MockObject { + $linkMock = $this->getMockBuilder(LinkInterface::class) + ->onlyMethods(['getId']) + ->addMethods(['getWebsitePrice', 'getStoreTitle']) + ->getMockForAbstractClass(); + $linkMock->expects($this->any()) + ->method('getId') + ->willReturn($productData['link_id']); + $linkMock->expects($this->any()) + ->method('getTitle') + ->willReturn($productData['title']); + $linkMock->expects($this->any()) + ->method('getPrice') + ->willReturn($productData['price']); + $linkMock->expects($this->any()) + ->method('getNumberOfDownloads') + ->willReturn($productData['number_of_downloads']); + $linkMock->expects($this->any()) + ->method('getIsShareable') + ->willReturn($productData['is_shareable']); + $linkMock->expects($this->any()) + ->method('getLinkUrl') + ->willReturn($productData['link_url']); + $linkMock->expects($this->any()) + ->method('getLinkType') + ->willReturn($productData['type']); + $linkMock->expects($this->any()) + ->method('getSampleUrl') + ->willReturn($productData['sample']['url']); + $linkMock->expects($this->any()) + ->method('getSampleType') + ->willReturn($productData['sample']['type']); + $linkMock->expects($this->any()) + ->method('getSortOrder') + ->willReturn($productData['sort_order']); + $linkMock->expects($this->any()) + ->method('getWebsitePrice') + ->willReturn($useDefaultPrice); + $linkMock->expects($this->any()) + ->method('getStoreTitle') + ->willReturn($useDefaultTitle); + return $linkMock; + } } diff --git a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Data/Links.php b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Data/Links.php index 3be1094f7a4b..7c3c30482fd8 100644 --- a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Data/Links.php +++ b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Data/Links.php @@ -120,7 +120,7 @@ public function getLinksData() $linkData = []; $linkData['link_id'] = $link->getId(); $linkData['title'] = $this->escaper->escapeHtml($link->getTitle()); - $linkData['price'] = $this->getPriceValue($link->getPrice()); + $linkData['price'] = $this->getPriceValue((float) $link->getPrice()); $linkData['number_of_downloads'] = $link->getNumberOfDownloads(); $linkData['is_shareable'] = $link->getIsShareable(); $linkData['link_url'] = $link->getLinkUrl();