Skip to content

Commit 2c812ba

Browse files
committed
#27337: GraphQl. Add a mutation for subscribe feature
1 parent b307274 commit 2c812ba

File tree

12 files changed

+661
-0
lines changed

12 files changed

+661
-0
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\NewsletterGraphQl\Model\Resolver;
9+
10+
use Magento\Customer\Api\AccountManagementInterface as CustomerAccountManagement;
11+
use Magento\Customer\Api\CustomerRepositoryInterface;
12+
use Magento\Framework\App\Config\ScopeConfigInterface;
13+
use Magento\Framework\Exception\LocalizedException;
14+
use Magento\Framework\Exception\NoSuchEntityException;
15+
use Magento\Framework\GraphQl\Config\Element\Field;
16+
use Magento\Framework\GraphQl\Exception\GraphQlAlreadyExistsException;
17+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
18+
use Magento\Framework\GraphQl\Query\EnumLookup;
19+
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
20+
use Magento\Framework\GraphQl\Query\ResolverInterface;
21+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
22+
use Magento\Framework\Validator\EmailAddress as EmailValidator;
23+
use Magento\Newsletter\Model\ResourceModel\Subscriber as SubscriberResourceModel;
24+
use Magento\Newsletter\Model\Subscriber;
25+
use Magento\Newsletter\Model\SubscriptionManagerInterface;
26+
use Magento\Store\Model\ScopeInterface;
27+
28+
/**
29+
* Resolver class for the `subscribeEmailToNewsletter` mutation. Adds an email into a newsletter subscription.
30+
*/
31+
class SubscribeEmailToNewsletter implements ResolverInterface
32+
{
33+
/**
34+
* @var CustomerAccountManagement
35+
*/
36+
private $customerAccountManagement;
37+
38+
/**
39+
* @var CustomerRepositoryInterface
40+
*/
41+
private $customerRepository;
42+
43+
/**
44+
* @var EmailValidator
45+
*/
46+
private $emailValidator;
47+
48+
/**
49+
* @var EnumLookup
50+
*/
51+
private $enumLookup;
52+
53+
/**
54+
* @var ScopeConfigInterface
55+
*/
56+
private $scopeConfig;
57+
58+
/**
59+
* @var SubscriberResourceModel
60+
*/
61+
private $subscriberResource;
62+
63+
/**
64+
* @var SubscriptionManagerInterface
65+
*/
66+
private $subscriptionManager;
67+
68+
/**
69+
* SubscribeEmailToNewsletter constructor.
70+
*
71+
* @param CustomerAccountManagement $customerAccountManagement
72+
* @param CustomerRepositoryInterface $customerRepository
73+
* @param EmailValidator $emailValidator
74+
* @param EnumLookup $enumLookup
75+
* @param ScopeConfigInterface $scopeConfig
76+
* @param SubscriberResourceModel $subscriberResource
77+
* @param SubscriptionManagerInterface $subscriptionManager
78+
*/
79+
public function __construct(
80+
CustomerAccountManagement $customerAccountManagement,
81+
CustomerRepositoryInterface $customerRepository,
82+
EmailValidator $emailValidator,
83+
EnumLookup $enumLookup,
84+
ScopeConfigInterface $scopeConfig,
85+
SubscriberResourceModel $subscriberResource,
86+
SubscriptionManagerInterface $subscriptionManager
87+
) {
88+
$this->customerAccountManagement = $customerAccountManagement;
89+
$this->customerRepository = $customerRepository;
90+
$this->emailValidator = $emailValidator;
91+
$this->enumLookup = $enumLookup;
92+
$this->scopeConfig = $scopeConfig;
93+
$this->subscriberResource = $subscriberResource;
94+
$this->subscriptionManager = $subscriptionManager;
95+
}
96+
97+
/**
98+
* @inheritdoc
99+
*/
100+
public function resolve(
101+
Field $field,
102+
$context,
103+
ResolveInfo $info,
104+
array $value = null,
105+
array $args = null
106+
) {
107+
$email = trim($args['email']);
108+
109+
if (empty($email)) {
110+
throw new GraphQlInputException(__('You must specify an email address to subscribe to a newsletter.'));
111+
}
112+
113+
try {
114+
$currentUserId = (int)$context->getUserId();
115+
$storeId = (int)$context->getExtensionAttributes()->getStore()->getId();
116+
$websiteId = (int)$context->getExtensionAttributes()->getStore()->getWebsiteId();
117+
118+
$this->validateEmailFormat($email);
119+
$this->validateGuestSubscription($context);
120+
$this->validateEmailAvailable($email, $currentUserId, $websiteId);
121+
$this->validateAlreadySubscribed($email, $websiteId);
122+
123+
$subscriber = $this->isCustomerSubscription($email, $currentUserId)
124+
? $this->subscriptionManager->subscribeCustomer($currentUserId, $storeId)
125+
: $this->subscriptionManager->subscribe($email, $storeId);
126+
127+
$status = $this->enumLookup->getEnumValueFromField(
128+
'SubscriptionStatusesEnum',
129+
(string)$subscriber->getSubscriberStatus()
130+
);
131+
} catch (LocalizedException $e) {
132+
throw new GraphQlInputException(__($e->getMessage()));
133+
}
134+
135+
return [
136+
'status' => $status
137+
];
138+
}
139+
140+
/**
141+
* Validate the format of the email address
142+
*
143+
* @param string $email
144+
* @throws GraphQlInputException
145+
*/
146+
private function validateEmailFormat(string $email): void
147+
{
148+
if (!$this->emailValidator->isValid($email)) {
149+
throw new GraphQlInputException(__('Enter a valid email address.'));
150+
}
151+
}
152+
153+
/**
154+
* Validate if a guest user can be subscribed to a newsletter.
155+
*
156+
* @param ContextInterface $context
157+
* @throws GraphQlInputException
158+
*/
159+
private function validateGuestSubscription(ContextInterface $context): void
160+
{
161+
if (false === $context->getExtensionAttributes()->getIsCustomer()
162+
&& !$this->scopeConfig->getValue(
163+
Subscriber::XML_PATH_ALLOW_GUEST_SUBSCRIBE_FLAG,
164+
ScopeInterface::SCOPE_STORE
165+
)
166+
) {
167+
throw new GraphQlInputException(
168+
__('Guests can not subscribe to the newsletter. You must create an account to subscribe.')
169+
);
170+
}
171+
}
172+
173+
/**
174+
* Validates that the email address isn't being used by a different account.
175+
*
176+
* @param string $email
177+
* @param int $currentUserId
178+
* @param int $websiteId
179+
* @throws GraphQlInputException
180+
* @throws LocalizedException
181+
* @throws NoSuchEntityException
182+
*/
183+
private function validateEmailAvailable(string $email, int $currentUserId, int $websiteId): void
184+
{
185+
if ($currentUserId > 0) {
186+
$customer = $this->customerRepository->getById($currentUserId);
187+
188+
if ($customer->getEmail() != $email
189+
&& !$this->customerAccountManagement->isEmailAvailable($email, $websiteId)) {
190+
throw new GraphQlInputException(
191+
__('This email address is already assigned to another user.')
192+
);
193+
}
194+
}
195+
}
196+
197+
/**
198+
* Verify if email is already subscribed
199+
*
200+
* @param string $email
201+
* @param int $websiteId
202+
* @throws GraphQlAlreadyExistsException
203+
*/
204+
private function validateAlreadySubscribed(string $email, int $websiteId): void
205+
{
206+
$subscriberData = $this->subscriberResource->loadBySubscriberEmail($email, $websiteId);
207+
208+
if (isset($subscriberData['subscriber_status'])
209+
&& (int)$subscriberData['subscriber_status'] === Subscriber::STATUS_SUBSCRIBED) {
210+
throw new GraphQlAlreadyExistsException(
211+
__('This email address is already subscribed.')
212+
);
213+
}
214+
}
215+
216+
/**
217+
* Returns true if a provided email equals to a current customer one
218+
*
219+
* @param string $email
220+
* @param int $currentUserId
221+
* @return bool
222+
* @throws LocalizedException
223+
* @throws NoSuchEntityException
224+
*/
225+
private function isCustomerSubscription(string $email, int $currentUserId): bool
226+
{
227+
if ($currentUserId > 0) {
228+
$customer = $this->customerRepository->getById($currentUserId);
229+
230+
if ($customer->getEmail() == $email) {
231+
return true;
232+
}
233+
}
234+
235+
return false;
236+
}
237+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The Magento_NewsletterGraphQl module allows a shopper to subscribe to a newsletter using GraphQL.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "magento/module-newsletter-graph-ql",
3+
"description": "Provides GraphQl functionality for the newsletter subscriptions.",
4+
"config": {
5+
"sort-packages": true
6+
},
7+
"require": {
8+
"php": "~7.1.3||~7.2.0||~7.3.0",
9+
"magento/framework": "*",
10+
"magento/module-customer": "*",
11+
"magento/module-newsletter": "*",
12+
"magento/module-store": "*"
13+
},
14+
"type": "magento2-module",
15+
"license": [
16+
"OSL-3.0",
17+
"AFL-3.0"
18+
],
19+
"autoload": {
20+
"files": [
21+
"registration.php"
22+
],
23+
"psr-4": {
24+
"Magento\\NewsletterGraphQl\\": ""
25+
}
26+
}
27+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
9+
<type name="Magento\Framework\GraphQl\Schema\Type\Enum\DefaultDataMapper">
10+
<arguments>
11+
<argument name="map" xsi:type="array">
12+
<item name="SubscriptionStatusesEnum" xsi:type="array">
13+
<item name="subscribed" xsi:type="string">1</item>
14+
<item name="not_active" xsi:type="string">2</item>
15+
<item name="unsubscribed" xsi:type="string">3</item>
16+
<item name="unconfirmed" xsi:type="string">4</item>
17+
</item>
18+
</argument>
19+
</arguments>
20+
</type>
21+
</config>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
9+
<module name="Magento_NewsletterGraphQl">
10+
<sequence>
11+
<module name="Magento_Newsletter"/>
12+
</sequence>
13+
</module>
14+
</config>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright © Magento, Inc. All rights reserved.
2+
# See COPYING.txt for license details.
3+
4+
type Mutation {
5+
subscribeEmailToNewsletter(email: String!): SubscribeEmailToNewsletterOutput @doc(description:"Subscribes the specified email to a newsletter") @resolver(class: "Magento\\NewsletterGraphQl\\Model\\Resolver\\SubscribeEmailToNewsletter")
6+
}
7+
8+
type SubscribeEmailToNewsletterOutput {
9+
status: SubscriptionStatusesEnum @doc(description: "Returns the status of the subscription request")
10+
}
11+
12+
enum SubscriptionStatusesEnum {
13+
NOT_ACTIVE
14+
SUBSCRIBED
15+
UNSUBSCRIBED
16+
UNCONFIRMED
17+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
use Magento\Framework\Component\ComponentRegistrar;
8+
9+
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_NewsletterGraphQl', __DIR__);

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@
208208
"magento/module-mysql-mq": "*",
209209
"magento/module-new-relic-reporting": "*",
210210
"magento/module-newsletter": "*",
211+
"magento/module-newsletter-graph-ql": "*",
211212
"magento/module-offline-payments": "*",
212213
"magento/module-offline-shipping": "*",
213214
"magento/module-page-cache": "*",

0 commit comments

Comments
 (0)