Passed
Push — master ( 7a2782...032b3c )
by Christian
11:03
created

SalesChannelContextPersister::save()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 16
nc 2
nop 3
dl 0
loc 30
rs 9.7333
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\System\SalesChannel\Context;
4
5
use Doctrine\DBAL\Connection;
6
use Shopware\Core\Framework\Feature;
7
use Shopware\Core\Framework\Util\Random;
8
use Shopware\Core\Framework\Uuid\Uuid;
9
use Shopware\Core\System\SalesChannel\Event\SalesChannelContextTokenChangeEvent;
10
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
11
12
class SalesChannelContextPersister
13
{
14
    /**
15
     * @var Connection
16
     */
17
    private $connection;
18
19
    /**
20
     * @var EventDispatcherInterface
21
     */
22
    private $eventDispatcher;
23
24
    public function __construct(Connection $connection, EventDispatcherInterface $eventDispatcher)
25
    {
26
        $this->connection = $connection;
27
        $this->eventDispatcher = $eventDispatcher;
28
    }
29
30
    public function save(string $token, array $parameters, ?string $customerId = null): void
31
    {
32
        if (Feature::isActive('FEATURE_NEXT_10058')) {
33
            $existing = $this->load($token, $customerId);
34
35
            $parameters = array_replace_recursive($existing, $parameters);
36
37
            unset($parameters['token']);
38
39
            $this->connection->executeUpdate(
40
                'REPLACE INTO sales_channel_api_context (`token`, `payload`, `customer_id`) VALUES (:token, :payload, :customerId)',
41
                [
42
                    'token' => $token,
43
                    'payload' => json_encode($parameters),
44
                    'customerId' => $customerId ? Uuid::fromHexToBytes($customerId) : null,
45
                ]
46
            );
47
48
            return;
49
        }
50
51
        $existing = $this->load($token);
52
53
        $parameters = array_replace_recursive($existing, $parameters);
54
55
        $this->connection->executeUpdate(
56
            'REPLACE INTO sales_channel_api_context (`token`, `payload`) VALUES (:token, :payload)',
57
            [
58
                'token' => $token,
59
                'payload' => json_encode($parameters),
60
            ]
61
        );
62
    }
63
64
    public function delete(string $token): void
65
    {
66
        $this->connection->executeUpdate(
67
            'DELETE FROM sales_channel_api_context WHERE token = :token',
68
            [
69
                'token' => $token,
70
            ]
71
        );
72
    }
73
74
    public function replace(string $oldToken/*, ?SalesChannelContext $context = null*/): string
75
    {
76
        $newToken = Random::getAlphanumericString(32);
77
78
        $affected = $this->connection->executeUpdate(
79
            'UPDATE `sales_channel_api_context`
80
                   SET `token` = :newToken
81
                   WHERE `token` = :oldToken',
82
            [
83
                'newToken' => $newToken,
84
                'oldToken' => $oldToken,
85
            ]
86
        );
87
88
        if ($affected === 0) {
89
            $this->connection->insert('sales_channel_api_context', [
90
                'token' => $newToken,
91
                'payload' => json_encode([]),
92
            ]);
93
        }
94
95
        $this->connection->executeUpdate(
96
            'UPDATE `cart`
97
                   SET `token` = :newToken
98
                   WHERE `token` = :oldToken',
99
            [
100
                'newToken' => $newToken,
101
                'oldToken' => $oldToken,
102
            ]
103
        );
104
105
        // @deprecated tag:v6.4.0.0 - $context will be required
106
        if (func_num_args() === 2) {
107
            $context = func_get_arg(1);
108
            $context->assign(['token' => $newToken]);
109
            $this->eventDispatcher->dispatch(new SalesChannelContextTokenChangeEvent($context, $oldToken, $newToken));
110
        }
111
112
        return $newToken;
113
    }
114
115
    public function load(string $token/*, ?string $customerId* = null*/): array
116
    {
117
        if (Feature::isActive('FEATURE_NEXT_10058')) {
118
            if (func_num_args() === 2) {
119
                $customerId = func_get_arg(1);
120
121
                $data = $this->connection->fetchAll('SELECT * FROM sales_channel_api_context WHERE customer_id = :customerId OR token = :token LIMIT 2', [
122
                    'customerId' => $customerId ? Uuid::fromHexToBytes($customerId) : null,
123
                    'token' => $token,
124
                ]);
125
126
                if (empty($data)) {
127
                    return [];
128
                }
129
130
                $customerContext = $customerId ? $this->getCustomerContext($data, $customerId) : null;
131
132
                $context = $customerContext ?? array_shift($data);
133
134
                $payload = array_filter(json_decode($context['payload'], true));
135
                $payload['token'] = $context['token'];
136
137
                return $payload;
138
            }
139
        }
140
141
        $parameter = $this->connection->fetchColumn(
142
            'SELECT `payload` FROM sales_channel_api_context WHERE token = :token',
143
            ['token' => $token]
144
        );
145
146
        if (!$parameter) {
147
            return [];
148
        }
149
150
        return array_filter(json_decode($parameter, true));
151
    }
152
153
    public function revokeAllCustomerTokens(string $customerId, string ...$preserveTokens): void
154
    {
155
        $revokeParams = [
156
            'customerId' => null,
157
            'billingAddressId' => null,
158
            'shippingAddressId' => null,
159
        ];
160
161
        $qb = $this->connection->createQueryBuilder();
162
        $qb
163
            ->update('sales_channel_api_context')
164
            ->set('payload', ':payload')
165
            ->where('JSON_EXTRACT(payload, :customerPath) = :customerId')
166
            ->setParameter(':payload', json_encode($revokeParams))
167
            ->setParameter(':customerPath', '$.customerId')
168
            ->setParameter(':customerId', $customerId);
169
170
        if (Feature::isActive('FEATURE_NEXT_10058')) {
171
            $qb->set('customer_id', 'NULL');
172
        }
173
174
        // keep tokens valid, which are given in $preserveTokens
175
        if ($preserveTokens) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $preserveTokens of type array<integer,string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
176
            $qb
177
                ->andWhere($qb->expr()->notIn('token', ':preserveTokens'))
178
                ->setParameter(':preserveTokens', $preserveTokens, Connection::PARAM_STR_ARRAY);
179
        }
180
181
        $qb->execute();
182
    }
183
184
    private function getCustomerContext(array $data, string $customerId): ?array
185
    {
186
        foreach ($data as $row) {
187
            if (!empty($row['customer_id']) && Uuid::fromBytesToHex($row['customer_id']) === $customerId) {
188
                return $row;
189
            }
190
        }
191
192
        return null;
193
    }
194
}
195