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

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