hn-8.x-1.x-dev/modules/hn_cache_session/src/EventSubscriber/EventSubscriber.php
modules/hn_cache_session/src/EventSubscriber/EventSubscriber.php
<?php namespace Drupal\hn_cache_session\EventSubscriber; use Drupal\Component\Uuid\UuidInterface; use Drupal\hn\Event\HnResponseEvent; use Drupal\user\SharedTempStoreFactory; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Class DefaultSubscriber. */ class EventSubscriber implements EventSubscriberInterface { /** * {@inheritdoc} */ public static function getSubscribedEvents() { return [ HnResponseEvent::PRE_SEND => 'alterResponseData', ]; } const DONT_CACHE_KEYS = ['__hn']; /** * The storage that will be used to get and set data to. * * @var \Drupal\user\SharedTempStore */ private $session; /** * The session factory that is used to get and set previous requests. * * @var \Drupal\user\SharedTempStoreFactory */ private $sessionFactory; /** * The UUID Generator. * * @var \Drupal\Component\Uuid\UuidInterface */ private $uuid; /** * Creates a new EventSubscriber. */ public function __construct(SharedTempStoreFactory $sharedTempStoreFactory, UuidInterface $uuid) { $this->uuid = $uuid; $this->sessionFactory = $sharedTempStoreFactory; } /** * Alters the response data. * * @param \Drupal\hn\Event\HnResponseEvent $event * The event that was dispatched. */ public function alterResponseData(HnResponseEvent $event) { // Get or generate a user ID, where all requests are stored. // We don't use the PHP session because of issue #2743931 and so it is // easier to pass the user id when pre-rendering on the server. $user = \Drupal::request()->query->get('_hn_user') ?: $this->uuid->generate(); $this->session = $this->sessionFactory->get('hn_cache_session.' . $user, $user); $responseData = $event->getResponseData(); // First, parse the ?_hn_verify and transfer requests from unverified to // verified. $this->verifyRequests(); // Remove all fields from requests that are already verified. $verifiedRequests = $this->getVerifiedRequests(); foreach ($verifiedRequests as $data_key => $data_fields) { if (!empty($responseData['data'][$data_key])) { foreach ($data_fields as $field) { unset($responseData['data'][$data_key][$field]); } // Remove the data key if all properties are removed. // See issue #2918729. if (empty($responseData['data'][$data_key])) { unset($responseData['data'][$data_key]); } } } // Save the current request to the session for all next requests. $token = $this->saveCurrentRequest($responseData); // Add the user and token to the response so the client can send them with // their next request. $responseData['__hn']['request']['user'] = $user; $responseData['__hn']['request']['token'] = $token; $event->setResponseData($responseData); } /** * Verify all requests that are passed by the user. * * All requests are saved. To make sure the user has actually received the * request, it must sent all non-verified request tokens with the next * request. This way the tokens will be verified, and the entities that * were in that request aren't sent again. */ private function verifyRequests() { // Get all the verifies provided by the user. The keys are the tokens. $verify = \Drupal::request()->query->get('_hn_verify'); // Verify should be an array or string. If it isn't, don't verify anything. if (empty($verify) || !is_array($verify)) { if (is_string($verify)) { $verify = [$verify]; } else { return; } } $verify = array_flip($verify); // Get all stored requests that aren't verified. $unverifiedRequests = $this->getUnverifiedRequests(); // Intersect both arrays to get all requests that can be verified. $requestsToVerify = array_intersect_key($unverifiedRequests, $verify); // Only continue if there are requests to verify. if (empty($requestsToVerify)) { return; } $this->setUnverifiedRequests(array_diff_key($unverifiedRequests, $requestsToVerify)); $verifiedRequests = $this->getVerifiedRequests(); foreach ($requestsToVerify as $request) { foreach ($request as $data_key => $data) { if (!isset($verifiedRequests[$data_key])) { $verifiedRequests[$data_key] = []; } $verifiedRequests[$data_key] = array_merge($data, $verifiedRequests[$data_key] ?: []); } } $this->setVerifiedRequests($verifiedRequests); } /** * Saves the current request to the session, and returns the token. * * @param $responseData * * @return mixed */ private function saveCurrentRequest($responseData) { /** @var \Drupal\Component\Uuid\UuidInterface $uuidService */ $uuidService = \Drupal::service('uuid'); $uuid = $uuidService->generate(); $unverifiedRequest = []; foreach ($responseData['data'] as $key => $data) { $unverifiedRequest[$key] = array_values(array_diff(array_keys($data), self::DONT_CACHE_KEYS)); } $this->setUnverifiedRequests($this->getUnverifiedRequests() + [$uuid => $unverifiedRequest]); return $uuid; } /** * */ private function getUnverifiedRequests() { return $this->session->get('requests.unverified') ?: []; } /** * */ private function setUnverifiedRequests(array $requests) { $this->session->set('requests.unverified', $requests); } /** * */ private function getVerifiedRequests() { return $this->session->get('requests.verified') ?: []; } /** * */ private function setVerifiedRequests(array $requests) { $this->session->set('requests.verified', $requests); } }