contacts_events-8.x-1.x-dev/src/Entity/OrderItemHooks.php
src/Entity/OrderItemHooks.php
<?php namespace Drupal\contacts_events\Entity; use Drupal\commerce_order\Entity\OrderItemInterface; use Drupal\contacts_events\OrderItemStateTrait; use Drupal\contacts_events\Plugin\Commerce\CheckoutFlow\BookingFlow; use Drupal\Core\Access\AccessResult; use Drupal\Core\Routing\CurrentRouteMatch; use Drupal\Core\Session\AccountInterface; /** * Implementations for Order Item hooks. */ class OrderItemHooks { use OrderItemStateTrait; /** * The current route match. * * @var \Drupal\Core\Routing\CurrentRouteMatch */ protected $routeMatch; /** * Construct the OrderItemHooks service. * * @param \Drupal\Core\Routing\CurrentRouteMatch $route_match * The current route match. */ public function __construct(CurrentRouteMatch $route_match) { $this->routeMatch = $route_match; } /** * Access checks for order items. * * @param \Drupal\commerce_order\Entity\OrderItemInterface $item * The order item. * @param string $operation * The operation being checked. * @param \Drupal\Core\Session\AccountInterface $account * The account performing the operation. * * @return \Drupal\Core\Access\AccessResultInterface * The access result. */ public function access(OrderItemInterface $item, $operation, AccountInterface $account) { // Only change access for booking order types. if ($item->getOrder()->bundle() == 'contacts_booking') { $method = "{$operation}Access"; if (method_exists($this, $method)) { return $this->{$method}($item, $account); } } return AccessResult::neutral(); } /** * Update access check for order items. * * @param \Drupal\commerce_order\Entity\OrderItemInterface $item * The order item. * @param \Drupal\Core\Session\AccountInterface $account * The account performing the operation. * * @return \Drupal\Core\Access\AccessResultInterface * The access result. */ protected function updateAccess(OrderItemInterface $item, AccountInterface $account) { // For additional charges, only staff can add/edit them. if ($item->bundle() == 'additional_charge') { return AccessResult::allowedIfHasPermission($account, 'can manage bookings for contacts_events'); } // Forbid if the item is confirmed. It should only be cancelled. $order = $item->getOrder(); $result = AccessResult::allowedIf($account->isAuthenticated() && $order->getCustomerId() == $account->id()) ->addCacheableDependency($item) ->addCacheableDependency($order) ->addCacheableDependency($account); if ($item->hasField('state')) { $result = $result->andIf(AccessResult::allowedIf($item->get('state')->value == 'pending')); } return $result; } /** * Delete access check for order items. * * @param \Drupal\commerce_order\Entity\OrderItemInterface $item * The order item. * @param \Drupal\Core\Session\AccountInterface $account * The account performing the operation. * * @return \Drupal\Core\Access\AccessResultInterface * The access result. */ protected function deleteAccess(OrderItemInterface $item, AccountInterface $account) { // For additional charges, only staff can add/edit them. if ($item->bundle() == 'additional_charge') { return AccessResult::allowedIfHasPermission($account, 'can manage bookings for contacts_events'); } // Globally forbid if the item is confirmed. It should only be cancelled. if ($item->hasField('state') && $item->get('state')->value != 'pending') { return AccessResult::forbidden() ->addCacheableDependency($item); } // Otherwise use the same rules as update access. return $this->updateAccess($item, $account); } /** * Create access check for order items. * * @param string $entity_bundle * The bundle to be created. * @param array $context * The context, if any. * @param \Drupal\Core\Session\AccountInterface $account * The account performing the operation. * * @return \Drupal\Core\Access\AccessResultInterface * The access result. */ public function createAccess($entity_bundle, array $context, AccountInterface $account) { // Inline entity form doesn't give us any context, so if we are on the // checkout tickets page, we will assume this is a check for adding a ticket // and allow access if the order from the route belongs to the user we're // checking access for. // @todo See if we can get InlineEntityForm to provide some context. // @todo Expand this for non ticket line items. if ($entity_bundle == 'contacts_ticket' && $this->routeMatch->getRouteName() == BookingFlow::ROUTE_NAME) { /** @var \Drupal\commerce_order\Entity\OrderInterface $order */ $order = $this->routeMatch->getParameter('commerce_order'); return AccessResult::allowedIf($account->isAuthenticated() && $order->getCustomerId() == $account->id()) ->addCacheableDependency($order) ->addCacheableDependency($account); } elseif ($entity_bundle == 'additional_charge') { return AccessResult::allowedIfHasPermission($account, 'can manage bookings for contacts_events'); } return AccessResult::neutral(); } /** * Cancel access check for order items. * * @param \Drupal\commerce_order\Entity\OrderItemInterface $item * The order item. * @param \Drupal\Core\Session\AccountInterface $account * The account performing the operation. * * @return \Drupal\Core\Access\AccessResultInterface * The access result. */ public function cancelAccess(OrderItemInterface $item, AccountInterface $account) { if (!$item->hasField('state')) { return AccessResult::forbidden()->addCacheableDependency($item); } // Get our state and workflow. /** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface $state */ $state = $item->get('state')->first(); $workflow = $state->getWorkflow(); // Check if the cancel transition is allowed. /** @var \Drupal\workflows\Transition[] $transitions */ $transitions = $workflow->getAllowedTransitions($state->value, $item); if (!isset($transitions['cancel'])) { return AccessResult::forbidden('Cancel transition unavailable.') ->addCacheableDependency($item); } return AccessResult::allowedIfHasPermission($account, 'can manage bookings for contacts_events') ->addCacheableDependency($item); } /** * React to an order item being deleted. * * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item * The order item. */ public function delete(OrderItemInterface $order_item) { // Clean up single use purchasable entities. $purchased_entity = $order_item->getPurchasedEntity(); if ($purchased_entity instanceof SingleUsePurchasableEntityInterface) { $purchased_entity->delete(); } } /** * Pre-save for order items. * * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item * The order item. * @param \Drupal\commerce_order\Entity\OrderItemInterface|null $original * The original order item, if any. */ public function preSave(OrderItemInterface $order_item, OrderItemInterface $original = NULL): void { if (!$order_item->hasField('state')) { return; } // Nothing to do if this is a new item, has no original or is pending. if (!$original || $order_item->isNew() || $order_item->get('state')->value == 'pending') { return; } // If there is already a transition happening, we don't want to interfere. if ($order_item->get('state')->value != $original->get('state')->value) { return; } // See if the price has changed. $total = $order_item->getAdjustedTotalPrice(); $original_total = $original->getAdjustedTotalPrice(); if (($total xor $original_total) || (!$total && !$original_total) || !$total->equals($original_total)) { // If it has changed, check the state of the order item. $this->checkOrderItemStatus($order_item); } } }