contacts_events-8.x-1.x-dev/modules/accommodation/src/BookingAccommodationHelper.php
modules/accommodation/src/BookingAccommodationHelper.php
<?php
namespace Drupal\contacts_events_accommodation;
use Drupal\commerce_order\Entity\OrderItemInterface;
use Drupal\contacts_events\PriceCalculator;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
/**
* Helper for working with accommodation for a booking.
*/
class BookingAccommodationHelper implements BookingAccommodationHelperInterface {
/**
* The order items field items we are working with.
*
* @var \Drupal\Core\Field\EntityReferenceFieldItemList
*/
protected $orderItems;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The price calculator service.
*
* @var \Drupal\contacts_events\PriceCalculator
*/
protected $priceCalculator;
/**
* The number of confirmed delegates.
*
* Keys are:
* - confirmed: The confirmed count.
* - total: The total count.
*
* @var array
*/
protected $delegates;
/**
* The accommodation totals for this order.
*
* Outer keys are accommodation ID. Each value is an array containing:
* - confirmed: The confirmed count.
* - total: The total count.
*
* @var array
*/
protected $accommodation;
/**
* {@inheritdoc}
*/
public function __construct(EntityReferenceFieldItemListInterface $items, EntityTypeManagerInterface $entity_type_manager, PriceCalculator $price_calculator) {
$this->orderItems = $items;
$this->entityTypeManager = $entity_type_manager;
$this->priceCalculator = $price_calculator;
}
/**
* {@inheritdoc}
*/
public function getConfirmedDelegates(): int {
$this->calculateDelegates();
return $this->delegates['confirmed'];
}
/**
* {@inheritdoc}
*/
public function getTotalDelegates(): int {
$this->calculateDelegates();
return $this->delegates['total'];
}
/**
* Calculate the delegate counts for the order.
*/
protected function calculateDelegates(): void {
if (isset($this->delegates)) {
return;
}
$this->delegates = [
'confirmed' => 0,
'total' => 0,
];
/** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */
// phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
$order_items = array_filter($this->orderItems->referencedEntities(), [$this, 'filterDelegateOrderItems']);
foreach ($order_items as $order_item) {
// All tickets count towards the total.
$this->delegates['total']++;
// Non-pending tickets count as confirmed.
if ($order_item->get('state')->value !== 'pending') {
$this->delegates['confirmed']++;
}
}
}
/**
* Filter delegate order items eligible for accommodation.
*
* @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
* The order item.
*
* @return bool
* Whether the delegate is eligible for accommodation.
*/
protected function filterDelegateOrderItems(OrderItemInterface $order_item): bool {
if ($order_item->bundle() != 'contacts_ticket') {
return FALSE;
}
// Cancelled tickets don't count for anything.
if ($order_item->get('state')->value == 'cancelled') {
return FALSE;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getConfirmedAccommodation(int $id): int {
$this->calculateAccommodation();
return $this->accommodation[$id]['confirmed'] ?? 0;
}
/**
* {@inheritdoc}
*/
public function getTotalAccommodation(int $id): int {
$this->calculateAccommodation();
return $this->accommodation[$id]['total'] ?? 0;
}
/**
* {@inheritdoc}
*/
public function getAllAccommodation(): array {
$this->calculateAccommodation();
return $this->accommodation;
}
/**
* Calculate the accommodation counts for the order.
*/
protected function calculateAccommodation(): void {
if (isset($this->accommodation)) {
return;
}
$this->accommodation = [];
$event_id = $this->orderItems->getEntity()->get('event')->target_id;
/** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */
foreach ($this->orderItems->referencedEntities() as $order_item) {
if (substr($order_item->bundle(), 0, 9) == 'ce_accom_') {
$accommodation = $order_item->getPurchasedEntity();
// Make sure it's for the right event. If something has somehow caused
// the accommodation to be associated with the wrong event we don't
// want it to count towards the totals.
if ($accommodation->get('event')->target_id != $event_id) {
continue;
}
$accommodation_id = $order_item->getPurchasedEntityId();
if (!isset($this->accommodation[$accommodation_id])) {
$this->accommodation[$accommodation_id] = [
'total' => 0,
'confirmed' => 0,
];
}
switch ($order_item->get('state')->value) {
case 'confirmed':
case 'paid_in_full':
$this->accommodation[$accommodation_id]['confirmed'] += $order_item->getQuantity();
case 'pending':
$this->accommodation[$accommodation_id]['total'] += $order_item->getQuantity();
break;
}
}
}
}
/**
* {@inheritdoc}
*/
public function getMaxAllowedAccommodation(AccommodationInterface $accommodation): ?int {
$min_delegates = $accommodation->getMinDelegates();
if ($min_delegates) {
$max_allowed = ceil($this->getTotalDelegates() / $min_delegates);
}
elseif (!isset($min_delegates)) {
$max_allowed = 10;
}
else {
$max_allowed = $this->getTotalDelegates();
}
return max($max_allowed, $this->getTotalAccommodation($accommodation->id()));
}
/**
* {@inheritdoc}
*/
public function updateUnconfirmedItems(array $quantities) {
$this->calculateAccommodation();
$modified = [];
foreach ($quantities as $id => $quantity) {
$diff = $quantity - $this->getTotalAccommodation((int) $id);
if ($diff !== 0) {
$item = $this->getUnconfirmedItem($id);
$item->setQuantity((int) $item->getQuantity() + $diff);
if (!$item->isNew()) {
$modified[$id] = $item;
}
}
}
// Remove any zero quantity order items. Do this in reverse to avoid re-keys
// changing our deltas.
/** @var \Drupal\commerce_order\Entity\OrderItemInterface[] $items */
$items = array_reverse($this->orderItems->referencedEntities(), TRUE);
foreach ($items as $delta => $item) {
if ((int) $item->getQuantity() === 0) {
$this->orderItems->removeItem($delta);
}
}
return $modified;
}
/**
* Get an order item for unconfirmed accommodation of the given ID.
*
* @param int $accommodation_id
* The accommodation ID.
*
* @return \Drupal\commerce_order\Entity\OrderItemInterface
* The unconfirmed order item for the accommodation.
*/
protected function getUnconfirmedItem(int $accommodation_id) {
/** @var \Drupal\contacts_events_accommodation\AccommodationInterface $accommodation */
$accommodation = $this->entityTypeManager
->getStorage('c_events_accommodation')
->load($accommodation_id);
// See if we already have a suitable order item.
/** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */
foreach ($this->orderItems->referencedEntities() as $order_item) {
if ($order_item->bundle() == $accommodation->getOrderItemTypeId()) {
if ($order_item->getPurchasedEntityId() == $accommodation_id && $order_item->get('state')->value == 'pending') {
return $order_item;
}
}
}
// Otherwise create a new one and append it.
/** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */
$order_item = $this->entityTypeManager
->getStorage('commerce_order_item')
->createFromPurchasableEntity($accommodation);
$order_item->setQuantity(0);
$this->priceCalculator->calculatePrice($order_item);
$this->orderItems->appendItem($order_item);
return $order_item;
}
}
