contacts_events-8.x-1.x-dev/contacts_events.module

contacts_events.module
<?php

/**
 * @file
 * Contains contacts_events.module.
 */

use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_order\Entity\OrderItemInterface;
use Drupal\commerce_order\Entity\OrderItemType;
use Drupal\contacts_events\AdminPaymentEmailService;
use Drupal\contacts_events\ContactsPaymentListBuilder;
use Drupal\contacts_events\Entity\EventInterface;
use Drupal\contacts_events\Entity\EventType;
use Drupal\contacts_events\Entity\Ticket;
use Drupal\contacts_events\Entity\TicketInterface;
use Drupal\contacts_events\Form\AdminOrderForm;
use Drupal\contacts_events\Form\OrderItemCancelForm;
use Drupal\contacts_events\Form\ResendBookingConfirmationForm;
use Drupal\contacts_events\ManualPaymentNotifications;
use Drupal\contacts_events\Plugin\Commerce\PaymentGateway\ContactsEventsStripe;
use Drupal\contacts_events\Plugin\Field\BookingsDelegateItemList;
use Drupal\contacts_events\Plugin\Field\BookingsManagedItemList;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\entity\BundleFieldDefinition;
use Drupal\user\UserInterface;

include 'contacts_events.theme.inc';

/**
 * Implements hook_help().
 */
function contacts_events_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    // Main module help for the contacts_events module.
    case 'help.page.contacts_events':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Event promotion and booking functionality for Contacts suite.') . '</p>';
      return $output;

    default:
  }
}

/**
 * Implements hook_module_implements_alter().
 */
function contacts_events_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'commerce_checkout_pane_info_alter') {
    unset($implementations['commerce_payment']);
  }
  elseif ($hook == 'commerce_payment_create') {
    $group = $implementations['contacts_events'];
    unset($implementations['contacts_events']);
    $implementations = ['contacts_events' => $group] + $implementations;
  }
}

/**
 * Implements hook_entity_type_build().
 */
function contacts_events_entity_type_build(array &$entity_types) {
  /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
  $entity_types['commerce_order']->setLinkTemplate('booking_process', '/booking/{commerce_order}');
  $entity_types['commerce_order_item']->setLinkTemplate('cancel', '/order-item/{commerce_order_item}/cancel');

  $entity_types['commerce_order_item']->setFormClass('cancel', OrderItemCancelForm::class);

  $entity_types['commerce_payment']->setListBuilderClass(ContactsPaymentListBuilder::class);

}

/**
 * Implements hook_entity_type_alter().
 */
function contacts_events_entity_type_alter(array &$entity_types) {
  $entity_types['commerce_order']->setFormClass('booking_admin_add', AdminOrderForm::class);
  $entity_types['commerce_order']->setFormClass('booking_admin_tickets', ContentEntityForm::class);
  $entity_types['commerce_order']->setFormClass('booking_admin_additional_charges', ContentEntityForm::class);
}

/**
 * Implements hook_ENTITY_TYPE_create() for commerce_order_item.
 */
function contacts_events_commerce_order_item_create(OrderItemInterface $entity) {
  // @todo See if we can find a better place to do this.
  $parameters = \Drupal::routeMatch()->getParameters();
  if ($parameters->has('commerce_order')) {
    /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
    $order = $parameters->get('commerce_order');
    $entity->set('order_id', $order->id());
  }
}

/**
 * Implements hook_entity_field_storage_info().
 */
function contacts_events_entity_field_storage_info(EntityTypeInterface $entity_type) {
  $fields = [];

  if ($entity_type->id() == 'contacts_event') {
    $fields['settings'] = BundleFieldDefinition::create('contacts_events_settings')
      ->setName('settings')
      ->setTargetEntityTypeId($entity_type->id());
  }

  if ($entity_type->id() == 'commerce_order_item') {
    $fields['state'] = BundleFieldDefinition::create('state')
      ->setName('state')
      ->setLabel(new TranslatableMarkup('State'))
      ->setTargetEntityTypeId('commerce_order_item');

    $fields['confirmed'] = BundleFieldDefinition::create('timestamp')
      ->setName('confirmed')
      ->setLabel(new TranslatableMarkup('Confirmed'))
      ->setTargetEntityTypeId('commerce_order_item');

    $fields['mapped_price'] = BundleFieldDefinition::create('mapped_price_data')
      ->setName('mapped_price')
      ->setLabel(new TranslatableMarkup('Mapped price'))
      ->setTargetEntityTypeId('commerce_order_item');
  }

  if ($entity_type->id() == 'commerce_order') {
    $fields['creator'] = BundleFieldDefinition::create('entity_reference')
      ->setName('creator')
      ->setLabel(new TranslatableMarkup('Creator'))
      ->setTargetEntityTypeId('commerce_order')
      ->setTargetBundle('contacts_booking')
      ->setDescription(t('The user ID of the order creator.'))
      ->setRevisionable(TRUE)
      ->setSetting('target_type', 'user')
      ->setSetting('handler', 'default')
      ->setTranslatable(FALSE)
      ->setDisplayConfigurable('form', FALSE)
      ->setDisplayConfigurable('view', FALSE);

    $fields['state_log'] = BundleFieldDefinition::create('status_log')
      ->setName('state_log')
      ->setLabel(t('State History'))
      ->setTargetEntityTypeId('commerce_order')
      ->setTargetBundle('contacts_booking')
      ->setDescription(t('A log of when the status was changed and by whom.'))
      ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED)
      ->setSettings([
        'source_field' => 'state',
      ])
      ->setDisplayConfigurable('view', FALSE)
      ->setDisplayConfigurable('form', FALSE);

  }

  if ($entity_type->id() == 'commerce_payment') {
    $fields['creator'] = BaseFieldDefinition::create('entity_reference')
      ->setName('creator')
      ->setLabel(new TranslatableMarkup('Creator'))
      ->setTargetEntityTypeId('commerce_payment')
      ->setDescription(new TranslatableMarkup('The user ID of the payment creator.'))
      ->setRevisionable(TRUE)
      ->setSetting('target_type', 'user')
      ->setSetting('handler', 'default')
      ->setTranslatable(FALSE)
      ->setDisplayConfigurable('form', FALSE)
      ->setDisplayConfigurable('view', FALSE);
  }

  return $fields;
}

