vendor/shopware/core/System/SalesChannel/Context/SalesChannelContextPersister.php line 55

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