Passed
Push — master ( 823fed...05456b )
by Christian
12:13 queued 10s
created

revokeAllCustomerTokens()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 19
nc 4
nop 2
dl 0
loc 29
rs 9.6333
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 Doctrine\DBAL\Driver\ResultStatement;
7
use Shopware\Core\Framework\Feature;
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
14
class SalesChannelContextPersister
15
{
16
    /**
17
     * @var Connection
18
     */
19
    private $connection;
20
21
    /**
22
     * @var EventDispatcherInterface
23
     */
24
    private $eventDispatcher;
25
26
    public function __construct(Connection $connection, EventDispatcherInterface $eventDispatcher)
27
    {
28
        $this->connection = $connection;
29
        $this->eventDispatcher = $eventDispatcher;
30
    }
31
32
    /*
33
     * @feature-deprecated (flag:FEATURE_NEXT_10058) tag:v6.4.0 - $salesChannelId will be required
34
     */
35
    public function save(string $token, array $parameters, ?string $salesChannelId = null, ?string $customerId = null): void
36
    {
37
        if (Feature::isActive('FEATURE_NEXT_10058')) {
38
            $existing = $this->load($token, $salesChannelId, $customerId);
39
40
            $parameters = array_replace_recursive($existing, $parameters);
41
42
            unset($parameters['token']);
43
44
            $this->connection->executeUpdate(
45
                'REPLACE INTO sales_channel_api_context (`token`, `payload`, `sales_channel_id`, `customer_id`) VALUES (:token, :payload, :salesChannelId, :customerId)',
46
                [
47
                    'token' => $token,
48
                    'payload' => json_encode($parameters),
49
                    'salesChannelId' => $salesChannelId ? Uuid::fromHexToBytes($salesChannelId) : null,
50
                    'customerId' => $customerId ? Uuid::fromHexToBytes($customerId) : null,
51
                ]
52
            );
53
54
            return;
55
        }
56
57
        $existing = $this->load($token);
58
59
        $parameters = array_replace_recursive($existing, $parameters);
60
61
        $this->connection->executeUpdate(
62
            'REPLACE INTO sales_channel_api_context (`token`, `payload`) VALUES (:token, :payload)',
63
            [
64
                'token' => $token,
65
                'payload' => json_encode($parameters),
66
            ]
67
        );
68
    }
69
70
    public function delete(string $token): void
71
    {
72
        $this->connection->executeUpdate(
73
            'DELETE FROM sales_channel_api_context WHERE token = :token',
74
            [
75
                'token' => $token,
76
            ]
77
        );
78
    }
79
80
    public function replace(string $oldToken/*, ?SalesChannelContext $context = null*/): string
81
    {
82
        $newToken = Random::getAlphanumericString(32);
83
84
        $affected = $this->connection->executeUpdate(
85
            'UPDATE `sales_channel_api_context`
86
                   SET `token` = :newToken
87
                   WHERE `token` = :oldToken',
88
            [
89
                'newToken' => $newToken,
90
                'oldToken' => $oldToken,
91
            ]
92
        );
93
94
        if (Feature::isActive('FEATURE_NEXT_10058') && $affected === 0 && func_num_args() === 2) {
95
            /** @var SalesChannelContext $context */
96
            $context = func_get_arg(1);
97
98
            $customer = $context->getCustomer();
99
100
            $this->connection->insert('sales_channel_api_context', [
101
                'token' => $newToken,
102
                'payload' => json_encode([]),
103
                'sales_channel_id' => Uuid::fromHexToBytes($context->getSalesChannel()->getId()),
104
                'customer_id' => $customer ? Uuid::fromHexToBytes($customer->getId()) : null,
105
            ]);
106
        } elseif ($affected === 0) {
107
            $this->connection->insert('sales_channel_api_context', [
108
                'token' => $newToken,
109
                'payload' => json_encode([]),
110
            ]);
111
        }
112
113
        $this->connection->executeUpdate(
114
            'UPDATE `cart`
115
                   SET `token` = :newToken
116
                   WHERE `token` = :oldToken',
117
            [
118
                'newToken' => $newToken,
119
                'oldToken' => $oldToken,
120
            ]
121
        );
122
123
        // @deprecated tag:v6.4.0.0 - $context will be required
124
        if (func_num_args() === 2) {
125
            $context = func_get_arg(1);
126
            $context->assign(['token' => $newToken]);
127
            $this->eventDispatcher->dispatch(new SalesChannelContextTokenChangeEvent($context, $oldToken, $newToken));
128
        }
129
130
        return $newToken;
131
    }
132
133
    /*
134
     * @feature-deprecated (flag:FEATURE_NEXT_10058) tag:v6.4.0 - $salesChannelId will be required
135
    */
136
    public function load(string $token, ?string $salesChannelId = null, ?string $customerId = null): array
137
    {
138
        if (Feature::isActive('FEATURE_NEXT_10058')) {
139
            $qb = $this->connection->createQueryBuilder();
140
141
            $qb->select('*');
142
            $qb->from('sales_channel_api_context');
143
144
            if ($salesChannelId !== null) {
145
                $qb->where('sales_channel_id = :salesChannelId');
146
                $qb->setParameter(':salesChannelId', Uuid::fromHexToBytes($salesChannelId));
147
148
                if ($customerId !== null) {
149
                    $qb->andWhere('token = :token OR customer_id = :customerId');
150
                    $qb->setParameter(':token', $token);
151
                    $qb->setParameter(':customerId', Uuid::fromHexToBytes($customerId));
152
                    $qb->setMaxResults(2);
153
                } else {
154
                    $qb->andWhere('token = :token');
155
                    $qb->setParameter(':token', $token);
156
                    $qb->setMaxResults(1);
157
                }
158
            } else {
159
                $qb->where('token = :token');
160
                $qb->setParameter(':token', $token);
161
                $qb->setMaxResults(1);
162
            }
163
164
            /** @var ResultStatement $statement */
165
            $statement = $qb->execute();
166
167
            if (!$statement instanceof ResultStatement) {
0 ignored issues
show
introduced by
$statement is always a sub-type of Doctrine\DBAL\Driver\ResultStatement.
Loading history...
168
                return [];
169
            }
170
171
            $data = $statement->fetchAll();
172
173
            if (empty($data)) {
174
                return [];
175
            }
176
177
            $customerContext = $salesChannelId && $customerId ? $this->getCustomerContext($data, $salesChannelId, $customerId) : null;
178
179
            $context = $customerContext ?? array_shift($data);
180
181
            $payload = array_filter(json_decode($context['payload'], true));
182
183
            if ($customerId) {
184
                $payload['token'] = $context['token'];
185
            }
186
187
            return $payload;
188
        }
189
190
        $parameter = $this->connection->fetchColumn(
191
            'SELECT `payload` FROM sales_channel_api_context WHERE token = :token',
192
            ['token' => $token]
193
        );
194
195
        if (!$parameter) {
196
            return [];
197
        }
198
199
        return array_filter(json_decode($parameter, true));
200
    }
201
202
    public function revokeAllCustomerTokens(string $customerId, string ...$preserveTokens): void
203
    {
204
        $revokeParams = [
205
            'customerId' => null,
206
            'billingAddressId' => null,
207
            'shippingAddressId' => null,
208
        ];
209
210
        $qb = $this->connection->createQueryBuilder();
211
        $qb
212
            ->update('sales_channel_api_context')
213
            ->set('payload', ':payload')
214
            ->where('JSON_EXTRACT(payload, :customerPath) = :customerId')
215
            ->setParameter(':payload', json_encode($revokeParams))
216
            ->setParameter(':customerPath', '$.customerId')
217
            ->setParameter(':customerId', $customerId);
218
219
        if (Feature::isActive('FEATURE_NEXT_10058')) {
220
            $qb->set('customer_id', 'NULL');
221
        }
222
223
        // keep tokens valid, which are given in $preserveTokens
224
        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...
225
            $qb
226
                ->andWhere($qb->expr()->notIn('token', ':preserveTokens'))
227
                ->setParameter(':preserveTokens', $preserveTokens, Connection::PARAM_STR_ARRAY);
228
        }
229
230
        $qb->execute();
231
    }
232
233
    private function getCustomerContext(array $data, string $salesChannelId, string $customerId): ?array
234
    {
235
        foreach ($data as $row) {
236
            if (!empty($row['customer_id'])
237
                && Uuid::fromBytesToHex($row['sales_channel_id']) === $salesChannelId
238
                && Uuid::fromBytesToHex($row['customer_id']) === $customerId
239
            ) {
240
                return $row;
241
            }
242
        }
243
244
        return null;
245
    }
246
}
247