1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace Shopware\Core\Checkout\Cart; |
4
|
|
|
|
5
|
|
|
use Psr\Log\LoggerInterface; |
6
|
|
|
use Shopware\Core\Checkout\Cart\Exception\CartTokenNotFoundException; |
7
|
|
|
use Shopware\Core\Content\Rule\RuleCollection; |
8
|
|
|
use Shopware\Core\Framework\Context; |
9
|
|
|
use Shopware\Core\System\SalesChannel\SalesChannelContext; |
10
|
|
|
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; |
11
|
|
|
|
12
|
|
|
class CartRuleLoader |
13
|
|
|
{ |
14
|
|
|
public const CHECKOUT_RULE_LOADER_CACHE_KEY = 'all-rules'; |
15
|
|
|
private const MAX_ITERATION = 7; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* @var CartPersisterInterface |
19
|
|
|
*/ |
20
|
|
|
private $cartPersister; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* @var RuleCollection|null |
24
|
|
|
*/ |
25
|
|
|
private $rules; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var Processor |
29
|
|
|
*/ |
30
|
|
|
private $processor; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var LoggerInterface |
34
|
|
|
*/ |
35
|
|
|
private $logger; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var TagAwareAdapterInterface |
39
|
|
|
*/ |
40
|
|
|
private $cache; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var RuleLoader |
44
|
|
|
*/ |
45
|
|
|
private $ruleLoader; |
46
|
|
|
|
47
|
|
|
public function __construct( |
48
|
|
|
CartPersisterInterface $cartPersister, |
49
|
|
|
Processor $processor, |
50
|
|
|
LoggerInterface $logger, |
51
|
|
|
TagAwareAdapterInterface $cache, |
52
|
|
|
RuleLoader $loader |
53
|
|
|
) { |
54
|
|
|
$this->cartPersister = $cartPersister; |
55
|
|
|
$this->processor = $processor; |
56
|
|
|
$this->logger = $logger; |
57
|
|
|
$this->cache = $cache; |
58
|
|
|
$this->ruleLoader = $loader; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
public function loadByToken(SalesChannelContext $context, string $cartToken): RuleLoaderResult |
62
|
|
|
{ |
63
|
|
|
try { |
64
|
|
|
$cart = $this->cartPersister->load($cartToken, $context); |
65
|
|
|
} catch (CartTokenNotFoundException $e) { |
66
|
|
|
$cart = new Cart($context->getSalesChannel()->getTypeId(), $cartToken); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
return $this->loadByCart($context, $cart, new CartBehavior($context->getPermissions())); |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
public function loadByCart(SalesChannelContext $context, Cart $cart, CartBehavior $behaviorContext): RuleLoaderResult |
73
|
|
|
{ |
74
|
|
|
return $this->load($context, $cart, $behaviorContext); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
public function reset(): void |
78
|
|
|
{ |
79
|
|
|
$this->rules = null; |
80
|
|
|
$this->cache->deleteItem(self::CHECKOUT_RULE_LOADER_CACHE_KEY); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
private function load(SalesChannelContext $context, Cart $cart, CartBehavior $behaviorContext): RuleLoaderResult |
84
|
|
|
{ |
85
|
|
|
$rules = $this->loadRules($context->getContext()); |
86
|
|
|
|
87
|
|
|
// save all rules for later usage |
88
|
|
|
$all = $rules; |
89
|
|
|
|
90
|
|
|
// update rules in current context |
91
|
|
|
$context->setRuleIds($rules->getIds()); |
92
|
|
|
|
93
|
|
|
$iteration = 1; |
94
|
|
|
|
95
|
|
|
// start first cart calculation to have all objects enriched |
96
|
|
|
$cart = $this->processor->process($cart, $context, $behaviorContext); |
97
|
|
|
|
98
|
|
|
do { |
99
|
|
|
$compare = $cart; |
100
|
|
|
|
101
|
|
|
if ($iteration > self::MAX_ITERATION) { |
102
|
|
|
break; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
// filter rules which matches to current scope |
106
|
|
|
$rules = $rules->filterMatchingRules($cart, $context); |
107
|
|
|
|
108
|
|
|
// update matching rules in context |
109
|
|
|
$context->setRuleIds($rules->getIds()); |
110
|
|
|
|
111
|
|
|
// calculate cart again |
112
|
|
|
$cart = $this->processor->process($cart, $context, $behaviorContext); |
113
|
|
|
|
114
|
|
|
// check if the cart changed, in this case we have to recalculate the cart again |
115
|
|
|
$recalculate = $this->cartChanged($cart, $compare); |
116
|
|
|
|
117
|
|
|
// check if rules changed for the last calculated cart, in this case we have to recalculate |
118
|
|
|
$ruleCompare = $all->filterMatchingRules($cart, $context); |
119
|
|
|
|
120
|
|
|
if (!$rules->equals($ruleCompare)) { |
121
|
|
|
$recalculate = true; |
122
|
|
|
$rules = $ruleCompare; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
++$iteration; |
126
|
|
|
} while ($recalculate); |
127
|
|
|
|
128
|
|
|
$index = 0; |
129
|
|
|
foreach ($rules as $rule) { |
130
|
|
|
++$index; |
131
|
|
|
$this->logger->info( |
132
|
|
|
sprintf('#%s Rule detection: %s with priority %s (id: %s)', $index, $rule->getName(), $rule->getPriority(), $rule->getId()) |
133
|
|
|
); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
$context->setRuleIds($rules->getIds()); |
137
|
|
|
|
138
|
|
|
// save the cart if errors exist, so the errors get persisted |
139
|
|
|
if ($cart->getErrors()->count() > 0) { |
140
|
|
|
$this->cartPersister->save($cart, $context); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
return new RuleLoaderResult($cart, $rules); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
private function loadRules(Context $context): RuleCollection |
147
|
|
|
{ |
148
|
|
|
if ($this->rules !== null) { |
149
|
|
|
return $this->rules; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
$item = $this->cache->getItem(self::CHECKOUT_RULE_LOADER_CACHE_KEY); |
153
|
|
|
|
154
|
|
|
$rules = $item->get(); |
155
|
|
|
if ($item->isHit() && $rules) { |
156
|
|
|
return $this->rules = $rules; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
$rules = $this->ruleLoader->load($context); |
160
|
|
|
|
161
|
|
|
$item->set($rules); |
162
|
|
|
|
163
|
|
|
$this->cache->save($item); |
164
|
|
|
|
165
|
|
|
return $this->rules = $rules; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
private function cartChanged(Cart $previous, Cart $current): bool |
169
|
|
|
{ |
170
|
|
|
$previousLineItems = $previous->getLineItems(); |
171
|
|
|
$currentLineItems = $current->getLineItems(); |
172
|
|
|
|
173
|
|
|
return $previousLineItems->count() !== $currentLineItems->count() |
174
|
|
|
|| $previous->getPrice()->getTotalPrice() !== $current->getPrice()->getTotalPrice() |
175
|
|
|
|| $previousLineItems->getKeys() !== $currentLineItems->getKeys() |
176
|
|
|
|| $previousLineItems->getTypes() !== $currentLineItems->getTypes() |
177
|
|
|
; |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
|