Skip to content

magento/magento2#27337: GraphQl. Add a mutation for subscribe feature #27586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public function setMessagesScope($scope)
* @param string $email
* @param int $websiteId
* @return array
* @throws LocalizedException
*/
public function loadBySubscriberEmail(string $email, int $websiteId): array
{
Expand Down
3 changes: 3 additions & 0 deletions app/code/Magento/Newsletter/i18n/en_US.csv
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,6 @@ Store,Store
"Newsletter Subscriptions","Newsletter Subscriptions"
"We have updated your subscription.","We have updated your subscription."
"Are you sure you want to delete the selected subscriber(s)?","Are you sure you want to delete the selected subscriber(s)?"
"Cannot create a newsletter subscription.","Cannot create a newsletter subscription."
"Enter a valid email address.","Enter a valid email address."
"Guests can not subscribe to the newsletter. You must create an account to subscribe.","Guests can not subscribe to the newsletter. You must create an account to subscribe."
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\NewsletterGraphQl\Model\Resolver;

use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Query\EnumLookup;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Newsletter\Model\SubscriptionManagerInterface;
use Magento\NewsletterGraphQl\Model\SubscribeEmailToNewsletter\Validation;
use Psr\Log\LoggerInterface;

/**
* Resolver class for the `subscribeEmailToNewsletter` mutation. Adds an email into a newsletter subscription.
*/
class SubscribeEmailToNewsletter implements ResolverInterface
{
/**
* @var CustomerRepositoryInterface
*/
private $customerRepository;

/**
* @var EnumLookup
*/
private $enumLookup;

/**
* @var LoggerInterface
*/
private $logger;

/**
* @var SubscriptionManagerInterface
*/
private $subscriptionManager;

/**
* @var Validation
*/
private $validator;

/**
* SubscribeEmailToNewsletter constructor.
*
* @param CustomerRepositoryInterface $customerRepository
* @param EnumLookup $enumLookup
* @param LoggerInterface $logger
* @param SubscriptionManagerInterface $subscriptionManager
* @param Validation $validator
*/
public function __construct(
CustomerRepositoryInterface $customerRepository,
EnumLookup $enumLookup,
LoggerInterface $logger,
SubscriptionManagerInterface $subscriptionManager,
Validation $validator
) {
$this->customerRepository = $customerRepository;
$this->enumLookup = $enumLookup;
$this->logger = $logger;
$this->subscriptionManager = $subscriptionManager;
$this->validator = $validator;
}

/**
* @inheritDoc
*/
public function resolve(
Field $field,
$context,
ResolveInfo $info,
array $value = null,
array $args = null
) {
$email = trim($args['email']);

if (empty($email)) {
throw new GraphQlInputException(
__('You must specify an email address to subscribe to a newsletter.')
);
}

$currentUserId = (int)$context->getUserId();
$storeId = (int)$context->getExtensionAttributes()->getStore()->getId();
$websiteId = (int)$context->getExtensionAttributes()->getStore()->getWebsiteId();

$this->validator->execute($email, $currentUserId, $websiteId);

try {
$subscriber = $this->isCustomerSubscription($email, $currentUserId)
? $this->subscriptionManager->subscribeCustomer($currentUserId, $storeId)
: $this->subscriptionManager->subscribe($email, $storeId);

$status = $this->enumLookup->getEnumValueFromField(
'SubscriptionStatusesEnum',
(string)$subscriber->getSubscriberStatus()
);
} catch (LocalizedException $e) {
$this->logger->error($e->getMessage());

throw new GraphQlInputException(
__('Cannot create a newsletter subscription.')
);
}

return [
'status' => $status
];
}

/**
* Returns true if a provided email equals to a current customer one
*
* @param string $email
* @param int $currentUserId
* @return bool
* @throws LocalizedException
* @throws NoSuchEntityException
*/
private function isCustomerSubscription(string $email, int $currentUserId): bool
{
if ($currentUserId > 0) {
$customer = $this->customerRepository->getById($currentUserId);

if ($customer->getEmail() == $email) {
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\NewsletterGraphQl\Model\SubscribeEmailToNewsletter;

use Magento\Customer\Api\AccountManagementInterface as CustomerAccountManagement;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Exception\GraphQlAlreadyExistsException;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\Validator\EmailAddress as EmailValidator;
use Magento\Newsletter\Model\ResourceModel\Subscriber as SubscriberResourceModel;
use Magento\Newsletter\Model\Subscriber;
use Magento\Store\Model\ScopeInterface;
use Psr\Log\LoggerInterface;

/**
* Validation class for the "subscribeEmailToNewsletter" mutation
*/
class Validation
{
/**
* @var CustomerAccountManagement
*/
private $customerAccountManagement;

/**
* @var CustomerRepositoryInterface
*/
private $customerRepository;

/**
* @var EmailValidator
*/
private $emailValidator;

/**
* @var LoggerInterface
*/
private $logger;

/**
* @var ScopeConfigInterface
*/
private $scopeConfig;

/**
* @var SubscriberResourceModel
*/
private $subscriberResource;

/**
* Validation constructor.
*
* @param CustomerAccountManagement $customerAccountManagement
* @param CustomerRepositoryInterface $customerRepository
* @param EmailValidator $emailValidator
* @param LoggerInterface $logger
* @param ScopeConfigInterface $scopeConfig
* @param SubscriberResourceModel $subscriberResource
*/
public function __construct(
CustomerAccountManagement $customerAccountManagement,
CustomerRepositoryInterface $customerRepository,
EmailValidator $emailValidator,
LoggerInterface $logger,
ScopeConfigInterface $scopeConfig,
SubscriberResourceModel $subscriberResource
) {
$this->customerAccountManagement = $customerAccountManagement;
$this->customerRepository = $customerRepository;
$this->emailValidator = $emailValidator;
$this->logger = $logger;
$this->scopeConfig = $scopeConfig;
$this->subscriberResource = $subscriberResource;
}

/**
* Validate the next cases:
* - email format
* - email address isn't being used by a different account
* - if a guest user can be subscribed to a newsletter
* - verify if email is already subscribed
*
* @param string $email
* @param int $currentUserId
* @param int $websiteId
* @throws GraphQlAlreadyExistsException
* @throws GraphQlInputException
*/
public function execute(string $email = '', int $currentUserId = 0, int $websiteId = 1): void
{
$this->validateEmailFormat($email);

if ($currentUserId > 0) {
$this->validateEmailAvailable($email, $currentUserId, $websiteId);
} else {
$this->validateGuestSubscription();
}

$this->validateAlreadySubscribed($email, $websiteId);
}

/**
* Validate the format of the email address
*
* @param string $email
* @throws GraphQlInputException
*/
private function validateEmailFormat(string $email): void
{
if (!$this->emailValidator->isValid($email)) {
throw new GraphQlInputException(__('Enter a valid email address.'));
}
}

/**
* Validate that the email address isn't being used by a different account.
*
* @param string $email
* @param int $currentUserId
* @param int $websiteId
* @throws GraphQlInputException
*/
private function validateEmailAvailable(string $email, int $currentUserId, int $websiteId): void
{
try {
$customer = $this->customerRepository->getById($currentUserId);
$customerEmail = $customer->getEmail();
} catch (LocalizedException $e) {
$customerEmail = '';
}

try {
$emailAvailable = $this->customerAccountManagement->isEmailAvailable($email, $websiteId);
} catch (LocalizedException $e) {
$emailAvailable = false;
}

if (!$emailAvailable && $customerEmail != $email) {
$this->logger->error(
__('This email address is already assigned to another user.')
);

throw new GraphQlInputException(
__('Cannot create a newsletter subscription.')
);
}
}

/**
* Validate if a guest user can be subscribed to a newsletter.
*
* @throws GraphQlInputException
*/
private function validateGuestSubscription(): void
{
if (!$this->scopeConfig->getValue(
Subscriber::XML_PATH_ALLOW_GUEST_SUBSCRIBE_FLAG,
ScopeInterface::SCOPE_STORE
)) {
throw new GraphQlInputException(
__('Guests can not subscribe to the newsletter. You must create an account to subscribe.')
);
}
}

/**
* Verify if email is already subscribed
*
* @param string $email
* @param int $websiteId
* @throws GraphQlAlreadyExistsException
*/
private function validateAlreadySubscribed(string $email, int $websiteId): void
{
try {
$subscriberData = $this->subscriberResource->loadBySubscriberEmail($email, $websiteId);
} catch (LocalizedException $e) {
$subscriberData = [];
}

if (isset($subscriberData['subscriber_status'])
&& (int)$subscriberData['subscriber_status'] === Subscriber::STATUS_SUBSCRIBED) {
throw new GraphQlAlreadyExistsException(
__('This email address is already subscribed.')
);
}
}
}
1 change: 1 addition & 0 deletions app/code/Magento/NewsletterGraphQl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The Magento_NewsletterGraphQl module allows a shopper to subscribe to a newsletter using GraphQL.
30 changes: 30 additions & 0 deletions app/code/Magento/NewsletterGraphQl/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "magento/module-newsletter-graph-ql",
"description": "Provides GraphQl functionality for the newsletter subscriptions.",
"config": {
"sort-packages": true
},
"type": "magento2-module",
"require": {
"php": "~7.3.0||~7.4.0",
"magento/framework": "*",
"magento/module-customer": "*",
"magento/module-newsletter": "*",
"magento/module-store": "*"
},
"suggest": {
"magento/module-graph-ql": "*"
},
"license": [
"OSL-3.0",
"AFL-3.0"
],
"autoload": {
"files": [
"registration.php"
],
"psr-4": {
"Magento\\NewsletterGraphQl\\": ""
}
}
}
Loading