Skip to content

Commit 995a05c

Browse files
authored
Merge pull request #8890 from magento-lynx/graphql-api-enhancements
2 parents be7b88d + df72199 commit 995a05c

File tree

29 files changed

+711
-153
lines changed

29 files changed

+711
-153
lines changed

app/code/Magento/Catalog/Test/Fixture/Product.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ class Product implements RevertibleDataFixtureInterface
3636
'visibility' => Visibility::VISIBILITY_BOTH,
3737
'status' => Status::STATUS_ENABLED,
3838
'custom_attributes' => [
39-
'tax_class_id' => '2'
39+
'tax_class_id' => '2',
40+
'special_price' => null,
4041
],
4142
'extension_attributes' => [
4243
'website_ids' => [1],

app/code/Magento/CatalogInventoryGraphQl/Model/Resolver/OnlyXLeftInStockResolver.php

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Magento\CatalogInventoryGraphQl\Model\Resolver;
99

1010
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Catalog\Api\ProductRepositoryInterface;
1112
use Magento\CatalogInventory\Api\StockRegistryInterface;
1213
use Magento\CatalogInventory\Model\Configuration;
1314
use Magento\Framework\App\Config\ScopeConfigInterface;
@@ -23,25 +24,20 @@
2324
class OnlyXLeftInStockResolver implements ResolverInterface
2425
{
2526
/**
26-
* @var ScopeConfigInterface
27+
* Configurable product type code
2728
*/
28-
private $scopeConfig;
29-
30-
/**
31-
* @var StockRegistryInterface
32-
*/
33-
private $stockRegistry;
29+
private const PRODUCT_TYPE_CONFIGURABLE = "configurable";
3430

3531
/**
3632
* @param ScopeConfigInterface $scopeConfig
3733
* @param StockRegistryInterface $stockRegistry
34+
* @param ProductRepositoryInterface $productRepositoryInterface
3835
*/
3936
public function __construct(
40-
ScopeConfigInterface $scopeConfig,
41-
StockRegistryInterface $stockRegistry
37+
private readonly ScopeConfigInterface $scopeConfig,
38+
private readonly StockRegistryInterface $stockRegistry,
39+
private readonly ProductRepositoryInterface $productRepositoryInterface
4240
) {
43-
$this->scopeConfig = $scopeConfig;
44-
$this->stockRegistry = $stockRegistry;
4541
}
4642

4743
/**
@@ -53,11 +49,12 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
5349
throw new LocalizedException(__('"model" value should be specified'));
5450
}
5551

56-
/* @var $product ProductInterface */
5752
$product = $value['model'];
58-
$onlyXLeftQty = $this->getOnlyXLeftQty($product);
59-
60-
return $onlyXLeftQty;
53+
if ($product->getTypeId() === self::PRODUCT_TYPE_CONFIGURABLE) {
54+
$variant = $this->productRepositoryInterface->get($product->getSku());
55+
return $this->getOnlyXLeftQty($variant);
56+
}
57+
return $this->getOnlyXLeftQty($product);
6158
}
6259

6360
/**
@@ -73,7 +70,7 @@ private function getOnlyXLeftQty(ProductInterface $product): ?float
7370
Configuration::XML_PATH_STOCK_THRESHOLD_QTY,
7471
ScopeInterface::SCOPE_STORE
7572
);
76-
if ($thresholdQty === 0) {
73+
if ($thresholdQty === 0.0) {
7774
return null;
7875
}
7976

app/code/Magento/CatalogInventoryGraphQl/Test/Unit/Model/Resolver/OnlyXLeftInStockResolverTest.php

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,11 @@ protected function setUp(): void
115115
$this->stockStatusMock = $this->getMockBuilder(StockStatusInterface::class)->getMock();
116116
$this->productModelMock->expects($this->any())->method('getId')
117117
->willReturn(1);
118-
$this->productModelMock->expects($this->once())->method('getStore')
118+
$this->productModelMock->expects($this->atMost(1))->method('getStore')
119119
->willReturn($this->storeMock);
120-
$this->stockRegistryMock->expects($this->once())->method('getStockStatus')
120+
$this->stockRegistryMock->expects($this->atMost(1))->method('getStockStatus')
121121
->willReturn($this->stockStatusMock);
122-
$this->storeMock->expects($this->once())->method('getWebsiteId')->willReturn(1);
122+
$this->storeMock->expects($this->atMost(1))->method('getWebsiteId')->willReturn(1);
123123

124124
$this->resolver = $this->objectManager->getObject(
125125
OnlyXLeftInStockResolver::class,
@@ -181,15 +181,10 @@ public function testResolveOutStock()
181181

182182
public function testResolveNoThresholdQty()
183183
{
184-
$stockCurrentQty = 3;
185-
$minQty = 2;
186184
$thresholdQty = null;
187-
$this->stockItemMock->expects($this->once())->method('getMinQty')
188-
->willReturn($minQty);
189-
$this->stockStatusMock->expects($this->once())->method('getQty')
190-
->willReturn($stockCurrentQty);
191-
$this->stockRegistryMock->expects($this->once())->method('getStockItem')
192-
->willReturn($this->stockItemMock);
185+
$this->stockItemMock->expects($this->never())->method('getMinQty');
186+
$this->stockStatusMock->expects($this->never())->method('getQty');
187+
$this->stockRegistryMock->expects($this->never())->method('getStockItem');
193188
$this->scopeConfigMock->method('getValue')->willReturn($thresholdQty);
194189

195190
$this->assertEquals(

app/code/Magento/CatalogInventoryGraphQl/etc/schema.graphqls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# See COPYING.txt for license details.
33

44
interface ProductInterface {
5-
only_x_left_in_stock: Float @doc(description: "The value assigned to the Only X Left Threshold option in the Admin.") @resolver(class: "Magento\\CatalogInventoryGraphQl\\Model\\Resolver\\OnlyXLeftInStockResolver")
5+
only_x_left_in_stock: Float @doc(description: "Remaining stock if it is below the value assigned to the Only X Left Threshold option in the Admin.") @resolver(class: "Magento\\CatalogInventoryGraphQl\\Model\\Resolver\\OnlyXLeftInStockResolver")
66
stock_status: ProductStockStatus @doc(description: "The stock status of the product.") @resolver(class: "Magento\\CatalogInventoryGraphQl\\Model\\Resolver\\StockStatusProvider")
77
}
88

app/code/Magento/Customer/Model/AccountManagement/Authenticate.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,18 @@ public function execute(string $email, string $password): CustomerInterface
9292
if ($this->authentication->isLocked($customerId)) {
9393
throw new UserLockedException(__('The account is locked.'));
9494
}
95-
try {
96-
$this->authentication->authenticate($customerId, $password);
97-
} catch (InvalidEmailOrPasswordException $exception) {
98-
throw new InvalidEmailOrPasswordException(__('Invalid login or password.'));
99-
}
10095

10196
if ($customer->getConfirmation()
10297
&& ($this->isConfirmationRequired($customer) || $this->isEmailChangedConfirmationRequired($customer))) {
10398
throw new EmailNotConfirmedException(__('This account isn\'t confirmed. Verify and try again.'));
10499
}
105100

101+
try {
102+
$this->authentication->authenticate($customerId, $password);
103+
} catch (InvalidEmailOrPasswordException $exception) {
104+
throw new InvalidEmailOrPasswordException(__('Invalid login or password.'));
105+
}
106+
106107
$customerModel = $this->customerFactory->create()->updateData($customer);
107108
$this->eventManager->dispatch(
108109
'customer_customer_authenticated',

app/code/Magento/Customer/view/frontend/email/account_new_confirmation.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<!--@subject {{trans "Please confirm your %store_name account" store_name=$store.frontend_name}} @-->
88
<!--@vars {
99
"var store.frontend_name":"Store Name",
10-
"var this.getUrl($store,'customer/account/confirm/',[_query:[id:$customer.id,key:$customer.confirmation,back_url:$back_url],_nosid:1])":"Account Confirmation URL",
10+
"var this.getUrl($store,'customer/account/confirm/',[_query:[id:$customer.id,key:$customer.confirmation,back_url:$back_url,email:$customer.email],_nosid:1])":"Account Confirmation URL",
1111
"var this.getUrl($store, 'customer/account/')":"Customer Account URL",
1212
"var customer.email":"Customer Email",
1313
"var customer.name":"Customer Name"
@@ -24,7 +24,7 @@
2424
<table class="inner-wrapper" border="0" cellspacing="0" cellpadding="0" align="center">
2525
<tr>
2626
<td align="center">
27-
<a href="{{var this.getUrl($store,'customer/account/confirm/',[_query:[id:$customer.id,key:$customer.confirmation,back_url:$back_url],_nosid:1])}}" target="_blank">{{trans "Confirm Your Account"}}</a>
27+
<a href="{{var this.getUrl($store,'customer/account/confirm/',[_query:[id:$customer.id,key:$customer.confirmation,back_url:$back_url,email:$customer.email],_nosid:1])}}" target="_blank">{{trans "Confirm Your Account"}}</a>
2828
</td>
2929
</tr>
3030
</table>

app/code/Magento/Customer/view/frontend/email/password_new.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<!--@vars {
99
"var store.frontend_name":"Store Name",
1010
"var this.getUrl($store, 'customer/account/')":"Customer Account URL",
11-
"var this.getUrl($store,'customer/account/createPassword',[_query:[id:$customer.id,token:$customer.rp_token],_nosid:1])":"Password Reset URL",
11+
"var this.getUrl($store,'customer/account/createPassword',[_query:[id:$customer.id,token:$customer.rp_token,email:$customer.email],_nosid:1])":"Password Reset URL",
1212
"var customer.name":"Customer Name"
1313
} @-->
1414
{{template config_path="design/email/header_template"}}
@@ -23,7 +23,7 @@
2323
<table class="inner-wrapper" border="0" cellspacing="0" cellpadding="0" align="center">
2424
<tr>
2525
<td align="center">
26-
<a href="{{var this.getUrl($store,'customer/account/createPassword',[_query:[id:$customer.id,token:$customer.rp_token],_nosid:1])}}" target="_blank">{{trans "Set a New Password"}}</a>
26+
<a href="{{var this.getUrl($store,'customer/account/createPassword',[_query:[id:$customer.id,token:$customer.rp_token,email:$customer.email],_nosid:1])}}" target="_blank">{{trans "Set a New Password"}}</a>
2727
</td>
2828
</tr>
2929
</table>

app/code/Magento/Customer/view/frontend/email/password_reset_confirmation.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<!--@vars {
99
"var store.frontend_name":"Store Name",
1010
"var customer.name":"Customer Name",
11-
"var this.getUrl($store,'customer/account/createPassword/',[_query:[id:$customer.id,token:$customer.rp_token],_nosid:1])":"Reset Password URL"
11+
"var this.getUrl($store,'customer/account/createPassword/',[_query:[id:$customer.id,token:$customer.rp_token,email:$customer.email],_nosid:1])":"Reset Password URL"
1212
} @-->
1313
{{template config_path="design/email/header_template"}}
1414

@@ -22,7 +22,7 @@
2222
<table class="inner-wrapper" border="0" cellspacing="0" cellpadding="0" align="center">
2323
<tr>
2424
<td align="center">
25-
<a href="{{var this.getUrl($store,'customer/account/createPassword/',[_query:[id:$customer.id,token:$customer.rp_token],_nosid:1])}}" target="_blank">{{trans "Set a New Password"}}</a>
25+
<a href="{{var this.getUrl($store,'customer/account/createPassword/',[_query:[id:$customer.id,token:$customer.rp_token,email:$customer.email],_nosid:1])}}" target="_blank">{{trans "Set a New Password"}}</a>
2626
</td>
2727
</tr>
2828
</table>

app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Magento\CustomerGraphQl\Model\Resolver;
99

1010
use Magento\Framework\Exception\AuthenticationException;
11+
use Magento\Framework\Exception\EmailNotConfirmedException;
1112
use Magento\Framework\GraphQl\Config\Element\Field;
1213
use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException;
1314
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
@@ -55,8 +56,8 @@ public function resolve(
5556
try {
5657
$token = $this->customerTokenService->createCustomerAccessToken($args['email'], $args['password']);
5758
return ['token' => $token];
58-
} catch (AuthenticationException $e) {
59-
throw new GraphQlAuthenticationException(__($e->getMessage()), $e);
59+
} catch (EmailNotConfirmedException|AuthenticationException $e) {
60+
throw new GraphQlAuthenticationException(__($e->getRawMessage()), $e);
6061
}
6162
}
6263
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*
6+
* NOTICE: All information contained herein is, and remains
7+
* the property of Adobe and its suppliers, if any. The intellectual
8+
* and technical concepts contained herein are proprietary to Adobe
9+
* and its suppliers and are protected by all applicable intellectual
10+
* property laws, including trade secret and copyright laws.
11+
* Dissemination of this information or reproduction of this material
12+
* is strictly forbidden unless prior written permission is obtained from
13+
* Adobe.
14+
*/
15+
declare(strict_types=1);
16+
17+
namespace Magento\CustomerGraphQl\Model\Resolver;
18+
19+
use Magento\Customer\Api\AccountManagementInterface;
20+
use Magento\Framework\Exception\NoSuchEntityException;
21+
use Magento\Framework\Exception\State\InvalidTransitionException;
22+
use Magento\Framework\GraphQl\Config\Element\Field;
23+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
24+
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
25+
use Magento\Framework\GraphQl\Query\ResolverInterface;
26+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
27+
use Magento\Framework\Validator\EmailAddress as EmailValidator;
28+
29+
/**
30+
* Customer resend confirmation email, used for GraphQL request processing
31+
*/
32+
class ResendConfirmationEmail implements ResolverInterface
33+
{
34+
/**
35+
* @param AccountManagementInterface $accountManagement
36+
* @param EmailValidator $emailValidator
37+
*/
38+
public function __construct(
39+
private readonly AccountManagementInterface $accountManagement,
40+
private readonly EmailValidator $emailValidator,
41+
) {
42+
}
43+
44+
/**
45+
* Resend confirmation customer email mutation
46+
*
47+
* @param Field $field
48+
* @param ContextInterface $context
49+
* @param ResolveInfo $info
50+
* @param array|null $value
51+
* @param array|null $args
52+
* @return bool
53+
* @throws \Exception
54+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
55+
*/
56+
public function resolve(
57+
Field $field,
58+
$context,
59+
ResolveInfo $info,
60+
array $value = null,
61+
array $args = null
62+
) {
63+
if (!$this->emailValidator->isValid($args['email'])) {
64+
throw new GraphQlInputException(__('Email address is not valid'));
65+
}
66+
try {
67+
$this->accountManagement->resendConfirmation($args['email']);
68+
} catch (InvalidTransitionException $e) {
69+
throw new GraphQlInputException(__($e->getRawMessage()));
70+
} catch (NoSuchEntityException) {
71+
throw new GraphQlInputException(__('There is no user registered with that email address.'));
72+
} catch (\Exception) {
73+
throw new GraphQlInputException(__('There was an error when sending the confirmation email'));
74+
}
75+
return true;
76+
}
77+
}

0 commit comments

Comments
 (0)