diff --git a/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Cart/GiftMessage.php b/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Cart/GiftMessage.php index c317221fb6ef7..2ce51c8bbf19d 100644 --- a/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Cart/GiftMessage.php +++ b/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Cart/GiftMessage.php @@ -66,7 +66,7 @@ public function resolve( array $args = null ) { if (!isset($value['model'])) { - throw new GraphQlInputException(__('"model" value should be specified')); + throw new GraphQlInputException(__('"model" value must be specified')); } $cart = $value['model']; diff --git a/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Cart/Item/GiftMessage.php b/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Cart/Item/GiftMessage.php new file mode 100644 index 0000000000000..a9a8e682612cc --- /dev/null +++ b/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Cart/Item/GiftMessage.php @@ -0,0 +1,97 @@ +itemRepository = $itemRepository; + $this->giftMessageHelper = $giftMessageHelper; + } + + /** + * Return information about Gift message for item cart + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return array|Value|mixed + * @throws GraphQlInputException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($value['model'])) { + throw new GraphQlInputException(__('"model" value must be specified')); + } + + $quoteItem = $value['model']; + + if (!$this->giftMessageHelper->isMessagesAllowed('items', $quoteItem)) { + return null; + } + + if (!$this->giftMessageHelper->isMessagesAllowed('item', $quoteItem)) { + return null; + } + + try { + $giftItemMessage = $this->itemRepository->get($quoteItem->getQuoteId(), $quoteItem->getItemId()); + } catch (LocalizedException $e) { + throw new GraphQlInputException(__('Can\'t load cart item')); + } + + if (!isset($giftItemMessage)) { + return null; + } + + return [ + 'to' => $giftItemMessage->getRecipient() ?? '', + 'from' => $giftItemMessage->getSender() ?? '', + 'message'=> $giftItemMessage->getMessage() ?? '' + ]; + } +} diff --git a/app/code/Magento/GiftMessageGraphQl/README.md b/app/code/Magento/GiftMessageGraphQl/README.md index fa2e02116b66c..d73a058e0db9c 100644 --- a/app/code/Magento/GiftMessageGraphQl/README.md +++ b/app/code/Magento/GiftMessageGraphQl/README.md @@ -1,3 +1,3 @@ # GiftMessageGraphQl -**GiftMessageGraphQl** provides information about gift messages for cart, cart items, order and order items. +**GiftMessageGraphQl** provides information about gift messages for carts, cart items, orders and order items. diff --git a/app/code/Magento/GiftMessageGraphQl/etc/graphql/di.xml b/app/code/Magento/GiftMessageGraphQl/etc/graphql/di.xml new file mode 100644 index 0000000000000..bce5b7063e6b9 --- /dev/null +++ b/app/code/Magento/GiftMessageGraphQl/etc/graphql/di.xml @@ -0,0 +1,18 @@ + + + + + + + sales/gift_options/allow_order + sales/gift_options/allow_items + + + + diff --git a/app/code/Magento/GiftMessageGraphQl/etc/schema.graphqls b/app/code/Magento/GiftMessageGraphQl/etc/schema.graphqls index f14c812a9a5f3..ad18054abca13 100644 --- a/app/code/Magento/GiftMessageGraphQl/etc/schema.graphqls +++ b/app/code/Magento/GiftMessageGraphQl/etc/schema.graphqls @@ -1,20 +1,47 @@ # Copyright © Magento, Inc. All rights reserved. # See COPYING.txt for license details. +type StoreConfig { + allow_order : String @doc(description: "The value of the Allow Gift Messages on Order Level option") + allow_items : String @doc(description: "The value of the Allow Gift Messages for Order Items option") +} + type Cart { gift_message: GiftMessage @resolver (class: "\\Magento\\GiftMessageGraphQl\\Model\\Resolver\\Cart\\GiftMessage") @doc(description: "The entered gift message for the cart") } -type SalesItemInterface { - gift_message: GiftMessage @doc(description: "The entered gift message for the order item") +type SimpleCartItem { + gift_message: GiftMessage @resolver (class: "\\Magento\\GiftMessageGraphQl\\Model\\Resolver\\Cart\\Item\\GiftMessage") @doc(description: "The entered gift message for the cart item") } -type CustomerOrder { - gift_message: GiftMessage @resolver (class: "\\Magento\\GiftMessageGraphQl\\Model\\Resolver\\Order\\GiftMessage") @doc(description: "The entered gift message for the order") +type ConfigurableCartItem { + gift_message: GiftMessage @resolver (class: "\\Magento\\GiftMessageGraphQl\\Model\\Resolver\\Cart\\Item\\GiftMessage") @doc(description: "The entered gift message for the cart item") +} + +type BundleCartItem { + gift_message: GiftMessage @resolver (class: "\\Magento\\GiftMessageGraphQl\\Model\\Resolver\\Cart\\Item\\GiftMessage") @doc(description: "The entered gift message for the cart item") +} + +type GiftMessage @doc(description: "Contains the text of a gift message, its sender, and recipient") { + to: String! @doc(description: "Recipient name") + from: String! @doc(description: "Sender name") + message: String! @doc(description: "Gift message text") +} + +input CartItemUpdateInput { + gift_message: GiftMessageInput @doc(description: "Gift message details for the cart item") } -type GiftMessage { - to: String! @doc(description: "Recepient name") +input GiftMessageInput @doc(description: "Contains the text of a gift message, its sender, and recipient") { + to: String! @doc(description: "Recipient name") from: String! @doc(description: "Sender name") message: String! @doc(description: "Gift message text") } + +type SalesItemInterface { + gift_message: GiftMessage @doc(description: "The entered gift message for the order item") +} + +type CustomerOrder { + gift_message: GiftMessage @resolver (class: "\\Magento\\GiftMessageGraphQl\\Model\\Resolver\\Order\\GiftMessage") @doc(description: "The entered gift message for the order") +} diff --git a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/UpdateCartItems.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/UpdateCartItems.php new file mode 100644 index 0000000000000..c2e94b215956e --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/UpdateCartItems.php @@ -0,0 +1,157 @@ +cartItemRepository = $cartItemRepository; + $this->updateCartItem = $updateCartItem; + $this->itemRepository = $itemRepository; + $this->giftMessageHelper = $giftMessageHelper; + $this->giftMessageFactory = $giftMessageFactory; + } + + /** + * Process cart items + * + * @param Quote $cart + * @param array $items + * + * @throws GraphQlInputException + * @throws LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public function processCartItems(Quote $cart, array $items): void + { + foreach ($items as $item) { + if (empty($item['cart_item_id'])) { + throw new GraphQlInputException(__('Required parameter "cart_item_id" for "cart_items" is missing.')); + } + + $itemId = (int)$item['cart_item_id']; + $customizableOptions = $item['customizable_options'] ?? []; + $cartItem = $cart->getItemById($itemId); + + if ($cartItem && $cartItem->getParentItemId()) { + throw new GraphQlInputException(__('Child items may not be updated.')); + } + + if (count($customizableOptions) === 0 && !isset($item['quantity'])) { + throw new GraphQlInputException(__('Required parameter "quantity" for "cart_items" is missing.')); + } + + $quantity = (float)$item['quantity']; + + if ($quantity <= 0.0) { + $this->cartItemRepository->deleteById((int)$cart->getId(), $itemId); + } else { + $this->updateCartItem->execute($cart, $itemId, $quantity, $customizableOptions); + } + + if (!empty($item['gift_message'])) { + try { + if (!$this->giftMessageHelper->isMessagesAllowed('items', $cartItem)) { + continue; + } + if (!$this->giftMessageHelper->isMessagesAllowed('item', $cartItem)) { + continue; + } + + /** @var MessageInterface $giftItemMessage */ + $giftItemMessage = $this->itemRepository->get($cart->getEntityId(), $itemId); + + if (empty($giftItemMessage)) { + /** @var MessageInterface $giftMessage */ + $giftMessage = $this->giftMessageFactory->create(); + $this->updateGiftMessageForItem($cart, $giftMessage, $item, $itemId); + continue; + } + } catch (LocalizedException $exception) { + throw new GraphQlInputException(__('Gift Message cannot be updated.')); + } + + $this->updateGiftMessageForItem($cart, $giftItemMessage, $item, $itemId); + } + } + } + + /** + * Update Gift Message for Quote item + * + * @param Quote $cart + * @param MessageInterface $giftItemMessage + * @param array $item + * @param int $itemId + * + * @throws GraphQlInputException + */ + private function updateGiftMessageForItem(Quote $cart, MessageInterface $giftItemMessage, array $item, int $itemId) + { + try { + $giftItemMessage->setRecipient($item['gift_message']['to']); + $giftItemMessage->setSender($item['gift_message']['from']); + $giftItemMessage->setMessage($item['gift_message']['message']); + $this->itemRepository->save($cart->getEntityId(), $giftItemMessage, $itemId); + } catch (LocalizedException $exception) { + throw new GraphQlInputException(__('Gift Message cannot be updated')); + } + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php index fa90f08e4b553..005baaad0e1e5 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php @@ -14,53 +14,43 @@ use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\Quote\Api\CartItemRepositoryInterface; use Magento\Quote\Api\CartRepositoryInterface; -use Magento\Quote\Model\Quote; use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; -use Magento\QuoteGraphQl\Model\Cart\UpdateCartItem; +use Magento\QuoteGraphQl\Model\CartItem\DataProvider\UpdateCartItems as UpdateCartItemsProvider; /** * @inheritdoc */ class UpdateCartItems implements ResolverInterface { - /** - * @var UpdateCartItem - */ - private $updateCartItem; - /** * @var GetCartForUser */ private $getCartForUser; /** - * @var CartItemRepositoryInterface + * @var CartRepositoryInterface */ - private $cartItemRepository; + private $cartRepository; /** - * @var CartRepositoryInterface + * @var UpdateCartItemsProvider */ - private $cartRepository; + private $updateCartItems; /** - * @param GetCartForUser $getCartForUser - * @param CartItemRepositoryInterface $cartItemRepository - * @param UpdateCartItem $updateCartItem + * @param GetCartForUser $getCartForUser * @param CartRepositoryInterface $cartRepository + * @param UpdateCartItemsProvider $updateCartItems */ public function __construct( GetCartForUser $getCartForUser, - CartItemRepositoryInterface $cartItemRepository, - UpdateCartItem $updateCartItem, - CartRepositoryInterface $cartRepository + CartRepositoryInterface $cartRepository, + UpdateCartItemsProvider $updateCartItems ) { $this->getCartForUser = $getCartForUser; - $this->cartItemRepository = $cartItemRepository; - $this->updateCartItem = $updateCartItem; $this->cartRepository = $cartRepository; + $this->updateCartItems = $updateCartItems; } /** @@ -71,6 +61,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value if (empty($args['input']['cart_id'])) { throw new GraphQlInputException(__('Required parameter "cart_id" is missing.')); } + $maskedCartId = $args['input']['cart_id']; if (empty($args['input']['cart_items']) @@ -78,13 +69,13 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value ) { throw new GraphQlInputException(__('Required parameter "cart_items" is missing.')); } - $cartItems = $args['input']['cart_items']; + $cartItems = $args['input']['cart_items']; $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId); try { - $this->processCartItems($cart, $cartItems); + $this->updateCartItems->processCartItems($cart, $cartItems); $this->cartRepository->save($cart); } catch (NoSuchEntityException $e) { throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); @@ -98,39 +89,4 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value ], ]; } - - /** - * Process cart items - * - * @param Quote $cart - * @param array $items - * @throws GraphQlInputException - * @throws LocalizedException - */ - private function processCartItems(Quote $cart, array $items): void - { - foreach ($items as $item) { - if (empty($item['cart_item_id'])) { - throw new GraphQlInputException(__('Required parameter "cart_item_id" for "cart_items" is missing.')); - } - $itemId = (int)$item['cart_item_id']; - $customizableOptions = $item['customizable_options'] ?? []; - - $cartItem = $cart->getItemById($itemId); - if ($cartItem && $cartItem->getParentItemId()) { - throw new GraphQlInputException(__('Child items may not be updated.')); - } - - if (count($customizableOptions) === 0 && !isset($item['quantity'])) { - throw new GraphQlInputException(__('Required parameter "quantity" for "cart_items" is missing.')); - } - $quantity = (float)$item['quantity']; - - if ($quantity <= 0.0) { - $this->cartItemRepository->deleteById((int)$cart->getId(), $itemId); - } else { - $this->updateCartItem->execute($cart, $itemId, $quantity, $customizableOptions); - } - } - } } diff --git a/app/code/Magento/QuoteGraphQl/composer.json b/app/code/Magento/QuoteGraphQl/composer.json index 0652d39b5f426..25f089cf75a62 100644 --- a/app/code/Magento/QuoteGraphQl/composer.json +++ b/app/code/Magento/QuoteGraphQl/composer.json @@ -13,7 +13,8 @@ "magento/module-customer-graph-ql": "*", "magento/module-sales": "*", "magento/module-directory": "*", - "magento/module-graph-ql": "*" + "magento/module-graph-ql": "*", + "magento/module-gift-message": "*" }, "suggest": { "magento/module-graph-ql-cache": "*" diff --git a/app/code/Magento/Wishlist/Model/Wishlist/AddProductsToWishlist.php b/app/code/Magento/Wishlist/Model/Wishlist/AddProductsToWishlist.php new file mode 100644 index 0000000000000..7acfb503a5ad0 --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/AddProductsToWishlist.php @@ -0,0 +1,164 @@ +productRepository = $productRepository; + $this->buyRequestBuilder = $buyRequestBuilder; + $this->wishlistResource = $wishlistResource; + } + + /** + * Adding products to wishlist + * + * @param Wishlist $wishlist + * @param array $wishlistItems + * + * @return WishlistOutput + * + * @throws AlreadyExistsException + */ + public function execute(Wishlist $wishlist, array $wishlistItems): WishlistOutput + { + foreach ($wishlistItems as $wishlistItem) { + $this->addItemToWishlist($wishlist, $wishlistItem); + } + + $wishlistOutput = $this->prepareOutput($wishlist); + + if ($wishlist->isObjectNew() || count($wishlistOutput->getErrors()) !== count($wishlistItems)) { + $this->wishlistResource->save($wishlist); + } + + return $wishlistOutput; + } + + /** + * Add product item to wishlist + * + * @param Wishlist $wishlist + * @param WishlistItem $wishlistItem + * + * @return void + */ + private function addItemToWishlist(Wishlist $wishlist, WishlistItem $wishlistItem): void + { + $sku = $wishlistItem->getParentSku() ?? $wishlistItem->getSku(); + + try { + $product = $this->productRepository->get($sku, false, null, true); + } catch (NoSuchEntityException $e) { + $this->addError( + __('Could not find a product with SKU "%sku"', ['sku' => $sku])->render(), + self::ERROR_PRODUCT_NOT_FOUND + ); + + return; + } + + try { + $options = $this->buyRequestBuilder->build($wishlistItem, (int) $product->getId()); + $result = $wishlist->addNewItem($product, $options); + + if (is_string($result)) { + $this->addError($result); + } + } catch (LocalizedException $exception) { + $this->addError($exception->getMessage()); + } catch (\Throwable $e) { + $this->addError( + __( + 'Could not add the product with SKU "%sku" to the wishlist:: %message', + ['sku' => $sku, 'message' => $e->getMessage()] + )->render() + ); + } + } + + /** + * Add wishlist line item error + * + * @param string $message + * @param string|null $code + * + * @return void + */ + private function addError(string $message, string $code = null): void + { + $this->errors[] = new Data\Error( + $message, + $code ?? self::ERROR_UNDEFINED + ); + } + + /** + * Prepare output + * + * @param Wishlist $wishlist + * + * @return WishlistOutput + */ + private function prepareOutput(Wishlist $wishlist): WishlistOutput + { + $output = new WishlistOutput($wishlist, $this->errors); + $this->errors = []; + + return $output; + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/BundleDataProvider.php b/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/BundleDataProvider.php new file mode 100644 index 0000000000000..1cfa316c3cd01 --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/BundleDataProvider.php @@ -0,0 +1,55 @@ +getSelectedOptions() as $optionData) { + $optionData = \explode('/', base64_decode($optionData->getId())); + + if ($this->isProviderApplicable($optionData) === false) { + continue; + } + + [, $optionId, $optionValueId, $optionQuantity] = $optionData; + + $bundleOptionsData['bundle_option'][$optionId] = $optionValueId; + $bundleOptionsData['bundle_option_qty'][$optionId] = $optionQuantity; + } + + return $bundleOptionsData; + } + + /** + * Checks whether this provider is applicable for the current option + * + * @param array $optionData + * + * @return bool + */ + private function isProviderApplicable(array $optionData): bool + { + return $optionData[0] === self::PROVIDER_OPTION_TYPE; + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/BuyRequestBuilder.php b/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/BuyRequestBuilder.php new file mode 100644 index 0000000000000..1f7ddce345b1c --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/BuyRequestBuilder.php @@ -0,0 +1,63 @@ +dataObjectFactory = $dataObjectFactory; + $this->providers = $providers; + } + + /** + * Build product buy request for adding to wishlist + * + * @param WishlistItem $wishlistItemData + * @param int|null $productId + * + * @return DataObject + */ + public function build(WishlistItem $wishlistItemData, ?int $productId = null): DataObject + { + $requestData = [ + [ + 'qty' => $wishlistItemData->getQuantity(), + ] + ]; + + foreach ($this->providers as $provider) { + $requestData[] = $provider->execute($wishlistItemData, $productId); + } + + return $this->dataObjectFactory->create(['data' => array_merge(...$requestData)]); + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/BuyRequestDataProviderInterface.php b/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/BuyRequestDataProviderInterface.php new file mode 100644 index 0000000000000..fac45d7f86c7c --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/BuyRequestDataProviderInterface.php @@ -0,0 +1,26 @@ +getSelectedOptions() as $optionData) { + $optionData = \explode('/', base64_decode($optionData->getId())); + + if ($this->isProviderApplicable($optionData) === false) { + continue; + } + + [, $optionId, $optionValue] = $optionData; + + $customizableOptionsData[$optionId][] = $optionValue; + } + + foreach ($wishlistItemData->getEnteredOptions() as $option) { + $optionData = \explode('/', base64_decode($option->getId())); + + if ($this->isProviderApplicable($optionData) === false) { + continue; + } + + [, $optionId] = $optionData; + + $customizableOptionsData[$optionId][] = $option->getValue(); + } + + if (empty($customizableOptionsData)) { + return $customizableOptionsData; + } + + $result = ['options' => $this->flattenOptionValues($customizableOptionsData)]; + + if ($productId) { + $result += ['product' => $productId]; + } + + return $result; + } + + /** + * Flatten option values for non-multiselect customizable options + * + * @param array $customizableOptionsData + * + * @return array + */ + private function flattenOptionValues(array $customizableOptionsData): array + { + foreach ($customizableOptionsData as $optionId => $optionValue) { + if (count($optionValue) === 1) { + $customizableOptionsData[$optionId] = $optionValue[0]; + } + } + + return $customizableOptionsData; + } + + /** + * Checks whether this provider is applicable for the current option + * + * @param array $optionData + * @return bool + */ + private function isProviderApplicable(array $optionData): bool + { + return $optionData[0] === self::PROVIDER_OPTION_TYPE; + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/DownloadableLinkDataProvider.php b/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/DownloadableLinkDataProvider.php new file mode 100644 index 0000000000000..1ad1a0b64706a --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/DownloadableLinkDataProvider.php @@ -0,0 +1,54 @@ +getSelectedOptions() as $optionData) { + $optionData = \explode('/', base64_decode($optionData->getId())); + + if ($this->isProviderApplicable($optionData) === false) { + continue; + } + + [, $linkId] = $optionData; + + $linksData[] = $linkId; + } + + return $linksData ? ['links' => $linksData] : []; + } + + /** + * Checks whether this provider is applicable for the current option + * + * @param array $optionData + * + * @return bool + */ + private function isProviderApplicable(array $optionData): bool + { + return $optionData[0] === self::PROVIDER_OPTION_TYPE; + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/SuperAttributeDataProvider.php b/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/SuperAttributeDataProvider.php new file mode 100644 index 0000000000000..01e29bcf80c0b --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/SuperAttributeDataProvider.php @@ -0,0 +1,64 @@ +getSelectedOptions() as $optionData) { + $optionData = \explode('/', base64_decode($optionData->getId())); + + if ($this->isProviderApplicable($optionData) === false) { + continue; + } + + [, $attributeId, $valueIndex] = $optionData; + + $configurableData[$attributeId] = $valueIndex; + } + + if (empty($configurableData)) { + return $configurableData; + } + + $result = ['super_attribute' => $configurableData]; + + if ($productId) { + $result += ['product' => $productId]; + } + + return $result; + } + + /** + * Checks whether this provider is applicable for the current option + * + * @param array $optionData + * + * @return bool + */ + private function isProviderApplicable(array $optionData): bool + { + return $optionData[0] === self::PROVIDER_OPTION_TYPE; + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/SuperGroupDataProvider.php b/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/SuperGroupDataProvider.php new file mode 100644 index 0000000000000..a11f631f1f5aa --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/BuyRequest/SuperGroupDataProvider.php @@ -0,0 +1,64 @@ +getSelectedOptions() as $optionData) { + $optionData = \explode('/', base64_decode($optionData->getId())); + + if ($this->isProviderApplicable($optionData) === false) { + continue; + } + + [, $simpleProductId, $quantity] = $optionData; + + $groupedData[$simpleProductId] = $quantity; + } + + if (empty($groupedData)) { + return $groupedData; + } + + $result = ['super_group' => $groupedData]; + + if ($productId) { + $result += ['product' => $productId]; + } + + return $result; + } + + /** + * Checks whether this provider is applicable for the current option + * + * @param array $optionData + * + * @return bool + */ + private function isProviderApplicable(array $optionData): bool + { + return $optionData[0] === self::PROVIDER_OPTION_TYPE; + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/Config.php b/app/code/Magento/Wishlist/Model/Wishlist/Config.php new file mode 100644 index 0000000000000..041e9f1ca0a21 --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/Config.php @@ -0,0 +1,46 @@ +scopeConfig = $scopeConfig; + } + + /** + * Check whether the wishlist is enabled or not + * + * @return bool + */ + public function isEnabled(): bool + { + return $this->scopeConfig->isSetFlag( + self::XML_PATH_WISHLIST_ACTIVE, + ScopeInterface::SCOPE_STORES + ); + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/Data/EnteredOption.php b/app/code/Magento/Wishlist/Model/Wishlist/Data/EnteredOption.php new file mode 100644 index 0000000000000..0d6b2a2302540 --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/Data/EnteredOption.php @@ -0,0 +1,54 @@ +id = $id; + $this->value = $value; + } + + /** + * Get entered option id + * + * @return string + */ + public function getId(): string + { + return $this->id; + } + + /** + * Get entered option value + * + * @return string + */ + public function getValue(): string + { + return $this->value; + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/Data/Error.php b/app/code/Magento/Wishlist/Model/Wishlist/Data/Error.php new file mode 100644 index 0000000000000..cb8420169fa8a --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/Data/Error.php @@ -0,0 +1,54 @@ +message = $message; + $this->code = $code; + } + + /** + * Get error message + * + * @return string + */ + public function getMessage(): string + { + return $this->message; + } + + /** + * Get error code + * + * @return string + */ + public function getCode(): string + { + return $this->code; + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/Data/SelectedOption.php b/app/code/Magento/Wishlist/Model/Wishlist/Data/SelectedOption.php new file mode 100644 index 0000000000000..129a61c0a2a6c --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/Data/SelectedOption.php @@ -0,0 +1,37 @@ +id = $id; + } + + /** + * Get selected option id + * + * @return string + */ + public function getId(): string + { + return $this->id; + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/Data/WishlistItem.php b/app/code/Magento/Wishlist/Model/Wishlist/Data/WishlistItem.php new file mode 100644 index 0000000000000..236b7f1eee72d --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/Data/WishlistItem.php @@ -0,0 +1,146 @@ +quantity = $quantity; + $this->sku = $sku; + $this->parentSku = $parentSku; + $this->id = $id; + $this->description = $description; + $this->selectedOptions = $selectedOptions; + $this->enteredOptions = $enteredOptions; + } + + /** + * Get wishlist item id + * + * @return int|null + */ + public function getId(): ?int + { + return $this->id; + } + + /** + * Get wishlist item description + * + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * Get sku + * + * @return string|null + */ + public function getSku(): ?string + { + return $this->sku; + } + + /** + * Get quantity + * + * @return float + */ + public function getQuantity(): float + { + return $this->quantity; + } + + /** + * Get parent sku + * + * @return string|null + */ + public function getParentSku(): ?string + { + return $this->parentSku; + } + + /** + * Get selected options + * + * @return SelectedOption[]|null + */ + public function getSelectedOptions(): ?array + { + return $this->selectedOptions; + } + + /** + * Get entered options + * + * @return EnteredOption[]|null + */ + public function getEnteredOptions(): ?array + { + return $this->enteredOptions; + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/Data/WishlistItemFactory.php b/app/code/Magento/Wishlist/Model/Wishlist/Data/WishlistItemFactory.php new file mode 100644 index 0000000000000..153e8451bae31 --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/Data/WishlistItemFactory.php @@ -0,0 +1,75 @@ +createSelectedOptions($data['selected_options']) : [], + isset($data['entered_options']) ? $this->createEnteredOptions($data['entered_options']) : [] + ); + } + + /** + * Create array of Entered Options + * + * @param array $options + * + * @return EnteredOption[] + */ + private function createEnteredOptions(array $options): array + { + return \array_map( + function (array $option) { + if (!isset($option['id'], $option['value'])) { + throw new InputException( + __('Required fields are not present EnteredOption.id, EnteredOption.value') + ); + } + return new EnteredOption($option['id'], $option['value']); + }, + $options + ); + } + + /** + * Create array of Selected Options + * + * @param string[] $options + * + * @return SelectedOption[] + */ + private function createSelectedOptions(array $options): array + { + return \array_map( + function ($option) { + return new SelectedOption($option); + }, + $options + ); + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/Data/WishlistOutput.php b/app/code/Magento/Wishlist/Model/Wishlist/Data/WishlistOutput.php new file mode 100644 index 0000000000000..fc7db9ec910fb --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/Data/WishlistOutput.php @@ -0,0 +1,56 @@ +wishlist = $wishlist; + $this->errors = $errors; + } + + /** + * Get Wishlist + * + * @return Wishlist + */ + public function getWishlist(): Wishlist + { + return $this->wishlist; + } + + /** + * Get errors happened during adding products to wishlist + * + * @return Error[] + */ + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/RemoveProductsFromWishlist.php b/app/code/Magento/Wishlist/Model/Wishlist/RemoveProductsFromWishlist.php new file mode 100644 index 0000000000000..d143830064752 --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/RemoveProductsFromWishlist.php @@ -0,0 +1,133 @@ +wishlistItemFactory = $wishlistItemFactory; + $this->wishlistItemResource = $wishlistItemResource; + } + + /** + * Removing items from wishlist + * + * @param Wishlist $wishlist + * @param array $wishlistItemsIds + * + * @return WishlistOutput + */ + public function execute(Wishlist $wishlist, array $wishlistItemsIds): WishlistOutput + { + foreach ($wishlistItemsIds as $wishlistItemId) { + $this->removeItemFromWishlist((int) $wishlistItemId); + } + + return $this->prepareOutput($wishlist); + } + + /** + * Remove product item from wishlist + * + * @param int $wishlistItemId + * + * @return void + */ + private function removeItemFromWishlist(int $wishlistItemId): void + { + try { + /** @var WishlistItem $wishlistItem */ + $wishlistItem = $this->wishlistItemFactory->create(); + $this->wishlistItemResource->load($wishlistItem, $wishlistItemId); + if (!$wishlistItem->getId()) { + $this->addError( + __('Could not find a wishlist item with ID "%id"', ['id' => $wishlistItemId])->render(), + self::ERROR_PRODUCT_NOT_FOUND + ); + } + + $this->wishlistItemResource->delete($wishlistItem); + } catch (\Exception $e) { + $this->addError( + __( + 'We can\'t delete the item with ID "%id" from the Wish List right now.', + ['id' => $wishlistItemId] + )->render() + ); + } + } + + /** + * Add wishlist line item error + * + * @param string $message + * @param string|null $code + * + * @return void + */ + private function addError(string $message, string $code = null): void + { + $this->errors[] = new Data\Error( + $message, + $code ?? self::ERROR_UNDEFINED + ); + } + + /** + * Prepare output + * + * @param Wishlist $wishlist + * + * @return WishlistOutput + */ + private function prepareOutput(Wishlist $wishlist): WishlistOutput + { + $output = new WishlistOutput($wishlist, $this->errors); + $this->errors = []; + + return $output; + } +} diff --git a/app/code/Magento/Wishlist/Model/Wishlist/UpdateProductsInWishlist.php b/app/code/Magento/Wishlist/Model/Wishlist/UpdateProductsInWishlist.php new file mode 100644 index 0000000000000..4abcada138362 --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Wishlist/UpdateProductsInWishlist.php @@ -0,0 +1,138 @@ +buyRequestBuilder = $buyRequestBuilder; + $this->wishlistItemFactory = $wishlistItemFactory; + $this->wishlistItemResource = $wishlistItemResource; + } + + /** + * Adding products to wishlist + * + * @param Wishlist $wishlist + * @param array $wishlistItems + * + * @return WishlistOutput + */ + public function execute(Wishlist $wishlist, array $wishlistItems): WishlistOutput + { + foreach ($wishlistItems as $wishlistItem) { + $this->updateItemInWishlist($wishlist, $wishlistItem); + } + + return $this->prepareOutput($wishlist); + } + + /** + * Update product item in wishlist + * + * @param Wishlist $wishlist + * @param WishlistItemData $wishlistItemData + * + * @return void + */ + private function updateItemInWishlist(Wishlist $wishlist, WishlistItemData $wishlistItemData): void + { + try { + $options = $this->buyRequestBuilder->build($wishlistItemData); + /** @var WishlistItem $wishlistItem */ + $wishlistItem = $this->wishlistItemFactory->create(); + $this->wishlistItemResource->load($wishlistItem, $wishlistItemData->getId()); + $wishlistItem->setDescription($wishlistItemData->getDescription()); + $resultItem = $wishlist->updateItem($wishlistItem, $options); + + if (is_string($resultItem)) { + $this->addError($resultItem); + } + } catch (LocalizedException $exception) { + $this->addError($exception->getMessage()); + } + } + + /** + * Add wishlist line item error + * + * @param string $message + * @param string|null $code + * + * @return void + */ + private function addError(string $message, string $code = null): void + { + $this->errors[] = new Data\Error( + $message, + $code ?? self::ERROR_UNDEFINED + ); + } + + /** + * Prepare output + * + * @param Wishlist $wishlist + * + * @return WishlistOutput + */ + private function prepareOutput(Wishlist $wishlist): WishlistOutput + { + $output = new WishlistOutput($wishlist, $this->errors); + $this->errors = []; + + return $output; + } +} diff --git a/app/code/Magento/Wishlist/etc/graphql/di.xml b/app/code/Magento/Wishlist/etc/graphql/di.xml new file mode 100644 index 0000000000000..9726376bf30be --- /dev/null +++ b/app/code/Magento/Wishlist/etc/graphql/di.xml @@ -0,0 +1,20 @@ + + + + + + + Magento\Wishlist\Model\Wishlist\BuyRequest\SuperAttributeDataProvider + Magento\Wishlist\Model\Wishlist\BuyRequest\CustomizableOptionDataProvider + Magento\Wishlist\Model\Wishlist\BuyRequest\BundleDataProvider + Magento\Wishlist\Model\Wishlist\BuyRequest\DownloadableLinkDataProvider + Magento\Wishlist\Model\Wishlist\BuyRequest\SuperGroupDataProvider + + + + diff --git a/app/code/Magento/WishlistGraphQl/Mapper/WishlistDataMapper.php b/app/code/Magento/WishlistGraphQl/Mapper/WishlistDataMapper.php new file mode 100644 index 0000000000000..9cc1404613e41 --- /dev/null +++ b/app/code/Magento/WishlistGraphQl/Mapper/WishlistDataMapper.php @@ -0,0 +1,35 @@ + $wishlist->getId(), + 'sharing_code' => $wishlist->getSharingCode(), + 'updated_at' => $wishlist->getUpdatedAt(), + 'items_count' => $wishlist->getItemsCount(), + 'name' => $wishlist->getName(), + 'model' => $wishlist, + ]; + } +} diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/AddProductsToWishlist.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/AddProductsToWishlist.php new file mode 100644 index 0000000000000..11c8446a72a9d --- /dev/null +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/AddProductsToWishlist.php @@ -0,0 +1,158 @@ +wishlistResource = $wishlistResource; + $this->wishlistFactory = $wishlistFactory; + $this->wishlistConfig = $wishlistConfig; + $this->addProductsToWishlist = $addProductsToWishlist; + $this->wishlistDataMapper = $wishlistDataMapper; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!$this->wishlistConfig->isEnabled()) { + throw new GraphQlInputException(__('The wishlist is not currently available.')); + } + + $customerId = $context->getUserId(); + + /* Guest checking */ + if (null === $customerId || 0 === $customerId) { + throw new GraphQlAuthorizationException(__('The current user cannot perform operations on wishlist')); + } + + $wishlistId = ((int) $args['wishlistId']) ?: null; + $wishlist = $this->getWishlist($wishlistId, $customerId); + + if (null === $wishlist->getId() || $customerId !== (int) $wishlist->getCustomerId()) { + throw new GraphQlInputException(__('The wishlist was not found.')); + } + + $wishlistItems = $this->getWishlistItems($args['wishlistItems']); + $wishlistOutput = $this->addProductsToWishlist->execute($wishlist, $wishlistItems); + + return [ + 'wishlist' => $this->wishlistDataMapper->map($wishlistOutput->getWishlist()), + 'userInputErrors' => array_map( + function (Error $error) { + return [ + 'code' => $error->getCode(), + 'message' => $error->getMessage(), + ]; + }, + $wishlistOutput->getErrors() + ) + ]; + } + + /** + * Get wishlist items + * + * @param array $wishlistItemsData + * + * @return array + */ + private function getWishlistItems(array $wishlistItemsData): array + { + $wishlistItems = []; + + foreach ($wishlistItemsData as $wishlistItemData) { + $wishlistItems[] = (new WishlistItemFactory())->create($wishlistItemData); + } + + return $wishlistItems; + } + + /** + * Get customer wishlist + * + * @param int|null $wishlistId + * @param int|null $customerId + * + * @return Wishlist + */ + private function getWishlist(?int $wishlistId, ?int $customerId): Wishlist + { + $wishlist = $this->wishlistFactory->create(); + + if ($wishlistId !== null && $wishlistId > 0) { + $this->wishlistResource->load($wishlist, $wishlistId); + } elseif ($customerId !== null) { + $wishlist->loadByCustomerId($customerId, true); + } + + return $wishlist; + } +} diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/CustomerWishlistResolver.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/CustomerWishlistResolver.php index a84ce0e965b6d..cad574ef56ed2 100644 --- a/app/code/Magento/WishlistGraphQl/Model/Resolver/CustomerWishlistResolver.php +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/CustomerWishlistResolver.php @@ -9,9 +9,11 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Wishlist\Model\Wishlist; +use Magento\Wishlist\Model\Wishlist\Config as WishlistConfig; use Magento\Wishlist\Model\WishlistFactory; /** @@ -24,12 +26,21 @@ class CustomerWishlistResolver implements ResolverInterface */ private $wishlistFactory; + /** + * @var WishlistConfig + */ + private $wishlistConfig; + /** * @param WishlistFactory $wishlistFactory + * @param WishlistConfig $wishlistConfig */ - public function __construct(WishlistFactory $wishlistFactory) - { + public function __construct( + WishlistFactory $wishlistFactory, + WishlistConfig $wishlistConfig + ) { $this->wishlistFactory = $wishlistFactory; + $this->wishlistConfig = $wishlistConfig; } /** @@ -42,6 +53,10 @@ public function resolve( array $value = null, array $args = null ) { + if (!$this->wishlistConfig->isEnabled()) { + throw new GraphQlInputException(__('The wishlist is not currently available.')); + } + if (false === $context->getExtensionAttributes()->getIsCustomer()) { throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.')); } diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/RemoveProductsFromWishlist.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/RemoveProductsFromWishlist.php new file mode 100644 index 0000000000000..1c741361ea7b7 --- /dev/null +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/RemoveProductsFromWishlist.php @@ -0,0 +1,144 @@ +wishlistResource = $wishlistResource; + $this->wishlistConfig = $wishlistConfig; + $this->wishlistFactory = $wishlistFactory; + $this->wishlistDataMapper = $wishlistDataMapper; + $this->removeProductsFromWishlist = $removeProductsFromWishlist; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!$this->wishlistConfig->isEnabled()) { + throw new GraphQlInputException(__('The wishlist is not currently available.')); + } + + $customerId = $context->getUserId(); + + /* Guest checking */ + if ($customerId === null || 0 === $customerId) { + throw new GraphQlAuthorizationException(__('The current user cannot perform operations on wishlist')); + } + + $wishlistId = ((int) $args['wishlistId']) ?: null; + $wishlist = $this->getWishlist($wishlistId, $customerId); + + if (null === $wishlist->getId() || $customerId !== (int) $wishlist->getCustomerId()) { + throw new GraphQlInputException(__('The wishlist was not found.')); + } + + $wishlistItemsIds = $args['wishlistItemsIds']; + $wishlistOutput = $this->removeProductsFromWishlist->execute($wishlist, $wishlistItemsIds); + + if (!empty($wishlistItemsIds)) { + $this->wishlistResource->save($wishlist); + } + + return [ + 'wishlist' => $this->wishlistDataMapper->map($wishlistOutput->getWishlist()), + 'userInputErrors' => \array_map( + function (Error $error) { + return [ + 'code' => $error->getCode(), + 'message' => $error->getMessage(), + ]; + }, + $wishlistOutput->getErrors() + ) + ]; + } + + /** + * Get customer wishlist + * + * @param int|null $wishlistId + * @param int|null $customerId + * + * @return Wishlist + */ + private function getWishlist(?int $wishlistId, ?int $customerId): Wishlist + { + $wishlist = $this->wishlistFactory->create(); + + if ($wishlistId !== null && $wishlistId > 0) { + $this->wishlistResource->load($wishlist, $wishlistId); + } elseif ($customerId !== null) { + $wishlist->loadByCustomerId($customerId, true); + } + + return $wishlist; + } +} diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/UpdateProductsInWishlist.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/UpdateProductsInWishlist.php new file mode 100644 index 0000000000000..50a56863596c0 --- /dev/null +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/UpdateProductsInWishlist.php @@ -0,0 +1,163 @@ +wishlistResource = $wishlistResource; + $this->wishlistFactory = $wishlistFactory; + $this->wishlistConfig = $wishlistConfig; + $this->updateProductsInWishlist = $updateProductsInWishlist; + $this->wishlistDataMapper = $wishlistDataMapper; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!$this->wishlistConfig->isEnabled()) { + throw new GraphQlInputException(__('The wishlist is not currently available.')); + } + + $customerId = $context->getUserId(); + + /* Guest checking */ + if (null === $customerId || $customerId === 0) { + throw new GraphQlAuthorizationException(__('The current user cannot perform operations on wishlist')); + } + + $wishlistId = ((int) $args['wishlistId']) ?: null; + $wishlist = $this->getWishlist($wishlistId, $customerId); + + if (null === $wishlist->getId() || $customerId !== (int) $wishlist->getCustomerId()) { + throw new GraphQlInputException(__('The wishlist was not found.')); + } + + $wishlistItems = $args['wishlistItems']; + $wishlistItems = $this->getWishlistItems($wishlistItems); + $wishlistOutput = $this->updateProductsInWishlist->execute($wishlist, $wishlistItems); + + if (count($wishlistOutput->getErrors()) !== count($wishlistItems)) { + $this->wishlistResource->save($wishlist); + } + + return [ + 'wishlist' => $this->wishlistDataMapper->map($wishlistOutput->getWishlist()), + 'userInputErrors' => \array_map( + function (Error $error) { + return [ + 'code' => $error->getCode(), + 'message' => $error->getMessage(), + ]; + }, + $wishlistOutput->getErrors() + ) + ]; + } + + /** + * Get DTO wishlist items + * + * @param array $wishlistItemsData + * + * @return array + */ + private function getWishlistItems(array $wishlistItemsData): array + { + $wishlistItems = []; + + foreach ($wishlistItemsData as $wishlistItemData) { + $wishlistItems[] = (new WishlistItemFactory())->create($wishlistItemData); + } + + return $wishlistItems; + } + + /** + * Get customer wishlist + * + * @param int|null $wishlistId + * @param int|null $customerId + * + * @return Wishlist + */ + private function getWishlist(?int $wishlistId, ?int $customerId): Wishlist + { + $wishlist = $this->wishlistFactory->create(); + + if (null !== $wishlistId && 0 < $wishlistId) { + $this->wishlistResource->load($wishlist, $wishlistId); + } elseif ($customerId !== null) { + $wishlist->loadByCustomerId($customerId, true); + } + + return $wishlist; + } +} diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php index 792928ab61aaf..09c0a8a935a6c 100644 --- a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php @@ -8,10 +8,12 @@ namespace Magento\WishlistGraphQl\Model\Resolver; use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Wishlist\Model\ResourceModel\Wishlist as WishlistResourceModel; use Magento\Wishlist\Model\Wishlist; +use Magento\Wishlist\Model\Wishlist\Config as WishlistConfig; use Magento\Wishlist\Model\WishlistFactory; use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; @@ -30,14 +32,24 @@ class WishlistResolver implements ResolverInterface */ private $wishlistFactory; + /** + * @var WishlistConfig + */ + private $wishlistConfig; + /** * @param WishlistResourceModel $wishlistResource * @param WishlistFactory $wishlistFactory + * @param WishlistConfig $wishlistConfig */ - public function __construct(WishlistResourceModel $wishlistResource, WishlistFactory $wishlistFactory) - { + public function __construct( + WishlistResourceModel $wishlistResource, + WishlistFactory $wishlistFactory, + WishlistConfig $wishlistConfig + ) { $this->wishlistResource = $wishlistResource; $this->wishlistFactory = $wishlistFactory; + $this->wishlistConfig = $wishlistConfig; } /** @@ -50,6 +62,10 @@ public function resolve( array $value = null, array $args = null ) { + if (!$this->wishlistConfig->isEnabled()) { + throw new GraphQlInputException(__('The wishlist is not currently available.')); + } + $customerId = $context->getUserId(); /* Guest checking */ diff --git a/app/code/Magento/WishlistGraphQl/Test/Unit/CustomerWishlistResolverTest.php b/app/code/Magento/WishlistGraphQl/Test/Unit/CustomerWishlistResolverTest.php index 8385d3ca852a4..017462b4c94c6 100644 --- a/app/code/Magento/WishlistGraphQl/Test/Unit/CustomerWishlistResolverTest.php +++ b/app/code/Magento/WishlistGraphQl/Test/Unit/CustomerWishlistResolverTest.php @@ -14,6 +14,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\GraphQl\Model\Query\ContextExtensionInterface; use Magento\Wishlist\Model\Wishlist; +use Magento\Wishlist\Model\Wishlist\Config; use Magento\Wishlist\Model\WishlistFactory; use Magento\WishlistGraphQl\Model\Resolver\CustomerWishlistResolver; use PHPUnit\Framework\MockObject\MockObject; @@ -48,6 +49,11 @@ class CustomerWishlistResolverTest extends TestCase */ private $resolver; + /** + * @var Config|MockObject + */ + private $wishlistConfigMock; + /** * Build the Testing Environment */ @@ -74,9 +80,12 @@ protected function setUp(): void ->setMethods(['loadByCustomerId', 'getId', 'getSharingCode', 'getUpdatedAt', 'getItemsCount']) ->getMock(); + $this->wishlistConfigMock = $this->createMock(Config::class); + $objectManager = new ObjectManager($this); $this->resolver = $objectManager->getObject(CustomerWishlistResolver::class, [ - 'wishlistFactory' => $this->wishlistFactoryMock + 'wishlistFactory' => $this->wishlistFactoryMock, + 'wishlistConfig' => $this->wishlistConfigMock ]); } @@ -85,6 +94,8 @@ protected function setUp(): void */ public function testThrowExceptionWhenUserNotAuthorized(): void { + $this->wishlistConfigMock->method('isEnabled')->willReturn(true); + // Given $this->extensionAttributesMock->method('getIsCustomer') ->willReturn(false); @@ -107,6 +118,8 @@ public function testThrowExceptionWhenUserNotAuthorized(): void */ public function testFactoryCreatesWishlistByAuthorizedCustomerId(): void { + $this->wishlistConfigMock->method('isEnabled')->willReturn(true); + // Given $this->extensionAttributesMock->method('getIsCustomer') ->willReturn(true); diff --git a/app/code/Magento/WishlistGraphQl/etc/schema.graphqls b/app/code/Magento/WishlistGraphQl/etc/schema.graphqls index deaa66921ba7c..794e90ed9f9a9 100644 --- a/app/code/Magento/WishlistGraphQl/etc/schema.graphqls +++ b/app/code/Magento/WishlistGraphQl/etc/schema.graphqls @@ -6,7 +6,7 @@ type Query { } type Customer { - wishlist: Wishlist! @resolver(class:"\\Magento\\WishlistGraphQl\\Model\\Resolver\\CustomerWishlistResolver") @doc(description: "The wishlist query returns the contents of a customer's wish lists") @cache(cacheable: false) + wishlist: Wishlist! @resolver(class:"\\Magento\\WishlistGraphQl\\Model\\Resolver\\CustomerWishlistResolver") @doc(description: "Contains the contents of a customer's wish lists") @cache(cacheable: false) } type WishlistOutput @doc(description: "Deprecated: `Wishlist` type should be used instead") { @@ -32,3 +32,45 @@ type WishlistItem { added_at: String @doc(description: "The time when the customer added the item to the wish list"), product: ProductInterface @resolver(class: "\\Magento\\WishlistGraphQl\\Model\\Resolver\\ProductResolver") } + +type Mutation { + addProductsToWishlist(wishlistId: ID!, wishlistItems: [WishlistItemInput!]!): AddProductsToWishlistOutput @doc(description: "Adds one or more products to the specified wish list. This mutation supports all product types") @resolver(class: "\\Magento\\WishlistGraphQl\\Model\\Resolver\\AddProductsToWishlist") + removeProductsFromWishlist(wishlistId: ID!, wishlistItemsIds: [ID!]!): RemoveProductsFromWishlistOutput @doc(description: "Removes one or more products from the specified wish list") @resolver(class: "\\Magento\\WishlistGraphQl\\Model\\Resolver\\RemoveProductsFromWishlist") + updateProductsInWishlist(wishlistId: ID!, wishlistItems: [WishlistItemUpdateInput!]!): UpdateProductsInWishlistOutput @doc(description: "Updates one or more products in the specified wish list") @resolver(class: "\\Magento\\WishlistGraphQl\\Model\\Resolver\\UpdateProductsInWishlist") +} + +input WishlistItemInput @doc(description: "Defines the items to add to a wish list") { + sku: String @doc(description: "The SKU of the product to add. For complex product types, specify the child product SKU") + quantity: Float @doc(description: "The amount or number of items to add") + parent_sku: String @doc(description: "For complex product types, the SKU of the parent product") + selected_options: [String!] @doc(description: "An array of strings corresponding to options the customer selected") + entered_options: [EnteredOptionInput!] @doc(description: "An array of options that the customer entered") +} + +type AddProductsToWishlistOutput @doc(description: "Contains the customer's wish list and any errors encountered") { + wishlist: Wishlist! @doc(description: "Contains the wish list with all items that were successfully added") + userInputErrors:[CheckoutUserInputError]! @doc(description: "An array of errors encountered while adding products to a wish list") +} + +input EnteredOptionInput @doc(description: "Defines a customer-entered option") { + id: String! @doc(description: "A base64 encoded ID") + value: String! @doc(description: "Text the customer entered") +} + +type RemoveProductsFromWishlistOutput @doc(description: "Contains the customer's wish list and any errors encountered") { + wishlist: Wishlist! @doc(description: "Contains the wish list with after items were successfully deleted") + userInputErrors:[CheckoutUserInputError]! @doc(description:"An array of errors encountered while deleting products from a wish list") +} + +input WishlistItemUpdateInput @doc(description: "Defines updates to items in a wish list") { + wishlist_item_id: ID @doc(description: "The ID of the wishlist item to update") + quantity: Float @doc(description: "The new amount or number of this item") + description: String @doc(description: "Describes the update") + selected_options: [String!] @doc(description: "An array of strings corresponding to options the customer selected") + entered_options: [EnteredOptionInput!] @doc(description: "An array of options that the customer entered") +} + +type UpdateProductsInWishlistOutput @doc(description: "Contains the customer's wish list and any errors encountered") { + wishlist: Wishlist! @doc(description: "Contains the wish list with all items that were successfully updated") + userInputErrors:[CheckoutUserInputError]! @doc(description:"An array of errors encountered while updating products in a wish list") +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/GiftMessage/Cart/Item/GiftMessageTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/GiftMessage/Cart/Item/GiftMessageTest.php new file mode 100644 index 0000000000000..fa0909d556b3a --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/GiftMessage/Cart/Item/GiftMessageTest.php @@ -0,0 +1,89 @@ +getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoConfigFixture default_store sales/gift_options/allow_items 0 + * @magentoApiDataFixture Magento/GiftMessage/_files/guest/quote_with_item_message.php + * @throws NoSuchEntityException + * @throws Exception + */ + public function testGiftMessageCartForItemNotAllow() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_guest_order_with_gift_message'); + foreach ($this->requestCartResult($maskedQuoteId)['cart']['items'] as $item) { + self::assertArrayHasKey('gift_message', $item); + self::assertNull($item['gift_message']); + } + } + + /** + * @magentoConfigFixture default_store sales/gift_options/allow_items 1 + * @magentoApiDataFixture Magento/GiftMessage/_files/guest/quote_with_item_message.php + * @throws NoSuchEntityException + * @throws Exception + */ + public function testGiftMessageCartForItem() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_guest_order_with_gift_message'); + foreach ($this->requestCartResult($maskedQuoteId)['cart']['items'] as $item) { + self::assertArrayHasKey('gift_message', $item); + self::assertArrayHasKey('to', $item['gift_message']); + self::assertArrayHasKey('from', $item['gift_message']); + self::assertArrayHasKey('message', $item['gift_message']); + } + } + + /** + * @param string $quoteId + * + * @return array|bool|float|int|string + * @throws Exception + */ + private function requestCartResult(string $quoteId) + { + $query = <<graphQlQuery($query); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php index a17bc1aa3821a..0a22f3ca9721c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php @@ -7,8 +7,9 @@ namespace Magento\GraphQl\Quote\Guest; -use Magento\Quote\Model\QuoteFactory; +use Exception; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Quote\Model\QuoteFactory; use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\TestFramework\Helper\Bootstrap; @@ -273,6 +274,81 @@ private function getCartQuery(string $maskedQuoteId) } } } +QUERY; + } + + /** + * @magentoConfigFixture default_store sales/gift_options/allow_items 0 + * @magentoApiDataFixture Magento/GiftMessage/_files/guest/quote_with_item_message.php + * @throws Exception + */ + public function testUpdateGiftMessageCartForItemNotAllow() + { + $query = $this->getUpdateGiftMessageQuery(); + foreach ($this->graphQlMutation($query)['updateCartItems']['cart']['items'] as $item) { + self::assertNull($item['gift_message']); + } + } + + /** + * @magentoConfigFixture default_store sales/gift_options/allow_items 1 + * @magentoApiDataFixture Magento/GiftMessage/_files/guest/quote_with_item_message.php + * @throws Exception + */ + public function testUpdateGiftMessageCartForItem() + { + $query = $this->getUpdateGiftMessageQuery(); + foreach ($this->graphQlMutation($query)['updateCartItems']['cart']['items'] as $item) { + self::assertArrayHasKey('gift_message', $item); + self::assertSame('Alex', $item['gift_message']['to']); + self::assertSame('Mike', $item['gift_message']['from']); + self::assertSame('Best regards.', $item['gift_message']['message']); + } + } + + private function getUpdateGiftMessageQuery() + { + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, 'test_guest_order_with_gift_message', 'reserved_order_id'); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); + $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId(); + + return <<customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->wishlistFactory = $objectManager->get(WishlistFactory::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + } + + /** + * @magentoConfigFixture default_store wishlist/general/active 1 + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Bundle/_files/product_1.php + * + * @throws Exception + */ + public function testAddBundleProductWithOptions(): void + { + $sku = 'bundle-product'; + $product = $this->productRepository->get($sku); + $customerId = 1; + $qty = 2; + $optionQty = 1; + + /** @var Type $typeInstance */ + $typeInstance = $product->getTypeInstance(); + $typeInstance->setStoreFilter($product->getStoreId(), $product); + /** @var Option $option */ + $option = $typeInstance->getOptionsCollection($product)->getFirstItem(); + /** @var Product $selection */ + $selection = $typeInstance->getSelectionsCollection([$option->getId()], $product)->getFirstItem(); + $optionId = $option->getId(); + $selectionId = $selection->getSelectionId(); + $bundleOptions = $this->generateBundleOptionIdV2((int) $optionId, (int) $selectionId, $optionQty); + + $query = $this->getQuery($sku, $qty, $bundleOptions); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + $wishlist = $this->wishlistFactory->create()->loadByCustomerId($customerId, true); + /** @var Item $item */ + $item = $wishlist->getItemCollection()->getFirstItem(); + + $this->assertArrayHasKey('addProductsToWishlist', $response); + $this->assertArrayHasKey('wishlist', $response['addProductsToWishlist']); + $response = $response['addProductsToWishlist']['wishlist']; + $this->assertEquals($wishlist->getItemsCount(), $response['items_count']); + $this->assertEquals($wishlist->getSharingCode(), $response['sharing_code']); + $this->assertEquals($wishlist->getUpdatedAt(), $response['updated_at']); + $this->assertEquals($item->getData('qty'), $response['items'][0]['qty']); + $this->assertEquals($item->getDescription(), $response['items'][0]['description']); + $this->assertEquals($item->getAddedAt(), $response['items'][0]['added_at']); + } + + /** + * Authentication header map + * + * @param string $username + * @param string $password + * + * @return array + * + * @throws AuthenticationException + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + + return ['Authorization' => 'Bearer ' . $customerToken]; + } + + /** + * Returns GraphQl mutation string + * + * @param string $sku + * @param int $qty + * @param string $bundleOptions + * @param int $wishlistId + * + * @return string + */ + private function getQuery( + string $sku, + int $qty, + string $bundleOptions, + int $wishlistId = 0 + ): string { + return <<customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->wishlistFactory = $objectManager->get(WishlistFactory::class); + } + + /** + * @magentoConfigFixture default_store wishlist/general/active 1 + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * + * @throws Exception + */ + public function testAddDownloadableProductWithOptions(): void + { + $product = $this->getConfigurableProductInfo(); + $customerId = 1; + $qty = 2; + $attributeId = (int) $product['configurable_options'][0]['attribute_id']; + $valueIndex = $product['configurable_options'][0]['values'][0]['value_index']; + $childSku = $product['variants'][0]['product']['sku']; + $parentSku = $product['sku']; + $selectedConfigurableOptionsQuery = $this->generateSuperAttributesIdV2Query($attributeId, $valueIndex); + + $query = $this->getQuery($parentSku, $childSku, $qty, $selectedConfigurableOptionsQuery); + + $response = $this->graphQlMutation($query, [], '', $this->getHeadersMap()); + $wishlist = $this->wishlistFactory->create()->loadByCustomerId($customerId, true); + /** @var Item $wishlistItem */ + $wishlistItem = $wishlist->getItemCollection()->getFirstItem(); + + self::assertArrayHasKey('addProductsToWishlist', $response); + self::assertArrayHasKey('wishlist', $response['addProductsToWishlist']); + $wishlistResponse = $response['addProductsToWishlist']['wishlist']; + self::assertEquals($wishlist->getItemsCount(), $wishlistResponse['items_count']); + self::assertEquals($wishlist->getSharingCode(), $wishlistResponse['sharing_code']); + self::assertEquals($wishlist->getUpdatedAt(), $wishlistResponse['updated_at']); + self::assertEquals($wishlistItem->getId(), $wishlistResponse['items'][0]['id']); + self::assertEquals($wishlistItem->getData('qty'), $wishlistResponse['items'][0]['qty']); + self::assertEquals($wishlistItem->getDescription(), $wishlistResponse['items'][0]['description']); + self::assertEquals($wishlistItem->getAddedAt(), $wishlistResponse['items'][0]['added_at']); + } + + /** + * Authentication header map + * + * @param string $username + * @param string $password + * + * @return array + * + * @throws AuthenticationException + */ + private function getHeadersMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + + return ['Authorization' => 'Bearer ' . $customerToken]; + } + + /** + * Returns GraphQl mutation string + * + * @param string $parentSku + * @param string $childSku + * @param int $qty + * @param string $customizableOptions + * @param int $wishlistId + * + * @return string + */ + private function getQuery( + string $parentSku, + string $childSku, + int $qty, + string $customizableOptions, + int $wishlistId = 0 + ): string { + return <<graphQlQuery($this->getFetchProductQuery('configurable')); + + return current($searchResponse['products']['items']); + } + + /** + * Returns GraphQl query for fetching configurable product information + * + * @param string $term + * + * @return string + */ + private function getFetchProductQuery(string $term): string + { + return <<objectManager = Bootstrap::getObjectManager(); + $this->customerTokenService = $this->objectManager->get(CustomerTokenServiceInterface::class); + $this->wishlistFactory = $this->objectManager->get(WishlistFactory::class); + $this->getCustomOptionsWithIDV2ForQueryBySku = + $this->objectManager->get(GetCustomOptionsWithIDV2ForQueryBySku::class); + } + + /** + * @magentoConfigFixture default_store wishlist/general/active 1 + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_custom_options.php + */ + public function testAddDownloadableProductWithOptions(): void + { + $customerId = 1; + $sku = 'downloadable-product-with-purchased-separately-links'; + $qty = 2; + $links = $this->getProductsLinks($sku); + $linkId = key($links); + $itemOptions = $this->getCustomOptionsWithIDV2ForQueryBySku->execute($sku); + $itemOptions['selected_options'][] = $this->generateProductLinkSelectedOptions($linkId); + $productOptionsQuery = preg_replace( + '/"([^"]+)"\s*:\s*/', + '$1:', + json_encode($itemOptions) + ); + $query = $this->getQuery($qty, $sku, trim($productOptionsQuery, '{}')); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + $wishlist = $this->wishlistFactory->create(); + $wishlist->loadByCustomerId($customerId, true); + /** @var Item $wishlistItem */ + $wishlistItem = $wishlist->getItemCollection()->getFirstItem(); + + self::assertArrayHasKey('addProductsToWishlist', $response); + self::assertArrayHasKey('wishlist', $response['addProductsToWishlist']); + $wishlistResponse = $response['addProductsToWishlist']['wishlist']; + self::assertEquals($wishlist->getItemsCount(), $wishlistResponse['items_count']); + self::assertEquals($wishlist->getSharingCode(), $wishlistResponse['sharing_code']); + self::assertEquals($wishlist->getUpdatedAt(), $wishlistResponse['updated_at']); + self::assertEquals($wishlistItem->getId(), $wishlistResponse['items'][0]['id']); + self::assertEquals($wishlistItem->getData('qty'), $wishlistResponse['items'][0]['qty']); + self::assertEquals($wishlistItem->getDescription(), $wishlistResponse['items'][0]['description']); + self::assertEquals($wishlistItem->getAddedAt(), $wishlistResponse['items'][0]['added_at']); + } + + /** + * @magentoConfigFixture default_store wishlist/general/active 0 + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_custom_options.php + */ + public function testAddDownloadableProductOnDisabledWishlist(): void + { + $qty = 2; + $sku = 'downloadable-product-with-purchased-separately-links'; + $links = $this->getProductsLinks($sku); + $linkId = key($links); + $itemOptions = $this->getCustomOptionsWithIDV2ForQueryBySku->execute($sku); + $itemOptions['selected_options'][] = $this->generateProductLinkSelectedOptions($linkId); + $productOptionsQuery = trim(preg_replace( + '/"([^"]+)"\s*:\s*/', + '$1:', + json_encode($itemOptions) + ), '{}'); + $query = $this->getQuery($qty, $sku, $productOptionsQuery); + $this->expectExceptionMessage('The wishlist is not currently available.'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * Function returns array of all product's links + * + * @param string $sku + * + * @return array + */ + private function getProductsLinks(string $sku): array + { + $result = []; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $product = $productRepository->get($sku, false, null, true); + + foreach ($product->getDownloadableLinks() as $linkObject) { + $result[$linkObject->getLinkId()] = [ + 'title' => $linkObject->getTitle(), + 'price' => $linkObject->getPrice(), + ]; + } + + return $result; + } + + /** + * Authentication header map + * + * @param string $username + * @param string $password + * + * @return array + * + * @throws AuthenticationException + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + + return ['Authorization' => 'Bearer ' . $customerToken]; + } + + /** + * Returns GraphQl mutation string + * + * @param int $qty + * @param string $sku + * @param string $customizableOptions + * + * @return string + */ + private function getQuery( + int $qty, + string $sku, + string $customizableOptions + ): string { + return <<graphQlQuery($query); } + /** + * @magentoConfigFixture default_store wishlist/general/active 0 + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testCustomerCannotGetWishlistWhenDisabled() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('The wishlist is not currently available.'); + + $query = + <<graphQlQuery( + $query, + [], + '', + $this->getCustomerAuthHeaders('customer@example.com', 'password') + ); + } + /** * @param string $email * @param string $password diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/DeleteProductsFromWishlistTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/DeleteProductsFromWishlistTest.php new file mode 100644 index 0000000000000..2e203e3ff4228 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/DeleteProductsFromWishlistTest.php @@ -0,0 +1,147 @@ +customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoConfigFixture default_store wishlist/general/active 1 + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Wishlist/_files/wishlist_with_simple_product.php + */ + public function testDeleteWishlistItemFromWishlist(): void + { + $wishlist = $this->getWishlist(); + $wishlistId = $wishlist['customer']['wishlist']['id']; + $wishlist = $wishlist['customer']['wishlist']; + $wishlistItems = $wishlist['items']; + self::assertEquals(1, $wishlist['items_count']); + + $query = $this->getQuery((int) $wishlistId, (int) $wishlistItems[0]['id']); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('removeProductsFromWishlist', $response); + self::assertArrayHasKey('wishlist', $response['removeProductsFromWishlist']); + $wishlistResponse = $response['removeProductsFromWishlist']['wishlist']; + self::assertEquals(0, $wishlistResponse['items_count']); + self::assertEmpty($wishlistResponse['items']); + } + + /** + * Authentication header map + * + * @param string $username + * @param string $password + * + * @return array + * + * @throws AuthenticationException + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + + return ['Authorization' => 'Bearer ' . $customerToken]; + } + + /** + * Returns GraphQl mutation string + * + * @param int $wishlistId + * @param int $wishlistItemId + * + * @return string + */ + private function getQuery( + int $wishlistId, + int $wishlistItemId + ): string { + return <<graphQlQuery($this->getCustomerWishlistQuery(), [], '', $this->getHeaderMap()); + } + + /** + * Get customer wishlist query + * + * @return string + */ + private function getCustomerWishlistQuery(): string + { + return <<productCustomOptionRepository = $productCustomOptionRepository; + } + + /** + * Returns array of custom options for the product + * + * @param string $sku + * + * @return array + */ + public function execute(string $sku): array + { + $customOptions = $this->productCustomOptionRepository->getList($sku); + $selectedOptions = []; + $enteredOptions = []; + + foreach ($customOptions as $customOption) { + $optionType = $customOption->getType(); + + if ($optionType === 'field' || $optionType === 'area' || $optionType === 'date') { + $enteredOptions[] = [ + 'id' => $this->encodeEnteredOption((int)$customOption->getOptionId()), + 'value' => '2012-12-12' + ]; + } elseif ($optionType === 'drop_down') { + $optionSelectValues = $customOption->getValues(); + $selectedOptions[] = $this->encodeSelectedOption( + (int)$customOption->getOptionId(), + (int)reset($optionSelectValues)->getOptionTypeId() + ); + } elseif ($optionType === 'multiple') { + foreach ($customOption->getValues() as $optionValue) { + $selectedOptions[] = $this->encodeSelectedOption( + (int)$customOption->getOptionId(), + (int)$optionValue->getOptionTypeId() + ); + } + } + } + + return [ + 'selected_options' => $selectedOptions, + 'entered_options' => $enteredOptions + ]; + } + + /** + * Returns id_v2 of the selected custom option + * + * @param int $optionId + * @param int $optionValueId + * + * @return string + */ + private function encodeSelectedOption(int $optionId, int $optionValueId): string + { + return base64_encode("custom-option/$optionId/$optionValueId"); + } + + /** + * Returns id_v2 of the entered custom option + * + * @param int $optionId + * + * @return string + */ + private function encodeEnteredOption(int $optionId): string + { + return base64_encode("custom-option/$optionId"); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/UpdateProductsFromWishlistTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/UpdateProductsFromWishlistTest.php new file mode 100644 index 0000000000000..9e96bdc5d7079 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/UpdateProductsFromWishlistTest.php @@ -0,0 +1,159 @@ +customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoConfigFixture default_store wishlist/general/active 1 + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Wishlist/_files/wishlist_with_simple_product.php + */ + public function testUpdateSimpleProductFromWishlist(): void + { + $wishlist = $this->getWishlist(); + $qty = 5; + $description = 'New Description'; + $wishlistId = $wishlist['customer']['wishlist']['id']; + $wishlistItem = $wishlist['customer']['wishlist']['items'][0]; + self::assertNotEquals($description, $wishlistItem['description']); + self::assertNotEquals($qty, $wishlistItem['qty']); + + $query = $this->getQuery((int) $wishlistId, (int) $wishlistItem['id'], $qty, $description); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('updateProductsInWishlist', $response); + self::assertArrayHasKey('wishlist', $response['updateProductsInWishlist']); + $wishlistResponse = $response['updateProductsInWishlist']['wishlist']; + self::assertEquals($qty, $wishlistResponse['items'][0]['qty']); + self::assertEquals($description, $wishlistResponse['items'][0]['description']); + } + + /** + * Authentication header map + * + * @param string $username + * @param string $password + * + * @return array + * + * @throws AuthenticationException + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + + return ['Authorization' => 'Bearer ' . $customerToken]; + } + + /** + * Returns GraphQl mutation string + * + * @param int $wishlistId + * @param int $wishlistItemId + * @param int $qty + * @param string $description + * + * @return string + */ + private function getQuery( + int $wishlistId, + int $wishlistItemId, + int $qty, + string $description + ): string { + return <<graphQlQuery($this->getCustomerWishlistQuery(), [], '', $this->getHeaderMap()); + } + + /** + * Get customer wishlist query + * + * @return string + */ + private function getCustomerWishlistQuery(): string + { + return <<requireDataFixture('Magento/Catalog/_files/products.php'); + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +/** @var QuoteResource $quote */ +$quote = $objectManager->create(QuoteResource::class); + +/** @var Quote $quoteModel */ +$quoteModel = $objectManager->create(Quote::class); +$quoteModel->setData(['store_id' => 1, 'is_active' => 1, 'is_multi_shipping' => 0]); +$quote->save($quoteModel); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$product = $productRepository->get('simple'); + +$quoteModel->setReservedOrderId('test_guest_order_with_gift_message') + ->addProduct($product, 1); +$quoteModel->collectTotals(); +$quote->save($quoteModel); + +/** @var MessageResource $message */ +$message = $objectManager->create(MessageResource::class); + +/** @var Message $message */ +$messageModel = $objectManager->create(Message::class); + +$messageModel->setSender('John Doe'); +$messageModel->setRecipient('Jane Roe'); +$messageModel->setMessage('Gift Message Text'); +$message->save($messageModel); + +$quoteModel->getItemByProduct($product)->setGiftMessageId($messageModel->getId()); +$quote->save($quoteModel); + +/** @var QuoteIdMaskResource $quoteIdMask */ +$quoteIdMask = Bootstrap::getObjectManager() + ->create(QuoteIdMaskFactory::class) + ->create(); + +/** @var QuoteIdMask $quoteIdMaskModel */ +$quoteIdMaskModel = $objectManager->create(QuoteIdMask::class); + +$quoteIdMaskModel->setQuoteId($quoteModel->getId()); +$quoteIdMaskModel->setDataChanges(true); +$quoteIdMask->save($quoteIdMaskModel); diff --git a/dev/tests/integration/testsuite/Magento/GiftMessage/_files/guest/quote_with_item_message_rollback.php b/dev/tests/integration/testsuite/Magento/GiftMessage/_files/guest/quote_with_item_message_rollback.php new file mode 100644 index 0000000000000..9c215cb432b45 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GiftMessage/_files/guest/quote_with_item_message_rollback.php @@ -0,0 +1,32 @@ +get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +$objectManager = Bootstrap::getObjectManager(); +$quote = $objectManager->create(Quote::class); +$quote->load('test_guest_order_with_gift_message', 'reserved_order_id'); +$message = $objectManager->create(Message::class); +$product = $objectManager->create(Product::class); +foreach ($quote->getAllItems() as $item) { + $message->load($item->getGiftMessageId()); + $message->delete(); + $sku = $item->getSku(); + $product->load($product->getIdBySku($sku)); + if ($product->getId()) { + $product->delete(); + } +} +$quote->delete(); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false);