bee-8.x-1.1/src/Form/AddReservationForm.php
src/Form/AddReservationForm.php
<?php namespace Drupal\bee\Form; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\bat_event_series\Entity\EventSeries; use Drupal\commerce_cart\CartManagerInterface; use Drupal\commerce_cart\CartProviderInterface; use Drupal\node\Entity\NodeType; use Drupal\node\NodeInterface; use RRule\RRule; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Add reservation form for a BEE node. */ class AddReservationForm extends FormBase { /** * The entity type manager. * * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ protected $entityTypeManager; /** * The order item storage. * * @var \Drupal\commerce_order\OrderItemStorageInterface */ protected $orderItemStorage; /** * The config factory. * * @var \Drupal\Core\Config\ConfigFactoryInterface */ protected $configFactory; /** * The cart manager. * * @var \Drupal\commerce_cart\CartManagerInterface */ protected $cartManager; /** * The cart provider. * * @var \Drupal\commerce_cart\CartProviderInterface */ protected $cartProvider; /** * Constructs a new AddReservationForm object. * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. * @param \Drupal\commerce_cart\CartManagerInterface|null $cart_manager * The cart manager. * @param \Drupal\commerce_cart\CartProviderInterface|null $cart_provider * The cart provider. */ public function __construct(EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, ?CartManagerInterface $cart_manager, ?CartProviderInterface $cart_provider) { $this->entityTypeManager = $entity_type_manager; $this->configFactory = $config_factory; $this->cartManager = $cart_manager; $this->cartProvider = $cart_provider; if ($entity_type_manager->hasHandler('commerce_order_item', 'storage')) { $this->orderItemStorage = $entity_type_manager->getStorage('commerce_order_item'); } $this->nodetypeStorage = $this->entityTypeManager->getStorage('node_type'); $this->nodeStorage = $this->entityTypeManager->getStorage('node'); } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('entity_type.manager'), $container->get('config.factory'), $container->get('commerce_cart.cart_manager', ContainerInterface::IGNORE_ON_INVALID_REFERENCE), $container->get('commerce_cart.cart_provider', ContainerInterface::IGNORE_ON_INVALID_REFERENCE) ); } /** * {@inheritdoc} */ public function getFormId() { return 'bee_add_reservation_form'; } /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state, NodeInterface $node = NULL, EventSeries $bat_event_series = NULL) { $node_type = $this->nodetypeStorage->load($node->bundle()); assert($node_type instanceof NodeType); $bee_settings = $node_type->getThirdPartySetting('bee', 'bee'); $today = new \DateTime(); $tomorrow = clone($today); $tomorrow->modify('+1 day'); $one_hour_later = clone($today); $one_hour_later->modify('+1 hour'); $form['node'] = [ '#type' => 'hidden', '#value' => $node->id(), ]; $form['start_date'] = [ '#type' => ($bee_settings['bookable_type'] == 'daily') ? 'date' : 'datetime', '#title' => $this->t('Start date'), '#default_value' => ($bee_settings['bookable_type'] == 'daily') ? $today->format('Y-m-d') : new DrupalDateTime($today->format('Y-m-d H:00')), '#date_increment' => 60, ]; $form['end_date'] = [ '#type' => ($bee_settings['bookable_type'] == 'daily') ? 'date' : 'datetime', '#title' => $this->t('End date'), '#default_value' => ($bee_settings['bookable_type'] == 'daily') ? $tomorrow->format('Y-m-d') : new DrupalDateTime($one_hour_later->format('Y-m-d H:00')), '#date_increment' => 60, ]; if ($bat_event_series) { $form['event_series'] = [ '#type' => 'hidden', '#value' => $bat_event_series->id(), ]; } else { $form['repeat'] = [ '#type' => 'checkbox', '#title' => $this->t('This booking repeats'), '#prefix' => '<div class="form-row">', ]; $form['repeat_frequency'] = [ '#type' => 'select', '#title' => $this->t('Repeat frequency'), '#options' => [ 'daily' => $this->t('Daily'), 'weekly' => $this->t('Weekly'), 'monthly' => $this->t('Monthly'), ], '#states' => [ 'visible' => [ ':input[name="repeat"]' => ['checked' => TRUE], ], ], ]; $form['repeat_until'] = [ '#type' => 'date', '#title' => $this->t('Repeat until'), '#states' => [ 'visible' => [ ':input[name="repeat"]' => ['checked' => TRUE], ], ], '#suffix' => '</div>', ]; } $max_capacity = 10; $options = []; for ($o = 1; $o <= $max_capacity; $o++) { $options[$o] = $o . " " . $this->t("Capacity"); } $form['capacity'] = [ '#type' => 'select', '#title' => $this->t('Capacity'), '#options' => $options, '#default_value' => 1, '#required' => TRUE, ]; $form['submit'] = [ '#type' => 'submit', '#value' => $this->t('Add Reservation'), ]; $form['#attached']['library'][] = 'bee/bee_form'; return $form; } /** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { $values = $form_state->getValues(); $node = $this->nodeStorage->load($values['node']); $node_type = $this->nodetypeStorage->load($node->bundle()); assert($node_type instanceof NodeType); $bee_settings = $node_type->getThirdPartySetting('bee', 'bee'); $start_date = $values['start_date']; $end_date = $values['end_date']; if ($bee_settings['bookable_type'] == 'daily') { $start_date = new \DateTime($start_date); $end_date = new \DateTime($end_date); } $dates_valid = TRUE; if ($bee_settings['bookable_type'] == 'hourly') { // Validate the input dates. if (!$start_date instanceof DrupalDateTime) { $form_state->setErrorByName('start_date', $this->t('The start date is not valid.')); $dates_valid = FALSE; } if (!$end_date instanceof DrupalDateTime) { $form_state->setErrorByName('end_date', $this->t('The end date is not valid.')); $dates_valid = FALSE; } } if ($dates_valid) { if ($end_date <= $start_date) { $form_state->setErrorByName('end_date', $this->t('End date must be after the start date.')); return; } if ($bee_settings['bookable_type'] == 'hourly') { if ($node->get('field_use_open_hours')->value) { // All opening slots. $open_hours = $node->get('field_open_hours')->getValue(); // Only slots that are in the requested starting day. $open_hours = array_filter($open_hours, function ($open_hour) use ($start_date) { return $open_hour['day'] === $start_date->format('w'); }); // Only slots that end after the requested starting time. $open_hours = array_filter($open_hours, function ($open_hour) use ($end_date) { $end_date_time = (int) $end_date->format('Gi'); return $end_date_time <= $open_hour['endhours']; }); $relevant_open_hour = reset($open_hours); if (count($open_hours) === 0 || $relevant_open_hour['starthours'] > (int) $start_date->format('Gi')) { $form_state->setError($form, $this->t('Please select start and end times within the opening hours.')); } } } if (isset($values['repeat']) && $values['repeat']) { if ($bee_settings['payment']) { if (!$this->checkSeriesAvailability($values['node'], $values['start_date'], $values['end_date'], $values['repeat_frequency'], $values['repeat_until'])) { $form_state->setError($form, $this->t('Some events on this series are not available.')); } } } else { $available_units = $this->getAvailableUnits($values['node'], $values['start_date'], $values['end_date']); if (empty($available_units)) { $form_state->setError($form, $this->t('No available units.')); } } } if (isset($values['repeat']) && $values['repeat']) { if (empty($values['repeat_until'])) { $form_state->setErrorByName('repeat_until', $this->t('Repeat until is required if "This booking repeats" is checked.')); } } } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { $values = $form_state->getValues(); $start_date = $values['start_date']; $end_date = $values['end_date']; $node = $this->nodeStorage->load($values['node']); $node_type = $this->nodetypeStorage->load($node->bundle()); assert($node_type instanceof NodeType); $bee_settings = $node_type->getThirdPartySetting('bee', 'bee'); if ($bee_settings['bookable_type'] == 'daily') { $start_date = new \DateTime($start_date); $end_date = new \DateTime($end_date); } else { $start_date = new \DateTime($start_date->format('Y-m-d H:i')); $end_date = new \DateTime($end_date->format('Y-m-d H:i')); } if ($bee_settings['payment']) { $quantity = 1; $booking = bat_booking_create([ 'type' => 'bee', 'label' => $node->label(), ]); $booking->set('booking_start_date', $start_date->format('Y-m-d\TH:i:s')); $booking->set('booking_end_date', $end_date->format('Y-m-d\TH:i:s')); if (isset($values['repeat']) && $values['repeat']) { $booking->set('booking_repeat_frequency', $values['repeat_frequency']); $booking->set('booking_repeat_until', $values['repeat_until']); $quantity = $this->getRepeatingEventsCount($start_date, $values['repeat_frequency'], $values['repeat_until']); } if (isset($values['event_series'])) { $booking->set('booking_event_series_reference', $values['event_series']); } if (isset($values['capacity'])) { $booking->set('booking_capacity', $values['capacity']); } $booking->save(); $product = $node->get('field_product')->entity; $stores = $product->getStores(); $store = reset($stores); $variations = $product->getVariations(); $product_variation = reset($variations); $cart = $this->cartProvider->getCart('default', $store); if (!$cart) { $cart = $this->cartProvider->createCart('default', $store); } else { $this->cartManager->emptyCart($cart); } $unit_price = bee_get_unit_price($node, $booking, $start_date, $end_date); $order_item = $this->orderItemStorage->create([ 'title' => $node->label(), 'type' => 'bee', 'purchased_entity' => $product_variation->id(), 'quantity' => $quantity, 'unit_price' => $product_variation->getPrice(), ]); $order_item->set('field_booking', $booking); $order_item->set('field_node', $node); $order_item->setUnitPrice($unit_price, TRUE); $order_item->save(); $this->cartManager->addOrderItem($cart, $order_item); $form_state->setRedirect('commerce_checkout.form', ['commerce_order' => $cart->id()]); } else { if ($bee_settings['bookable_type'] == 'daily') { $booked_state = bat_event_load_state_by_machine_name('bee_daily_booked'); if (isset($values['repeat']) && $values['repeat']) { $repeat_until = new \DateTime($values['repeat_until'] . 'T235959Z'); $frequency = $this->t('Day'); if ($values['repeat_frequency'] == 'weekly') { $frequency = $start_date->format('l'); } elseif ($values['repeat_frequency'] == 'monthly') { $frequency = $this->t('@day of Month', ['@day' => $start_date->format('jS')]); } $label = $this->t('Reservations for @node Every @frequency from @start_date -> @end_date', [ '@node' => $node->label(), '@frequency' => $frequency, '@start_date' => $start_date->format('M j Y'), '@end_date' => $repeat_until->format('M j Y'), ]); $rrule = new RRule([ 'FREQ' => strtoupper($values['repeat_frequency']), 'UNTIL' => $values['repeat_until'] . 'T235959Z', ]); $event = bat_event_series_create([ 'type' => 'availability_daily', 'label' => $label, 'rrule' => $rrule->rfcString(), ]); } else { $event = bat_event_create(['type' => 'availability_daily']); } $event_dates = [ 'value' => $start_date->format('Y-m-d\TH:i:00'), 'end_value' => $end_date->format('Y-m-d\TH:i:00'), ]; $event->set('event_dates', $event_dates); $event->set('event_state_reference', $booked_state->id()); } else { $booked_state = bat_event_load_state_by_machine_name('bee_hourly_booked'); if (isset($values['repeat']) && $values['repeat']) { $repeat_until = new \DateTime($values['repeat_until'] . 'T235959Z'); $frequency = $this->t('Day'); if ($values['repeat_frequency'] == 'weekly') { $frequency = $start_date->format('l'); } elseif ($values['repeat_frequency'] == 'monthly') { $frequency = $this->t('@day of Month', ['@day' => $start_date->format('jS')]); } $label = $this->t('Reservations for @node Every @frequency from @start_time-@end_time from @start_date -> @end_date', [ '@node' => $node->label(), '@frequency' => $frequency, '@start_time' => $start_date->format('gA'), '@end_time' => $end_date->format('gA'), '@start_date' => $start_date->format('M j Y'), '@end_date' => $repeat_until->format('M j Y'), ]); $rrule = new RRule([ 'FREQ' => strtoupper($values['repeat_frequency']), 'UNTIL' => $values['repeat_until'] . 'T235959Z', ]); $event = bat_event_series_create([ 'type' => 'availability_hourly', 'label' => $label, 'rrule' => $rrule->rfcString(), ]); } else { $event = bat_event_create(['type' => 'availability_hourly']); } $event_dates = [ 'value' => $start_date->format('Y-m-d\TH:i:00'), 'end_value' => $end_date->format('Y-m-d\TH:i:00'), ]; $event->set('event_dates', $event_dates); $event->set('event_state_reference', $booked_state->id()); } $available_units = $this->getAvailableUnits($values['node'], $values['start_date'], $values['end_date']); $event->set('event_bat_unit_reference', reset($available_units)); if (isset($values['event_series'])) { $event->set('event_series', $values['event_series']); } $event->save(); $this->messenger()->addMessage($this->t('Reservation created!')); if (isset($values['event_series'])) { $form_state->setRedirect('entity.bat_event_series.canonical', ['bat_event_series' => $values['event_series']]); } else { $form_state->setRedirect('entity.node.canonical', ['node' => $node->id()]); } } } /** * Get number of events in the repeating sequence. * * @param \DateTime $start_date * A DateTime object. * @param string $repeat_frequency * A string. * @param string $repeat_until * A string. * * @return int * A counter integer. */ protected function getRepeatingEventsCount(\DateTime $start_date, $repeat_frequency, $repeat_until) { $rrule = new RRule([ 'FREQ' => strtoupper($repeat_frequency), 'UNTIL' => $repeat_until . 'T235959Z', 'DTSTART' => $start_date, ]); return $rrule->count(); } /** * Check Series Availability. * * @param int $nid * Id of a bee node. * @param string $start * A string. * @param string $end * A string. * @param string $repeat_frequency * A string. * @param string $repeat_until * A string. * * @return bool * a boolean value */ protected function checkSeriesAvailability($nid, $start, $end, $repeat_frequency, $repeat_until) { $node = $this->nodeStorage->load($nid); $node_type = $this->nodetypeStorage->load($node->bundle()); assert($node_type instanceof NodeType); $bee_settings = $node_type->getThirdPartySetting('bee', 'bee'); $start = new \DateTime($start); $end = new \DateTime($end); $rrule = new RRule([ 'FREQ' => strtoupper($repeat_frequency), 'UNTIL' => $repeat_until . 'T235959Z', 'DTSTART' => $start, ]); foreach ($rrule as $occurrence) { $start_date = clone($occurrence); $end_date = clone($occurrence); if ($bee_settings['bookable_type'] == 'daily') { $end_date->add($start->diff($end)); $start_date = $start_date->format('Y-m-d'); $end_date = $end_date->format('Y-m-d'); } else { $start_date->setTime($start->format('H'), $start->format('i')); $end_date->setTime($start->format('H'), $start->format('i')); $end_date->add($start->diff($end)); } if (empty($this->getAvailableUnits($nid, $start_date, $end_date))) { return FALSE; } } return TRUE; } /** * Get available Units. * * @param int $nid * Id of a bee node. * @param string $start_date * A string. * @param string $end_date * A string. * * @return array * An array with unit ids. */ protected function getAvailableUnits($nid, $start_date, $end_date) { $node = $this->nodeStorage->load($nid); $node_type = $this->nodetypeStorage->load($node->bundle()); assert($node_type instanceof NodeType); $bee_settings = $node_type->getThirdPartySetting('bee', 'bee'); $units_ids = []; foreach ($node->get('field_availability_' . $bee_settings['bookable_type']) as $unit) { if ($unit->entity) { $units_ids[] = $unit->entity->id(); } } if ($bee_settings['bookable_type'] == 'daily') { $start_date = new \DateTime($start_date); $end_date = new \DateTime($end_date); $end_date->sub(new \DateInterval('PT1M')); $available_units_ids = bat_event_get_matching_units($start_date, $end_date, ['bee_daily_available'], [$bee_settings['type_id']], 'availability_daily'); } else { $start_date = new \DateTime($start_date->format('Y-m-d H:i')); $end_date = new \DateTime($end_date->format('Y-m-d H:i')); $end_date->sub(new \DateInterval('PT1M')); $available_units_ids = bat_event_get_matching_units($start_date, $end_date, ['bee_hourly_available'], [$bee_settings['type_id']], 'availability_hourly'); } return array_intersect($units_ids, $available_units_ids); } }