diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php index f5d56dc9e6b0e..3f94ffd0909aa 100644 --- a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php +++ b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php @@ -7,6 +7,7 @@ namespace Magento\Catalog\Controller\Product\Compare; use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\ResourceModel\Product\Compare\Item\Collection; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Exception\NoSuchEntityException; diff --git a/app/code/Magento/Catalog/Model/CompareList.php b/app/code/Magento/Catalog/Model/CompareList.php new file mode 100644 index 0000000000000..5be30d40aacce --- /dev/null +++ b/app/code/Magento/Catalog/Model/CompareList.php @@ -0,0 +1,24 @@ +_init(ResourceModel\Product\Compare\CompareList::class); + } +} diff --git a/app/code/Magento/Catalog/Model/CompareListIdToMaskedListId.php b/app/code/Magento/Catalog/Model/CompareListIdToMaskedListId.php new file mode 100644 index 0000000000000..a911980b98894 --- /dev/null +++ b/app/code/Magento/Catalog/Model/CompareListIdToMaskedListId.php @@ -0,0 +1,58 @@ +compareListFactory = $compareListFactory; + $this->compareListResource = $compareListResource; + } + + /** + * Get listIdMask by listId + * + * @param int $listId + * + * @param int|null $customerId + * @return null|string + * @throws LocalizedException + */ + public function execute(int $listId, int $customerId = null): ?string + { + $compareList = $this->compareListFactory->create(); + $this->compareListResource->load($compareList, $listId, 'list_id'); + if ((int)$compareList->getCustomerId() !== (int)$customerId) { + throw new LocalizedException(__('This customer is not authorized to access this list')); + } + return $compareList->getListIdMask() ?? null; + } +} diff --git a/app/code/Magento/Catalog/Model/MaskedListIdToCompareListId.php b/app/code/Magento/Catalog/Model/MaskedListIdToCompareListId.php new file mode 100644 index 0000000000000..cd1506c970763 --- /dev/null +++ b/app/code/Magento/Catalog/Model/MaskedListIdToCompareListId.php @@ -0,0 +1,57 @@ +compareListFactory = $compareListFactory; + $this->compareListResource = $compareListResource; + } + + /** + * Get maskedId by listId + * + * @param string $maskedListId + * @param int $customerId + * @return int + * @throws LocalizedException + */ + public function execute(string $maskedListId, int $customerId = null): int + { + $compareList = $this->compareListFactory->create(); + $this->compareListResource->load($compareList, $maskedListId, 'list_id_mask'); + if ((int)$compareList->getCustomerId() !== (int)$customerId) { + throw new LocalizedException(__('This customer is not authorized to access this list')); + } + return (int)$compareList->getListId(); + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/CompareList.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/CompareList.php new file mode 100644 index 0000000000000..4185df079d55d --- /dev/null +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/CompareList.php @@ -0,0 +1,25 @@ +_init('catalog_compare_list', 'list_id'); + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item.php index 7eb0552e355fc..ff29a5afa7eda 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item.php @@ -45,6 +45,10 @@ public function loadByProduct(\Magento\Catalog\Model\Product\Compare\Item $objec $select->where('visitor_id = ?', (int)$object->getVisitorId()); } + if ($object->getListId()) { + $select->where('list_id = ?', (int)$object->getListId()); + } + $data = $connection->fetchRow($select); if (!$data) { @@ -140,6 +144,7 @@ public function purgeVisitorByCustomer($object) /** * Update (Merge) customer data from visitor + * * After Login process * * @param \Magento\Catalog\Model\Product\Compare\Item $object diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php index 92741cf9ba88e..76f566a364769 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php @@ -31,6 +31,13 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection */ protected $_visitorId = 0; + /** + * List Id Filter + * + * @var int + */ + protected $listId = 0; + /** * Comparable attributes cache * @@ -156,6 +163,30 @@ public function setCustomerId($customerId) return $this; } + /** + * Set listId filter to collection + * + * @param int $listId + * + * @return $this + */ + public function setListId(int $listId) + { + $this->listId = $listId; + $this->_addJoinToSelect(); + return $this; + } + + /** + * Retrieve listId filter applied to collection + * + * @return int + */ + public function getListId(): int + { + return (int)$this->listId; + } + /** * Set visitor filter to collection * @@ -204,6 +235,10 @@ public function getConditionForJoin() return ['visitor_id' => $this->getVisitorId()]; } + if ($this->getListId()) { + return ['list_id' => $this->getListId()]; + } + return ['customer_id' => ['null' => true], 'visitor_id' => '0']; } @@ -232,6 +267,82 @@ public function _addJoinToSelect() return $this; } + /** + * Get products ids by for compare list + * + * @param int $listId + * + * @return array + */ + public function getProductsByListId(int $listId): array + { + $select = $this->getConnection()->select()-> + from( + $this->getTable('catalog_compare_item'), + 'product_id' + )->where( + 'list_id = ?', + $listId + ); + return $this->getConnection()->fetchCol($select); + } + + + /** + * Set list_id for customer compare item + * + * @param int $listId + * @param int $customerId + */ + public function setListIdToCustomerCompareItems(int $listId, int $customerId) + { + foreach ($this->getCustomerCompareItems($customerId) as $itemId) { + $this->getConnection()->update( + $this->getTable('catalog_compare_item'), + ['list_id' => $listId], + ['catalog_compare_item_id = ?' => (int)$itemId] + ); + } + } + + /** + * Remove compare list if customer compare list empty + * + * @param int|null $customerId + */ + public function removeCompareList(?int $customerId) + { + if (empty($this->getCustomerCompareItems($customerId))) { + $this->getConnection()->delete( + $this->getTable('catalog_compare_list'), + ['customer_id = ?' => $customerId] + ); + } + } + + /** + * Get customer compare items + * + * @param int|null $customerId + * @return array + */ + private function getCustomerCompareItems(?int $customerId): array + { + if ($customerId) { + $select = $this->getConnection()->select()-> + from( + $this->getTable('catalog_compare_item') + )->where( + 'customer_id = ?', + $customerId + ); + + return $this->getConnection()->fetchCol($select); + } + + return []; + } + /** * Retrieve comapre products attribute set ids * diff --git a/app/code/Magento/Catalog/etc/db_schema.xml b/app/code/Magento/Catalog/etc/db_schema.xml index ddd66a5bf04bd..ce34914a2f5d4 100644 --- a/app/code/Magento/Catalog/etc/db_schema.xml +++ b/app/code/Magento/Catalog/etc/db_schema.xml @@ -547,6 +547,8 @@ default="0" comment="Product ID"/> + @@ -558,6 +560,8 @@ referenceColumn="entity_id" onDelete="CASCADE"/> + @@ -573,6 +577,25 @@ + + + + + + + + + + + + + + +
addProductToCompareList = $addProductToCompareList; + $this->getCompareList = $getCompareList; + $this->maskedListIdToCompareListId = $maskedListIdToCompareListId; + $this->getListIdByCustomerId = $getListIdByCustomerId; + } + + /** + * Add products to compare list + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return Value|mixed|void + * + * @throws GraphQlInputException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (empty($args['input']['uid'])) { + throw new GraphQlInputException(__('"uid" value must be specified.')); + } + + if (!isset($args['input']['products'])) { + throw new GraphQlInputException(__('"products" value must be specified.')); + } + + try { + $listId = $this->maskedListIdToCompareListId->execute($args['input']['uid'], $context->getUserId()); + } catch (LocalizedException $exception) { + throw new GraphQlInputException(__($exception->getMessage())); + } + + + if (!$listId) { + throw new GraphQlInputException(__('"uid" value does not exist')); + } + + try { + $this->addProductToCompareList->execute($listId, $args['input']['products'], $context); + } catch (\Exception $exception) { + throw new GraphQlInputException(__($exception->getMessage())); + } + + return $this->getCompareList->execute($listId, $context); + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Resolver/AssignCompareListToCustomer.php b/app/code/Magento/CompareListGraphQl/Model/Resolver/AssignCompareListToCustomer.php new file mode 100644 index 0000000000000..8c43fcf5e9299 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Resolver/AssignCompareListToCustomer.php @@ -0,0 +1,110 @@ +setCustomerToCompareList = $setCustomerToCompareList; + $this->maskedListIdToCompareListId = $maskedListIdToCompareListId; + $this->getCompareList = $getCompareList; + } + + /** + * Assign compare list to customer + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return Value|mixed|void + * + * @throws GraphQlInputException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (empty($args['uid'])) { + throw new GraphQlInputException(__('"uid" value must be specified')); + } + + if (!$context->getUserId()) { + throw new GraphQlInputException(__('Customer must be logged')); + } + + try { + $listId = $this->maskedListIdToCompareListId->execute($args['uid']); + } catch (LocalizedException $exception) { + throw new GraphQlInputException(__($exception->getMessage())); + } + + if ($listId) { + try { + $result = $this->setCustomerToCompareList->execute($listId, $context->getUserId(), $context); + if ($result) { + return [ + 'result' => true, + 'compare_list' => $this->getCompareList->execute((int)$result->getListId(), $context) + ]; + } + } catch (LocalizedException $exception) { + throw new GraphQlInputException( + __('Something was wrong during assigning customer.') + ); + } + } + + return [ + 'result' => false + ]; + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Resolver/CompareList.php b/app/code/Magento/CompareListGraphQl/Model/Resolver/CompareList.php new file mode 100644 index 0000000000000..861f3ce36d555 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Resolver/CompareList.php @@ -0,0 +1,85 @@ +getCompareList = $getCompareList; + $this->maskedListIdToCompareListId = $maskedListIdToCompareListId; + } + + /** + * Get compare list + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return array|Value|mixed + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @throws GraphQlInputException + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (empty($args['uid'])) { + throw new GraphQlInputException(__('"uid" value must be specified')); + } + + try { + $listId = $this->maskedListIdToCompareListId->execute($args['uid'], $context->getUserId()); + } catch (LocalizedException $exception) { + throw new GraphQlInputException(__($exception->getMessage())); + } + + if (!$listId) { + return null; + } + + return $this->getCompareList->execute($listId, $context); + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Resolver/CreateCompareList.php b/app/code/Magento/CompareListGraphQl/Model/Resolver/CreateCompareList.php new file mode 100644 index 0000000000000..9b0e8fd18298f --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Resolver/CreateCompareList.php @@ -0,0 +1,124 @@ +mathRandom = $mathRandom; + $this->getListIdByCustomerId = $getListIdByCustomerId; + $this->addProductToCompareList = $addProductToCompareList; + $this->getCompareList = $getCompareList; + $this->createCompareList = $createCompareList; + } + + /** + * Create compare list + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return Value|mixed|void + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @throws GraphQlInputException + * @throws LocalizedException + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $customerId = $context->getUserId(); + $products = !empty($args['input']['products']) ? $args['input']['products'] : []; + $generatedListId = $this->mathRandom->getUniqueHash(); + $listId = 0; + + try { + if ((0 === $customerId || null === $customerId)) { + $listId = $this->createCompareList->execute($generatedListId); + $this->addProductToCompareList->execute($listId, $products, $context); + } + + if ($customerId) { + $listId = $this->getListIdByCustomerId->execute($customerId); + if ($listId) { + $this->addProductToCompareList->execute($listId, $products, $context); + } else { + $listId = $this->createCompareList->execute($generatedListId, $customerId); + $this->addProductToCompareList->execute($listId, $products, $context); + } + } + } catch (LocalizedException $exception) { + throw new GraphQlInputException( + __('Something was wrong during creating compare list') + ); + } + + return $this->getCompareList->execute($listId, $context); + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Resolver/CustomerCompareList.php b/app/code/Magento/CompareListGraphQl/Model/Resolver/CustomerCompareList.php new file mode 100644 index 0000000000000..84d0aad0c9a71 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Resolver/CustomerCompareList.php @@ -0,0 +1,73 @@ +getCompareList = $getCompareList; + $this->getListIdByCustomerId = $getListIdByCustomerId; + } + + /** + * Get customer compare list + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return Value|mixed|void + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $listId = $this->getListIdByCustomerId->execute((int)$context->getUserId()); + + if (!$listId) { + return null; + } + + return $this->getCompareList->execute($listId, $context); + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Resolver/DeleteCompareList.php b/app/code/Magento/CompareListGraphQl/Model/Resolver/DeleteCompareList.php new file mode 100644 index 0000000000000..a811478718985 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Resolver/DeleteCompareList.php @@ -0,0 +1,137 @@ +compareListFactory = $compareListFactory; + $this->compareListResource = $compareListResource; + $this->maskedListIdToCompareListId = $maskedListIdToCompareListId; + $this->getListIdByCustomerId = $getListIdByCustomerId; + } + + /** + * Remove compare list + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return Value|mixed|void + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @throws GraphQlInputException + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (empty($args['uid'])) { + throw new GraphQlInputException(__('"uid" value must be specified')); + } + + try { + $listId = $this->maskedListIdToCompareListId->execute($args['uid'], $context->getUserId()); + } catch (LocalizedException $exception) { + throw new GraphQlInputException(__($exception->getMessage())); + } + $removed = ['result' => false]; + + if ($userId = $context->getUserId()) { + $customerListId = $this->getListIdByCustomerId->execute($userId); + if ($listId === $customerListId) { + try { + $removed['result'] = $this->deleteCompareList($customerListId); + } catch (LocalizedException $exception) { + throw new GraphQlInputException( + __('Something was wrong during removing compare list') + ); + } + } + } + + if ($listId) { + try { + $removed['result'] = $this->deleteCompareList($listId); + } catch (LocalizedException $exception) { + throw new GraphQlInputException( + __('Something was wrong during removing compare list') + ); + } + } + + return $removed; + } + + /** + * Delete compare list + * + * @param int|null $listId + * @return bool + */ + private function deleteCompareList(?int $listId): bool + { + $compareList = $this->compareListFactory->create(); + $compareList->setListId($listId); + $this->compareListResource->delete($compareList); + + return true; + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Resolver/RemoveProductsFromCompareList.php b/app/code/Magento/CompareListGraphQl/Model/Resolver/RemoveProductsFromCompareList.php new file mode 100644 index 0000000000000..6e4e4d8951cb9 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Resolver/RemoveProductsFromCompareList.php @@ -0,0 +1,140 @@ +getCompareList = $getCompareList; + $this->removeFromCompareList = $removeFromCompareList; + $this->maskedListIdToCompareListId = $maskedListIdToCompareListId; + $this->getListIdByCustomerId = $getListIdByCustomerId; + } + + /** + * Remove products from compare list + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return Value|mixed|void + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @throws GraphQlInputException + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($args['input']['products'])) { + throw new GraphQlInputException(__('"products" value must be specified.')); + } + + if (empty($args['input']['uid'])) { + throw new GraphQlInputException(__('"uid" value must be specified.')); + } + + try { + $listId = $this->maskedListIdToCompareListId->execute($args['input']['uid'], $context->getUserId()); + } catch (LocalizedException $exception) { + throw new GraphQlInputException(__($exception->getMessage())); + } + + if (!$listId) { + throw new GraphQlInputException(__('"uid" value does not exist')); + } + + if ($userId = $context->getUserId()) { + $customerListId = $this->getListIdByCustomerId->execute($userId); + if ($listId === $customerListId) { + $this->removeFromCompareList($customerListId, $args); + } + } + + try { + $this->removeFromCompareList->execute($listId, $args['input']['products']); + } catch (LocalizedException $exception) { + throw new GraphQlInputException( + __('Something was wrong during removing products from compare list') + ); + } + + return $this->getCompareList->execute($listId, $context); + } + + /** + * Remove products from compare list + * + * @param int $listId + * @param array $args + * @throws GraphQlInputException + */ + private function removeFromCompareList(int $listId, array $args): void + { + try { + $this->removeFromCompareList->execute($listId, $args['input']['products']); + } catch (LocalizedException $exception) { + throw new GraphQlInputException( + __('Something was wrong during removing products from compare list') + ); + } + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Service/AddToCompareList.php b/app/code/Magento/CompareListGraphQl/Model/Service/AddToCompareList.php new file mode 100644 index 0000000000000..6f976bdd07d4d --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Service/AddToCompareList.php @@ -0,0 +1,106 @@ +compareItemFactory = $compareItemFactory; + $this->productRepository = $productRepository; + $this->itemCollection = $collection; + } + + /** + * Add products to compare list + * + * @param int $listId + * @param array $products + * @param ContextInterface $context + * + * @return int + * @throws \Exception + */ + public function execute(int $listId, array $products, ContextInterface $context): int + { + $storeId = (int)$context->getExtensionAttributes()->getStore()->getStoreId(); + $customerId = $context->getUserId(); + if ($customerId) { + $this->itemCollection->setListIdToCustomerCompareItems($listId, $customerId); + } + + if (count($products)) { + $existedProducts = $this->itemCollection->getProductsByListId($listId); + foreach ($products as $productId) { + if (array_search($productId, $existedProducts) === false) { + if ($this->productExists($productId)) { + $item = $this->compareItemFactory->create(); + if ($customerId) { + $item->setCustomerId($customerId); + } + $item->addProductData($productId); + $item->setStoreId($storeId); + $item->setListId($listId); + $item->save(); + } + } + } + } + + return (int)$listId; + } + + /** + * Check product exists. + * + * @param int $productId + * + * @return bool + */ + private function productExists($productId) + { + try { + $product = $this->productRepository->getById((int)$productId); + return !empty($product->getId()); + } catch (NoSuchEntityException $e) { + return false; + } + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Service/Collection/GetComparableItemsCollection.php b/app/code/Magento/CompareListGraphQl/Model/Service/Collection/GetComparableItemsCollection.php new file mode 100644 index 0000000000000..e766ec85248a1 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Service/Collection/GetComparableItemsCollection.php @@ -0,0 +1,87 @@ +itemCollectionFactory = $itemCollectionFactory; + $this->catalogProductVisibility = $catalogProductVisibility; + $this->catalogConfig = $catalogConfig; + $this->compareProduct = $compareHelper; + } + + /** + * Get collection of comparable items + * + * @param int $listId + * @param ContextInterface $context + * + * @return Collection + */ + public function execute(int $listId, ContextInterface $context): Collection + { + $this->compareProduct->setAllowUsedFlat(false); + $this->items = $this->itemCollectionFactory->create(); + $this->items->setListId($listId); + $this->items->useProductItem()->setStoreId($context->getExtensionAttributes()->getStore()->getStoreId()); + $this->items->addAttributeToSelect( + $this->catalogConfig->getProductAttributes() + )->loadComparableAttributes()->addMinimalPrice()->addTaxPercents()->setVisibility( + $this->catalogProductVisibility->getVisibleInSiteIds() + ); + + return $this->items; + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Service/CreateCompareList.php b/app/code/Magento/CompareListGraphQl/Model/Service/CreateCompareList.php new file mode 100644 index 0000000000000..089bdb1adef17 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Service/CreateCompareList.php @@ -0,0 +1,57 @@ +compareListFactory = $compareListFactory; + $this->compareListResource = $compareListResource; + } + + /** + * Created new compare list + * + * @param string $maskedId + * @param int $customerId + * + * @return int + */ + public function execute(string $maskedId, ?int $customerId = null): int + { + $compareList = $this->compareListFactory->create(); + $compareList->setListIdMask($maskedId); + $compareList->setCustomerId($customerId); + $this->compareListResource->save($compareList); + + return (int)$compareList->getListId(); + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Service/Customer/GetListIdByCustomerId.php b/app/code/Magento/CompareListGraphQl/Model/Service/Customer/GetListIdByCustomerId.php new file mode 100644 index 0000000000000..c6437683f6ba7 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Service/Customer/GetListIdByCustomerId.php @@ -0,0 +1,59 @@ +compareListFactory = $compareListFactory; + $this->resourceCompareList = $resourceCompareList; + } + + /** + * Get listId by Customer ID + * + * @param int $customerId + * + * @return int|null + */ + public function execute(int $customerId): ?int + { + if ($customerId) { + /** @var CompareList $compareList */ + $compareList = $this->compareListFactory->create(); + $this->resourceCompareList->load($compareList, $customerId, 'customer_id'); + return (int)$compareList->getListId(); + } + + return null; + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Service/Customer/SetCustomerToCompareList.php b/app/code/Magento/CompareListGraphQl/Model/Service/Customer/SetCustomerToCompareList.php new file mode 100644 index 0000000000000..72216c6c70a16 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Service/Customer/SetCustomerToCompareList.php @@ -0,0 +1,117 @@ +validateCustomer = $validateCustomer; + $this->compareListFactory = $compareListFactory; + $this->resourceCompareList = $resourceCompareList; + $this->getListIdByCustomerId = $getListIdByCustomerId; + $this->itemCollectionFactory = $itemCollectionFactory; + $this->addProductToCompareList = $addProductToCompareList; + } + + /** + * Set customer to compare list + * + * @param int $listId + * @param int $customerId + * + * @return CompareList + * + * @throws GraphQlAuthenticationException + * @throws GraphQlInputException + * @throws GraphQlNoSuchEntityException + */ + public function execute(int $listId, int $customerId, ContextInterface $context): ?CompareList + { + if ($this->validateCustomer->execute($customerId)) { + /** @var CompareList $compareListModel */ + $compareList = $this->compareListFactory->create(); + $customerListId = $this->getListIdByCustomerId->execute($customerId); + $this->resourceCompareList->load($compareList, $listId, 'list_id'); + if ($customerListId) { + $this->items = $this->itemCollectionFactory->create(); + $products = $this->items->getProductsByListId($listId); + $this->addProductToCompareList->execute($customerListId, $products, $context); + $this->resourceCompareList->delete($compareList); + $compareList = $this->compareListFactory->create(); + $this->resourceCompareList->load($compareList, $customerListId, 'list_id'); + return $compareList; + } + $compareList->setCustomerId($customerId); + $this->resourceCompareList->save($compareList); + return $compareList; + } + + return null; + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Service/Customer/ValidateCustomer.php b/app/code/Magento/CompareListGraphQl/Model/Service/Customer/ValidateCustomer.php new file mode 100644 index 0000000000000..ab16b17240b1c --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Service/Customer/ValidateCustomer.php @@ -0,0 +1,94 @@ +authentication = $authentication; + $this->accountManagement = $accountManagement; + $this->customerRepository = $customerRepository; + } + + /** + * Customer validate + * + * @param int $customerId + * + * @return int + * + * @throws GraphQlAuthenticationException + * @throws GraphQlInputException + * @throws GraphQlNoSuchEntityException + */ + public function execute(int $customerId): int + { + try { + $customer = $this->customerRepository->getById($customerId); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException( + __('Customer with id "%customer_id" does not exist.', ['customer_id' => $customerId]), + $e + ); + } catch (LocalizedException $e) { + throw new GraphQlInputException(__($e->getMessage())); + } + + if (true === $this->authentication->isLocked($customerId)) { + throw new GraphQlAuthenticationException(__('The account is locked.')); + } + + try { + $confirmationStatus = $this->accountManagement->getConfirmationStatus($customerId); + } catch (LocalizedException $e) { + throw new GraphQlInputException(__($e->getMessage())); + } + + if ($confirmationStatus === AccountManagementInterface::ACCOUNT_CONFIRMATION_REQUIRED) { + throw new GraphQlAuthenticationException(__("This account isn't confirmed. Verify and try again.")); + } + + return (int)$customer->getId(); + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Service/GetComparableAttributes.php b/app/code/Magento/CompareListGraphQl/Model/Service/GetComparableAttributes.php new file mode 100644 index 0000000000000..5b051a3825aac --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Service/GetComparableAttributes.php @@ -0,0 +1,53 @@ +comparableItemsCollection = $comparableItemsCollection; + } + + /** + * Get comparable attributes + * + * @param int $listId + * @param ContextInterface $context + * + * @return array + */ + public function execute(int $listId, ContextInterface $context): array + { + $attributes = []; + $itemsCollection = $this->comparableItemsCollection->execute($listId, $context); + foreach ($itemsCollection->getComparableAttributes() as $item) { + $attributes[] = [ + 'code' => $item->getAttributeCode(), + 'label' => $item->getStoreLabel() + ]; + } + + return $attributes; + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Service/GetComparableItems.php b/app/code/Magento/CompareListGraphQl/Model/Service/GetComparableItems.php new file mode 100644 index 0000000000000..1cf42553718fd --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Service/GetComparableItems.php @@ -0,0 +1,122 @@ +blockListCompare = $listCompare; + $this->comparableItemsCollection = $comparableItemsCollection; + $this->productRepository = $productRepository; + } + + /** + * Get comparable items + * + * @param int $listId + * @param ContextInterface $context + * + * @return array + * @throws GraphQlInputException + */ + public function execute(int $listId, ContextInterface $context) + { + $items = []; + foreach ($this->comparableItemsCollection->execute($listId, $context) as $item) { + /** @var Product $item */ + $items[] = [ + 'uid' => $item->getId(), + 'product' => $this->getProductData((int)$item->getId()), + 'attributes' => $this->getProductComparableAttributes($listId, $item, $context) + ]; + } + + return $items; + } + + /** + * Get product data + * + * @param int $productId + * + * @return array + * + * @throws GraphQlInputException + */ + private function getProductData(int $productId): array + { + $productData = []; + try { + $item = $this->productRepository->getById($productId); + $productData = $item->getData(); + $productData['model'] = $item; + } catch (LocalizedException $e) { + throw new GraphQlInputException(__($e->getMessage())); + } + + return $productData; + } + + /** + * Get comparable attributes for product + * + * @param int $listId + * @param Product $product + * @param ContextInterface $context + * + * @return array + */ + private function getProductComparableAttributes(int $listId, Product $product, ContextInterface $context): array + { + $attributes = []; + $itemsCollection = $this->comparableItemsCollection->execute($listId, $context); + foreach ($itemsCollection->getComparableAttributes() as $item) { + $attributes[] = [ + 'code' => $item->getAttributeCode(), + 'value' => $this->blockListCompare->getProductAttributeValue($product, $item) + ]; + } + + return $attributes; + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Service/GetCompareList.php b/app/code/Magento/CompareListGraphQl/Model/Service/GetCompareList.php new file mode 100644 index 0000000000000..5cef555479838 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Service/GetCompareList.php @@ -0,0 +1,75 @@ +comparableItemsService = $comparableItemsService; + $this->comparableAttributesService = $comparableAttributesService; + $this->compareListIdToMaskedListId = $compareListIdToMaskedListId; + } + + /** + * Get compare list information + * + * @param int $listId + * @param ContextInterface $context + * + * @return array + * @throws GraphQlInputException + */ + public function execute(int $listId, ContextInterface $context) + { + try { + $maskedListId = $this->compareListIdToMaskedListId->execute($listId, $context->getUserId()); + } catch (LocalizedException $exception) { + throw new GraphQlInputException(__($exception->getMessage())); + } + $comparableItems = $this->comparableItemsService->execute($listId, $context); + + return [ + 'uid' => $maskedListId, + 'items' => $comparableItems, + 'attributes' => $this->comparableAttributesService->execute($listId, $context), + 'item_count' => count($comparableItems) + ]; + } +} diff --git a/app/code/Magento/CompareListGraphQl/Model/Service/RemoveFromCompareList.php b/app/code/Magento/CompareListGraphQl/Model/Service/RemoveFromCompareList.php new file mode 100644 index 0000000000000..dda400559a412 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/Model/Service/RemoveFromCompareList.php @@ -0,0 +1,59 @@ +compareItemFactory = $compareItemFactory; + $this->compareItemResource = $compareItemResource; + } + + /** + * Remove products from compare list + * + * @param int $listId + * @param array $products + */ + public function execute(int $listId, array $products) + { + foreach ($products as $productId) { + /* @var $item Item */ + $item = $this->compareItemFactory->create(); + $item->setListId($listId); + $this->compareItemResource->loadByProduct($item, $productId); + if ($item->getId()) { + $this->compareItemResource->delete($item); + } + } + } +} diff --git a/app/code/Magento/CompareListGraphQl/README.md b/app/code/Magento/CompareListGraphQl/README.md new file mode 100644 index 0000000000000..ed1c38ab33a3b --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/README.md @@ -0,0 +1,4 @@ +# CompareListGraphQl module + +The CompareListGraphQl module is designed to implement compare product functionality. + diff --git a/app/code/Magento/CompareListGraphQl/composer.json b/app/code/Magento/CompareListGraphQl/composer.json new file mode 100644 index 0000000000000..dd9c998857258 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/composer.json @@ -0,0 +1,23 @@ +{ + "name": "magento/module-compare-list-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.3.0||~7.4.0", + "magento/framework": "*", + "magento/module-catalog": "*", + "magento/module-customer": "*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\CompareListGraphQl\\": "" + } + } +} diff --git a/app/code/Magento/CompareListGraphQl/etc/module.xml b/app/code/Magento/CompareListGraphQl/etc/module.xml new file mode 100644 index 0000000000000..b3c330fc38df2 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/etc/module.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/app/code/Magento/CompareListGraphQl/etc/schema.graphqls b/app/code/Magento/CompareListGraphQl/etc/schema.graphqls new file mode 100644 index 0000000000000..e533d476ddd59 --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/etc/schema.graphqls @@ -0,0 +1,64 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +type ComparableItem { + uid: ID! @doc(description: "The unique ID of an item in a compare list") + product: ProductInterface! @doc(description: "Contains details about a product in a compare list") + attributes: [ProductAttribute]! @doc(description: "An array of product attributes that can be used to compare products") +} + +type ProductAttribute { + code: String! @doc(description: "The unique identifier for a product attribute code.") + value: String! @doc(description:"The display value of the attribute") +} + +type ComparableAttribute { + code: String! @doc(description: "An attribute code that is enabled for product comparisons") + label: String! @doc(description: "The label of the attribute code") +} + +type CompareList { + uid: ID! @doc(description: "The unique ID assigned to the compare list") + items: [ComparableItem] @doc(description: "An array of products to compare") + attributes: [ComparableAttribute] @doc(description: "An array of attributes that can be used for comparing products") + item_count: Int! @doc(description: "The number of items in the compare list") +} + +type Customer { + compare_list: CompareList @resolver(class: "\\Magento\\CompareListGraphQl\\Model\\Resolver\\CustomerCompareList") @doc(description: "The contents of the customer's compare list") +} + +type Query { + compareList(uid: ID!): CompareList @resolver(class: "\\Magento\\CompareListGraphQl\\Model\\Resolver\\CompareList") @doc(description: "Return products that have been added to the specified compare list") +} + +type Mutation { + createCompareList(input: CreateCompareListInput): CompareList @resolver(class: "\\Magento\\CompareListGraphQl\\Model\\Resolver\\CreateCompareList") @doc(description: "Creates a new compare list. The compare list is saved for logged in customers") + addProductsToCompareList(input: AddProductsToCompareListInput): CompareList @resolver(class: "\\Magento\\CompareListGraphQl\\Model\\Resolver\\AddProductsToCompareList") @doc(description: "Add products to the specified compare list") + removeProductsFromCompareList(input: RemoveProductsFromCompareListInput): CompareList @resolver(class: "\\Magento\\CompareListGraphQl\\Model\\Resolver\\RemoveProductsFromCompareList") @doc(description: "Remove products from the specified compare list") + assignCompareListToCustomer(uid: ID!): AssignCompareListToCustomerOutput @resolver(class: "\\Magento\\CompareListGraphQl\\Model\\Resolver\\AssignCompareListToCustomer") @doc(description: "Assign the specified compare list to the logged in customer") + deleteCompareList(uid: ID!): DeleteCompareListOutput @resolver(class: "\\Magento\\CompareListGraphQl\\Model\\Resolver\\DeleteCompareList") @doc(description: "Delete the specified compare list") +} + +input CreateCompareListInput { + products: [ID!] @doc(description: "An array of product IDs to add to the compare list") +} + +input AddProductsToCompareListInput { + uid: ID!, @doc(description: "The unique identifier of the compare list to modify") + products: [ID!]! @doc(description: "An array of product IDs to add to the compare list") +} + +input RemoveProductsFromCompareListInput { + uid: ID!, @doc(description: "The unique identifier of the compare list to modify") + products: [ID!]! @doc(description: "An array of product IDs to remove from the compare list") +} + +type DeleteCompareListOutput { + result: Boolean! @doc(description: "Indicates whether the compare list was successfully deleted") +} + +type AssignCompareListToCustomerOutput { + result: Boolean! + compare_list: CompareList @doc(description: "The contents of the customer's compare list") +} diff --git a/app/code/Magento/CompareListGraphQl/registration.php b/app/code/Magento/CompareListGraphQl/registration.php new file mode 100644 index 0000000000000..bb764b439273d --- /dev/null +++ b/app/code/Magento/CompareListGraphQl/registration.php @@ -0,0 +1,14 @@ +productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); + } + /** + * Create compare list without product + */ + public function testCreateCompareListWithoutProducts() + { + $response = $this->createCompareList(); + $uid = $response['createCompareList']['uid']; + $this->uidAssertion($uid); + } + + /** + * Create compare list with products + * + * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + */ + public function testCreateCompareListWithProducts() + { + $product1 = $this->productRepository->get(self::PRODUCT_SKU_1); + $product2 = $this->productRepository->get(self::PRODUCT_SKU_2); + + $mutation = <<getId()}, {$product2->getId()}]}){ + uid + items { + product { + sku + } + } + } +} +MUTATION; + $response = $this->graphQlMutation($mutation); + $uid = $response['createCompareList']['uid']; + $this->uidAssertion($uid); + $this->itemsAssertion($response['createCompareList']['items']); + } + + /** + * Add products to compare list + * + * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + */ + public function testAddProductToCompareList() + { + $compareList = $this->createCompareList(); + $uid = $compareList['createCompareList']['uid']; + $this->assertEquals(0, $compareList['createCompareList']['item_count'],'Incorrect count'); + $this->uidAssertion($uid); + $response = $this->addProductsToCompareList($uid); + $resultUid = $response['addProductsToCompareList']['uid']; + $this->uidAssertion($resultUid); + $this->itemsAssertion($response['addProductsToCompareList']['items']); + $this->assertEquals(2, $response['addProductsToCompareList']['item_count'],'Incorrect count'); + $this->assertResponseFields( + $response['addProductsToCompareList']['attributes'], + [ + [ + 'code'=> 'sku', + 'label'=> 'SKU' + ], + [ + 'code'=> 'description', + 'label'=> 'Description' + ], + [ + 'code'=> 'short_description', + 'label'=> 'Short Description' + ] + ] + ); + } + + /** + * Remove products from compare list + * + * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + */ + public function testRemoveProductFromCompareList() + { + $compareList = $this->createCompareList(); + $uid = $compareList['createCompareList']['uid']; + $this->uidAssertion($uid); + $addProducts = $this->addProductsToCompareList($uid); + $this->itemsAssertion($addProducts['addProductsToCompareList']['items']); + $this->assertCount(2, $addProducts['addProductsToCompareList']['items']); + $product = $this->productRepository->get(self::PRODUCT_SKU_1); + $removeFromCompareList = <<getId()}]}) { + uid + items { + product { + sku + } + } + } +} +MUTATION; + $response = $this->graphQlMutation($removeFromCompareList); + $this->assertCount(1, $response['removeProductsFromCompareList']['items']); + } + + /** + * Get compare list query + * + * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + */ + public function testGetCompareList() + { + $compareList = $this->createCompareList(); + $uid = $compareList['createCompareList']['uid']; + $this->uidAssertion($uid); + $addProducts = $this->addProductsToCompareList($uid); + $this->itemsAssertion($addProducts['addProductsToCompareList']['items']); + $query = <<graphQlQuery($query); + $this->itemsAssertion($response['compareList']['items']); + } + + /** + * Remove compare list + * + * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + */ + public function testDeleteCompareList() + { + $compareList = $this->createCompareList(); + $uid = $compareList['createCompareList']['uid']; + $this->uidAssertion($uid); + $addProducts = $this->addProductsToCompareList($uid); + $this->itemsAssertion($addProducts['addProductsToCompareList']['items']); + $deleteCompareList = <<graphQlMutation($deleteCompareList); + $this->assertTrue($response['deleteCompareList']['result']); + $response1 = $this->graphQlMutation($deleteCompareList); + $this->assertFalse($response1['deleteCompareList']['result']); + } + + /** + * Assign compare list to customer + * + * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testAssignCompareListToCustomer() + { + $compareList = $this->createCompareList(); + $uid = $compareList['createCompareList']['uid']; + $this->uidAssertion($uid); + $addProducts = $this->addProductsToCompareList($uid); + $this->itemsAssertion($addProducts['addProductsToCompareList']['items']); + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + $customerQuery = <<graphQlQuery( + $customerQuery, + [], + '', + $this->getCustomerAuthHeaders($currentEmail, $currentPassword) + ); + $this->assertArrayHasKey('compare_list', $customerResponse['customer']); + $this->assertNull($customerResponse['customer']['compare_list']); + + $assignCompareListToCustomer = <<graphQlMutation( + $assignCompareListToCustomer, + [], + '', + $this->getCustomerAuthHeaders($currentEmail, $currentPassword) + ); + $this->assertTrue($assignResponse['assignCompareListToCustomer']['result']); + + $customerAssignedResponse = $this->graphQlQuery( + $customerQuery, + [], + '', + $this->getCustomerAuthHeaders($currentEmail, $currentPassword) + ); + + $this->assertArrayHasKey('compare_list', $customerAssignedResponse['customer']); + $this->uidAssertion($customerAssignedResponse['customer']['compare_list']['uid']); + $this->itemsAssertion($customerAssignedResponse['customer']['compare_list']['items']); + } + + /** + * Assign compare list of one customer to another customer + * + * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + * @magentoApiDataFixture Magento/Customer/_files/two_customers.php + */ + public function testCompareListsNotAccessibleBetweenCustomers() + { + $uidCustomer1 = $this->createCompareListForCustomer('customer@example.com', 'password'); + $uidcustomer2 = $this->createCompareListForCustomer('customer_two@example.com', 'password'); + $assignCompareListToCustomer = <<expectException(ResponseContainsErrorsException::class); + $this->expectExceptionMessage($expectedExceptionsMessage); + //customer2 not allowed to assign compareList belonging to customer1 + $this->graphQlMutation( + $assignCompareListToCustomer, + [], + '', + $this->getCustomerAuthHeaders('customer_two@example.com', 'password') + ); + + $deleteCompareList = <<expectException(ResponseContainsErrorsException::class); + $this->expectExceptionMessage($expectedExceptionsMessage); + //customer1 not allowed to delete compareList belonging to customer2 + $this->graphQlMutation( + $assignCompareListToCustomer, + [], + '', + $this->getCustomerAuthHeaders('customer@example.com', 'password') + ); + + } + + /** + * Get customer Header + * + * @param string $email + * @param string $password + * + * @return array + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; + } + + /** + * Create compare list + * + * @return array + */ + private function createCompareList(): array + { + $mutation = <<graphQlMutation($mutation); + } + + private function createCompareListForCustomer(string $username, string $password): string + { + $compareListCustomer = <<graphQlMutation( + $compareListCustomer, + [], + '', + $this->getCustomerAuthHeaders($username, $password) + ); + + return $response['createCompareList']['uid']; + } + + /** + * Add products to compare list + * + * @param $uid + * + * @return array + */ + private function addProductsToCompareList($uid): array + { + $product1 = $this->productRepository->get(self::PRODUCT_SKU_1); + $product2 = $this->productRepository->get(self::PRODUCT_SKU_2); + $addProductsToCompareList = <<getId()}, {$product2->getId()}]}) { + uid + item_count + attributes{code label} + items { + product { + sku + } + } + } +} +MUTATION; + return $this->graphQlMutation($addProductsToCompareList); + } + + /** + * Assert UID + * + * @param string $uid + */ + private function uidAssertion(string $uid) + { + $this->assertIsString($uid); + $this->assertEquals(32, strlen($uid)); + } + + /** + * Assert products + * + * @param array $items + */ + private function itemsAssertion(array $items) + { + $this->assertArrayHasKey(0, $items); + $this->assertArrayHasKey(1, $items); + $this->assertEquals(self::PRODUCT_SKU_1, $items[0]['product']['sku']); + $this->assertEquals(self::PRODUCT_SKU_2, $items[1]['product']['sku']); + } +}