/**
 * Implements hook_entity_bundle_field_info().
 */
function contacts_events_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  $fields = [];

  if ($entity_type->id() == 'commerce_order_item') {

    /** @var \Drupal\commerce_order\Entity\OrderItemTypeInterface $order_item_type */
    $order_item_type = OrderItemType::load($bundle);

    if ($order_item_type->getOrderTypeId() == 'contacts_booking') {
      // These field definitions are duplicated in
      // contacts_events_entity_bundle_create. Make sure any new fields are
      // added to both places.
      $fields['state'] = BundleFieldDefinition::create('state')
        ->setName('state')
        ->setTargetEntityTypeId('commerce_order_item')
        ->setTargetBundle('contacts_ticket')
        ->setLabel(new TranslatableMarkup('State'))
        ->setDescription(new TranslatableMarkup('The order item state.'))
        ->setRequired(TRUE)
        ->setSetting('max_length', 255)
        ->setSetting('workflow', 'contacts_events_order_item_process')
        ->setDisplayOptions('view', [
          'label' => 'hidden',
          'type' => 'state_transition_form',
          'weight' => 10,
        ])
        ->setDisplayConfigurable('form', TRUE)
        ->setDisplayConfigurable('view', TRUE);

      $fields['confirmed'] = BundleFieldDefinition::create('timestamp')
        ->setName('confirmed')
        ->setTargetEntityTypeId('commerce_order_item')
        ->setTargetBundle('contacts_ticket')
        ->setLabel(new TranslatableMarkup('Confirmed'))
        ->setDescription(new TranslatableMarkup('The time when the order item was confirmed.'))
        ->setReadOnly(TRUE)
        ->setDisplayConfigurable('form', FALSE)
        ->setDisplayConfigurable('view', TRUE);
    }

    if ($bundle == 'contacts_ticket') {
      $fields['mapped_price'] = BundleFieldDefinition::create('mapped_price_data')
        ->setName('mapped_price')
        ->setTargetEntityTypeId('commerce_order_item')
        ->setTargetBundle('contacts_ticket')
        ->setLabel(new TranslatableMarkup('Mapped price'))
        ->setRequired(TRUE)
        ->setDisplayOptions('form', ['region' => 'hidden'])
        ->setDisplayOptions('view', ['region' => 'hidden'])
        ->setDisplayConfigurable('form', TRUE)
        ->setDisplayConfigurable('view', TRUE);
    }
  }

  // Don't allow duplicate bookings for the same user to the same event.
  if ($entity_type->id() == 'commerce_order' && $bundle == 'contacts_booking' && !empty($base_field_definitions['uid'])) {
    $fields['uid'] = $base_field_definitions['uid'];
    $fields['uid']->addConstraint('UniqueFieldValueForGivenReferenceField', [
      'groupingField' => 'event',
      'message' => 'A booking for this event already exists for this user.',
    ]);
    // Use the search api selection handler.
    // @todo move this to contacts module.
    $fields['uid']->setSetting('handler', 'search_api');
    $fields['uid']->setSetting('handler_settings', [
      'index' => 'contacts_index',
      'conditions' => [['roles', 'crm_indiv']],
    ]);
  }

  if ($entity_type->id() == 'commerce_order' && $bundle == 'contacts_booking') {
    $fields['creator'] = BundleFieldDefinition::create('entity_reference')
      ->setName('creator')
      ->setTargetEntityTypeId('commerce_order')
      ->setTargetBundle('contacts_booking')
      ->setLabel(t('Creator'))
      ->setDescription(t('The user ID of the order creator.'))
      ->setRevisionable(TRUE)
      ->setSetting('target_type', 'user')
      ->setSetting('handler', 'default')
      ->setTranslatable(FALSE)
      ->setDisplayConfigurable('form', FALSE)
      ->setDisplayConfigurable('view', FALSE);

    $fields['state_log'] = BundleFieldDefinition::create('status_log')
      ->setName('state_log')
      ->setLabel(t('State History'))
      ->setTargetEntityTypeId('commerce_order')
      ->setTargetBundle('contacts_booking')
      ->setDescription(t('A log of when the status was changed and by whom.'))
      ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED)
      ->setSettings([
        'source_field' => 'state',
      ])
      ->setDisplayConfigurable('view', FALSE)
      ->setDisplayConfigurable('form', FALSE);
  }

  return $fields;
}

