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