src/Core/System/SalesChannel/Context/SalesChannelContextPersister.php line 129

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\System\SalesChannel\Context;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\Checkout\Cart\AbstractCartPersister;
  5. use Shopware\Core\Defaults;
  6. use Shopware\Core\Framework\Log\Package;
  7. use Shopware\Core\Framework\Util\Random;
  8. use Shopware\Core\Framework\Uuid\Uuid;
  9. use Shopware\Core\System\SalesChannel\Event\SalesChannelContextTokenChangeEvent;
  10. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  11. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  12. #[Package('core')]
  13. class SalesChannelContextPersister
  14. {
  15.     private readonly string $lifetimeInterval;
  16.     /**
  17.      * @internal
  18.      */
  19.     public function __construct(
  20.         private readonly Connection $connection,
  21.         private readonly EventDispatcherInterface $eventDispatcher,
  22.         private readonly AbstractCartPersister $cartPersister,
  23.         ?string $lifetimeInterval 'P1D'
  24.     ) {
  25.         $this->lifetimeInterval $lifetimeInterval ?? 'P1D';
  26.     }
  27.     /**
  28.      * @param array<string, mixed> $newParameters
  29.      */
  30.     public function save(string $token, array $newParametersstring $salesChannelId, ?string $customerId null): void
  31.     {
  32.         $existing $this->load($token$salesChannelId$customerId);
  33.         $parameters array_replace_recursive($existing$newParameters);
  34.         if (isset($newParameters['permissions']) && $newParameters['permissions'] === []) {
  35.             $parameters['permissions'] = [];
  36.         }
  37.         unset($parameters['token']);
  38.         $this->connection->executeStatement(
  39.             'REPLACE INTO sales_channel_api_context (`token`, `payload`, `sales_channel_id`, `customer_id`, `updated_at`)
  40.                 VALUES (:token, :payload, :salesChannelId, :customerId, :updatedAt)',
  41.             [
  42.                 'token' => $token,
  43.                 'payload' => json_encode($parameters\JSON_THROW_ON_ERROR),
  44.                 'salesChannelId' => $salesChannelId Uuid::fromHexToBytes($salesChannelId) : null,
  45.                 'customerId' => $customerId Uuid::fromHexToBytes($customerId) : null,
  46.                 'updatedAt' => (new \DateTimeImmutable())->format(Defaults::STORAGE_DATE_TIME_FORMAT),
  47.             ]
  48.         );
  49.     }
  50.     public function delete(string $tokenstring $salesChannelId, ?string $customerId null): void
  51.     {
  52.         $this->connection->executeStatement(
  53.             'DELETE FROM sales_channel_api_context WHERE token = :token',
  54.             [
  55.                 'token' => $token,
  56.             ]
  57.         );
  58.     }
  59.     public function replace(string $oldTokenSalesChannelContext $context): string
  60.     {
  61.         $newToken Random::getAlphanumericString(32);
  62.         $affected $this->connection->executeStatement(
  63.             'UPDATE `sales_channel_api_context`
  64.                    SET `token` = :newToken,
  65.                        `updated_at` = :updatedAt
  66.                    WHERE `token` = :oldToken',
  67.             [
  68.                 'newToken' => $newToken,
  69.                 'oldToken' => $oldToken,
  70.                 'updatedAt' => (new \DateTimeImmutable())->format(Defaults::STORAGE_DATE_TIME_FORMAT),
  71.             ]
  72.         );
  73.         if ($affected === 0) {
  74.             $customer $context->getCustomer();
  75.             $this->connection->insert('sales_channel_api_context', [
  76.                 'token' => $newToken,
  77.                 'payload' => json_encode([]),
  78.                 'sales_channel_id' => Uuid::fromHexToBytes($context->getSalesChannel()->getId()),
  79.                 'customer_id' => $customer Uuid::fromHexToBytes($customer->getId()) : null,
  80.                 'updated_at' => (new \DateTimeImmutable())->format(Defaults::STORAGE_DATE_TIME_FORMAT),
  81.             ]);
  82.         }
  83.         $this->cartPersister->replace($oldToken$newToken$context);
  84.         $context->assign(['token' => $newToken]);
  85.         $this->eventDispatcher->dispatch(new SalesChannelContextTokenChangeEvent($context$oldToken$newToken));
  86.         return $newToken;
  87.     }
  88.     /**
  89.      * @return array<string, mixed>
  90.      */
  91.     public function load(string $tokenstring $salesChannelId, ?string $customerId null): array
  92.     {
  93.         $qb $this->connection->createQueryBuilder();
  94.         $qb->select('*');
  95.         $qb->from('sales_channel_api_context');
  96.         $qb->where('sales_channel_id = :salesChannelId');
  97.         $qb->setParameter('salesChannelId'Uuid::fromHexToBytes($salesChannelId));
  98.         if ($customerId !== null) {
  99.             $qb->andWhere('(token = :token OR customer_id = :customerId)');
  100.             $qb->setParameter('token'$token);
  101.             $qb->setParameter('customerId'Uuid::fromHexToBytes($customerId));
  102.             $qb->setMaxResults(2);
  103.         } else {
  104.             $qb->andWhere('token = :token');
  105.             $qb->setParameter('token'$token);
  106.             $qb->setMaxResults(1);
  107.         }
  108.         $data $qb->executeQuery()->fetchAllAssociative();
  109.         if (empty($data)) {
  110.             return [];
  111.         }
  112.         $customerContext $salesChannelId && $customerId $this->getCustomerContext($data$salesChannelId$customerId) : null;
  113.         $context $customerContext ?? array_shift($data);
  114.         $updatedAt = new \DateTimeImmutable($context['updated_at']);
  115.         $expiredTime $updatedAt->add(new \DateInterval($this->lifetimeInterval));
  116.         $payload array_filter(json_decode((string) $context['payload'], true512\JSON_THROW_ON_ERROR));
  117.         $now = new \DateTimeImmutable();
  118.         if ($expiredTime $now) {
  119.             // context is expired
  120.             $payload = ['expired' => true];
  121.         } else {
  122.             $payload['expired'] = false;
  123.         }
  124.         if ($customerId) {
  125.             $payload['token'] = $context['token'];
  126.         }
  127.         return $payload;
  128.     }
  129.     public function revokeAllCustomerTokens(string $customerIdstring ...$preserveTokens): void
  130.     {
  131.         $revokeParams = [
  132.             'customerId' => null,
  133.             'billingAddressId' => null,
  134.             'shippingAddressId' => null,
  135.         ];
  136.         $qb $this->connection->createQueryBuilder();
  137.         $qb
  138.             ->update('sales_channel_api_context')
  139.             ->set('payload'':payload')
  140.             ->set('customer_id''NULL')
  141.             ->set('updated_at'':updatedAt')
  142.             ->where('customer_id = :customerId')
  143.             ->setParameter('updatedAt', (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT))
  144.             ->setParameter('payload'json_encode($revokeParams))
  145.             ->setParameter('customerId'Uuid::fromHexToBytes($customerId));
  146.         // keep tokens valid, which are given in $preserveTokens
  147.         if ($preserveTokens) {
  148.             $qb
  149.                 ->andWhere($qb->expr()->notIn('token'':preserveTokens'))
  150.                 ->setParameter('preserveTokens'$preserveTokensConnection::PARAM_STR_ARRAY);
  151.         }
  152.         $qb->executeStatement();
  153.     }
  154.     /**
  155.      * @param list<array<string, mixed>> $data
  156.      *
  157.      * @return array<string, mixed>|null
  158.      */
  159.     private function getCustomerContext(array $datastring $salesChannelIdstring $customerId): ?array
  160.     {
  161.         foreach ($data as $row) {
  162.             if (!empty($row['customer_id'])
  163.                 && Uuid::fromBytesToHex($row['sales_channel_id']) === $salesChannelId
  164.                 && Uuid::fromBytesToHex($row['customer_id']) === $customerId
  165.             ) {
  166.                 return $row;
  167.             }
  168.         }
  169.         return null;
  170.     }
  171. }