/**
 * Implements hook_entity_base_field_info().
 */
function contacts_events_entity_base_field_info(EntityTypeInterface $entity_type) {
  $fields = [];

  if ($entity_type->id() == 'commerce_payment') {
    $fields['creator'] = BaseFieldDefinition::create('entity_reference')
      ->setName('creator')
      ->setTargetEntityTypeId('commerce_payment')
      ->setLabel(new TranslatableMarkup('Creator'))
      ->setDescription(new TranslatableMarkup('The user ID of the payment creator.'))
      ->setRevisionable(TRUE)
      ->setSetting('target_type', 'user')
      ->setSetting('handler', 'default')
      ->setTranslatable(FALSE)
      ->setDisplayConfigurable('form', FALSE)
      ->setDisplayConfigurable('view', FALSE);
  }

  if ($entity_type->id() == 'user') {
    $fields['ce_bookings_managed'] = BaseFieldDefinition::create('entity_reference')
      ->setName('ce_bookings_manager')
      ->setTargetEntityTypeId('user')
      ->setLabel(new TranslatableMarkup('Bookings (manager)'))
      ->setDescription(new TranslatableMarkup('Bookings this user manages.'))
      ->setRevisionable(FALSE)
      ->setSetting('target_type', 'commerce_order')
      ->setDisplayConfigurable('form', FALSE)
      ->setDisplayConfigurable('view', FALSE)
      ->setReadOnly(TRUE)
      ->setComputed(TRUE)
      ->setClass(BookingsManagedItemList::class);

    $fields['ce_bookings_delegate'] = BaseFieldDefinition::create('entity_reference')
      ->setName('ce_bookings_delegate')
      ->setTargetEntityTypeId('user')
      ->setLabel(new TranslatableMarkup('Bookings (delegate)'))
      ->setDescription(new TranslatableMarkup('Bookings this user has a ticket on.'))
      ->setRevisionable(FALSE)
      ->setSetting('target_type', 'commerce_order')
      ->setDisplayConfigurable('form', FALSE)
      ->setDisplayConfigurable('view', FALSE)
      ->setReadOnly(TRUE)
      ->setComputed(TRUE)
      ->setClass(BookingsDelegateItemList::class);
  }

  return $fields;
}

/**
 * Implements hook_ENTITY_TYPE_insert() for commerce_order.
 */
function contacts_events_commerce_order_insert(OrderInterface $entity) {
  if ($entity->bundle() == 'contacts_booking') {
    if (!$entity->getOrderNumber()) {
      $number = $entity->get('event')->entity->code->value;
      if ($number) {
        $number .= '-';
      }
      $number .= str_pad($entity->id(), 8, '0', STR_PAD_LEFT);
      $entity->setOrderNumber($number);
      $entity->save();
    }

    // Track search index changes.
    _contacts_events_entity_postsave_update_index($entity->getCustomer());
  }
}

/**
 * Implements hook_ENTITY_TYPE_insert() for contacts_ticket.
 */
function contacts_events_contacts_ticket_insert(TicketInterface $entity) {
  // Track search index changes.
  _contacts_events_entity_postsave_update_index($entity->getTicketHolder());
}

/**
 * Implements hook_ENTITY_TYPE_update() for contacts_ticket.
 */
function contacts_events_contacts_ticket_update(TicketInterface $entity) {
  // Track search index changes.
  _contacts_events_entity_postsave_update_index($entity->getTicketHolder());

  if (isset($entity->original)) {
    if ($entity->getTicketHolderId() !== $entity->original->getTicketHolderId()) {
      _contacts_events_entity_postsave_update_index($entity->original->getTicketHolder());
    }
  }
}

/**
 * Update contacts search index for when related entity is updated.
 *
 * @param \Drupal\user\UserInterface|null $entity
 *   The user to update. NULL is allowed so you can, for example, pass in
 *   $order->getCustomer() without checking whether there is a customer. NULL is
 *   a no-op.
 *
 * @todo Move this to an entity service post save.
 */
function _contacts_events_entity_postsave_update_index(?UserInterface $entity) {
  if (!$entity) {
    return;
  }

  $entity_type_manager = \Drupal::entityTypeManager();
  if ($entity_type_manager->hasDefinition('search_api_index')) {
    $index = $entity_type_manager->getStorage('search_api_index')->load('contacts_index');
    $updated_item_ids = $entity->getTranslationLanguages();
    $entity_id = $entity->id();
    $combine_id = function ($langcode) use ($entity_id) {
      return $entity_id . ':' . $langcode;
    };
    $updated_item_ids = array_map($combine_id, array_keys($updated_item_ids));

    $index->trackItemsUpdated('entity:user', $updated_item_ids);
  }
}

/**
 * Implements hook_ENTITY_TYPE_update() for contacts_event.
 *
 * @todo See if we can make this not specific to event entities.
 */
function contacts_events_contacts_event_update(EventInterface $entity) {
  \Drupal::service('contacts_events.price_calculator')
    ->onEntityUpdate($entity, $entity->original);
}

/**
 * Implements hook_cron().
 */
function contacts_events_cron() {
  /** @var \Drupal\contacts_events\Cron\CronInterface $cron */
  $cron = \Drupal::service('contacts_events.cron.recalculate_booking_windows');
  $cron->invokeOnSchedule();
}

