contacts_subscriptions-1.x-dev/contacts_subscriptions.module
contacts_subscriptions.module
<?php
/**
* @file
* Primary module hooks for Contacts Jobs Subscriptions module.
*/
use Drupal\contacts_subscriptions\Entity\SubscriptionInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Template\Attribute;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\entity\BundleFieldDefinition;
/**
* Implements hook_cron().
*/
function contacts_subscriptions_cron() {
$last_run_timestamp = \Drupal::state()->get('contacts_subscriptions_last_run', 0);
$now_timestamp = time();
// Run check every 6 hours.
$six_hours = (60 * 60 * 6);
if ($now_timestamp > ($last_run_timestamp + $six_hours)) {
$renewing = [];
/** @var \Drupal\contacts_subscriptions\Entity\SubscriptionType $type */
foreach (\Drupal::entityTypeManager()->getStorage('contacts_subscription_type')->loadMultiple() as $type) {
$renewal = new DrupalDateTime();
$renewal->format(DateTimeItemInterface::DATE_STORAGE_FORMAT);
if ($type->getAutomaticRenewals()) {
$queue = \Drupal::queue('contacts_subscription_generate_renewals');
foreach ($type->getRenewable($renewal) as $id) {
$renewing[$id] = $id;
$queue->createItem($id);
}
}
if ($type->getAutomaticCancellations()) {
$queue = \Drupal::queue('contacts_subscription_cancel_expired');
foreach ($type->getCancellable($renewal) as $id) {
// We don't want to cancel any subscriptions that might generate a
// successful renewal, so just to be safe we'll exclude those we've
// added to that queue.
if (!in_array($id, $renewing)) {
$queue->createItem($id);
}
}
}
}
\Drupal::state()->set('contacts_subscriptions_last_run', time());
}
}
/**
* Implements hook_entity_field_storage_info().
*/
function contacts_subscriptions_entity_field_storage_info(EntityTypeInterface $entity_type) {
switch ($entity_type->id()) {
case 'commerce_order':
$fields['subscription'] = BundleFieldDefinition::create('entity_reference')
->setName('subscription')
->setLabel('Subscription')
->setTargetEntityTypeId('commerce_order')
->setTargetBundle('contacts_subscription')
->setSetting('target_type', 'contacts_subscription')
->setCardinality(1)
->setTranslatable(FALSE);
return $fields;
case 'commerce_product':
$fields['subscription_type'] = BundleFieldDefinition::create('entity_reference')
->setName('subscription_type')
->setLabel('Subscription type')
->setTargetEntityTypeId('commerce_product')
->setTargetBundle('subscription')
->setRequired(TRUE)
->setTranslatable(FALSE)
->setSetting('target_type', 'contacts_subscription_type')
->setCardinality(1);
return $fields;
case 'commerce_product_variation':
$fields['subscription_length'] = BundleFieldDefinition::create('integer')
->setName('subscription_length')
->setLabel('Subscription length')
->setTargetEntityTypeId('commerce_product_variation')
->setTargetBundle('subscription')
->setRequired(TRUE)
->setTranslatable(FALSE)
->setSetting('unsigned', FALSE)
->setSetting('size', 'normal')
->setCardinality(1);
return $fields;
}
}
/**
* Implements hook_entity_bundle_field_info().
*/
function contacts_subscriptions_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
switch ($entity_type->id()) {
case 'commerce_order':
if ($bundle === 'contacts_subscription') {
$fields = [];
$storage_definitions = contacts_subscriptions_entity_field_storage_info($entity_type);
$fields['subscription'] = BundleFieldDefinition::createFromFieldStorageDefinition($storage_definitions['subscription'])
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
return $fields;
}
break;
case 'commerce_product':
if ($bundle === 'subscription') {
$fields = [];
$storage_definitions = contacts_subscriptions_entity_field_storage_info($entity_type);
$fields['subscription_type'] = BundleFieldDefinition::createFromFieldStorageDefinition($storage_definitions['subscription_type'])
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE)
->setSetting('handler', 'default:contacts_subscription_type');
return $fields;
}
break;
case 'commerce_product_variation':
if ($bundle === 'subscription') {
$fields = [];
$storage_definitions = contacts_subscriptions_entity_field_storage_info($entity_type);
$fields['subscription_length'] = BundleFieldDefinition::createFromFieldStorageDefinition($storage_definitions['subscription_length'])
->setDescription('How long this purchase will make the subscription active for, in months.')
->setSetting('min', 1)
->setSetting('max', NULL)
->setSetting('prefix', '')
->setSetting('suffix', 'months')
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
return $fields;
}
break;
}
}
/**
* Implements hook_theme().
*/
function contacts_subscriptions_theme($existing, $type, $theme, $path) {
return [
'contacts_subscription_options' => [
'variables' => [
'options' => [],
'wrapper_attributes' => [],
'show_links' => TRUE,
'show_intro_offer' => TRUE,
],
],
'contacts_subscription_mail_payment_success' => [
'variables' => [
'order_entity' => NULL,
'subscription_entity' => NULL,
'subscription' => NULL,
'billing_information' => NULL,
'payment_method' => NULL,
'totals' => NULL,
],
],
'contacts_subscription_mail_payment_failure' => [
'variables' => [
'order_entity' => NULL,
'subscription_entity' => NULL,
'subscription' => NULL,
'billing_information' => NULL,
'payment_method' => NULL,
],
],
];
}
/**
* Implements hook_contacts_user_dashboard_local_tasks_allowed_alter().
*/
function contacts_subscriptions_contacts_user_dashboard_local_tasks_allowed_alter(&$allowed_items) {
$allowed_items[] = 'contacts_subscriptions.manage';
}
/**
* Implements hook_workflows_alter().
*/
function contacts_subscriptions_workflows_alter(array &$workflows) {
// Add failed states to the payment default workflow.
$workflows['payment_default']['states']['cs_failed_declined'] = [
'label' => 'Failed (Declined)',
];
$workflows['payment_default']['transitions']['cs_failed_declined'] = [
'label' => 'Declined',
'from' => ['new'],
'to' => 'cs_failed_declined',
];
$workflows['payment_default']['states']['cs_failed_error'] = [
'label' => 'Failed (Error)',
];
$workflows['payment_default']['transitions']['cs_failed_error'] = [
'label' => 'Declined',
'from' => ['new'],
'to' => 'cs_failed_error',
];
}
/**
* Implements hook_mail().
*/
function contacts_subscriptions_mail($key, &$message, $params) {
$mailer = \Drupal::service('contacts_subscriptions.mail');
if (method_exists($mailer, $key)) {
$mailer->{$key}($message, $params);
}
}
/**
* Template process for the subcription options theme.
*
* @param array $variables
* The variable array containing:
* - wrapper_attributes: An array of attributes for the wrapper.
* - options: An array of options. Each option is an array containing:
* - is_active: Whether this is the active option.
* - title: The title.
* - price: Optionally the price text.
* - description: Optionally the description.
* - link: Optionally an action link.
* - attributes: Optionally an array of attributes.
*/
function template_preprocess_contacts_subscription_options(array &$variables) {
$variables['wrapper_attributes'] = new Attribute($variables['wrapper_attributes']);
$variables['has_links'] = FALSE;
foreach ($variables['options'] as &$option) {
$option += [
'is_active' => FALSE,
'title' => NULL,
'price' => NULL,
'description' => NULL,
'link' => NULL,
'link_is_cancel' => FALSE,
'attributes' => [],
];
$option['attributes'] = new Attribute($option['attributes']);
// Clear links if we're not showing them.
if (!$variables['show_links']) {
$option['link'] = NULL;
}
if ($option['link']) {
$variables['has_links'] = TRUE;
if ($option['link'] instanceof Link) {
$option['link'] = $option['link']->toRenderable();
}
$option['link']['#attributes']['class'][] = 'btn';
if ($option['is_active']) {
$option['link']['#attributes']['class'][] =
$option['link_is_cancel'] ? 'btn-danger' : 'btn-secondary';
}
else {
$option['link']['#attributes']['class'][] = 'btn-primary';
}
}
}
}
/**
* Implements hook_field_widget_form_alter().
*/
function contacts_subscriptions_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
if ($context['items']->getName() == 'uid') {
$form = $form_state->getFormObject();
if ($form instanceof ContentEntityForm) {
if ($entity = $form->getEntity()) {
if ($entity instanceof SubscriptionInterface) {
$role = $entity->bundle->entity->getWhoCanPurchase();
// If the subscription type is locked to either individuals or
// organisations, we can improve the functionality by making the
// entity reference field only search for these. If both are allowed,
// we don't need to do anything.
switch ($role) {
case 'crm_org':
case 'crm_indiv':
$element['target_id']['#selection_settings']['conditions'] = [
[
'roles',
$role,
],
];
break;
}
}
}
}
}
}
/**
* Implements hook_ENTITY_TYPE_access().
*/
function contacts_subscriptions_commerce_order_access(EntityInterface $entity, $operation, AccountInterface $account) {
$return = AccessResult::neutral();
/** @var \Drupal\commerce_order\Entity\OrderInterface $entity */
if ($entity->getCustomerId() !== $account->id()) {
// If the user has the permission to manage their organisation's membership
// we should let them access the organisation's orders.
if ($account->hasPermission('manage my organisation membership')) {
// Ensure we have a fully loaded user.
$account = \Drupal::entityTypeManager()->getStorage('user')->load($account->id());
if ($account->hasField('organisations')) {
foreach ($account->organisations as $item) {
if ($group = $item->membership->getGroup()) {
if ($group->contacts_org->target_id == $entity->getCustomerId()) {
$return = AccessResult::allowed();
}
}
}
}
}
}
return $return;
}
/**
* Implements hook_preprocess_HOOK().
*/
function contacts_subscriptions_preprocess_input(&$variables) {
// Some themes remove the button--primary class from inputs in favour of
// btn-primary, but Stripe's JS relies on the submit button having this class
// so we'll put it back.
if ($variables['element']['#type'] == 'submit') {
if (in_array('btn-primary', $variables['attributes']['class'])) {
$variables['attributes']['class'][] = 'button--primary';
}
}
}
/**
* Implements hook_theme_registry_alter().
*/
function contacts_subscriptions_theme_registry_alter(&$theme_registry) {
// Ensure our preprocess runs after any others.
if (isset($theme_registry['input']['preprocess functions'])) {
$preprocesses = $theme_registry['input']['preprocess functions'];
if ($key = array_search('contacts_subscriptions_preprocess_input', $preprocesses)) {
unset($preprocesses[$key]);
$preprocesses[] = 'contacts_subscriptions_preprocess_input';
$theme_registry['input']['preprocess functions'] = array_values($preprocesses);
}
}
}
