billwerk_subscriptions-1.x-dev/modules/billwerk_subscriptions_handler_default/src/EventSubscriber/SubscriberRefreshUserSubscriber.php
modules/billwerk_subscriptions_handler_default/src/EventSubscriber/SubscriberRefreshUserSubscriber.php
<?php
declare(strict_types=1);
namespace Drupal\billwerk_subscriptions_handler_default\EventSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\billwerk_subscriptions\BillwerkRolesManager;
use Drupal\billwerk_subscriptions\DataObject\BillwerkContractSubscription;
use Drupal\billwerk_subscriptions\Environment;
use Drupal\billwerk_subscriptions\Event\SubscriberRefreshUserEvent;
use Drupal\billwerk_subscriptions\Exception\SubscriberException;
use Drupal\billwerk_subscriptions\LogHelper;
use Drupal\billwerk_subscriptions_entities\BillwerkEntitiesHelper;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Subscriber for whenever the user should be refreshed from the subscription.
*
* Implements the specific actions to take on the user profile in such a case,
* like updating the user data with values from the Billwerk Customer and
* the user roles with the roles associated with the Billwerk Contract
* subscriptions.
*/
class SubscriberRefreshUserSubscriber implements EventSubscriberInterface {
/**
* The settings config.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected ImmutableConfig $config;
/**
* Constructs a SubscriberRefreshUserSubscriber object.
*/
public function __construct(
protected readonly LogHelper $logHelper,
ConfigFactoryInterface $configFactory,
protected readonly Environment $environment,
protected readonly BillwerkEntitiesHelper $billwerkEntitiesHelper,
protected readonly BillwerkRolesManager $billwerkRolesManager,
) {
$this->config = $configFactory->get('billwerk_subscriptions_handler_default.settings');
}
/**
* Handles a subscriber refresh user event.
*
* @param \Drupal\billwerk_subscriptions\Event\SubscriberRefreshUserEvent $event
* The subscriber refresh user event.
*
* @throws \Drupal\billwerk_subscriptions\Exception\SubscriberException
* Thrown when the user could not be loaded from the subscriber.
*/
public function onSubscriberRefreshUser(SubscriberRefreshUserEvent $event): void {
$subscriber = $event->getSubscriber();
$user = $subscriber->getUser();
if (empty($user) || empty($user->id())) {
throw new SubscriberException('User could not be loaded from the subscriber');
}
$userChanged = $this->updateUserData($event);
$userChanged = $this->updateUserRoles($event) || $userChanged;
$userChanged = $this->updateUserStatus($event) || $userChanged;
if ($userChanged) {
// Save the user if it changed.
$user->save();
}
}
/**
* Updates the user data.
*
* @param \Drupal\billwerk_subscriptions\Event\SubscriberRefreshUserEvent $event
* The subscriber refresh user event.
*
* @return bool
* TRUE if the user data was updated, FALSE otherwise.
*/
protected function updateUserData(SubscriberRefreshUserEvent $event): bool {
$subscriber = $event->getSubscriber();
$user = $subscriber->getUser();
$subscriberBillwerkContract = $subscriber->getBillwerkContract();
$userChanged = FALSE;
// Update external ID, mail and status:
if ($this->config->get('fetch_overwrite_status') && $user->hasField('status')) {
if ($user->isActive()) {
if ($subscriberBillwerkContract->getBillwerkCustomer()->getIsLocked()) {
// The Drupal account is active, but the Billwerk Customer is locked!
$this->logHelper->info(
'User #@uid was active in Drupal, but his Billwerk Customer #@billwerkCustomerId is locked. Setting the Drupal user inactive for this reason!',
[
'@uid' => $user->id(),
'@billwerkCustomerId' => $subscriberBillwerkContract->getBillwerkCustomer()->getId(),
],
[],
__CLASS__ . __FUNCTION__
);
// Disable the Drupal user:
$user->set('status', FALSE);
$userChanged = TRUE;
}
if ($subscriberBillwerkContract->getBillwerkCustomer()->getIsHidden()) {
// The Drupal account is active, but the Billwerk Customer is hidden!
$this->logHelper->info(
'User #@uid was active in Drupal, but his Billwerk Customer #@billwerkCustomerId is hidden. Setting the Drupal user inactive for this reason!',
[
'@uid' => $user->id(),
'@billwerkCustomerId' => $subscriberBillwerkContract->getBillwerkCustomer()->getId(),
],
[],
__CLASS__ . __FUNCTION__
);
// Disable the Drupal user:
$user->set('status', FALSE);
$userChanged = TRUE;
}
}
else {
if (!$subscriberBillwerkContract->getBillwerkCustomer()->getIsLocked() && !$subscriberBillwerkContract->getBillwerkCustomer()->getIsHidden()) {
$this->logHelper->warning(
'User #@uid is blocked in Drupal, but his Billwerk Customer #@billwerkCustomerId is neither locked or hidden. We do not automatically (re-)activate Drupal accounts as we can not tell why the user is blocked and unblocking would be risky. Please check the user details and subscription manually.',
[
'@uid' => $user->id(),
'@billwerkCustomerId' => $subscriberBillwerkContract->getBillwerkCustomer()->getId(),
],
[],
__CLASS__ . __FUNCTION__
);
}
}
}
if ($this->config->get('fetch_overwrite_mail') && $user->hasField('mail')) {
$billwerkMailAddress = $subscriberBillwerkContract->getBillwerkCustomer()->getEmailAddress();
$drupalMailAddress = $user->getEmail();
if (!empty($billwerkMailAddress) && $drupalMailAddress !== $billwerkMailAddress) {
$this->logHelper->info(
'User #@uid email address was updated to "@billwerkMailAddress" from "@drupalMailAddress" because Email Address overwrite from Billwerk is enabled.',
[
'@uid' => $user->id(),
'@billwerkCustomerId' => $subscriberBillwerkContract->getBillwerkCustomer()->getId(),
'@drupalMailAddress' => $drupalMailAddress,
'@billwerkMailAddress' => $billwerkMailAddress,
],
[],
__CLASS__ . __FUNCTION__
);
$user->set('mail', $billwerkMailAddress);
$userChanged = TRUE;
}
}
return $userChanged;
}
/**
* Updates the user roles.
*
* @param \Drupal\billwerk_subscriptions\Event\SubscriberRefreshUserEvent $event
* The subscriber refresh user event.
*
* @return bool
* TRUE if the user roles were updated, FALSE otherwise.
*
* @throws \Drupal\billwerk_subscriptions\Exception\SubscriberException
* Thrown when the user could not be loaded from the subscriber.
*/
protected function updateUserRoles(SubscriberRefreshUserEvent $event): bool {
$subscriber = $event->getSubscriber();
$user = $subscriber->getUser();
$currentUserRoleIds = $user->getRoles();
if (empty($user) || empty($user->id())) {
throw new SubscriberException('User could not be loaded from the subscriber');
}
$subscriberBillwerkContract = $subscriber->getBillwerkContract();
$userChanged = FALSE;
/** @var \Drupal\user\Entity\Role[] $assignRoles */
$assignRoles = [];
$subscriberBillwerkContractSubscription = $subscriberBillwerkContract->getBillwerkContractSubscription();
// Get the ACTIVE user subscription and retrieve their role from
// billwerk_subscriptions_entities submodule:
$subscriptionPhaseType = $subscriberBillwerkContractSubscription->getPhaseType();
$subscriptionPlanVariantId = $subscriberBillwerkContractSubscription->getPlanVariantId();
if (in_array($subscriptionPhaseType,
[
BillwerkContractSubscription::PHASE_TYPE_NORMAL,
BillwerkContractSubscription::PHASE_TYPE_TRIAL,
]
)) {
// Active plan variant subscription!
$planVariantEntity = $this->billwerkEntitiesHelper->getBillwerkPlanVariantByPlanVariantId($subscriptionPlanVariantId, $this->environment, TRUE, FALSE);
if (!empty($planVariantEntity)) {
$assignRoles[] = $planVariantEntity->getRoleId();
}
else {
$this->logHelper->warning("Could not load the Plan Variant entity with Billwerk ID: #{$subscriptionPlanVariantId}. Typically all relevant Plan Variants in a contract assigned to a user should exist as Drupal entity.");
}
// Get the ACTIVE user subscriptions components and retrieve their roles
// from billwerk_subscriptions_entities:
$subscriberBillwerkContractComponentSubscriptions = $subscriberBillwerkContractSubscription->getComponentSubscriptions();
foreach ($subscriberBillwerkContractComponentSubscriptions as $componentSubscription) {
/** @var \Drupal\billwerk_subscriptions\DataObject\BillwerkContractComponentSubscription $componentSubscription */
// We currently don't use the quantity in our example.
// $componentQuantity = $componentSubscription->getQuantity();
$billwerkComponentId = $componentSubscription->getComponentId();
$componentEntity = $this->billwerkEntitiesHelper->getBillwerkComponentByComponentId($billwerkComponentId, $this->environment, TRUE, FALSE);
if (!empty($componentEntity)) {
$assignRoles[] = $componentEntity->getRoleId();
}
else {
$this->logHelper->warning("Could not load the Component entity with Billwerk ID: #{$billwerkComponentId}. Typically all relevant Components in a contract assigned to a user should exist as Drupal entity.");
}
}
}
elseif (in_array(
$subscriptionPhaseType,
[
BillwerkContractSubscription::PHASE_TYPE_INACTIVE,
]
)) {
// Inactive plan variant subscription!
// We do nothing here.
}
else {
throw new SubscriberException("Unhandled subscription phase type: {$subscriptionPhaseType}. All phase types should be handled to ensure correct functionality!");
}
// Compare the existing roles (that are handled by billwerk (Setting!)) to
// the calculated roles. Remove all roles (that are handled by billwerk
// (Setting!)) and set the new ones.
$billwerkRoleIds = $this->billwerkRolesManager->getBillwerkRoles();
$currentUserBillwerkRoleIds = array_intersect($currentUserRoleIds, $billwerkRoleIds);
$removeRoles = array_diff($currentUserBillwerkRoleIds, $assignRoles);
$addRoles = array_diff($assignRoles, $currentUserBillwerkRoleIds);
foreach ($removeRoles as $removeRole) {
$user->removeRole($removeRole);
$userChanged = TRUE;
}
foreach ($addRoles as $addRole) {
$user->addRole($addRole);
$userChanged = TRUE;
}
return $userChanged;
}
/**
* Updates the user status.
*
* @param \Drupal\billwerk_subscriptions\Event\SubscriberRefreshUserEvent $event
* The subscriber refresh user event.
*
* @return bool
* TRUE if the user status was updated, FALSE otherwise.
*/
public function updateUserStatus(SubscriberRefreshUserEvent $event): bool {
$subscriber = $event->getSubscriber();
$user = $subscriber->getUser();
$subscriberBillwerkContract = $subscriber->getBillwerkContract();
$subscriberBillwerkCustomer = $subscriberBillwerkContract->getBillwerkCustomer();
if (!$user->isBlocked() && ($subscriberBillwerkCustomer->getIsLocked())) {
// Block the user, if it is locked at Billwerk:
$user->block();
$this->logHelper->info(
'Blocked User #@uid because the Customer is locked at Billwerk.',
[
'@uid' => $subscriber->getUser()->id(),
],
NULL, __CLASS__ . __FUNCTION__);
return TRUE;
}
elseif (!$user->isBlocked() && ($subscriberBillwerkCustomer->isDeleted())) {
// Block the user, if it is locked at Billwerk:
$user->block();
$this->logHelper->info(
'Blocked User #@uid because the Customer is deleted at Billwerk. Former Billwerk Contract ID: @billwerk_contract_id',
[
'@uid' => $subscriber->getUser()->id(),
'@billwerk_contract_id' => $subscriberBillwerkContract->getId(),
],
NULL, __CLASS__ . __FUNCTION__);
return TRUE;
}
elseif ($user->isBlocked() && !$subscriberBillwerkCustomer->getIsLocked()) {
// Unblock the user if it is unblocked at Billwerk:
// @codingStandardsIgnoreStart
// We currently do NOT automatically unblock user accounts because that may be risky.
// This should be done manually to ensure no user is unblocked that should not be.
// $user->activate();
// $this->logHelper->info(
// 'Unblocked User #@uid because the Customer is unlocked at Billwerk.',
// [
// '@uid' => $subscriber->getUser()->id(),
// ],
// NULL, __CLASS__ . __FUNCTION__);
// return TRUE;.
// @codingStandardsIgnoreEnd
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [
SubscriberRefreshUserEvent::class => ['onSubscriberRefreshUser'],
];
}
}
