commerce_api-8.x-1.x-dev/src/EventSubscriber/CartTokenSubscriber.php
src/EventSubscriber/CartTokenSubscriber.php
<?php
namespace Drupal\commerce_api\EventSubscriber;
use Drupal\commerce_api\CartTokenSession;
use Drupal\commerce_cart\CartSessionInterface;
use Drupal\Core\TempStore\SharedTempStore;
use Drupal\Core\TempStore\SharedTempStoreFactory;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Cart token subscriber.
*
* This subscriber provides two pieces of functionality.
*
* On response, it ensures the Vary header contains the cart token header. This
* handles browser and reverse proxy caching handling.
*
* On request, it checks if the cart token query parameter is available. This
* ensures cart data is passed to the user's session. For example, a user that
* created a cart from a decoupled application but visits checkout using the
* cart token to finish order purchased.
*/
final class CartTokenSubscriber implements EventSubscriberInterface {
/**
* The tempstore service.
*
* @var \Drupal\Core\TempStore\SharedTempStore
*/
private SharedTempStore $tempStore;
/**
* Constructs a new CartTokenSubscriber object.
*
* @param \Drupal\commerce_cart\CartSessionInterface $cartSession
* The cart session.
* @param \Drupal\Core\TempStore\SharedTempStoreFactory $temp_store_factory
* The temp store factory.
*/
public function __construct(private CartSessionInterface $cartSession, SharedTempStoreFactory $temp_store_factory) {
$this->tempStore = $temp_store_factory->get('commerce_api_tokens');
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events = [];
// Run before router_listener so we execute before access checks, and before
// dynamic_page_cache so we can populate a session. The ensures proper
// access to CheckoutController.
$events[KernelEvents::REQUEST][] = ['onRequest', 100];
$events[KernelEvents::RESPONSE][] = ['onResponse', -10];
return $events;
}
/**
* Loads the token cart data and resets it to the session.
*
* @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
* The request event, which contains the current request.
*/
public function onRequest(RequestEvent $event) {
$cart_token = $event->getRequest()->query->get(CartTokenSession::QUERY_NAME);
if ($cart_token) {
$token_cart_data = $this->tempStore->get($cart_token);
foreach ([CartSessionInterface::ACTIVE, CartSessionInterface::COMPLETED] as $cart_type) {
if (isset($token_cart_data[$cart_type]) && is_array($token_cart_data[$cart_type])) {
foreach ($token_cart_data[$cart_type] as $token_cart_datum) {
$this->cartSession->addCartId($token_cart_datum, $cart_type);
}
}
}
}
}
/**
* Ensures the Vary header contains the cart token header name.
*
* @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
* The response event.
*/
public function onResponse(ResponseEvent $event) {
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
if ($request->headers->has(CartTokenSession::HEADER_NAME)) {
$response = $event->getResponse();
// The Vary header gets mangled with CORS.
// @see https://www.drupal.org/project/commerce_api/issues/3116590
$vary = array_filter($response->getVary());
$vary[] = CartTokenSession::HEADER_NAME;
$response->setVary(implode(', ', $vary));
}
}
}
