billwerk_subscriptions-1.x-dev/modules/billwerk_subscriptions_manage/src/Plugin/Block/BillwerkUserSubscriptionsBlock.php
modules/billwerk_subscriptions_manage/src/Plugin/Block/BillwerkUserSubscriptionsBlock.php
<?php
declare(strict_types=1);
namespace Drupal\billwerk_subscriptions_manage\Plugin\Block;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\CurrentRouteMatch;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\billwerk_subscriptions\Environment;
use Drupal\billwerk_subscriptions\Subscriber;
use Drupal\billwerk_subscriptions_entities\BillwerkEntitiesHelper;
use Drupal\billwerk_subscriptions_entities\SubscriberBillwerkEntitiesHelper;
use Drupal\billwerk_subscriptions_manage\Form\SettingsForm;
use Drupal\billwerk_subscriptions_manage\Helper;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a billwerk user subscriptions block.
*
* @Block(
* id = "billwerk_subscriptions_manage_billwerk_user_subscriptions",
* admin_label = @Translation("Billwerk Subscriptions Manage: My Subscription"),
* category = @Translation("Billwerk Subscriptions"),
* )
*/
final class BillwerkUserSubscriptionsBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The subscriber billwerk entities helper.
*
* @var \Drupal\billwerk_subscriptions_entities\SubscriberBillwerkEntitiesHelper
*/
protected readonly SubscriberBillwerkEntitiesHelper $subscriberBillwerkEntitiesHelper;
/**
* The owner of the block.
*
* @var ?\Drupal\Core\Session\AccountInterface
*/
protected readonly ?UserInterface $owner;
/**
* The subscriber.
*
* @var ?\Drupal\billwerk_subscriptions\Subscriber
*/
protected readonly ?Subscriber $subscriber;
/**
* Constructs the plugin instance.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
protected readonly CurrentRouteMatch $currentRouteMatch,
protected readonly AccountProxyInterface $currentUser,
protected readonly EntityTypeManagerInterface $entityTypeManager,
protected readonly BillwerkEntitiesHelper $billwerkEntitiesHelper,
protected readonly Environment $environment,
protected readonly LanguageManagerInterface $languageManager,
protected readonly RendererInterface $renderer,
protected readonly Helper $helper,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
switch ($this->configuration['source']) {
case 'current':
$this->owner = $entityTypeManager->getStorage('user')->load($currentUser->id());
break;
case 'param_user':
$this->owner = $currentRouteMatch->getParameter('user');
break;
default:
throw new \UnexpectedValueException("Unexpected source: \"{$this->configuration['source']}\"");
}
if (!$this->owner || $this->owner->isAnonymous()) {
// The anonymous user may never be a subscriber!
return;
}
$this->subscriber = Subscriber::load($this->owner);
$this->subscriberBillwerkEntitiesHelper = SubscriberBillwerkEntitiesHelper::create($this->subscriber, $billwerkEntitiesHelper, $environment);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self {
return new self(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('current_route_match'),
$container->get('current_user'),
$container->get('entity_type.manager'),
$container->get('billwerk_subscriptions_entities.billwerk_entities_helper'),
$container->get('billwerk_subscriptions.environment'),
$container->get('language_manager'),
$container->get('renderer'),
$container->get('billwerk_subscriptions_manage.helper'),
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration(): array {
return [
'source' => 'current',
'show_active_subscription' => TRUE,
'show_active_components' => TRUE,
'no_active_components_text' => [
'value' => '',
'format' => 'full_html',
],
'show_change_plan_variant_button' => TRUE,
'show_cancel_plan_variant_button' => FALSE,
'show_components_subscribe_button' => TRUE,
'show_components_unsubscribe_button' => TRUE,
'modal_actions' => TRUE,
];
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state): array {
$form['source'] = [
'#type' => 'select',
'#title' => $this->t('User source'),
'#options' => [
'current' => $this->t('Current user'),
'param_user' => $this->t('Route user account (Route parameter "user)'),
],
'#default_value' => $this->configuration['source'],
];
$form['show_active_subscription'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show active subscriptions'),
'#default_value' => $this->configuration['show_active_subscription'],
];
$form['show_active_components'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show active components'),
'#default_value' => $this->configuration['show_active_components'],
];
$form['no_active_components_text'] = [
'#type' => 'text_format',
'#title' => $this->t('No active components text'),
'#default_value' => $this->configuration['no_active_components_text']['value'],
'#format' => $this->configuration['no_active_components_text']['format'],
'#description' => $this->t('If left empty, the components list will be hidden.'),
];
$form['show_change_plan_variant_button'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show the "Change plan" button'),
'#default_value' => $this->configuration['show_change_plan_variant_button'],
];
$form['show_cancel_plan_variant_button'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show the "Cancel subscription" button'),
'#default_value' => $this->configuration['show_cancel_plan_variant_button'],
];
$form['show_components_subscribe_button'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show the "Subscribe add-ons" button'),
'#default_value' => $this->configuration['show_components_subscribe_button'],
];
$form['show_components_unsubscribe_button'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show the "Cancel add-on" button'),
'#default_value' => $this->configuration['show_components_unsubscribe_button'],
];
$form['modal_actions'] = [
'#type' => 'checkbox',
'#title' => $this->t('Open the actions in a modal'),
'#default_value' => $this->configuration['modal_actions'],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state): void {
$this->configuration['source'] = $form_state->getValue('source');
$this->configuration['show_active_subscription'] = $form_state->getValue('show_active_subscription');
$this->configuration['show_active_components'] = $form_state->getValue('show_active_components');
$this->configuration['no_active_components_text'] = $form_state->getValue('no_active_components_text');
$this->configuration['show_change_plan_variant_button'] = $form_state->getValue('show_change_plan_variant_button');
$this->configuration['show_cancel_plan_variant_button'] = $form_state->getValue('show_cancel_plan_variant_button');
$this->configuration['show_components_subscribe_button'] = $form_state->getValue('show_components_subscribe_button');
$this->configuration['show_components_unsubscribe_button'] = $form_state->getValue('show_components_unsubscribe_button');
$this->configuration['modal_actions'] = $form_state->getValue('modal_actions');
}
/**
* {@inheritdoc}
*/
public function build(): array {
$userLocale = $this->languageManager->getCurrentLanguage()->getId();
if (!$this->owner) {
// We can't show anything, if there's no owner.
return [];
}
// Show user information to prevent confusion. This is intended for the
// current user!
$build['current_user'] = [
'#type' => 'item',
'#title' => '<strong>' . $this->t('Username:') . '</strong>',
'#markup' => $this->owner->getDisplayName(),
'#wrapper_attributes' => ['class' => ['container-inline']],
];
if ($this->currentUser->id() != $this->owner->id()) {
$build['not_manageable'] = [
'message' => [
'#type' => 'inline_template',
'#template' => '<div class="messages messages--warning"><div class="message">{{ "Managing other user accounts here is not yet implemented."|t }}</div></div>',
'#context' => [],
],
];
return $build;
}
$build['plan'] = [
'#type' => 'details',
'#title' => $this->t('My subscription plan'),
'#open' => TRUE,
];
// Show active subscriptions.
if ($this->configuration['show_active_subscription']) {
$activeBillwerkContract = $this->subscriber->getBillwerkContract();
if (!empty($activeBillwerkContract)) {
$activeBillwerkContractSubscription = $activeBillwerkContract->getBillwerkContractSubscription();
$activeBillwerkPlanVariantId = $this->subscriber->getBillwerkContract()->getBillwerkContractSubscription()->getPlanVariantId();
if (!empty($activeBillwerkPlanVariantId)) {
$activeBillwerkPlanVariant = $this->billwerkEntitiesHelper->getBillwerkPlanVariantByPlanVariantId($activeBillwerkPlanVariantId, $this->environment, TRUE, FALSE);
if (!empty($activeBillwerkPlanVariant)) {
// List only known component entities:
$build['plan']['active_plan_variant_subscription'] = [
'#theme' => 'billwerk_plan_variant_subscription',
// @improve: We might make the view mode configurable one day.
'#billwerk_plan_variant' => $this->entityTypeManager->getViewBuilder('billwerk_plan_variant')->view($activeBillwerkPlanVariant, 'card'),
'#id' => $activeBillwerkContract->getId(),
'#planId' => $activeBillwerkContractSubscription->getPlanId(),
'#phaseType' => $activeBillwerkContractSubscription->getPhaseType(),
'#planVariantId' => $activeBillwerkContractSubscription->getPlanVariantId(),
'#startDate' => $activeBillwerkContractSubscription->getStartDate(),
'#endDate' => $activeBillwerkContractSubscription->getEndDate(),
'#trialEndDate' => $activeBillwerkContractSubscription->getTrialEndDate(),
'#billedUntil' => $activeBillwerkContractSubscription->getBilledUntil(),
'#lastBillingDate' => $activeBillwerkContractSubscription->getLastBillingDate(),
'#nextBillingDate' => $activeBillwerkContractSubscription->getNextBillingDate(),
'#currency' => $activeBillwerkContractSubscription->getCurrency(),
'#balance' => $activeBillwerkContractSubscription->getBalance(),
'#userLocale' => $userLocale,
];
}
}
}
}
if ($this->configuration['show_change_plan_variant_button'] || $this->configuration['show_cancel_plan_variant_button']) {
$build['plan']['plan_variant_actions'] = [
'#type' => 'actions',
'#attributes' => [
'class' => [
'plan-actions',
],
],
];
}
if (!$this->subscriber->billwerkHasPendingContractPhases()) {
// Change plan button:
if ($this->configuration['show_change_plan_variant_button']) {
$build['plan']['plan_variant_actions']['change_plan_variant_button'] = [
'#type' => 'link',
'#title' => $this->t('Change plan (Up- / downgrade)'),
'#url' => Url::fromRoute('billwerk_subscriptions_manage.current_user_change_plan_variant'),
'#attributes' => [
'class' => [
'button',
'button--change-plan',
],
],
];
if ($this->configuration['modal_actions']) {
// Add modal classes and library, if enabled:
$build['plan']['plan_variant_actions']['change_plan_variant_button']['#attributes']['class'][] = 'use-ajax';
$build['plan']['plan_variant_actions']['change_plan_variant_button']['#attributes']['data-dialog-type'] = 'modal';
$build['plan']['plan_variant_actions']['change_plan_variant_button']['#attributes']['data-dialog-options'] = Json::encode(['width' => '75%']);
$build['plan']['plan_variant_actions']['change_plan_variant_button']['#attached']['library'][] = 'core/drupal.dialog.ajax';
}
}
// Change plan button:
if ($this->configuration['show_cancel_plan_variant_button']) {
// Show button, if the plan isn't already the free one to downgrade to
// or if this is a real Billwerk cancellation.
if (($this->helper->getCancellationMethod() === SettingsForm::CANCELLATION_METHOD_DOWNGRADE
&& $this->helper->getBillwerkFreePlanVariantId() != $this->subscriber->getBillwerkContract()->getBillwerkContractSubscription()->getPlanVariantId())
|| ($this->helper->getCancellationMethod() === SettingsForm::CANCELLATION_METHOD_CANCEL_CONTRACT)) {
$build['plan']['plan_variant_actions']['cancel_plan_variant_button'] = [
'#type' => 'link',
'#title' => $this->t('Cancel plan'),
'#url' => Url::fromRoute('billwerk_subscriptions_manage.current_user_cancel_subscription'),
'#attributes' => [
'class' => [
'button',
'button--cancel-plan',
],
],
];
if ($this->configuration['modal_actions']) {
// Add modal classes and library, if enabled:
$build['plan']['plan_variant_actions']['cancel_plan_variant_button']['#attributes']['class'][] = 'use-ajax';
$build['plan']['plan_variant_actions']['cancel_plan_variant_button']['#attributes']['data-dialog-type'] = 'modal';
$build['plan']['plan_variant_actions']['cancel_plan_variant_button']['#attributes']['data-dialog-options'] = Json::encode(['width' => '75%']);
$build['plan']['plan_variant_actions']['cancel_plan_variant_button']['#attached']['library'][] = 'core/drupal.dialog.ajax';
}
}
}
}
else {
$build['plan']['plan_variant_actions']['not_manageable'] = [
'message' => [
'#type' => 'inline_template',
'#template' => '<div class="messages messages--warning"><div class="message">{{ "You have pending contract changes. Further contract amendments are not possible until then. Please contact us to revoke cancellations or for individual cases."|t }}</div></div>',
'#context' => [],
],
];
}
// Remove the plan if there are no elements visible:
if (empty(Element::getVisibleChildren($build['plan']))) {
unset($build['plan']); // @codingStandardsIgnoreLine
}
$build['components'] = [
'#type' => 'details',
'#title' => $this->t('My add-ons'),
'#open' => TRUE,
];
// Show active component subscriptions:
if ($this->configuration['show_active_components']) {
$activeBillwerkContract = $this->subscriber->getBillwerkContract();
if (!empty($activeBillwerkContract)) {
$activeBillwerkComponentSubscriptions = $activeBillwerkContract->getBillwerkContractSubscription()->getComponentSubscriptions();
if (!empty($activeBillwerkComponentSubscriptions)) {
$build['components']['active_component_subscriptions'] = [
'#type' => 'container',
];
foreach ($activeBillwerkComponentSubscriptions as $componentSubscriptionId => $subscriberCurrentComponentSubscription) {
$componentEntity = $this->billwerkEntitiesHelper->getBillwerkComponentByComponentId($subscriberCurrentComponentSubscription->getComponentId(), $this->environment, TRUE, FALSE);
if (!empty($componentEntity)) {
// List only known component entities:
$build['components']['active_component_subscriptions'][$componentSubscriptionId] = [
'#theme' => 'billwerk_component_subscription',
// @improve: We might make the view mode configurable one day.
'#billwerk_component' => $this->entityTypeManager->getViewBuilder('billwerk_component')->view($componentEntity, 'card'),
'#startDate' => $subscriberCurrentComponentSubscription->getStartDate(),
'#endDate' => $subscriberCurrentComponentSubscription->getEndDate(),
'#billedUntil' => $subscriberCurrentComponentSubscription->getBilledUntil(),
'#quantity' => $subscriberCurrentComponentSubscription->getQuantity(),
'#componentId' => $subscriberCurrentComponentSubscription->getComponentId(),
'#id' => $subscriberCurrentComponentSubscription->getId(),
'#userLocale' => $userLocale,
];
}
}
}
elseif (!empty($this->configuration['no_active_components_text']['value'])) {
// Show empty text, if set:
$build['components']['no_active_component_subscriptions'] = [
'#type' => 'processed_text',
'#text' => $this->configuration['no_active_components_text']['value'],
'#format' => $this->configuration['no_active_components_text']['format'],
];
}
}
else {
$build['components']['no_active_billwerk_contract'] = [
'#plain_text' => $this->t('No active contract'),
];
}
}
if ($this->configuration['show_components_subscribe_button'] || $this->configuration['show_components_unsubscribe_button']) {
$build['components']['components_actions'] = [
'#type' => 'actions',
'#attributes' => [
'class' => [
'component-actions',
],
],
];
}
// Subscribe component button:
if ($this->configuration['show_components_subscribe_button']) {
$build['components']['components_actions']['components_subscribe_button'] = [
'#type' => 'link',
'#title' => $this->t('Book add-ons'),
'#url' => Url::fromRoute('billwerk_subscriptions_manage.current_user_components_subscribe'),
'#attributes' => [
'class' => [
'button',
'button--components-subscribe',
],
],
];
if ($this->configuration['modal_actions']) {
// Add modal classes and library, if enabled:
$build['components']['components_actions']['components_subscribe_button']['#attributes']['class'][] = 'use-ajax';
$build['components']['components_actions']['components_subscribe_button']['#attributes']['data-dialog-type'] = 'modal';
$build['components']['components_actions']['components_subscribe_button']['#attributes']['data-dialog-options'] = Json::encode(['width' => '75%']);
$build['components']['components_actions']['components_subscribe_button']['#attached']['library'][] = 'core/drupal.dialog.ajax';
}
}
if ($this->configuration['show_components_unsubscribe_button'] && !empty($build['components']['active_component_subscriptions'])) {
$build['components']['components_actions']['components_unsubscribe_button'] = [
'#type' => 'link',
'#title' => $this->t('Cancel add-ons'),
'#url' => Url::fromRoute('billwerk_subscriptions_manage.current_user_components_unsubscribe'),
'#attributes' => [
'class' => [
'button',
'button--components-unsubscribe',
],
],
];
if ($this->configuration['modal_actions']) {
// Add modal classes and library, if enabled:
$build['components']['components_actions']['components_unsubscribe_button']['#attributes']['class'][] = 'use-ajax';
$build['components']['components_actions']['components_unsubscribe_button']['#attributes']['data-dialog-type'] = 'modal';
$build['components']['components_actions']['components_unsubscribe_button']['#attributes']['data-dialog-options'] = Json::encode(['width' => '75%']);
$build['components']['components_actions']['components_unsubscribe_button']['#attached']['library'][] = 'core/drupal.dialog.ajax';
}
}
// Remove the components if there are no elements visible:
if (empty(Element::getVisibleChildren($build['components']))) {
unset($build['components']); // @codingStandardsIgnoreLine
}
// Cache by user.
$build['#cache']['contexts'][] = 'user';
// Add the cacheable dependencies:
$this->renderer->addCacheableDependency($build, $this->owner);
$this->renderer->addCacheableDependency($build, $this);
return $build;
}
/**
* {@inheritdoc}
*/
protected function blockAccess(AccountInterface $account): AccessResult {
if (!$this->owner) {
return AccessResult::forbidden();
}
if ($account->id() === $this->owner->id()) {
// Is the current users account.
$access = AccessResult::allowedIfHasPermission($account, 'billwerk_subscriptions_selfservice_manage_own_contract');
}
else {
// Is a different users account.
$access = AccessResult::allowedIfHasPermission($account, 'billwerk_subscriptions_selfservice_manage_any_contract');
}
// The anonymous user may never be a subscriber!
$hasUserBillwerkContractId = $this->owner->isAnonymous() ? FALSE : $this->subscriber->hasUserBillwerkContractId();
return $access->andIf(AccessResult::allowedIf($hasUserBillwerkContractId));
}
}
