Skip to content
This repository has been archived by the owner on Dec 19, 2019. It is now read-only.

My Account > Change account information and Newsletter subscription #162

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -13,6 +13,11 @@
use Magento\Framework\Serialize\SerializerInterface;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this class has a lot of responsibility (looks like helper in Magento 1)
Need to split the class

use Magento\Framework\Webapi\ServiceOutputProcessor;
use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Newsletter\Model\SubscriberFactory;
use Magento\Customer\Model\CustomerRegistry;
use Magento\Framework\Encryption\EncryptorInterface as Encryptor;
use Magento\Store\Api\StoreResolverInterface;
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;

/**
* Customer field data provider, used for GraphQL request processing.
Expand All @@ -29,11 +34,31 @@ class CustomerDataProvider
*/
private $serviceOutputProcessor;

/**
* @var StoreResolverInterface
*/
private $storeResolver;

/**
* @var \Magento\Newsletter\Model\SubscriberFactory
*/
protected $subscriberFactory;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls, use private access


/**
* @var CustomerRegistry
*/
protected $customerRegistry;

/**
* @var SerializerInterface
*/
private $jsonSerializer;

/**
* @var Encryptor
*/
protected $encryptor;

/**
* @param CustomerRepositoryInterface $customerRepository
* @param ServiceOutputProcessor $serviceOutputProcessor
Expand All @@ -42,11 +67,32 @@ class CustomerDataProvider
public function __construct(
CustomerRepositoryInterface $customerRepository,
ServiceOutputProcessor $serviceOutputProcessor,
SerializerInterface $jsonSerializer
SerializerInterface $jsonSerializer,
SubscriberFactory $subscriberFactory,
CustomerRegistry $customerRegistry,
Encryptor $encryptor,
StoreResolverInterface $storeResolver
) {
$this->customerRepository = $customerRepository;
$this->serviceOutputProcessor = $serviceOutputProcessor;
$this->jsonSerializer = $jsonSerializer;
$this->subscriberFactory = $subscriberFactory;
$this->customerRegistry = $customerRegistry;
$this->encryptor = $encryptor;
$this->storeResolver = $storeResolver;
}

/**
* Load customer object
*
* @param int $customerId
* @return CustomerInterface
* @throws LocalizedException
* @throws NoSuchEntityException
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need mathod-wrapper over Repository?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I planned to use it in another location, but currently it not needed anymore

public function loadCustomerById(int $customerId): CustomerInterface
{
return $this->customerRepository->getById($customerId);
}

/**
Expand All @@ -56,7 +102,7 @@ public function __construct(
* @return array
* @throws NoSuchEntityException|LocalizedException
*/
public function getCustomerById(int $customerId) : array
public function getCustomerById(int $customerId): array
{
try {
$customerObject = $this->customerRepository->getById($customerId);
Expand All @@ -73,7 +119,7 @@ public function getCustomerById(int $customerId) : array
* @param CustomerInterface $customerObject
* @return array
*/
private function processCustomer(CustomerInterface $customerObject) : array
private function processCustomer(CustomerInterface $customerObject): array
{
$customer = $this->serviceOutputProcessor->process(
$customerObject,
Expand Down Expand Up @@ -110,4 +156,84 @@ private function processCustomer(CustomerInterface $customerObject) : array

return $customer;
}

/**
* Check if customer is subscribed to Newsletter
*
* @param int $customerId
* @return bool
*/
public function isSubscribed(int $customerId): bool
{
$checkSubscriber = $this->subscriberFactory->create()->loadByCustomerId($customerId);
return $checkSubscriber->isSubscribed();
}

/**
* Manage customer subscription. Subscribe OR unsubscribe if required
*
* @param int $customerId
* @param $newSubscriptionStatus
* @return bool
*/
public function manageSubscription(int $customerId, bool $newSubscriptionStatus): bool
{
$checkSubscriber = $this->subscriberFactory->create()->loadByCustomerId($customerId);
$isSubscribed = $this->isSubscribed($customerId);

if ($newSubscriptionStatus === true && !$isSubscribed) {
$this->subscriberFactory->create()->subscribeCustomerById($customerId);
} elseif ($newSubscriptionStatus === false && $checkSubscriber->isSubscribed()) {
$this->subscriberFactory->create()->unsubscribeCustomerById($customerId);
}
return true;
}

/**
* @param int $customerId
* @param array $customerData
* @return CustomerInterface
* @throws LocalizedException
* @throws NoSuchEntityException
* @throws \Magento\Framework\Exception\InputException
* @throws \Magento\Framework\Exception\State\InputMismatchException
*/
public function updateAccountInformation(int $customerId, array $customerData): CustomerInterface
{

$customer = $this->loadCustomerById($customerId);

if (isset($customerData['email'])
&& $customer->getEmail() !== $customerData['email']
&& isset($customerData['password'])) {
if ($this->isPasswordCorrect($customerData['password'], $customerId)) {
$customer->setEmail($customerData['email']);
} else {
throw new GraphQlAuthorizationException(__('Invalid current user password.'));
}
}

if (isset($customerData['firstname'])) {
$customer->setFirstname($customerData['firstname']);
}
if (isset($customerData['lastname'])) {
$customer->setLastname($customerData['lastname']);
}

$customer->setStoreId($this->storeResolver->getCurrentStoreId());
$this->customerRepository->save($customer);

return $customer;
}

private function isPasswordCorrect(string $password, int $customerId)
{

$customerSecure = $this->customerRegistry->retrieveSecureData($customerId);
$hash = $customerSecure->getPasswordHash();
if (!$this->encryptor->validateHash($password, $hash)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can simplify this condition

return false;
}
return true;
}
}
93 changes: 93 additions & 0 deletions app/code/Magento/CustomerGraphQl/Model/Resolver/CustomerUpdate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\CustomerGraphQl\Model\Resolver;

use Magento\Authorization\Model\UserContextInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\CustomerGraphQl\Model\Resolver\Customer\CustomerDataProvider;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
use Magento\Framework\GraphQl\Query\Resolver\Value;
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
use Magento\Framework\GraphQl\Query\ResolverInterface;

/**
* Customers field resolver, used for GraphQL request processing.
*/
class CustomerUpdate implements ResolverInterface
{
/**
* @var CustomerDataProvider
*/
private $customerResolver;

/**
* @var ValueFactory
*/
private $valueFactory;

/**
* @var \Magento\Newsletter\Model\SubscriberFactory
*/
protected $subscriberFactory;

/**
* @var CustomerRegistry
*/
protected $customerRegistry;

/**
* @param CustomerDataProvider $customerResolver
* @param ValueFactory $valueFactory
*/
public function __construct(
CustomerDataProvider $customerResolver,
ValueFactory $valueFactory,
\Magento\Newsletter\Model\SubscriberFactory $subscriberFactory
) {
$this->customerResolver = $customerResolver;
$this->valueFactory = $valueFactory;
$this->subscriberFactory = $subscriberFactory;
}

/**
* {@inheritdoc}
*/
public function resolve(
Field $field,
$context,
ResolveInfo $info,
array $value = null,
array $args = null
) : Value {

/** @var ContextInterface $context */
if ((!$context->getUserId()) || $context->getUserType() == UserContextInterface::USER_TYPE_GUEST) {
throw new GraphQlAuthorizationException(
__(
'Current customer does not have access to the resource "%1"',
[\Magento\Customer\Model\Customer::ENTITY]
)
);
}

$this->customerResolver->updateAccountInformation($context->getUserId(), $args);

if (isset($args['is_subscribed'])) {
$this->customerResolver->manageSubscription($context->getUserId(), $args['is_subscribed']);
}

$data = $args;
$result = function () use ($data) {
return !empty($data) ? $data : [];
};

return $this->valueFactory->create($result);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to return something if it is 'command' operation?

}
}
10 changes: 10 additions & 0 deletions app/code/Magento/CustomerGraphQl/etc/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,13 @@ type CustomerAddressRegion @doc(description: "CustomerAddressRegion defines the
region_id: Int @doc(description: "Uniquely identifies the region")
}

type Mutation {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, need to cover functionality with API-functional tests
Now we have a lot of examples and tests writing is very simple

customerUpdate (firstname: String, lastname: String, email: String, password: String, is_subscribed: Boolean): customerItem @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CustomerUpdate") @doc(description:"Update customer personal information")
}

type customerItem {
firstname: String
lastname: String
email: String
is_subscribed: Boolean
}