From dd9e24321ad1927f8aaca05ad52be2f5cc06ea1c Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Mon, 25 Sep 2023 14:33:36 +0200 Subject: [PATCH 01/16] First refactor for controller/Feed --- Controller/Index/Feed.php | 316 ++++++++++++++++++++------------------ 1 file changed, 169 insertions(+), 147 deletions(-) diff --git a/Controller/Index/Feed.php b/Controller/Index/Feed.php index 9afc598..3fb95ed 100755 --- a/Controller/Index/Feed.php +++ b/Controller/Index/Feed.php @@ -1,152 +1,123 @@ configHelper = $config; - $this->cache = $core; - $this->productModel = $product; $this->stockModel = $stockRegistryInterface; $this->imageHelper = $image; $this->storeModel = $storeManagerInterface; $this->productCollectionFactory = $productCollectionFactory; - $this->configurableType = $configurableType; - $this->curl = $curl; $this->resultFactory = $resultFactory; + $this->groupedProductModel = $groupedProductModel; + $this->configurableProductModel = $configurableProductModel; + $this->productRepositoryFactory = $productRepositoryFactory; } - private function getProductCollection() - { - $collection = $this->productCollectionFactory->create(); - /* Addtional */ - $collection - ->addMinimalPrice() - ->addFinalPrice() - ->addTaxPercents() - ->addAttributeToSelect('*') - ->addUrlRewrite(); - return $collection; - } - - private function validateImageUrl($imageUrl) + /** + * Return feed of all products + * + * @return Framework\App\ResponseInterface|Framework\Controller\Result\Raw + * @throws Framework\Exception\LocalizedException + * @throws Framework\Exception\NoSuchEntityException + */ + public function execute() { - $options = [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_HEADER => true, - CURLOPT_NOBODY => true, - ]; - - if (strpos($imageUrl, "Magento_Catalog/images/product/placeholder/image.jpg") !== false) { - return false; - } + $store = $this->storeModel->getStore(); - return true; - } + $productFeedEnabled = $this->configHelper->isProductFeedEnabled($store->getId()); + if (!$productFeedEnabled) { + $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); + $result->setContents("Product Feed is disabled"); - private function validateVariantUrl($url) - { - $options = [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_HEADER => true, - CURLOPT_NOBODY => true, - ]; - - try { - $this->curl->get($url); - $this->curl->setOptions($options); - - if ($this->curl->getStatus() == 200) { - return true; - } - } catch (\Exception $e) { - return false; + return $result; } - } - public function execute() - { // Set timelimit to 0 to avoid timeouts when generating feed. ob_start(); - set_time_limit(0); + set_time_limit(0); // even with that, timeout can be thrown with many products. - $store = $this->storeModel->getStore(); + // TODO:- Implement caching of Feed + $productFeed = " + + + <![CDATA[" . $store->getName() . "]]> + " . $store->getBaseUrl() . ""; - $productFeedEnabled = $this->configHelper->isProductFeedEnabled($store->getId()); - if ($productFeedEnabled) { - // TODO:- Implement caching of Feed - $productFeed = " - - - <![CDATA[" . $store->getName() . "]]> - " . $store->getBaseUrl() . ""; - - $products = $this->getProductCollection(); - - foreach ($products as $product) { - $parentProductId = null; - $groupedParentId = null; - $configurableParentId = null; - $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); - - if ($objectManager->create('Magento\GroupedProduct\Model\Product\Type\Grouped')->getParentIdsByChild($product->getId())) { - $groupedParentId = $objectManager->create('Magento\GroupedProduct\Model\Product\Type\Grouped')->getParentIdsByChild($product->getId()); - } - if ($objectManager->create('Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable')->getParentIdsByChild($product->getId())) { - $configurableParentId = $objectManager->create('Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable')->getParentIdsByChild($product->getId()); - } + $page = 0; + do { + $productCollection = $this->getProductCollection($page); - $parentId = null; - $parentProduct = null; + foreach ($productCollection as $product) { - if (isset($groupedParentId[0])) { - $parentId = $groupedParentId[0]; - } else if (isset($configurableParentId[0])) { - $parentId = $configurableParentId[0]; + $parentId = null; + if ($this->groupedProductModel->getParentIdsByChild($product->getId())) { + $groupedParentId = $this->groupedProductModel->getParentIdsByChild($product->getId()); + if (isset($groupedParentId[0])) { + $parentId = $groupedParentId[0]; + } + } + if ($this->configurableProductModel->getParentIdsByChild($product->getId())) { + $configurableParentId = $this->configurableProductModel->getParentIdsByChild($product->getId()); + if (isset($configurableParentId[0])) { + $parentId = $configurableParentId[0]; + } } // Load image url via helper. @@ -155,9 +126,11 @@ public function execute() $productUrl = $product->getProductUrl(); if (isset($parentId)) { - $parentProduct = $objectManager->create('Magento\Catalog\Model\Product')->load($parentId); + $parentProduct = $this->productRepositoryFactory->create()->getById($parentId); + + $parentProductImageUrl = $this->imageHelper + ->init($parentProduct, 'product_page_image_large')->getUrl(); - $parentProductImageUrl = $this->imageHelper->init($parentProduct, 'product_page_image_large')->getUrl(); $validVariantImage = $this->validateImageUrl($productImageUrl); if (!$validVariantImage) { $imageLink = $parentProductImageUrl; @@ -166,33 +139,43 @@ public function execute() $productUrl = $parentProduct->getProductUrl(); } - $brand = $product->hasData('manufacturer') ? $product->getAttributeText('manufacturer') : ($product->hasData('brand') ? $product->getAttributeText('brand') : 'Not Available'); + if ($product->hasData('brand')) { + $brand = $product->hasData('manufacturer') ? $product->getAttributeText('manufacturer') + : ($product->getAttributeText('brand')); + } else { + $brand = $product->hasData('manufacturer') ? $product->getAttributeText('manufacturer') + : ('Not Available'); + } + $price = $product->getPrice(); $finalPrice = $product->getFinalPrice(); $productFeed .= " - getSku() . "]]> - <![CDATA[" . $product->getName() . "]]> - - " . (!empty($price) ? number_format($price, 2) . " " . $store->getCurrentCurrency()->getCode() : '') . " - " . (!empty($finalPrice) ? number_format($finalPrice, 2) . " " . $store->getCurrentCurrency()->getCode() : '') . " - - new - - - hasData('mpn') ? $product->getData('mpn') : $product->getSku()) . "]]> - hasData('gtin') ? $product->getData('gtin') : ($product->hasData('upc') ? $product->getData('upc') : '')) . "]]> - getTypeID() . "]]> - - UK - Standard Free Shipping - 0 GBP - "; + getSku() . "]]> + <![CDATA[" . $product->getName() . "]]> + + " . (!empty($price) ? number_format($price, 2) . " " . $store->getCurrentCurrency()->getCode() : '') . " + " . (!empty($finalPrice) ? number_format($finalPrice, 2) . " " . $store->getCurrentCurrency()->getCode() : '') . " + + new + + + hasData('mpn') ? $product->getData('mpn') : $product->getSku()) . "]]> + hasData('gtin') ? $product->getData('gtin') : ($product->hasData('upc') ? $product->getData('upc') : '')) . "]]> + getTypeID() . "]]> + + UK + Standard Free Shipping + 0 GBP + "; $categoryCollection = $product->getCategoryCollection(); if (count($categoryCollection) > 0) { foreach ($categoryCollection as $category) { - $productFeed .= "getName() . "]]>"; + $productFeed .= sprintf( + "", + $category->getName() + ); } } @@ -210,17 +193,56 @@ public function execute() $parentProduct = null; } - $productFeed .= ""; + $page++; + } while ($productCollection->count()); - // TODO:- Implement caching of feed + $productFeed .= ""; - $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); - $result->setContents($productFeed); + // TODO:- Implement caching of feed + $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); + $result->setContents($productFeed); - return $result; - // exit(); - } else { - print "Product Feed is disabled."; + ob_end_clean(); + + return $result; + } + + /** + * Provide page of product collection + * + * @param int $page + * + * @return Collection + */ + private function getProductCollection(int $page): Collection + { + $collection = $this->productCollectionFactory->create(); + + $collection + ->addMinimalPrice() + ->addFinalPrice() + ->addTaxPercents() + ->addAttributeToSelect('*') + ->addUrlRewrite() + ->setPageSize(100) + ->setCurPage($page); + + return $collection; + } + + /** + * Validate Image Url + * + * @param string $imageUrl + * + * @return bool + */ + private function validateImageUrl(string $imageUrl): bool + { + if (str_contains($imageUrl, "Magento_Catalog/images/product/placeholder/image.jpg")) { + return false; } + + return true; } } From c5f737dd2e8d17f4a96799e532cacbb01ac15547 Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Mon, 25 Sep 2023 15:32:25 +0200 Subject: [PATCH 02/16] Refactor v2 for Controller/Index/Feed.php --- Controller/Index/Feed.php | 65 ++++++++++++++------------------------- 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/Controller/Index/Feed.php b/Controller/Index/Feed.php index 3fb95ed..d67e660 100755 --- a/Controller/Index/Feed.php +++ b/Controller/Index/Feed.php @@ -15,6 +15,7 @@ use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable as ConfigurableTypeResourceModel; use Magento\Framework; use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\ResponseInterface; use Magento\Framework\Controller\ResultFactory; use Magento\GroupedProduct\Model\Product\Type\Grouped; use Magento\Store\Model\StoreManagerInterface; @@ -25,23 +26,13 @@ */ class Feed implements HttpGetActionInterface { - private Config $configHelper; - private StockRegistryInterface $stockModel; - private Image $imageHelper; - private StoreManagerInterface $storeModel; - private CollectionFactory $productCollectionFactory; - private ResultFactory $resultFactory; - private Grouped $groupedProductModel; - private ConfigurableTypeResourceModel $configurableProductModel; - private ProductRepositoryInterfaceFactory $productRepositoryFactory; - /** - * Feed Constructor + * Constructor for Feed * - * @param StockRegistryInterface $stockRegistryInterface - * @param Image $image - * @param StoreManagerInterface $storeManagerInterface - * @param Config $config + * @param StockRegistryInterface $stockModel + * @param Image $imageHelper + * @param StoreManagerInterface $storeModel + * @param Config $configHelper * @param CollectionFactory $productCollectionFactory * @param ResultFactory $resultFactory * @param Grouped $groupedProductModel @@ -49,40 +40,26 @@ class Feed implements HttpGetActionInterface * @param ProductRepositoryInterfaceFactory $productRepositoryFactory */ public function __construct( - StockRegistryInterface $stockRegistryInterface, // StockRegistryInterface is deprecated. - Image $image, - StoreManagerInterface $storeManagerInterface, - Config $config, - CollectionFactory $productCollectionFactory, - ResultFactory $resultFactory, - Grouped $groupedProductModel, - ConfigurableTypeResourceModel $configurableProductModel, - ProductRepositoryInterfaceFactory $productRepositoryFactory + private readonly StockRegistryInterface $stockModel, // StockRegistryInterface is deprecated. + private readonly Image $imageHelper, + private readonly StoreManagerInterface $storeModel, + private readonly Config $configHelper, + private readonly CollectionFactory $productCollectionFactory, + private readonly ResultFactory $resultFactory, + private readonly Grouped $groupedProductModel, + private readonly ConfigurableTypeResourceModel $configurableProductModel, + private readonly ProductRepositoryInterfaceFactory $productRepositoryFactory ) { - $this->configHelper = $config; - $this->stockModel = $stockRegistryInterface; - $this->imageHelper = $image; - $this->storeModel = $storeManagerInterface; - $this->productCollectionFactory = $productCollectionFactory; - $this->resultFactory = $resultFactory; - $this->groupedProductModel = $groupedProductModel; - $this->configurableProductModel = $configurableProductModel; - $this->productRepositoryFactory = $productRepositoryFactory; } /** - * Return feed of all products - * - * @return Framework\App\ResponseInterface|Framework\Controller\Result\Raw - * @throws Framework\Exception\LocalizedException - * @throws Framework\Exception\NoSuchEntityException + * @inheritDoc */ public function execute() { $store = $this->storeModel->getStore(); - $productFeedEnabled = $this->configHelper->isProductFeedEnabled($store->getId()); - if (!$productFeedEnabled) { + if (!$this->configHelper->isProductFeedEnabled($store->getId())) { $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); $result->setContents("Product Feed is disabled"); @@ -91,7 +68,9 @@ public function execute() // Set timelimit to 0 to avoid timeouts when generating feed. ob_start(); - set_time_limit(0); // even with that, timeout can be thrown with many products. + set_time_limit(0); + // Basically not good solution. Even with that, timeout can be thrown with many products. + // Consider using pagination as for API Controller and remove the ob_start and set_time_limit // TODO:- Implement caching of Feed $productFeed = " @@ -179,6 +158,8 @@ public function execute() } } + // The StockRegistryInterface is deprecated. + // See https://developer.adobe.com/commerce/php/development/components/web-api/inventory-management/ $stock = $this->stockModel->getStockItem( $product->getId(), $product->getStore()->getWebsiteId() @@ -202,7 +183,7 @@ public function execute() $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); $result->setContents($productFeed); - ob_end_clean(); + ob_end_clean(); //Should occur when using ob_start return $result; } From d192278835f4b8300a56841ece518bebce74a2e0 Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Mon, 25 Sep 2023 16:41:28 +0200 Subject: [PATCH 03/16] Another refactor for Feed Controlelr --- Controller/Index/Feed.php | 93 ++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/Controller/Index/Feed.php b/Controller/Index/Feed.php index d67e660..90572ed 100755 --- a/Controller/Index/Feed.php +++ b/Controller/Index/Feed.php @@ -7,15 +7,15 @@ namespace Reviewscouk\Reviews\Controller\Index; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterfaceFactory; use Magento\Catalog\Helper\Image; +use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory; use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable as ConfigurableTypeResourceModel; -use Magento\Framework; use Magento\Framework\App\Action\HttpGetActionInterface; -use Magento\Framework\App\ResponseInterface; use Magento\Framework\Controller\ResultFactory; use Magento\GroupedProduct\Model\Product\Type\Grouped; use Magento\Store\Model\StoreManagerInterface; @@ -38,6 +38,7 @@ class Feed implements HttpGetActionInterface * @param Grouped $groupedProductModel * @param ConfigurableTypeResourceModel $configurableProductModel * @param ProductRepositoryInterfaceFactory $productRepositoryFactory + * @param CategoryCollectionFactory $categoryCollectionFactory */ public function __construct( private readonly StockRegistryInterface $stockModel, // StockRegistryInterface is deprecated. @@ -48,7 +49,8 @@ public function __construct( private readonly ResultFactory $resultFactory, private readonly Grouped $groupedProductModel, private readonly ConfigurableTypeResourceModel $configurableProductModel, - private readonly ProductRepositoryInterfaceFactory $productRepositoryFactory + private readonly ProductRepositoryInterfaceFactory $productRepositoryFactory, + private readonly CategoryCollectionFactory $categoryCollectionFactory ) { } @@ -71,18 +73,20 @@ public function execute() set_time_limit(0); // Basically not good solution. Even with that, timeout can be thrown with many products. // Consider using pagination as for API Controller and remove the ob_start and set_time_limit + // Eventually create cron, that would create the Feed in the background and the controller would only display it // TODO:- Implement caching of Feed $productFeed = " - - - <![CDATA[" . $store->getName() . "]]> - " . $store->getBaseUrl() . ""; + + + <![CDATA[" . $store->getName() . "]]> + " . $store->getBaseUrl() . ""; $page = 0; do { $productCollection = $this->getProductCollection($page); + /** @var ProductInterface $product */ foreach ($productCollection as $product) { $parentId = null; @@ -129,55 +133,70 @@ public function execute() $price = $product->getPrice(); $finalPrice = $product->getFinalPrice(); - $productFeed .= " - getSku() . "]]> - <![CDATA[" . $product->getName() . "]]> - - " . (!empty($price) ? number_format($price, 2) . " " . $store->getCurrentCurrency()->getCode() : '') . " - " . (!empty($finalPrice) ? number_format($finalPrice, 2) . " " . $store->getCurrentCurrency()->getCode() : '') . " - - new - - - hasData('mpn') ? $product->getData('mpn') : $product->getSku()) . "]]> - hasData('gtin') ? $product->getData('gtin') : ($product->hasData('upc') ? $product->getData('upc') : '')) . "]]> - getTypeID() . "]]> - - UK - Standard Free Shipping - 0 GBP - "; - - $categoryCollection = $product->getCategoryCollection(); - if (count($categoryCollection) > 0) { + // I dont think, UK should be hardcoded as Shipping. + $productFeed .= " + + getSku() . "]]> + <![CDATA[" . $product->getName() . "]]> + + " . (!empty($price) ? number_format($price, 2) . " " . $store->getCurrentCurrency()->getCode() : '') . " + " . (!empty($finalPrice) ? number_format($finalPrice, 2) . " " . $store->getCurrentCurrency()->getCode() : '') . " + + new + + + hasData('mpn') ? $product->getData('mpn') : $product->getSku()) . "]]> + hasData('gtin') ? $product->getData('gtin') : ($product->hasData('upc') ? $product->getData('upc') : '')) . "]]> + getTypeID() . "]]> + + UK + Standard Free Shipping + 0 GBP + "; + + // If You really need to provide also Category names, the Category collection have to be loaded as well. + // It is not very optimized for many products. + $categoryIds = $product->getCategoryIds(); + try { + $categoryCollection = $this->categoryCollectionFactory->create() + ->addAttributeToSelect(['name']) + ->addAttributeToFilter('entity_id', $categoryIds); + } catch (\Exception $e) { + $categoryCollection = null; + } + + if (!is_null($categoryCollection) && count($categoryCollection) > 0) { foreach ($categoryCollection as $category) { $productFeed .= sprintf( - "", + "\n\t\t\t", $category->getName() ); } } - // The StockRegistryInterface is deprecated. + // The StockRegistryInterface is deprecated. Implemented logic will not reflect real Stock Status. // See https://developer.adobe.com/commerce/php/development/components/web-api/inventory-management/ + // For quicker resolve, consider using $product->isSalable() instead $stock->getIsInStock() + // Otherwise if Shop uses MSI load StockInventories and check availability there $stock = $this->stockModel->getStockItem( $product->getId(), $product->getStore()->getWebsiteId() ); if ($stock->getIsInStock()) { - $productFeed .= "in stock"; + //if ($product->isSalable()) { + $productFeed .= "\n\t\t\tin stock"; } else { - $productFeed .= "out of stock"; + $productFeed .= "\n\t\t\tout of stock"; } - $productFeed .= ""; + $productFeed .= "\n\t\t"; $parentProduct = null; } $page++; } while ($productCollection->count()); - $productFeed .= ""; + $productFeed .= "\n\t\n"; // TODO:- Implement caching of feed $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); @@ -199,13 +218,15 @@ private function getProductCollection(int $page): Collection { $collection = $this->productCollectionFactory->create(); + // If You want to use Collection, do not load all Attributes if not required. Especially with big collection. + // Add just required attributes. $collection ->addMinimalPrice() ->addFinalPrice() ->addTaxPercents() - ->addAttributeToSelect('*') + ->addAttributeToSelect(['name', 'manufacturer', 'gtin', 'brand', 'image']) ->addUrlRewrite() - ->setPageSize(100) + ->setPageSize(2) ->setCurPage($page); return $collection; From c5c051937fbe779069078b8d143964bcd355356a Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Mon, 25 Sep 2023 16:55:33 +0200 Subject: [PATCH 04/16] Small reafctor for COntroller Feed --- Controller/Index/Feed.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Controller/Index/Feed.php b/Controller/Index/Feed.php index 90572ed..16d1949 100755 --- a/Controller/Index/Feed.php +++ b/Controller/Index/Feed.php @@ -145,8 +145,8 @@ public function execute() new - hasData('mpn') ? $product->getData('mpn') : $product->getSku()) . "]]> - hasData('gtin') ? $product->getData('gtin') : ($product->hasData('upc') ? $product->getData('upc') : '')) . "]]> + hasData('mpn') ?: $product->getSku()) . "]]> + hasData('gtin') ?: ($product->hasData('upc') ?: '')) . "]]> getTypeID() . "]]> UK @@ -224,7 +224,7 @@ private function getProductCollection(int $page): Collection ->addMinimalPrice() ->addFinalPrice() ->addTaxPercents() - ->addAttributeToSelect(['name', 'manufacturer', 'gtin', 'brand', 'image']) + ->addAttributeToSelect(['name', 'manufacturer', 'gtin', 'brand', 'image', 'upc', 'mpn']) ->addUrlRewrite() ->setPageSize(2) ->setCurPage($page); From dfeed01f3175205f3cc949ce9b72119f42f271fd Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Mon, 25 Sep 2023 18:50:53 +0200 Subject: [PATCH 05/16] Another refactor for Feed --- Controller/Index/Feed.php | 59 +++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/Controller/Index/Feed.php b/Controller/Index/Feed.php index 16d1949..535f6bc 100755 --- a/Controller/Index/Feed.php +++ b/Controller/Index/Feed.php @@ -89,33 +89,17 @@ public function execute() /** @var ProductInterface $product */ foreach ($productCollection as $product) { - $parentId = null; - if ($this->groupedProductModel->getParentIdsByChild($product->getId())) { - $groupedParentId = $this->groupedProductModel->getParentIdsByChild($product->getId()); - if (isset($groupedParentId[0])) { - $parentId = $groupedParentId[0]; - } - } - if ($this->configurableProductModel->getParentIdsByChild($product->getId())) { - $configurableParentId = $this->configurableProductModel->getParentIdsByChild($product->getId()); - if (isset($configurableParentId[0])) { - $parentId = $configurableParentId[0]; - } - } - // Load image url via helper. $productImageUrl = $this->imageHelper->init($product, 'product_page_image_large')->getUrl(); $imageLink = $productImageUrl; $productUrl = $product->getProductUrl(); - if (isset($parentId)) { - $parentProduct = $this->productRepositoryFactory->create()->getById($parentId); - + $parentProduct = $this->provideParentProduct($product); + if (!is_null($parentProduct)) { $parentProductImageUrl = $this->imageHelper ->init($parentProduct, 'product_page_image_large')->getUrl(); - $validVariantImage = $this->validateImageUrl($productImageUrl); - if (!$validVariantImage) { + if (!$this->validateImageUrl($productImageUrl)) { $imageLink = $parentProductImageUrl; } @@ -133,7 +117,7 @@ public function execute() $price = $product->getPrice(); $finalPrice = $product->getFinalPrice(); - // I dont think, UK should be hardcoded as Shipping. + // I dont think, UK should be hardcoded as Shipping. The implementation of it is not very pretty. $productFeed .= " getSku() . "]]> @@ -190,7 +174,6 @@ public function execute() } $productFeed .= "\n\t\t"; - $parentProduct = null; } $page++; @@ -232,6 +215,40 @@ private function getProductCollection(int $page): Collection return $collection; } + /** + * Provide Parent Product if available + * + * @param ProductInterface $product + * + * @return ProductInterface|null + */ + private function provideParentProduct(ProductInterface $product): ?ProductInterface + { + $parentId = null; + if ($this->groupedProductModel->getParentIdsByChild($product->getId())) { + $groupedParentId = $this->groupedProductModel->getParentIdsByChild($product->getId()); + if (isset($groupedParentId[0])) { + $parentId = $groupedParentId[0]; + } + } + if ($this->configurableProductModel->getParentIdsByChild($product->getId())) { + $configurableParentId = $this->configurableProductModel->getParentIdsByChild($product->getId()); + if (isset($configurableParentId[0])) { + $parentId = $configurableParentId[0]; + } + } + + $parentProduct = null; + if (isset($parentId)) { + try { + $parentProduct = $this->productRepositoryFactory->create()->getById($parentId); + } catch (\Exception $e) { + } + } + + return $parentProduct; + } + /** * Validate Image Url * From 94dc401cdf0c27693a8d4726c8833f401d17f52c Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Mon, 25 Sep 2023 18:51:09 +0200 Subject: [PATCH 06/16] Refactor for API Controller --- Controller/Index/API.php | 263 ++++++++++++++++++++++++--------------- 1 file changed, 160 insertions(+), 103 deletions(-) diff --git a/Controller/Index/API.php b/Controller/Index/API.php index 3fd889f..fc090a4 100644 --- a/Controller/Index/API.php +++ b/Controller/Index/API.php @@ -1,131 +1,188 @@ configHelper = $config; - $this->cache = $core; - $this->productModel = $product; - $this->stockModel = $stockRegistryInterface; - $this->imageHelper = $image; - $this->storeModel = $storeManagerInterface; - $this->productCollectionFactory = $productCollectionFactory; - } - - private function getProductCollection($page, $perPage) - { - $collection = $this->productCollectionFactory->create(); - /* Addtional */ - $collection - ->addMinimalPrice() - ->addFinalPrice() - ->addTaxPercents() - ->addAttributeToSelect('*') - ->addUrlRewrite() - ->setPageSize($perPage) - ->setCurPage($page); - return $collection; + private readonly StockRegistryInterface $stockModel, + private readonly Image $imageHelper, + private readonly StoreManagerInterface $storeModel, + private readonly Config $configHelper, + private readonly CollectionFactory $productCollectionFactory, + private readonly Http $request, + private readonly CategoryCollectionFactory $categoryCollectionFactory, + private readonly ResultFactory $resultFactory, + + ) { } + /** + * @inheritDoc + * + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws \JsonException + */ public function execute() { - ob_start(); - set_time_limit(0); - $store = $this->storeModel->getStore(); $productFeedEnabled = $this->configHelper->isProductFeedEnabled($store->getId()); - $auth['actual_key'] = $this->configHelper->getApiKey($store->getId()); - if ($productFeedEnabled) { + if (!$productFeedEnabled) { + $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); + $result->setContents(json_encode(['success' => false, 'message' => 'API disabled.'], JSON_THROW_ON_ERROR)); + + return $result; + } + + $auth['page'] = $this->request->getParam('page') ?: '1'; + $auth['per_page'] = $this->request->getParam('per_page') ?: 100; + + if($this->canAccessResource($store->getId())) { + $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); + $result->setContents(json_encode(['success' => false, 'message' => 'Unauthenticated.'], JSON_THROW_ON_ERROR)); + + return $result; + } - $auth['page'] = !empty($_GET['page']) ? $_GET['page'] : '1'; - $auth['per_page'] = !empty($_GET['per_page']) ? (int) $_GET['per_page'] : 100; - $auth['submitted_key'] = !empty($_GET['key']) ? $_GET['key'] : ''; + ob_start(); + set_time_limit(0); - //Authenticate - if(!isset($auth['submitted_key'], $auth['actual_key']) || $auth['actual_key'] != $auth['submitted_key']) { - echo json_encode(array('success' => false, 'message' => 'Unauthenticated.')); - die(); + $products = $this->getProductCollection(); + $collection = []; + + foreach ($products as $product) { + // Load image url via helper. Refers to every occurrence in module + $imageUrl = $this->imageHelper->init($product, 'product_page_image_large')->getUrl(); + // Basically nested ternary operator should not be used. + $brand = $product->hasData('manufacturer') ?: ($product->hasData('brand') ? $product->getAttributeText('brand') : 'Not Available'); + $price = $product->getPrice(); + $finalPrice = $product->getFinalPrice(); + + $item = [ + 'id' => $product->getSku(), + 'title' => $product->getName(), + 'link' => $product->getProductUrl(), + 'price' => (!empty($price) ? number_format($price, 2) . " " . $store->getCurrentCurrency()->getCode() : ''), + 'sale_price' => (!empty($finalPrice) ? number_format($finalPrice, 2) . " " . $store->getCurrentCurrency()->getCode() : ''), + 'image_link' => $imageUrl, + 'brand' => $brand, + 'mpn' => $product->hasData('mpn') ?: $product->getSku(), + 'gtin' => $product->hasData('gtin') ?: ($product->hasData('upc') ?: ''), + 'product_type' => $product->getTypeID(), + ]; + + $item['category'] = []; + + // Basically the same as for Feed. Moreover, it can be moved to Model or Service - Its used by Api and Feed + $categoryIds = $product->getCategoryIds(); + try { + $categoryCollection = $this->categoryCollectionFactory->create() + ->addAttributeToSelect(['name']) + ->addAttributeToFilter('entity_id', $categoryIds); + } catch (\Exception $e) { + $categoryCollection = null; } - $products = $this->getProductCollection($auth['page'], $auth['per_page']); - - $collection = []; - - foreach ($products as $product) { - // Load image url via helper. - $imageUrl = $this->imageHelper->init($product, 'product_page_image_large')->getUrl(); - $brand = $product->hasData('manufacturer') ? $product->getAttributeText('manufacturer') : ($product->hasData('brand') ? $product->getAttributeText('brand') : 'Not Available'); - $price = $product->getPrice(); - - $finalPrice = $product->getFinalPrice(); - - $item = [ - 'id' => $product->getSku(), - 'title' => $product->getName(), - 'link' => $product->getProductUrl(), - 'price' => (!empty($price) ? number_format($price, 2) . " " . $store->getCurrentCurrency()->getCode() : ''), - 'sale_price' => (!empty($finalPrice) ? number_format($finalPrice, 2) . " " . $store->getCurrentCurrency()->getCode() : ''), - 'image_link' => $imageUrl, - 'brand' => $brand, - 'mpn' => ($product->hasData('mpn') ? $product->getData('mpn') : $product->getSku()), - 'gtin' => ($product->hasData('gtin') ? $product->getData('gtin') : ($product->hasData('upc') ? $product->getData('upc') : '')), - 'product_type' => $product->getTypeID(), - ]; - - $item['category'] = []; - - $categoryCollection = $product->getCategoryCollection(); - if (count($categoryCollection) > 0) { - foreach ($categoryCollection as $category) { - $item['category'][] = $category->getName(); - } + if (!is_null($categoryCollection) && count($categoryCollection) > 0) { + foreach ($categoryCollection as $category) { + $item['category'][] = $category->getName(); } + } - $stock = $this->stockModel->getStockItem( - $product->getId(), - $product->getStore()->getWebsiteId() - ); + // Basically the same as for Feed. Moreover, it can be moved to Model or Service - Its used by Api and Feed + $stock = $this->stockModel->getStockItem( + $product->getId(), + $product->getStore()->getWebsiteId() + ); - $item['in_stock'] = $stock->getIsInStock() ? true : false; + $item['in_stock'] = (bool)$stock->getIsInStock(); - $collection[] = $item; - } + $collection[] = $item; + } + + $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); + $result->setContents(json_encode(['success' => true, 'products' => $collection, 'total' => count($collection)], JSON_THROW_ON_ERROR)); - echo json_encode(array('success' => true, 'products' => $collection, 'total' => count($collection))); - die(); + return $result; + } - } else { - echo json_encode(array('success' => false, 'message' => 'API disabled.')); - die(); + /** + * Check if Controller can be accessed. + * + * @param int $storeId + * + * @return bool + */ + private function canAccessResource(int $storeId): bool + { + $auth['actual_key'] = $this->configHelper->getApiKey($storeId); + $auth['submitted_key'] = !empty($_GET['key']) ? $_GET['key'] : ''; + + //Authenticate + if(!isset($auth['submitted_key'], $auth['actual_key']) || $auth['actual_key'] != $auth['submitted_key']) { + return false; } + + return true; + } + + /** + * Provide page of product collection + * + * Basically the same as for Feed. Can be moved to Model + * + * @return Collection + */ + private function getProductCollection(): Collection + { + $page = $this->request->getParam('page') ?: '1'; + $perPage = $this->request->getParam('per_page') ?: 10; + + $collection = $this->productCollectionFactory->create(); + $collection + ->addMinimalPrice() + ->addFinalPrice() + ->addTaxPercents() + ->addAttributeToSelect(['name', 'manufacturer', 'gtin', 'brand', 'image', 'upc', 'mpn']) + ->addUrlRewrite() + ->setPageSize($perPage) + ->setCurPage($page); + + return $collection; } } From 1b14f69abf28e8f3b8be9c6c431630f9f5b4eda2 Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Mon, 25 Sep 2023 18:54:02 +0200 Subject: [PATCH 07/16] Refactor for API Controller --- Controller/Index/API.php | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/Controller/Index/API.php b/Controller/Index/API.php index fc090a4..d539b9b 100644 --- a/Controller/Index/API.php +++ b/Controller/Index/API.php @@ -23,8 +23,6 @@ */ class API implements HttpGetActionInterface { - - /** * API Constructor * @@ -64,17 +62,24 @@ public function execute() if (!$productFeedEnabled) { $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); - $result->setContents(json_encode(['success' => false, 'message' => 'API disabled.'], JSON_THROW_ON_ERROR)); + $result->setContents( + json_encode( + ['success' => false, 'message' => 'API disabled.'], + JSON_THROW_ON_ERROR + ) + ); return $result; } - $auth['page'] = $this->request->getParam('page') ?: '1'; - $auth['per_page'] = $this->request->getParam('per_page') ?: 100; - if($this->canAccessResource($store->getId())) { $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); - $result->setContents(json_encode(['success' => false, 'message' => 'Unauthenticated.'], JSON_THROW_ON_ERROR)); + $result->setContents( + json_encode( + ['success' => false, 'message' => 'Unauthenticated.'], + JSON_THROW_ON_ERROR + ) + ); return $result; } @@ -89,7 +94,8 @@ public function execute() // Load image url via helper. Refers to every occurrence in module $imageUrl = $this->imageHelper->init($product, 'product_page_image_large')->getUrl(); // Basically nested ternary operator should not be used. - $brand = $product->hasData('manufacturer') ?: ($product->hasData('brand') ? $product->getAttributeText('brand') : 'Not Available'); + $brand = $product->hasData('manufacturer') + ?: ($product->hasData('brand') ? $product->getAttributeText('brand') : 'Not Available'); $price = $product->getPrice(); $finalPrice = $product->getFinalPrice(); @@ -97,8 +103,10 @@ public function execute() 'id' => $product->getSku(), 'title' => $product->getName(), 'link' => $product->getProductUrl(), - 'price' => (!empty($price) ? number_format($price, 2) . " " . $store->getCurrentCurrency()->getCode() : ''), - 'sale_price' => (!empty($finalPrice) ? number_format($finalPrice, 2) . " " . $store->getCurrentCurrency()->getCode() : ''), + 'price' => !empty($price) + ? number_format($price, 2) . " " . $store->getCurrentCurrency()->getCode() : '', + 'sale_price' => !empty($finalPrice) + ? number_format($finalPrice, 2) . " " . $store->getCurrentCurrency()->getCode() : '', 'image_link' => $imageUrl, 'brand' => $brand, 'mpn' => $product->hasData('mpn') ?: $product->getSku(), @@ -129,14 +137,18 @@ public function execute() $product->getId(), $product->getStore()->getWebsiteId() ); - $item['in_stock'] = (bool)$stock->getIsInStock(); $collection[] = $item; } $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); - $result->setContents(json_encode(['success' => true, 'products' => $collection, 'total' => count($collection)], JSON_THROW_ON_ERROR)); + $result->setContents( + json_encode( + ['success' => true, 'products' => $collection, 'total' => count($collection)], + JSON_THROW_ON_ERROR + ) + ); return $result; } From b5ef41a3b0d598d27f46b7da62c31057b4611121 Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Mon, 25 Sep 2023 19:09:39 +0200 Subject: [PATCH 08/16] Code Check refactor --- Controller/Index/API.php | 29 ++++++++++++++--------------- Controller/Index/Feed.php | 35 +++++++++++++++++------------------ 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/Controller/Index/API.php b/Controller/Index/API.php index d539b9b..c97fa01 100644 --- a/Controller/Index/API.php +++ b/Controller/Index/API.php @@ -36,15 +36,14 @@ class API implements HttpGetActionInterface * @param ResultFactory $resultFactory */ public function __construct( - private readonly StockRegistryInterface $stockModel, - private readonly Image $imageHelper, - private readonly StoreManagerInterface $storeModel, - private readonly Config $configHelper, - private readonly CollectionFactory $productCollectionFactory, - private readonly Http $request, + private readonly StockRegistryInterface $stockModel, + private readonly Image $imageHelper, + private readonly StoreManagerInterface $storeModel, + private readonly Config $configHelper, + private readonly CollectionFactory $productCollectionFactory, + private readonly Http $request, private readonly CategoryCollectionFactory $categoryCollectionFactory, private readonly ResultFactory $resultFactory, - ) { } @@ -72,7 +71,7 @@ public function execute() return $result; } - if($this->canAccessResource($store->getId())) { + if ($this->canAccessResource($store->getId())) { $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); $result->setContents( json_encode( @@ -93,7 +92,7 @@ public function execute() foreach ($products as $product) { // Load image url via helper. Refers to every occurrence in module $imageUrl = $this->imageHelper->init($product, 'product_page_image_large')->getUrl(); - // Basically nested ternary operator should not be used. + # Nested ternary operator should not be used. Change to if or so. $brand = $product->hasData('manufacturer') ?: ($product->hasData('brand') ? $product->getAttributeText('brand') : 'Not Available'); $price = $product->getPrice(); @@ -116,7 +115,7 @@ public function execute() $item['category'] = []; - // Basically the same as for Feed. Moreover, it can be moved to Model or Service - Its used by Api and Feed + # Basically the same as for Feed. Moreover, it can be moved to Model or Service - Its used by Api and Feed $categoryIds = $product->getCategoryIds(); try { $categoryCollection = $this->categoryCollectionFactory->create() @@ -126,13 +125,13 @@ public function execute() $categoryCollection = null; } - if (!is_null($categoryCollection) && count($categoryCollection) > 0) { + if ($categoryCollection !== null && count($categoryCollection) > 0) { foreach ($categoryCollection as $category) { $item['category'][] = $category->getName(); } } - // Basically the same as for Feed. Moreover, it can be moved to Model or Service - Its used by Api and Feed + # Basically the same as for Feed. Moreover, it can be moved to Model or Service - Its used by Api and Feed $stock = $this->stockModel->getStockItem( $product->getId(), $product->getStore()->getWebsiteId() @@ -163,10 +162,10 @@ public function execute() private function canAccessResource(int $storeId): bool { $auth['actual_key'] = $this->configHelper->getApiKey($storeId); - $auth['submitted_key'] = !empty($_GET['key']) ? $_GET['key'] : ''; + $auth['submitted_key'] = $this->request->getParam('key') ?: ''; //Authenticate - if(!isset($auth['submitted_key'], $auth['actual_key']) || $auth['actual_key'] != $auth['submitted_key']) { + if (!isset($auth['submitted_key'], $auth['actual_key']) || $auth['actual_key'] != $auth['submitted_key']) { return false; } @@ -176,7 +175,7 @@ private function canAccessResource(int $storeId): bool /** * Provide page of product collection * - * Basically the same as for Feed. Can be moved to Model + * # Basically the same as for Feed. Can be moved to Model * * @return Collection */ diff --git a/Controller/Index/Feed.php b/Controller/Index/Feed.php index 535f6bc..7a2a9de 100755 --- a/Controller/Index/Feed.php +++ b/Controller/Index/Feed.php @@ -41,7 +41,7 @@ class Feed implements HttpGetActionInterface * @param CategoryCollectionFactory $categoryCollectionFactory */ public function __construct( - private readonly StockRegistryInterface $stockModel, // StockRegistryInterface is deprecated. + private readonly StockRegistryInterface $stockModel, # StockRegistryInterface is deprecated. private readonly Image $imageHelper, private readonly StoreManagerInterface $storeModel, private readonly Config $configHelper, @@ -71,9 +71,9 @@ public function execute() // Set timelimit to 0 to avoid timeouts when generating feed. ob_start(); set_time_limit(0); - // Basically not good solution. Even with that, timeout can be thrown with many products. - // Consider using pagination as for API Controller and remove the ob_start and set_time_limit - // Eventually create cron, that would create the Feed in the background and the controller would only display it + # Basically not good solution. Even with that, timeout can be thrown with many products. + # Consider using pagination as for API Controller and remove the ob_start and set_time_limit + # Eventually create cron, that would create the Feed in the background and the controller would only display it // TODO:- Implement caching of Feed $productFeed = " @@ -88,14 +88,13 @@ public function execute() /** @var ProductInterface $product */ foreach ($productCollection as $product) { - // Load image url via helper. $productImageUrl = $this->imageHelper->init($product, 'product_page_image_large')->getUrl(); $imageLink = $productImageUrl; $productUrl = $product->getProductUrl(); $parentProduct = $this->provideParentProduct($product); - if (!is_null($parentProduct)) { + if ($parentProduct !== null) { $parentProductImageUrl = $this->imageHelper ->init($parentProduct, 'product_page_image_large')->getUrl(); @@ -117,7 +116,7 @@ public function execute() $price = $product->getPrice(); $finalPrice = $product->getFinalPrice(); - // I dont think, UK should be hardcoded as Shipping. The implementation of it is not very pretty. + # I dont think, UK should be hardcoded as Shipping. The implementation of it is not very pretty. $productFeed .= " getSku() . "]]> @@ -138,8 +137,8 @@ public function execute() 0 GBP "; - // If You really need to provide also Category names, the Category collection have to be loaded as well. - // It is not very optimized for many products. + # If You really need to provide also Category names, the Category collection have to be loaded as well. + # It is not very optimized for many products. $categoryIds = $product->getCategoryIds(); try { $categoryCollection = $this->categoryCollectionFactory->create() @@ -149,7 +148,7 @@ public function execute() $categoryCollection = null; } - if (!is_null($categoryCollection) && count($categoryCollection) > 0) { + if ($categoryCollection !== null && count($categoryCollection) > 0) { foreach ($categoryCollection as $category) { $productFeed .= sprintf( "\n\t\t\t", @@ -158,16 +157,16 @@ public function execute() } } - // The StockRegistryInterface is deprecated. Implemented logic will not reflect real Stock Status. - // See https://developer.adobe.com/commerce/php/development/components/web-api/inventory-management/ - // For quicker resolve, consider using $product->isSalable() instead $stock->getIsInStock() - // Otherwise if Shop uses MSI load StockInventories and check availability there + # The StockRegistryInterface is deprecated. Implemented logic will not reflect real Stock Status. + # See https://developer.adobe.com/commerce/php/development/components/web-api/inventory-management/ + # For quicker resolve, consider using $product->isSalable() instead $stock->getIsInStock() + # Otherwise if Shop uses MSI load StockInventories and check availability there $stock = $this->stockModel->getStockItem( $product->getId(), $product->getStore()->getWebsiteId() ); if ($stock->getIsInStock()) { - //if ($product->isSalable()) { + #if ($product->isSalable()) { $productFeed .= "\n\t\t\tin stock"; } else { $productFeed .= "\n\t\t\tout of stock"; @@ -185,7 +184,7 @@ public function execute() $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); $result->setContents($productFeed); - ob_end_clean(); //Should occur when using ob_start + ob_end_clean(); #Should occur when using ob_start return $result; } @@ -201,8 +200,8 @@ private function getProductCollection(int $page): Collection { $collection = $this->productCollectionFactory->create(); - // If You want to use Collection, do not load all Attributes if not required. Especially with big collection. - // Add just required attributes. + # If You want to use Collection, do not load all Attributes if not required. Especially with big collection. + # Add just required attributes. $collection ->addMinimalPrice() ->addFinalPrice() From cb227b5795f62208489710ff183fb71f155724d8 Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Tue, 26 Sep 2023 08:47:23 +0200 Subject: [PATCH 09/16] Refactor for Observer Send OrderDetaiols --- Observer/SendOrderDetails.php | 180 ++++++++++++++++++++++------------ 1 file changed, 117 insertions(+), 63 deletions(-) diff --git a/Observer/SendOrderDetails.php b/Observer/SendOrderDetails.php index 45c2431..deeba20 100755 --- a/Observer/SendOrderDetails.php +++ b/Observer/SendOrderDetails.php @@ -1,90 +1,144 @@ configHelper = $config; - $this->apiModel = $api; - $this->productModel = $product; - $this->imageHelper = $image; - $this->configProductModel = $configurable; } - public function execute(Framework\Event\Observer $observer) + /** + * @inheritDoc + */ + public function execute(Observer $observer) { $shipment = $observer->getEvent()->getShipment(); + /** @var OrderInterface $order */ $order = $shipment->getOrder(); - $this->dispatchNotification($order); + + if ($this->canSendOrderDetails($order)) { + $this->dispatchNotification($order); + } } - public function dispatchNotification($order) + /** + * Send Order data to Review + * + * @param OrderInterface $order + * + * @return void + */ + public function dispatchNotification(OrderInterface $order): void { - try { - $magento_store_id = $order->getStoreId(); + $storeId = $order->getStoreId(); + /** @var OrderItemInterface[] $items */ + $orderItems = $order->getAllVisibleItems(); - if ($this->configHelper->getStoreId($magento_store_id) && $this->configHelper->getApiKey($magento_store_id) && $this->configHelper->isProductReviewsEnabled($magento_store_id)) { - $items = $order->getAllVisibleItems(); - $p = array(); - foreach ($items as $item) { + $itemIds = []; + /** @var OrderItemInterface $orderItem */ + foreach ($orderItems as $orderItem) { + $itemIds[] = $orderItem->getProductId(); + } + $productCollection = $this->productCollectionFactory->create() + ->addAttributeToSelect(['name', 'url', 'image']) + ->addAttributeToFilter('entity_id', ['in' => $itemIds]); - if ($this->configHelper->isUsingGroupSkus($magento_store_id)) { - // If product is part of a configurable product, use the configurable product details. - if ($item->getProduct()->getTypeId() == 'simple' || $item->getProduct()->getTypeId() == \Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE) { - $productId = $item->getProduct()->getId(); - $model = $this->productModel->create(); - $item = $model->load($productId); - } - } - $imageUrl = $this->imageHelper->init($item, 'product_page_image_large')->getUrl(); - $p[] = [ - 'image' => $imageUrl, - 'id' => $item->getId(), - 'sku' => $item->getSku(), - 'name' => $item->getName(), - 'pageUrl' => $item->getProductUrl() - ]; - } + $productData = []; + foreach ($productCollection as $item) { + # The whole IF makes no sense. Right now, if the isUsingGroupSkus is true, then literally: + # Check if Product is Simple OR Config. If yes, then load Product via repository (->getProduct()) + # If not, use OrderItemInterface $Item from Order. + # Just to check its Id. Then load the same product via Model. Also the comment is not accurate at all. + # Especially using `$order->getAllVisibleItems()`. The child products will not be provided here. + # Consider removing that IF or refactor it to something useful. + # And for God’s sake, do not use deprecated methods like $model->load($productId) +// if ($this->configHelper->isUsingGroupSkus($storeId)) { +// // If product is part of a configurable product, use the configurable product details. +// if ($item->getProduct()->getTypeId() == 'simple' || $item->getProduct()->getTypeId() == Configurable::TYPE_CODE) { +// $productId = $item->getProduct()->getId(); +// $model = $this->productModel->create(); +// $item = $model->load($productId); +// } +// } - $name = $order->getCustomerName(); + $imageUrl = $this->imageHelper->init($item, 'product_page_image_large')->getUrl(); + $productData[] = [ + 'image' => $imageUrl, + 'id' => $item->getId(), + 'sku' => $item->getSku(), + 'name' => $item->getName(), + 'pageUrl' => $item->getProductUrl() + ]; + } - if ($order->getCustomerIsGuest()) { - $name = $order->getBillingAddress()->getFirstName(); - } + $name = $order->getCustomerName(); - $productResponse = $this->apiModel->apiPost('/invitation', [ - 'source' => 'magento', - 'name' => $name, - 'email' => $order->getCustomerEmail(), - 'order_id' => $order->getRealOrderId(), - 'country_code' => $order->getShippingAddress()->getCountryId(), - 'products' => $p - ], $magento_store_id); + if ($order->getCustomerIsGuest()) { + $name = $order->getBillingAddress()->getFirstName(); + } - $this->apiModel->addStatusMessage($productResponse, "Product Review Invitation"); + try { + $productResponse = $this->apiModel->apiPost('/invitation', [ + 'source' => 'magento', + 'name' => $name, + 'email' => $order->getCustomerEmail(), + 'order_id' => $order->getRealOrderId(), + 'country_code' => $order->getShippingAddress()->getCountryId(), + 'products' => $productData + ], $storeId); - } + $this->apiModel->addStatusMessage($productResponse, "Product Review Invitation"); } catch (\Exception $e) { } } + + /** + * Check if Order can be sent to Review + * + * @param OrderInterface $order + * + * @return bool + */ + private function canSendOrderDetails(OrderInterface $order): bool + { + if ($this->configHelper->getStoreId($order->getStoreId()) + && $this->configHelper->getApiKey($order->getStoreId()) + && $this->configHelper->isProductReviewsEnabled($order->getStoreId()) + ) { + return true; + } + return false; + } } From da113fd73c05826e864fc9b8619d5af3c6cba27b Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Tue, 26 Sep 2023 08:47:46 +0200 Subject: [PATCH 10/16] add comment for events file --- etc/events.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/etc/events.xml b/etc/events.xml index 2c489d1..c8b422c 100755 --- a/etc/events.xml +++ b/etc/events.xml @@ -1,6 +1,12 @@ + From f8f536b9b4569bddc4a7dc457a612fa739cc2ca2 Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Tue, 26 Sep 2023 09:23:05 +0200 Subject: [PATCH 11/16] small refactor and comments for phtml file --- .../templates/product/review_widget.phtml | 63 +++++++++++-------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/view/frontend/templates/product/review_widget.phtml b/view/frontend/templates/product/review_widget.phtml index b6325fc..0436be3 100755 --- a/view/frontend/templates/product/review_widget.phtml +++ b/view/frontend/templates/product/review_widget.phtml @@ -1,15 +1,38 @@ isProductWidgetEnabled() && $block->isTabMode()) { - if ($block->isIframeWidget()) { - $_data = $block->getSettings(); - $_skus = $block->getProductSkus(); +/** + * Add Your COPPYRIGHTS here + * + * See COPYING.txt for license details. + */ + +/** + * @var $block \Reviewscouk\Reviews\Block\Product\Reviewwidget + * @var $escaper \Magento\Framework\Escaper + */ +if (!isset($escaper)) { + $escaper = $block; +} + +# In phtml templates do not use normal `if () {}` statements. Also, reduce the length of ifs and complicity as much as possible. +# It would be much better to prepare the Child Templates and call them inside IFS, to have separated templates per different Widget +# Also be consistent in using it. Some templates use `if():` statements, some `if(){}`... +if (!($block->isProductWidgetEnabled() && $block->isTabMode())): + print $block->getStaticWidget(); +else: + $_data = $block->getSettings(); + $_skus = $block->getProductSkus(); + + if ($block->isIframeWidget()): ?> +
+ - isPolarisWidget()) { - $_data = $block->getSettings(); - $_skus = $block->getProductSkus(); - - ?> + isPolarisWidget()): ?>
+ - - -getStaticWidget(); - } -} -?> \ No newline at end of file + + From d420e10a7745aa5044abb626822b4dfbb4a744db Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Tue, 26 Sep 2023 09:24:55 +0200 Subject: [PATCH 12/16] add requirements for compsoer and etc/module --- composer.json | 6 ++++++ etc/module.xml | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 53b7392..04d71fc 100755 --- a/composer.json +++ b/composer.json @@ -8,6 +8,12 @@ "registration.php" ] }, + "require": { + "magento/module-sales": "^102.0.0 || ^103.0.0", + "magento/module-catalog": "^102.0.0 || ^103.0.0", + "magento/framework": "^102.0.0 || ^103.0.0", + "php": "~8.1" + }, "description": "Reviews.co.uk Magento 2 Integration", "type": "magento2-module", "authors": [ diff --git a/etc/module.xml b/etc/module.xml index 2803951..d31d8b8 100755 --- a/etc/module.xml +++ b/etc/module.xml @@ -4,6 +4,8 @@ + + -
\ No newline at end of file + From b85cbceab6a989605c55062033641e17951506a6 Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Tue, 26 Sep 2023 09:25:20 +0200 Subject: [PATCH 13/16] comments for not used files - mostlikely --- 1 | 1 + modman | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/1 b/1 index 63a2414..a312832 100644 --- a/1 +++ b/1 @@ -4,3 +4,4 @@ version 7c # Write a message for tag: # 0.0.7b # Lines starting with '#' will be ignored. +# is it even required? I guess the file can be removed. diff --git a/modman b/modman index 715b768..a86ae1c 100755 --- a/modman +++ b/modman @@ -1,3 +1,4 @@ # Modman configuration file +# is MOdman still in use? I gues it may be removed -/ app/code/Reviewscouk/Reviews \ No newline at end of file +/ app/code/Reviewscouk/Reviews From 5d1cc72dd0b5a2fc10855fa6015cb14aebf8a339 Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Tue, 26 Sep 2023 09:27:26 +0200 Subject: [PATCH 14/16] small refactor and commits for view/frontend/templates/product/rating_snippet_element.phtml --- .../templates/product/rating_snippet_element.phtml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/view/frontend/templates/product/rating_snippet_element.phtml b/view/frontend/templates/product/rating_snippet_element.phtml index ede7a6a..493db98 100644 --- a/view/frontend/templates/product/rating_snippet_element.phtml +++ b/view/frontend/templates/product/rating_snippet_element.phtml @@ -1,8 +1,10 @@ isProductRatingSnippetWidgetEnabled()) { +# add copyright block. +# Add definition of Block. +# Use escaper instead of $block for escaping +if ($block->isProductRatingSnippetWidgetEnabled()): $_data = $block->getSettings(); $_skus = $block->getProductSkus(); ?>
- From 4b6f380933d75615bf909371f746d4b27d0994df Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Tue, 26 Sep 2023 10:26:24 +0200 Subject: [PATCH 15/16] Update the comment f or event.xml --- etc/events.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/etc/events.xml b/etc/events.xml index c8b422c..1e170b4 100755 --- a/etc/events.xml +++ b/etc/events.xml @@ -5,8 +5,7 @@ Consider using event corresponding to marking order as Complete or paid OR Plugin. However, best approach would be to create Cron, running once per eg 5 or 15 mins and process the logic for all the orders that were marked as complete during that interval. Additional column like `sent_to_reviewio` in `sales_order` will be required, to mark already sent orders. - Right now, when order would be placed with a lot of products, Timeout may be thrown! - Current approach will increase the time of placing order. --> + Right now, when order would be placed with a lot of products, Timeout may be thrown for the event --> From 61bf0ba0521aae0278df70960ed396d4dad2b77d Mon Sep 17 00:00:00 2001 From: Konrad Siamro Date: Tue, 26 Sep 2023 11:23:49 +0200 Subject: [PATCH 16/16] Refactor for collection pagination --- Controller/Index/API.php | 4 ++-- Controller/Index/Feed.php | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Controller/Index/API.php b/Controller/Index/API.php index c97fa01..6499697 100644 --- a/Controller/Index/API.php +++ b/Controller/Index/API.php @@ -181,8 +181,8 @@ private function canAccessResource(int $storeId): bool */ private function getProductCollection(): Collection { - $page = $this->request->getParam('page') ?: '1'; - $perPage = $this->request->getParam('per_page') ?: 10; + $page = $this->request->getParam('page') ?: 0; + $perPage = $this->request->getParam('per_page') ?: 100; $collection = $this->productCollectionFactory->create(); $collection diff --git a/Controller/Index/Feed.php b/Controller/Index/Feed.php index 7a2a9de..aa92f43 100755 --- a/Controller/Index/Feed.php +++ b/Controller/Index/Feed.php @@ -16,6 +16,7 @@ use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable as ConfigurableTypeResourceModel; use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Request\Http; use Magento\Framework\Controller\ResultFactory; use Magento\GroupedProduct\Model\Product\Type\Grouped; use Magento\Store\Model\StoreManagerInterface; @@ -39,6 +40,7 @@ class Feed implements HttpGetActionInterface * @param ConfigurableTypeResourceModel $configurableProductModel * @param ProductRepositoryInterfaceFactory $productRepositoryFactory * @param CategoryCollectionFactory $categoryCollectionFactory + * @param Http $request */ public function __construct( private readonly StockRegistryInterface $stockModel, # StockRegistryInterface is deprecated. @@ -50,7 +52,8 @@ public function __construct( private readonly Grouped $groupedProductModel, private readonly ConfigurableTypeResourceModel $configurableProductModel, private readonly ProductRepositoryInterfaceFactory $productRepositoryFactory, - private readonly CategoryCollectionFactory $categoryCollectionFactory + private readonly CategoryCollectionFactory $categoryCollectionFactory, + private readonly Http $request, ) { } @@ -82,7 +85,8 @@ public function execute() <![CDATA[" . $store->getName() . "]]> " . $store->getBaseUrl() . ""; - $page = 0; + $page = $this->request->getParam('page') ?: 0; + # If You will use Pages and PageSizes, then remove `do / while` block, and use it as in API Controller. do { $productCollection = $this->getProductCollection($page); @@ -93,6 +97,7 @@ public function execute() $imageLink = $productImageUrl; $productUrl = $product->getProductUrl(); + /** @var ProductInterface $parentProduct */ $parentProduct = $this->provideParentProduct($product); if ($parentProduct !== null) { $parentProductImageUrl = $this->imageHelper @@ -198,8 +203,9 @@ public function execute() */ private function getProductCollection(int $page): Collection { - $collection = $this->productCollectionFactory->create(); + $perPage = $this->request->getParam('per_page') ?: 100; + $collection = $this->productCollectionFactory->create(); # If You want to use Collection, do not load all Attributes if not required. Especially with big collection. # Add just required attributes. $collection @@ -208,7 +214,7 @@ private function getProductCollection(int $page): Collection ->addTaxPercents() ->addAttributeToSelect(['name', 'manufacturer', 'gtin', 'brand', 'image', 'upc', 'mpn']) ->addUrlRewrite() - ->setPageSize(2) + ->setPageSize($perPage) ->setCurPage($page); return $collection;