contacts_subscriptions-1.x-dev/src/Controller/SubscriptionController.php
src/Controller/SubscriptionController.php
<?php
namespace Drupal\contacts_subscriptions\Controller;
use CommerceGuys\Intl\Formatter\CurrencyFormatterInterface;
use Drupal\contacts_subscriptions\InvoiceManager;
use Drupal\contacts_subscriptions\SubscriptionsHelper;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Link;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Returns responses for Contacts Jobs Subscriptions routes.
*/
class SubscriptionController extends ControllerBase {
/**
* The date formatter.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected DateFormatterInterface $dateFormatter;
/**
* The currency formatter.
*
* @var \CommerceGuys\Intl\Formatter\CurrencyFormatterInterface
*/
protected CurrencyFormatterInterface $currencyFormatter;
/**
* The CSRF token generator.
*
* @var \Drupal\Core\Access\CsrfTokenGenerator
*/
protected CsrfTokenGenerator $csrf;
/**
* The invoice manager.
*
* @var \Drupal\contacts_subscriptions\InvoiceManager
*/
protected InvoiceManager $invoiceManager;
/**
* The Subscriptions helper service.
*
* @var \Drupal\contacts_subscriptions\SubscriptionsHelper
*/
protected SubscriptionsHelper $subscriptionsHelper;
/**
* The controller constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter.
* @param \CommerceGuys\Intl\Formatter\CurrencyFormatterInterface $currency_formatter
* The currency formatter.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder.
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf
* The CSRF token generator.
* @param \Drupal\contacts_subscriptions\InvoiceManager $invoice_manager
* The invoice manager.
* @param \Drupal\contacts_subscriptions\SubscriptionsHelper $subscriptions_helper
* The subscriptions helper.
*/
public function __construct(
EntityTypeManagerInterface $entity_type_manager,
DateFormatterInterface $date_formatter,
CurrencyFormatterInterface $currency_formatter,
FormBuilderInterface $form_builder,
CsrfTokenGenerator $csrf,
InvoiceManager $invoice_manager,
SubscriptionsHelper $subscriptions_helper,
) {
$this->entityTypeManager = $entity_type_manager;
$this->dateFormatter = $date_formatter;
$this->currencyFormatter = $currency_formatter;
$this->formBuilder = $form_builder;
$this->csrf = $csrf;
$this->invoiceManager = $invoice_manager;
$this->subscriptionsHelper = $subscriptions_helper;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$controller = new static(
$container->get('entity_type.manager'),
$container->get('date.formatter'),
$container->get('commerce_price.currency_formatter'),
$container->get('form_builder'),
$container->get('csrf_token'),
$container->get('contacts_subscriptions.invoice_manager'),
$container->get('contacts_subscriptions.helper')
);
$controller->setMessenger($container->get('messenger'));
return $controller;
}
/**
* Builds the overview page.
*
* @return array
* The render array.
*/
public function overview(UserInterface $user) {
$build = [
'#cache' => [
'tags' => ['contacts_subscription_list'],
'contexts' => ['url.query_args'],
],
];
/** @var \Drupal\contacts_subscriptions\Entity\SubscriptionInterface $subscription */
$groups = [
[
'user' => $user,
'subscriptions' => $this->entityTypeManager
->getStorage('contacts_subscription')
->loadByUser($user),
],
];
if ($user->hasPermission('manage my organisation membership')) {
if ($user->hasField('organisations')) {
foreach ($user->organisations as $item) {
if ($group = $item->membership->getGroup()) {
$groups[] = [
'user' => $group->contacts_org->entity,
'subscriptions' => $this->entityTypeManager
->getStorage('contacts_subscription')
->loadByUser($group->contacts_org->entity),
];
}
}
}
}
foreach ($groups as $data) {
$group_name = 'group_' . $data['user']->id();
$build[$group_name] = [
'#type' => 'container',
'title' => [
'#type' => 'html_tag',
'#tag' => 'h3',
'#value' => $this->t('Memberships for :user', [
':user' => $data['user']->getDisplayName(),
]),
],
];
$common_route_params = [
'user' => $data['user']->id(),
];
foreach ($data['subscriptions'] as $subscription) {
// Get the activation route depending on whether payment details need to
// be collected/ve-validated.
$activate_route = $subscription->needsPaymentDetails() ?
'contacts_subscriptions.subscription_payment' :
'contacts_subscriptions.subscription_activate';
$show_intro_offer = FALSE;
$intro_offer_products = [];
// Will renew means an ongoing subscription.
if ($subscription->willRenew()) {
$this->messenger->addMessage($this->t(':user %subname membership is due for renewal on %renewal.', [
'%renewal' => $this->dateFormatter->format($subscription->getRenewalDate()
->getTimestamp(), 'short_date'),
'%subname' => $subscription->bundle(),
':user' => $data['user']->getDisplayName() . "'s",
]));
if (!$subscription->canChangeProduct()) {
$this->messenger->addMessage($this->t(':user has changed their membership to %product. The next renewal will be at the new price. You cannot change again this billing cycle.', [
'%product' => $subscription->getProduct(FALSE, FALSE)->label(),
':user' => $data['user']->getDisplayName(),
]));
}
}
// Flag payment issues.
elseif ($subscription->needsPaymentDetails()) {
if ($expiry = $this->invoiceManager->getExpiry($subscription)) {
$this->messenger->addError($this->t(':user membership payment failed. Please @link to check the payment details. :user membership will end on %end.', [
'@link' => Link::createFromRoute('click here', $activate_route, $common_route_params)
->toString(),
'%end' => $this->dateFormatter->format($expiry->getTimestamp(), 'short_date'),
':user' => $data['user']->getDisplayName() . "'s",
]));
}
else {
$this->messenger->addError($this->t(':user membership payment failed. Please @link to check the payment details.', [
'@link' => Link::createFromRoute('click here', $activate_route, $common_route_params)
->toString(),
':user' => $data['user']->getDisplayName() . "'s",
]));
}
}
// Any other currently active status means the subscription is pending
// cancellation.
elseif ($subscription->isActive()) {
$this->messenger->addWarning($this->t(':user has cancelled their membership and it will end on %end. Please @link to re-activate.', [
'@link' => Link::createFromRoute('click here', $activate_route, $common_route_params)
->toString(),
'%end' => $this->dateFormatter->format($subscription->getExpiryDate()
->getTimestamp(), 'short_date'),
':user' => $data['user']->getDisplayName(),
]));
}
// Inform if they have expired due to now payment.
elseif ($subscription->getStatusId() === 'expired_payment') {
$this->messenger->addError($this->t(':user membership has expired due to no payment.', [
':user' => $data['user']->getDisplayName() . "'s",
]));
}
// If there has never been a subscription, show introductory offers.
elseif ($subscription->getStatusId() === 'none') {
$show_intro_offer = TRUE;
$intro_offer_products = $this->subscriptionsHelper->getProducts($data['user'], 'INTRO');
}
}
$build[$group_name]['products'] = [
'#theme' => 'contacts_subscription_options',
'#show_intro_offer' => $show_intro_offer ?? FALSE,
];
/** @var \Drupal\contacts_subscriptions\Entity\SubscriptionType $subscription_type */
$types = $this->entityTypeManager->getStorage('contacts_subscription_type')->loadMultiple();
$types = array_reverse($types);
foreach ($types as $subscription_type) {
$current_subscription = array_filter($data['subscriptions'], function ($value) use ($subscription_type) {
return $value->bundle() === $subscription_type->id();
});
if (count($current_subscription) === 1) {
$current_subscription = reset($current_subscription);
}
foreach ($this->subscriptionsHelper->getProductsOfType($data['user'], $subscription_type) as $product_id => $variation) {
$standard_variation_id = NULL;
$product = $variation->getProduct();
$build[$group_name]['products']['#options'][$product_id] = [
'#variation' => $variation,
'title' => $product->label(),
'description' => $product->get('body')->view(['label' => 'hidden']),
];
$build[$group_name]['products']['#cache']['tags'][] = $variation->getEntityTypeId() . ':' . $variation->id();
$option = &$build[$group_name]['products']['#options'][$product_id];
// Work out any price text.
$price = $variation->getPrice();
if ($price && $price->isPositive()) {
$option['price'] = $this->currencyFormatter->format($price->getNumber(), $price->getCurrencyCode());
}
// If we have an introductory offer, store the standard variation ID
// for links, swap out the working variation and set the intro price
// text.
if (isset($intro_offer_products[$product_id]) && $intro_offer_products[$product_id]->id() !== $variation->id()) {
$standard_variation_id = $variation->id();
$variation = $intro_offer_products[$product_id];
// If we have a price, show it.
$intro_price = $variation->getPrice();
if ($intro_price && $intro_price->isPositive()) {
$option['price_intro'] = $this->t('Initially @price', [
'@price' => $this->currencyFormatter->format($intro_price->getNumber(), $intro_price->getCurrencyCode()),
]);
}
// Otherwise, use free text.
else {
$option['price_intro'] = $this->t('Initially free');
}
}
// Work out the correct links.
if ($current_subscription && $product_id === $current_subscription->getProductId(TRUE, TRUE)) {
$option['is_active'] = TRUE;
if ($current_subscription->willRenew()) {
if ($current_subscription->canChangeProduct()) {
$route_parameters = [
'subscription' => $current_subscription->id(),
] + $common_route_params;
$option['link'] = Link::createFromRoute($this->t('Cancel renewal'), 'contacts_subscriptions.subscription_cancel', $route_parameters);
$option['link_is_cancel'] = TRUE;
}
}
else {
// @todo Handle multiple variations.
$option['link'] = Link::createFromRoute($this->t('Re-activate'), $activate_route, [
'commerce_product_variation' => $variation->id(),
'token' => $this->csrf->get($current_subscription->getCsrfValue($variation)),
] + $common_route_params);
}
}
elseif ($current_subscription && $current_subscription->isActive()) {
if ($current_subscription->canChangeProduct()) {
$route_parameters = [
'commerce_product_variation' => $variation->id(),
'token' => $this->csrf->get($current_subscription->getCsrfValue($variation)),
] + $common_route_params;
if ($current_subscription->willRenew()) {
$option['link'] = Link::createFromRoute($this->t('Switch'), 'contacts_subscriptions.subscription_activate', $route_parameters);
}
// Only show the re-activate and switch link if we don't need
// payment details.
elseif (!$current_subscription->needsPaymentDetails()) {
$option['link'] = Link::createFromRoute($this->t('Re-activate & switch'), 'contacts_subscriptions.subscription_payment', $route_parameters);
}
}
}
else {
// Only show the create option if the subscription can be created
// for that kind of user.
$who_can = $subscription_type->getWhoCanPurchase();
$roles = ($who_can == 'both') ? ['crm_org', 'crm_indiv'] : [$who_can];
if (array_intersect($roles, $data['user']->getRoles())) {
$values = [
'uid' => $data['user']->id(),
'bundle' => $variation->getProduct()->subscription_type->target_id,
];
$new_subscription = $this->entityTypeManager()->getStorage('contacts_subscription')->create($values);
$route_parameters = [
'commerce_product_variation' => $variation->id(),
'token' => $this->csrf->get($new_subscription->getCsrfValue($variation)),
] + $common_route_params;
$options = [];
if ($standard_variation_id) {
$options['query']['vid'] = $standard_variation_id;
}
$option['link'] = Link::createFromRoute($this->t('Get started'), 'contacts_subscriptions.subscription_payment', $route_parameters, $options);
}
else {
// This product is not available for these users, so remove it.
unset($build[$group_name]['products']['#options'][$product_id]);
}
}
}
}
}
return $build;
}
}