/**
 * Implements hook_commerce_checkout_pane_info_alter().
 */
function contacts_events_commerce_checkout_pane_info_alter(&$definitions) {
  if (isset($definitions['billing_information'])) {
    $definitions['billing_information']['display_label'] = new TranslatableMarkup('Billing address');
    if (!isset($definitions['billing_information']['review_link'])) {
      $definitions['billing_information']['review_link'] = new TranslatableMarkup('Manage addresses');
    }
  }
}

/**
 * Implements hook_field_widget_form_alter().
 *
 * Make the date field increment in minutes.
 */
function contacts_events_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
  $definition = $context['items']->getFieldDefinition();
  /** @var \Drupal\Core\Field\FieldDefinitionInterface $definition */
  if ($definition->getType() == 'daterange' && $definition->getTargetEntityTypeId() == 'contacts_event') {
    if (isset($element['value']) && $element['value']['#type'] == 'datetime') {
      $element['value']['#date_increment'] = 60;
    }
    if (isset($element['end_value']) && $element['end_value']['#type'] == 'datetime') {
      $element['end_value']['#date_increment'] = 60;
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_presave() for commerce_order.
 */
function contacts_events_commerce_order_presave(EntityInterface $entity) {
  \Drupal::service('contacts_events.entity_hooks.commerce_order')
    ->preSave($entity, $entity->original ?? NULL);
}

/**
 * Implements hook_ENTITY_TYPE_update() for commerce_order.
 */
function contacts_events_commerce_order_update(OrderInterface $entity) {
  \Drupal::service('contacts_events.entity_hooks.commerce_order')
    ->postSave($entity, $entity->original ?? NULL);

  // Track search index changes.
  _contacts_events_entity_postsave_update_index($entity->getCustomer());

  if (isset($entity->original)) {
    if ($entity->getCustomerId() !== $entity->original->getCustomerId()) {
      _contacts_events_entity_postsave_update_index($entity->original->getCustomer());
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_access() for commerce_order.
 */
function contacts_events_commerce_order_access(EntityInterface $entity, $operation, AccountInterface $account) {
  return \Drupal::service('contacts_events.entity_hooks.commerce_order')
    ->access($entity, $operation, $account);
}

/**
 * Implements hook_ENTITY_TYPE_access() for commerce_order_item.
 */
function contacts_events_commerce_order_item_access(EntityInterface $entity, $operation, AccountInterface $account) {
  return \Drupal::service('contacts_events.entity_hooks.commerce_order_item')
    ->access($entity, $operation, $account);
}

/**
 * Implements hook_ENTITY_TYPE_create_access() for commerce_order_item.
 */
function contacts_events_commerce_order_item_create_access(AccountInterface $account, array $context, $entity_bundle) {
  return \Drupal::service('contacts_events.entity_hooks.commerce_order_item')
    ->createAccess($entity_bundle, $context, $account);
}

/**
 * Implements hook_ENTITY_TYPE_presave() for commerce_order_item.
 */
function contacts_events_commerce_order_item_presave(EntityInterface $entity) {
  \Drupal::service('contacts_events.entity_hooks.commerce_order_item')
    ->preSave($entity, $entity->original ?? NULL);
}

/**
 * Implements hook_ENTITY_TYPE_insert() for commerce_order_item.
 */
function contacts_events_commerce_order_item_insert(EntityInterface $entity) {
  if (\Drupal::hasService('contacts_events.entity_hooks.commerce_order_item.bookkeeping')) {
    \Drupal::service('contacts_events.entity_hooks.commerce_order_item.bookkeeping')
      ->postSave($entity);
  }
}

/**
 * Implements hook_ENTITY_TYPE_update() for commerce_order_item.
 */
function contacts_events_commerce_order_item_update(EntityInterface $entity) {
  if (\Drupal::hasService('contacts_events.entity_hooks.commerce_order_item.bookkeeping')) {
    \Drupal::service('contacts_events.entity_hooks.commerce_order_item.bookkeeping')
      ->postSave($entity, $entity->original);
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete() for commerce_order_item.
 */
function contacts_events_commerce_order_item_delete(EntityInterface $entity) {
  return \Drupal::service('contacts_events.entity_hooks.commerce_order_item')
    ->delete($entity);
}

/**
 * Implements hook_ENTITY_TYPE_create() for commerce_payment.
 */
function contacts_events_commerce_payment_create(EntityInterface $entity) {
  \Drupal::service('contacts_events.entity_hooks.commerce_payment')
    ->create($entity);
}

/**
 * Implements hook_ENTITY_TYPE_presave() for commerce_payment.
 */
function contacts_events_commerce_payment_presave(EntityInterface $entity) {
  \Drupal::service('contacts_events.entity_hooks.commerce_payment')
    ->preSave($entity, $entity->original ?? NULL);
}

/**
 * Implements hook_ENTITY_TYPE_insert() for commerce_payment.
 */
function contacts_events_commerce_payment_insert(EntityInterface $entity) {
  \Drupal::service('contacts_events.manual_payment_notifications')->onInsert($entity);
}

/**
 * Implements hook_ENTITY_TYPE_view_alter() for commerce_order.
 */
function contacts_events_commerce_order_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
  // Only alter bookings.
  if ($entity->bundle() != 'contacts_booking') {
    return;
  }

  // Adjust the order items table.
  if (isset($build['order_items'])) {
    if ($build['order_items']['#formatter'] == 'commerce_order_item_table') {
      $build['order_items'][0]['#name'] = 'contacts_events_commerce_order_item_table';
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for state_machine_transition_form.
 */
function contacts_events_form_state_machine_transition_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // For the state transition form that's shown on the Order page, hide all
  // transitions except the Confirm transition if this is a booking.
  /** @var \Drupal\state_machine\Form\StateTransitionForm $form_object */
  $form_object = $form_state->getFormObject();
  $entity = $form_object->getEntity();
  $field_name = $form_object->getFieldName();
  $state = $entity->get($field_name);
  if (!$state->isEmpty() && $state->first()->getWorkflow()->getId() == 'contacts_events_booking_process') {
    foreach (Element::children($form['actions']) as $transition_id) {
      // 'place' is the "Confirm" transition. This one is allowed.
      if ($transition_id != 'place') {
        unset($form['actions'][$transition_id]);
      }
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for contacts_event_default_edit_form.
 *
 * Alter the events form to turn on/off child/adult supervision.
 */
function contacts_events_form_contacts_event_default_edit_form_alter(&$form, FormStateInterface $form_state) {
  /** @var \Drupal\contacts_events\Entity\EventInterface $event */
  $event = $form_state->getFormObject()->getEntity();
  if (!$event->hasField('supervision_ratio')) {
    return;
  }

  $form['supervision'] = [
    '#type' => 'checkbox',
    '#title' => new TranslatableMarkup('Require a minimum ratio of adults to children'),
    '#default_value' => !$event->get('supervision_ratio')->isEmpty(),
    '#weight' => $form['supervision_ratio']['#weight'] - 0.1,
  ];

  $form['supervision_ratio']['#states']['visible'][] = [
    ':input[name="supervision"]' => ['checked' => TRUE],
  ];

  $form['supervision_ratio']['widget'][0]['value']['#states']['required'][] = [
    ':input[name="supervision"]' => ['checked' => TRUE],
  ];

  // Add submission handler to clear supervision_ratio if unchecked.
  array_unshift($form['actions']['submit']['#submit'], 'contacts_events_form_contacts_event_default_supervision_submit');
}

/**
 * Submission handler; clear supervision ratio if not required.
 *
 * @see contacts_events_form_contacts_event_default_edit_form_alter()
 */
function contacts_events_form_contacts_event_default_supervision_submit(&$form, FormStateInterface $form_state) {
  if (!$form_state->getValue('supervision')) {
    $form_state->setValue(['supervision_ratio', 0, 'value'], NULL);
  }
}

/**
 * Implements hook_contacts_user_dashboard_local_tasks_allowed_alter().
 *
 * Integrate event pages with the user dashboard tools.
 */
function contacts_events_contacts_user_dashboard_local_tasks_allowed_alter(&$item_list) {
  $item_list = array_merge($item_list, [
    'contacts_events.user_events',
    'contacts_events.user_events_default',
  ]);
}

/**
 * Implements hook_inline_entity_form_table_fields_alter().
 */
function contacts_events_inline_entity_form_table_fields_alter(&$fields, $context) {
  if ($context['entity_type'] == 'commerce_order_item' && $context['allowed_bundles'] == ['contacts_ticket']) {
    $fields['label'] = [
      'type' => 'callback',
      'label' => new TranslatableMarkup('Ticket holder'),
      'callback' => [Ticket::class, 'getNameLinkFromOrderItem'],
    ];
    $fields['unit_price']['label'] = new TranslatableMarkup('Price');
    unset($fields['quantity']);
    $fields['mapped_price'] = [
      'type' => 'field',
      'label' => new TranslatableMarkup('Class'),
      'display_options' => [
        'type' => 'mapped_price_class',
      ],
    ];
    if (\Drupal::currentUser()->hasPermission('can manage bookings for contacts_events')) {
      $fields['state'] = [
        'type' => 'field',
        'label' => new TranslatableMarkup('Status'),
        'weight' => 5,
      ];
      $fields['confirmed'] = [
        'type' => 'field',
        'label' => new TranslatableMarkup('Confirmed'),
        'weight' => 6,
      ];
    }
  }
}

/**
 * Implements hook_preprocess_HOOK() for commerce_order_receipt.
 *
 * Sets up additional variables used on the order confirmation receipt email.
 */
function contacts_events_preprocess_commerce_order_receipt__contacts_booking(&$variables) {
  $default_theme = \Drupal::config('system.theme')->get('default');
  $variables['event_entity'] = $variables['order_entity']->get('event')->entity;

  $events_start_date = new DrupalDateTime($variables['event_entity']->get('date')->value);
  $variables['event_start_date'] = $events_start_date->format('ga l F j, Y');
  $image = $variables['event_entity']->get('listing_image');
  $variables['event_image'] = !$image->isEmpty() ? $image->view()[0] : '';
  $variables['site_logo'] = file_create_url(ltrim(theme_get_setting('logo.url', $default_theme), '/'));
  $variables['site_name'] = \Drupal::config('system.site')->get('name');

  $variables['order_item_states'] = [];
  /** @var \Drupal\state_machine\Plugin\Workflow\WorkflowInterface $workflow */
  $workflow = \Drupal::service('plugin.manager.workflow')->createInstance('contacts_events_order_item_process');
  foreach ($workflow->getStates() as $state) {
    $variables['order_item_states'][$state->getId()] = $state->getLabel();
  }

  $variables['event_classes'] = [];
  $event_class_storage = \Drupal::entityTypeManager()->getStorage('contacts_events_class');
  foreach ($event_class_storage->loadMultiple() as $class) {
    $variables['event_classes'][$class->id()] = $class->label();
  }
}

/**
 * Implements hook_mail().
 */
function contacts_events_mail($key, &$message, $params) {
  if ($key == ManualPaymentNotifications::MAIL_KEY) {
    \Drupal::service('contacts_events.manual_payment_notifications')->buildMail($message, $params);
  }
  elseif ($key == AdminPaymentEmailService::MAIL_KEY) {
    \Drupal::service('contacts_events.admin_payment_email_service')->buildMail($message, $params);
  }
}

/**
 * Implements hook_mail_alter().
 */
function contacts_events_mail_alter(&$message) {
  // Update the subject line of the order confirmation email.
  if ($message['id'] == 'commerce_order_receipt') {
    /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
    $order = $message['params']['order'];
    if ($order->bundle() == 'contacts_booking') {
      // If there are no confirmed, non-cancelled tickets, prevent sending the
      // confirmation email.
      $has_confirmed = FALSE;
      foreach ($order->getItems() as $order_item) {
        if ($order_item->bundle() !== 'contacts_ticket') {
          continue;
        }

        // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
        if (!in_array($order_item->get('state')->value, ['pending', 'cancelled'])) {
          $has_confirmed = TRUE;
          break;
        }
      }
      if (!$has_confirmed) {
        $message['send'] = FALSE;
        return;
      }

      $message['subject'] = new TranslatableMarkup('Booking confirmation for @event', [
        '@event' => $order->get('event')->entity->label(),
      ]);
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * For commerce_order_contacts_booking_booking_admin_tickets_form.
 */
function contacts_events_form_commerce_order_contacts_booking_booking_admin_tickets_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  \Drupal::service('contacts_events.form_alter.booking_admin_tickets')->alter($form, $form_state, $form_id);
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * For commerce_order_contacts_booking_booking_admin_additional_charges_form.
 */
function contacts_events_form_commerce_order_contacts_booking_booking_admin_additional_charges_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  \Drupal::service('contacts_events.form_alter.booking_admin_additional_charges')->alter($form, $form_state, $form_id);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function contacts_events_form_commerce_checkout_flow_edit_form_alter(array &$form, FormStateInterface $form_state) {
  $form['#validate'][] = 'contacts_events_validate_checkout_flow';
}

/**
 * Validate callback for the checkout flow form.
 *
 * Prevents users from putting the BookingPaymentInformation and
 * BookingPaymentProcess panes on the same step, which would result in an
 * infinite loop.
 */
function contacts_events_validate_checkout_flow(array $form, FormStateInterface $form_state) {
  $pane_configuration = $form_state->getValue(['configuration', 'panes']);
  if (!isset($pane_configuration['booking_payment_information'], $pane_configuration['booking_payment_process'])) {
    return;
  }
  $payment_information_step = $pane_configuration['booking_payment_information']['step_id'];
  $payment_process_step = $pane_configuration['booking_payment_process']['step_id'];
  if ($payment_information_step !== '_disabled' && $payment_information_step === $payment_process_step) {
    $form_state->setError($form, t('<em>Booking payment information</em> and <em>Booking payment process</em> panes need to be on separate steps.'));
  }
}

/**
 * Implements hook_contacts_events_ticket_form_alter().
 *
 * Prevent date of birth values in the future.
 */
function contacts_events_contacts_events_ticket_form_alter(array &$form, FormStateInterface $form_state, Ticket $ticket, string $display_mode) {
  if (!empty($form['date_of_birth']['widget'][0]['value']) && $form['date_of_birth']['widget'][0]['value']['#type'] == 'datelist') {
    $form['date_of_birth']['widget'][0]['value']['#date_year_range'] = '1900:' . date('Y');
    $form['date_of_birth']['widget'][0]['value']['#process'][] = 'contacts_events_contacts_events_ticket_form_sort_process';
  }
}

/**
 * Form element process to change the order of the date of birth year options.
 *
 * @see contacts_events_contacts_events_ticket_form_alter()
 */
function contacts_events_contacts_events_ticket_form_sort_process(&$element, FormStateInterface $form_state, &$complete_form) {
  krsort($element['year']['#options']);
  return $element;
}

/**
 * Implements hook_preprocess_HOOK().
 */
function contacts_events_preprocess_commerce_order__admin(&$variables) {
  /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
  $order = $variables['order_entity'];

  $total = $order->getTotalPrice();
  if ($total) {
    $paid = $order->getTotalPaid();
    $balance = $order->getBalance();
    /** @var \CommerceGuys\Intl\Formatter\CurrencyFormatterInterface $formatter */
    $formatter = \Drupal::service('commerce_price.currency_formatter');

    $build = [
      '#type' => 'details',
      '#open' => TRUE,
      '#attributes' => [
        'class' => ['seven-details', 'container-inline'],
      ],
      '#title' => new TranslatableMarkup('Payments'),
    ];

    $build['total'] = [
      '#type' => 'item',
      '#title' => new TranslatableMarkup('Total'),
      '#markup' => $formatter->format($total->getNumber(), $total->getCurrencyCode()),
    ];

    if ($paid) {
      $build['paid'] = [
        '#type' => 'item',
        '#title' => new TranslatableMarkup('Paid'),
        '#markup' => $formatter->format($paid->getNumber(), $paid->getCurrencyCode()),
      ];
    }

    if ($balance) {
      $build['balance'] = [
        '#type' => 'item',
        '#title' => new TranslatableMarkup('Balance'),
        '#markup' => $formatter->format($balance->getNumber(), $balance->getCurrencyCode()),
      ];
    }

    $variables['order']['header_region'][] = $build;
  }

  if ($order->bundle() !== 'contacts_booking') {
    return;
  }

  /** @var \Drupal\contacts_events\Entity\EventInterface|null $event */
  if ($event = $order->get('event')->entity) {
    $variables['order']['header_region']['event'] = [
      '#type' => 'html_tag',
      '#tag' => 'div',
      '#attributes' => ['class' => ['entity-meta__header']],
      '#weight' => -99,
      'title' => [
        '#type' => 'html_tag',
        '#tag' => 'h3',
        '#attributes' => ['class' => ['entity-meta__title']],
        'link' => $event->toLink()->toRenderable(),
      ],
      'date' => $event->get('date')->view(['label' => 'hidden']),
      'venue' => $event->get('venue')->view(['label' => 'inline']),
    ];
  }

  /** @var \Drupal\contacts_events\SupervisionHelper $supervision_helper */
  $supervision_helper = \Drupal::service('contacts_events.supervision_helper');

  $ratio = $supervision_helper->getRatio($order);
  if ($ratio) {
    // Get the booking helper to calculate current ratios.
    $booking_helper = $supervision_helper->getBookingHelper($order->get('order_items'));
    $adults_total = $booking_helper->getAdultDelegates();
    $non_adult_total = $booking_helper->getNonAdultDelegates();

    // Show status message if not enough supervision.
    $supervision_helper->showMessage($order, TRUE);

    $build = [
      '#type' => 'details',
      '#open' => TRUE,
      '#attributes' => [
        'class' => ['seven-details', 'container-inline'],
      ],
      '#title' => new TranslatableMarkup('Child/Adult Ratio'),
    ];

    $build['ratio'] = [
      '#type' => 'item',
      '#title' => new TranslatableMarkup('Event ratio'),
      '#markup' => new PluralTranslatableMarkup($ratio, '1 child per adult', '@count children per adult'),
    ];

    $build['adults'] = [
      '#type' => 'item',
      '#title' => new TranslatableMarkup('Adults'),
      '#markup' => $adults_total,
    ];

    $build['children'] = [
      '#type' => 'item',
      '#title' => new TranslatableMarkup('Children'),
      '#markup' => $non_adult_total,
    ];

    $variables['order']['header_region'][] = $build;
  }

  // Add in an additional button to resend booking confirmation.
  $variables['order']['header_region'][] = [
    '#type' => 'details',
    '#title' => new TranslatableMarkup('Operations'),
    '#open' => TRUE,
    '#attributes' => [
      'class' => ['seven-details', 'container-inline'],
    ],
    'form' => \Drupal::formBuilder()->getForm(ResendBookingConfirmationForm::class, $order),
  ];
}

/**
 * Implements hook_form_FORM_ID_alter() for commerce_order_contacts_booking_edit_form.
 *
 * Show a status message if not enough supervision for event.
 */
function contacts_events_form_commerce_order_contacts_booking_edit_form_alter(&$form, FormStateInterface $form_state) {
  $supervision_helper = \Drupal::service('contacts_events.supervision_helper');
  $supervision_helper->showMessage($form_state->getFormObject()->getEntity(), TRUE);
}

/**
 * Implements hook_preprocess_HOOK().
 */
function contacts_events_preprocess_page(&$variables) {
  $route_match = \Drupal::routeMatch();
  $route_object = $route_match->getRouteObject();
  if ($route_object && substr($route_object->getPath(), 0, 39) == '/admin/commerce/orders/{commerce_order}') {
    $order = $route_match->getParameter('commerce_order');

    if (is_string($order)) {
      $order = \Drupal::entityTypeManager()
        ->getStorage('commerce_order')
        ->load($order);
    }

    if ($order && $order->bundle() == 'contacts_booking') {
      /** @var \Drupal\contacts_events\Entity\EventInterface $event */
      $event = $order->get('event')->entity;

      // If the event doesn't exist (e.g. deletion), show an error and return
      // before we do any checks on the event itself.
      if (!$event) {
        \Drupal::messenger()
          ->addError(new TranslatableMarkup('This booking is for an event that does not exist.'));
        return;
      }

      $params = ['%event' => $event->label()];

      if (!$event->isBookingEnabled()) {
        \Drupal::messenger()
          ->addError(new TranslatableMarkup('Bookings are disabled for %event.', $params));
      }
      elseif (!$event->isBookingOpen()) {
        \Drupal::messenger()
          ->addWarning(new TranslatableMarkup('%event is closed for new bookings.', $params));
      }
    }
  }
}

/**
 * Implements hook_contacts_user_dashboard_user_summary_blocks_alter().
 */
function contacts_events_contacts_user_dashboard_user_summary_blocks_alter(&$content, UserInterface $user) {
  $content['bookings'] = [
    '#type' => 'user_dashboard_summary',
    '#buttons' => [],
    '#title' => 'Your active bookings',
  ];

  $configuration = ['label_display' => FALSE];
  $parent_org_view_block = \Drupal::service('plugin.manager.block')->createInstance('views_block:contacts_events_events-upcoming_summary', $configuration);
  $block_content = $parent_org_view_block->build();
  $content['bookings']['#content'] = [
    '#theme' => 'block',
    '#attributes' => [],
    '#configuration' => $parent_org_view_block->getConfiguration(),
    '#plugin_id' => $parent_org_view_block->getPluginId(),
    '#base_plugin_id' => $parent_org_view_block->getBaseId(),
    '#derivative_plugin_id' => $parent_org_view_block->getDerivativeId(),
    '#weight' => $parent_org_view_block->getConfiguration()['weight'] ?? 0,
    'content' => $block_content,
  ];
}

/**
 * Implements hook_entity_extra_field_info().
 */
function contacts_events_entity_extra_field_info() {
  $extra = [];

  foreach (EventType::loadMultiple() as $bundle) {
    $extra['contacts_event'][$bundle->id()]['form']['settings_deposit'] = [
      'label' => new TranslatableMarkup('Deposit'),
      'visible' => FALSE,
    ];

    if (\Drupal::moduleHandler()->moduleExists('commerce_partial_payments')) {
      $extra['contacts_event'][$bundle->id()]['form']['settings_instalments'] = [
        'label' => new TranslatableMarkup('Instalments'),
        'visible' => FALSE,
      ];
    }
  }

  return $extra;
}

/**
 * Implements hook_query_TAG_alter() for contacts_events_finance.
 */
function contacts_events_query_contacts_events_finance_alter(AlterableInterface &$query) {
  /** @var \Drupal\Core\Database\Query\SelectInterface $query */
  if ($query->getMetaData('entity_type') === 'commerce_order_item') {
    // Add a join to the commerce_order.order_items field table to ensure we
    // don't have orphaned order items.
    $query->join('commerce_order__order_items', 'coi',
      '%alias.order_items_target_id = base_table.order_item_id AND %alias.entity_id = commerce_order.order_id');
  }
}

/**
 * Implements hook_commerce_payment_gateway_info_alter().
 */
function contacts_events_commerce_payment_gateway_info_alter(array &$definitions) {
  if (isset($definitions['stripe'])) {
    $definitions['stripe']['class'] = ContactsEventsStripe::class;
  }
}

/**
 * Implements hook_form_BASE_FORM_ID_alter() for commerce_payment_gateway_form.
 */
function contacts_events_form_commerce_payment_gateway_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  \Drupal::service('contacts_events.manual_payment_notifications')
    ->gatewayFormAlter($form, $form_state);
}

/**
 * Implements hook_form_FORM_ID_alter() for commerce_payment_add_form.
 */
function contacts_events_form_commerce_payment_add_form_alter(&$form, FormStateInterface $form_state) {
  \Drupal::service('contacts_events.form_alter.admin_payment_email')->alter($form, $form_state);
}

/**
 * Implements hook_entity_bundle_create().
 */
function contacts_events_entity_bundle_create($entity_type_id, $bundle) {
  if ($entity_type_id === 'commerce_order_item') {
    /** @var \Drupal\commerce_order\Entity\OrderItemType $order_item_type */
    $order_item_type = OrderItemType::load($bundle);
    if ($order_item_type->getOrderTypeId() == 'contacts_booking') {
      // These field definitions are duplicated in
      // contacts_events_entity_bundle_field_info. Make sure any new fields are
      // added to both places.
      $state_field = BundleFieldDefinition::create('state')
        ->setName('state')
        ->setTargetEntityTypeId('commerce_order_item')
        ->setTargetBundle($bundle)
        ->setLabel(new TranslatableMarkup('State'))
        ->setDescription(new TranslatableMarkup('The order item state.'))
        ->setRequired(TRUE)
        ->setSetting('max_length', 255)
        ->setSetting('workflow', 'contacts_events_order_item_process')
        ->setDisplayOptions('view', [
          'label' => 'hidden',
          'type' => 'state_transition_form',
          'weight' => 10,
        ])
        ->setDisplayConfigurable('form', TRUE)
        ->setDisplayConfigurable('view', TRUE);

      $confirmed_field = BundleFieldDefinition::create('timestamp')
        ->setName('confirmed')
        ->setTargetEntityTypeId('commerce_order_item')
        ->setTargetBundle($bundle)
        ->setLabel(new TranslatableMarkup('Confirmed'))
        ->setDescription(new TranslatableMarkup('The time when the order item was confirmed.'))
        ->setReadOnly(TRUE)
        ->setDisplayConfigurable('form', FALSE)
        ->setDisplayConfigurable('view', TRUE);

      /** @var \Drupal\Core\Field\FieldDefinitionListener $field_definition_listener*/
      $field_definition_listener = \Drupal::service('field_definition.listener');

      $field_definition_listener->onFieldDefinitionCreate($state_field);
      $field_definition_listener->onFieldDefinitionCreate($confirmed_field);
    }
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc