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