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); } } }