vendor/ibexa/cart/src/bundle/EventSubscriber/SecurityLoginSubscriber.php line 69

Open in your IDE?
  1. <?php
  2. /**
  3. * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4. * @license For full copyright and license information view LICENSE file distributed with this source code.
  5. */
  6. declare(strict_types=1);
  7. namespace Ibexa\Bundle\Cart\EventSubscriber;
  8. use DateTimeImmutable;
  9. use Ibexa\Cart\Persistence\Legacy\Cart\Gateway\StorageSchema as CartStorageSchema;
  10. use Ibexa\Cart\Persistence\Legacy\Cart\Handler\HandlerInterface as CartHandler;
  11. use Ibexa\Cart\Persistence\Legacy\CartEntry\Gateway\StorageSchema as CartEntryStorageSchema;
  12. use Ibexa\Cart\Persistence\Legacy\CartEntry\Handler\HandlerInterface as CartEntryHandler;
  13. use Ibexa\Cart\Persistence\Values\Cart;
  14. use Ibexa\Cart\Persistence\Values\CartCreateStruct;
  15. use Ibexa\Cart\Persistence\Values\CartEntry;
  16. use Ibexa\Cart\Persistence\Values\CartEntryCreateStruct;
  17. use Ibexa\Cart\Persistence\Values\CartEntryUpdateStruct;
  18. use Ibexa\Cart\Persistence\Values\CartMetadataUpdateStruct;
  19. use Ibexa\Contracts\Core\Persistence\TransactionHandler;
  20. use Ibexa\Contracts\Core\Repository\Values\User\User as ApiUser;
  21. use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
  22. use Ibexa\Core\Base\Exceptions\BadStateException;
  23. use Ibexa\Core\MVC\Symfony\Security\User;
  24. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  25. use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
  26. use Symfony\Component\Security\Http\SecurityEvents;
  27. use Symfony\Component\Uid\Uuid;
  28. final class SecurityLoginSubscriber implements EventSubscriberInterface
  29. {
  30. private ConfigResolverInterface $configResolver;
  31. private CartHandler $cartSessionHandler;
  32. private CartHandler $cartDatabaseHandler;
  33. private CartEntryHandler $cartEntrySessionHandler;
  34. private CartEntryHandler $cartEntryDatabaseHandler;
  35. private TransactionHandler $transactionHandler;
  36. public function __construct(
  37. ConfigResolverInterface $configResolver,
  38. CartHandler $cartSessionHandler,
  39. CartHandler $cartDatabaseHandler,
  40. CartEntryHandler $cartEntrySessionHandler,
  41. CartEntryHandler $cartEntryDatabaseHandler,
  42. TransactionHandler $transactionHandler
  43. ) {
  44. $this->configResolver = $configResolver;
  45. $this->cartSessionHandler = $cartSessionHandler;
  46. $this->cartDatabaseHandler = $cartDatabaseHandler;
  47. $this->cartEntrySessionHandler = $cartEntrySessionHandler;
  48. $this->cartEntryDatabaseHandler = $cartEntryDatabaseHandler;
  49. $this->transactionHandler = $transactionHandler;
  50. }
  51. public static function getSubscribedEvents(): array
  52. {
  53. return [
  54. SecurityEvents::INTERACTIVE_LOGIN => ['onInteractiveLogin', 100],
  55. ];
  56. }
  57. public function onInteractiveLogin(InteractiveLoginEvent $event): void
  58. {
  59. $token = $event->getAuthenticationToken();
  60. $user = $token->getUser();
  61. if (!$user instanceof User) {
  62. return;
  63. }
  64. $apiUser = $user->getAPIUser();
  65. if ($apiUser->getUserId() === $this->getAnonymousId()) {
  66. throw new BadStateException('$getUserId', 'Logged in as Anonymous');
  67. }
  68. // skip checking the cart session if the session is not running to prevent creating a new session
  69. if (!$event->getRequest()->getSession()->isStarted()) {
  70. return;
  71. }
  72. $sessionCart = $this->cartSessionHandler->findOneBy([]);
  73. $databaseCart = $this->cartDatabaseHandler->findOneBy([
  74. CartStorageSchema::COLUMN_OWNER_ID => $apiUser->getUserId(),
  75. ]);
  76. if (null === $sessionCart) {
  77. // nothing to merge
  78. return;
  79. }
  80. $entries = $this->cartEntrySessionHandler->findBy([
  81. CartEntryStorageSchema::COLUMN_CART_ID => $sessionCart->getId(),
  82. ]);
  83. if (null === $databaseCart) {
  84. $this->handleNewCart($sessionCart, $apiUser, $entries);
  85. } else {
  86. $this->handleExistingCart($databaseCart, $sessionCart, $entries);
  87. }
  88. $this->removeSessionCart($sessionCart, $entries);
  89. }
  90. private function getAnonymousId(): int
  91. {
  92. return (int) $this->configResolver->getParameter('anonymous_user_id');
  93. }
  94. /**
  95. * @param array<\Ibexa\Cart\Persistence\Values\CartEntry> $entries
  96. */
  97. private function getEntryUsingProductCode(array $entries, string $productCode): ?CartEntry
  98. {
  99. foreach ($entries as $entry) {
  100. if ($entry->getProductCode() === $productCode) {
  101. return $entry;
  102. }
  103. }
  104. return null;
  105. }
  106. /**
  107. * @param array<\Ibexa\Cart\Persistence\Values\CartEntry> $entries
  108. */
  109. private function removeSessionCart(Cart $sessionCart, array $entries): void
  110. {
  111. foreach ($entries as $entry) {
  112. $this->cartEntrySessionHandler->delete($entry->getId());
  113. }
  114. $this->cartSessionHandler->delete($sessionCart->getId());
  115. }
  116. private function updateEntry(CartEntry $existingDatabaseEntry, CartEntry $entry): void
  117. {
  118. $cartEntryUpdateStruct = new CartEntryUpdateStruct(
  119. $existingDatabaseEntry->getId(),
  120. $existingDatabaseEntry->getIdentifier(),
  121. $existingDatabaseEntry->getCartId(),
  122. $existingDatabaseEntry->getNames(),
  123. $existingDatabaseEntry->getQuantity() + $entry->getQuantity(),
  124. $existingDatabaseEntry->getPrice(),
  125. $existingDatabaseEntry->getCurrencyId(),
  126. $existingDatabaseEntry->getProductCode(),
  127. $existingDatabaseEntry->getContext()
  128. );
  129. $this->cartEntryDatabaseHandler->update($cartEntryUpdateStruct);
  130. }
  131. private function createEntry(Cart $databaseCart, CartEntry $entry): void
  132. {
  133. $cartEntryCreateStruct = new CartEntryCreateStruct(
  134. Uuid::v4()->toRfc4122(),
  135. $databaseCart->getId(),
  136. $entry->getNames(),
  137. $entry->getQuantity(),
  138. $entry->getPrice(),
  139. $entry->getCurrencyId(),
  140. $entry->getProductCode(),
  141. new DateTimeImmutable(),
  142. $entry->getContext()
  143. );
  144. $this->cartEntryDatabaseHandler->create($cartEntryCreateStruct);
  145. }
  146. private function createCart(Cart $sessionCart, ApiUser $user): Cart
  147. {
  148. $cartCreateStruct = new CartCreateStruct(
  149. $sessionCart->getIdentifier(),
  150. $sessionCart->getName(),
  151. $user->getUserId(),
  152. $sessionCart->getCurrencyId(),
  153. new DateTimeImmutable(),
  154. new DateTimeImmutable(),
  155. $sessionCart->getContext(),
  156. );
  157. return $this->cartDatabaseHandler->create($cartCreateStruct);
  158. }
  159. /**
  160. * @param array<\Ibexa\Cart\Persistence\Values\CartEntry> $entries
  161. */
  162. private function handleNewCart(Cart $sessionCart, ApiUser $user, array $entries): void
  163. {
  164. $this->transactionHandler->beginTransaction();
  165. try {
  166. $databaseCart = $this->createCart($sessionCart, $user);
  167. foreach ($entries as $entry) {
  168. $this->createEntry($databaseCart, $entry);
  169. }
  170. $this->transactionHandler->commit();
  171. } catch (\Exception $e) {
  172. $this->transactionHandler->rollback();
  173. throw $e;
  174. }
  175. }
  176. /**
  177. * @param array<\Ibexa\Cart\Persistence\Values\CartEntry> $entries
  178. */
  179. private function handleExistingCart(
  180. Cart $databaseCart,
  181. Cart $sessionCart,
  182. array $entries
  183. ): void {
  184. $databaseCartEntries = $this->cartEntryDatabaseHandler->findBy([
  185. CartEntryStorageSchema::COLUMN_CART_ID => $databaseCart->getId(),
  186. ]);
  187. $this->transactionHandler->beginTransaction();
  188. try {
  189. $this->updateCart($databaseCart, $sessionCart);
  190. foreach ($entries as $entry) {
  191. $existingDatabaseEntry = $this->getEntryUsingProductCode(
  192. $databaseCartEntries,
  193. $entry->getProductCode()
  194. );
  195. if (null !== $existingDatabaseEntry) {
  196. $this->updateEntry($existingDatabaseEntry, $entry);
  197. } else {
  198. $this->createEntry($databaseCart, $entry);
  199. }
  200. }
  201. $this->transactionHandler->commit();
  202. } catch (\Exception $e) {
  203. $this->transactionHandler->rollback();
  204. throw $e;
  205. }
  206. }
  207. private function updateCart(
  208. Cart $databaseCart,
  209. Cart $sessionCart
  210. ): void {
  211. $existingContext = $databaseCart->getContext();
  212. $sessionContext = $sessionCart->getContext();
  213. $context = $sessionContext;
  214. if ($existingContext !== null) {
  215. $context = array_merge($sessionContext ?? [], $existingContext);
  216. }
  217. $this->cartDatabaseHandler->update(
  218. new CartMetadataUpdateStruct(
  219. $databaseCart->getId(),
  220. $sessionCart->getIdentifier(),
  221. $sessionCart->getName(),
  222. $sessionCart->getCurrencyId(),
  223. $databaseCart->getOwnerId(),
  224. $databaseCart->getCreated(),
  225. new DateTimeImmutable(),
  226. $context
  227. )
  228. );
  229. }
  230. }