diff --git a/modules/checkout/commerce_checkout.services.yml b/modules/checkout/commerce_checkout.services.yml index 29b44dde..a8268254 100644 --- a/modules/checkout/commerce_checkout.services.yml +++ b/modules/checkout/commerce_checkout.services.yml @@ -12,7 +12,7 @@ services: commerce_checkout.checkout_order_manager: class: Drupal\commerce_checkout\CheckoutOrderManager - arguments: ['@commerce_checkout.chain_checkout_flow_resolver'] + arguments: ['@commerce_checkout.chain_checkout_flow_resolver', '@event_dispatcher'] plugin.manager.commerce_checkout_flow: class: Drupal\commerce_checkout\CheckoutFlowManager diff --git a/modules/checkout/src/CheckoutOrderManager.php b/modules/checkout/src/CheckoutOrderManager.php index f396d514..a7763537 100644 --- a/modules/checkout/src/CheckoutOrderManager.php +++ b/modules/checkout/src/CheckoutOrderManager.php @@ -2,8 +2,11 @@ namespace Drupal\commerce_checkout; +use Drupal\commerce_checkout\Event\CheckoutEvents; use Drupal\commerce_checkout\Resolver\ChainCheckoutFlowResolverInterface; use Drupal\commerce_order\Entity\OrderInterface; +use Drupal\commerce_order\Event\OrderEvent; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Manages checkout flows for orders. @@ -17,14 +20,24 @@ class CheckoutOrderManager implements CheckoutOrderManagerInterface { */ protected $chainCheckoutFlowResolver; + /** + * The event dispatcher. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $eventDispatcher; + /** * Constructs a new CheckoutOrderManager object. * * @param \Drupal\commerce_checkout\Resolver\ChainCheckoutFlowResolverInterface $chain_checkout_flow_resolver * The chain checkout flow resolver. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher + * The event dispatcher. */ - public function __construct(ChainCheckoutFlowResolverInterface $chain_checkout_flow_resolver) { + public function __construct(ChainCheckoutFlowResolverInterface $chain_checkout_flow_resolver, EventDispatcherInterface $event_dispatcher) { $this->chainCheckoutFlowResolver = $chain_checkout_flow_resolver; + $this->eventDispatcher = $event_dispatcher; } /** @@ -32,9 +45,7 @@ class CheckoutOrderManager implements CheckoutOrderManagerInterface { */ public function getCheckoutFlow(OrderInterface $order) { if (!$order->get('checkout_flow')->entity) { - $checkout_flow = $this->chainCheckoutFlowResolver->resolve($order); - $order->set('checkout_flow', $checkout_flow); - $order->save(); + $this->resolveCheckoutFlow($order); } return $order->get('checkout_flow')->entity; @@ -68,4 +79,40 @@ class CheckoutOrderManager implements CheckoutOrderManagerInterface { return $selected_step_id; } + /** + * {@inheritdoc} + */ + public function isInitialized(OrderInterface $order) { + // Note that we don't check whether the "checkout_init_event_dispatched" + // flag is set here on purpose to make sure we don't fire the event for + // existing orders that potentially already entered checkout. + return !$order->get('checkout_flow')->isEmpty() && $order->get('checkout_flow')->entity; + } + + /** + * {@inheritdoc} + */ + public function initialize(OrderInterface $order) { + if (!$order->getData('checkout_init_event_dispatched', FALSE)) { + $event = new OrderEvent($order); + $this->eventDispatcher->dispatch(CheckoutEvents::INIT, $event); + $order->setData('checkout_init_event_dispatched', TRUE); + } + $this->resolveCheckoutFlow($order); + } + + /** + * Resolves the checkout flow for the given order. + * + * @param \Drupal\commerce_order\Entity\OrderInterface $order + * The order. + * + * @throws \Drupal\Core\Entity\EntityStorageException + */ + protected function resolveCheckoutFlow(OrderInterface $order) { + $checkout_flow = $this->chainCheckoutFlowResolver->resolve($order); + $order->set('checkout_flow', $checkout_flow); + $order->save(); + } + } diff --git a/modules/checkout/src/CheckoutOrderManagerInterface.php b/modules/checkout/src/CheckoutOrderManagerInterface.php index fed44246..8c6a600e 100644 --- a/modules/checkout/src/CheckoutOrderManagerInterface.php +++ b/modules/checkout/src/CheckoutOrderManagerInterface.php @@ -16,7 +16,7 @@ interface CheckoutOrderManagerInterface { * The order. * * @return \Drupal\commerce_checkout\Entity\CheckoutFlowInterface - * THe checkout flow. + * The checkout flow. */ public function getCheckoutFlow(OrderInterface $order); @@ -37,4 +37,26 @@ interface CheckoutOrderManagerInterface { */ public function getCheckoutStepId(OrderInterface $order, $requested_step_id = NULL); + /** + * Determines whether the given order is initialized for checkout. + * + * @param \Drupal\commerce_order\Entity\OrderInterface $order + * The order. + * + * @return bool + * Returns whether the order is initialized for checkout. + */ + public function isInitialized(OrderInterface $order); + + /** + * Initializes the order for checkout + * + * This sets the order's checkout flow and dispatches the checkout init event + * for other modules to act on the order. + * + * @param \Drupal\commerce_order\Entity\OrderInterface $order + * The order. + */ + public function initialize(OrderInterface $order); + } diff --git a/modules/checkout/src/Controller/CheckoutController.php b/modules/checkout/src/Controller/CheckoutController.php index 87aa7019..6bf4f2a6 100644 --- a/modules/checkout/src/Controller/CheckoutController.php +++ b/modules/checkout/src/Controller/CheckoutController.php @@ -82,6 +82,11 @@ class CheckoutController implements ContainerInjectionInterface { public function formPage(RouteMatchInterface $route_match) { /** @var \Drupal\commerce_order\Entity\OrderInterface $order */ $order = $route_match->getParameter('commerce_order'); + + if (!$this->checkoutOrderManager->isInitialized($order)) { + $this->checkoutOrderManager->initialize($order); + } + $requested_step_id = $route_match->getParameter('step'); $step_id = $this->checkoutOrderManager->getCheckoutStepId($order, $requested_step_id); if ($requested_step_id != $step_id) { diff --git a/modules/checkout/src/Event/CheckoutEvents.php b/modules/checkout/src/Event/CheckoutEvents.php index 5a01f479..1859a7c6 100644 --- a/modules/checkout/src/Event/CheckoutEvents.php +++ b/modules/checkout/src/Event/CheckoutEvents.php @@ -16,4 +16,13 @@ final class CheckoutEvents { */ const COMPLETION_REGISTER = 'commerce_checkout.completion_register'; + /** + * Name of the event fired when the customer enters checkout the first time. + * + * @Event + * + * @see \Drupal\commerce_checkout\Event\CheckoutInitEvent + */ + const INIT = 'commerce_checkout.init'; + } diff --git a/modules/checkout/tests/src/Kernel/CheckoutOrderManagerTest.php b/modules/checkout/tests/src/Kernel/CheckoutOrderManagerTest.php index aaa4e5b6..eae86873 100644 --- a/modules/checkout/tests/src/Kernel/CheckoutOrderManagerTest.php +++ b/modules/checkout/tests/src/Kernel/CheckoutOrderManagerTest.php @@ -74,6 +74,20 @@ class CheckoutOrderManagerTest extends OrderKernelTestBase { $this->container->get('request_stack')->push($request); } + /** + * Tests initializing the order for checkout. + */ + public function testInitialize() { + $this->assertFalse($this->checkoutOrderManager->isInitialized($this->order)); + + $this->checkoutOrderManager->initialize($this->order); + $checkout_flow = $this->checkoutOrderManager->getCheckoutFlow($this->order); + $this->assertInstanceOf(CheckoutFlow::class, $checkout_flow); + $this->assertEquals('default', $checkout_flow->id()); + $this->assertTrue($this->order->getData('checkout_init_event_dispatched')); + $this->assertTrue($this->checkoutOrderManager->isInitialized($this->order)); + } + /** * Tests getting the order's checkout flow. */