1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace Shopware\Core\Checkout\Cart\LineItem\Group; |
4
|
|
|
|
5
|
|
|
use Shopware\Core\Checkout\Cart\Cart; |
6
|
|
|
use Shopware\Core\Checkout\Cart\Exception\InvalidQuantityException; |
7
|
|
|
use Shopware\Core\Checkout\Cart\Exception\LineItemNotStackableException; |
8
|
|
|
use Shopware\Core\Checkout\Cart\LineItem\LineItem; |
9
|
|
|
use Shopware\Core\Checkout\Cart\LineItem\LineItemCollection; |
10
|
|
|
use Shopware\Core\Checkout\Cart\LineItem\LineItemFlatCollection; |
11
|
|
|
use Shopware\Core\Checkout\Cart\LineItem\LineItemQuantitySplitter; |
12
|
|
|
use Shopware\Core\System\SalesChannel\SalesChannelContext; |
13
|
|
|
|
14
|
|
|
class LineItemGroupBuilder |
15
|
|
|
{ |
16
|
|
|
/** |
17
|
|
|
* @var LineItemGroupServiceRegistry |
18
|
|
|
*/ |
19
|
|
|
private $registry; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @var LineItemGroupRuleMatcherInterface |
23
|
|
|
*/ |
24
|
|
|
private $ruleMatcher; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @var LineItemQuantitySplitter |
28
|
|
|
*/ |
29
|
|
|
private $quantitySplitter; |
30
|
|
|
|
31
|
|
|
public function __construct(LineItemGroupServiceRegistry $registry, LineItemGroupRuleMatcherInterface $ruleMatcher, LineItemQuantitySplitter $lineItemQuantitySplitter) |
32
|
|
|
{ |
33
|
|
|
$this->registry = $registry; |
34
|
|
|
$this->ruleMatcher = $ruleMatcher; |
35
|
|
|
$this->quantitySplitter = $lineItemQuantitySplitter; |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Searches for all packages that can be built from the provided list of groups. |
40
|
|
|
* Every line item will be taken from the cart and only the ones that are left will |
41
|
|
|
* be checked for upcoming groups. |
42
|
|
|
* |
43
|
|
|
* @param LineItemGroupDefinition[] $groupDefinitions |
44
|
|
|
*/ |
45
|
|
|
public function findGroupPackages(array $groupDefinitions, Cart $cart, SalesChannelContext $context): LineItemGroupBuilderResult |
46
|
|
|
{ |
47
|
|
|
$result = new LineItemGroupBuilderResult(); |
48
|
|
|
|
49
|
|
|
// filter out all promotion items |
50
|
|
|
$cartProducts = $this->getCartProducts($cart); |
51
|
|
|
|
52
|
|
|
// split quantities into separate line items |
53
|
|
|
// so we have a real list of products like we would have |
54
|
|
|
// them when holding it in our actual hands. |
55
|
|
|
$restOfCart = $this->splitQuantities($cartProducts, $context); |
56
|
|
|
|
57
|
|
|
foreach ($groupDefinitions as $groupDefinition) { |
58
|
|
|
$sorter = $this->registry->getSorter($groupDefinition->getSorterKey()); |
59
|
|
|
$packager = $this->registry->getPackager($groupDefinition->getPackagerKey()); |
60
|
|
|
|
61
|
|
|
// we have to sort our items first |
62
|
|
|
// otherwise it would be a "random" order when |
63
|
|
|
// adjusting the rest of our cart... |
64
|
|
|
$restOfCart = $sorter->sort($restOfCart); |
65
|
|
|
|
66
|
|
|
// try as long as groups can be |
67
|
|
|
// found for the current definition |
68
|
|
|
while (true) { |
69
|
|
|
$itemsToConsider = $this->ruleMatcher->getMatchingItems($groupDefinition, $restOfCart, $context); |
70
|
|
|
|
71
|
|
|
// now build a package with our packager |
72
|
|
|
$group = $packager->buildGroupPackage($groupDefinition->getValue(), $itemsToConsider, $context); |
73
|
|
|
|
74
|
|
|
// if we have no found items in our group, quit |
75
|
|
|
if (!$group->hasItems()) { |
76
|
|
|
break; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
// append the currently found group of items |
80
|
|
|
// to our group definition inside our result object |
81
|
|
|
$result->addGroup($groupDefinition, $group); |
82
|
|
|
|
83
|
|
|
// decrease rest of cart items for next search |
84
|
|
|
$restOfCart = $this->adjustRestOfCart($group->getItems(), $restOfCart); |
85
|
|
|
} |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
return $result; |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* This is a very important function. |
93
|
|
|
* It removes our line items that are found in the group and returns the rest of the cart items. |
94
|
|
|
* So if we have 4 line items of 2 products with each quantity 1, and want to remove a product with qt 2, |
95
|
|
|
* then 2 line items will be removed and the new rest of the cart is being returned. |
96
|
|
|
* |
97
|
|
|
* @param LineItemQuantity[] $foundItems |
98
|
|
|
*/ |
99
|
|
|
private function adjustRestOfCart(array $foundItems, LineItemFlatCollection $restOfCart): LineItemFlatCollection |
100
|
|
|
{ |
101
|
|
|
// a holder for all foundItems indexed by lineItemId |
102
|
|
|
/** @var LineItemQuantity[] $lineItemsToRemove */ |
103
|
|
|
$lineItemsToRemove = []; |
104
|
|
|
|
105
|
|
|
// we prepare the removeLineItemIds array with all LineItemQuantity objects indexed by lineItemId |
106
|
|
|
foreach ($foundItems as $itemToRemove) { |
107
|
|
|
if (isset($lineItemsToRemove[$itemToRemove->getLineItemId()])) { |
108
|
|
|
$quantity = $lineItemsToRemove[$itemToRemove->getLineItemId()]; |
109
|
|
|
$lineItemsToRemove[$itemToRemove->getLineItemId()]->setQuantity($quantity->getQuantity() + $itemToRemove->getQuantity()); |
110
|
|
|
|
111
|
|
|
continue; |
112
|
|
|
} |
113
|
|
|
$lineItemsToRemove[$itemToRemove->getLineItemId()] = $itemToRemove; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** @var array $lineItemsToRemoveIDs */ |
117
|
|
|
$lineItemsToRemoveIDs = array_keys($lineItemsToRemove); |
118
|
|
|
|
119
|
|
|
$newRestOfCart = new LineItemFlatCollection(); |
120
|
|
|
|
121
|
|
|
// this is our running buffer |
122
|
|
|
// for the items that need to be removed |
123
|
|
|
$deleteBuffer = []; |
124
|
|
|
|
125
|
|
|
// make sure we have an ID index for |
126
|
|
|
// all our delete-items with a qty of 0 |
127
|
|
|
foreach (array_keys($lineItemsToRemove) as $id) { |
128
|
|
|
$deleteBuffer[$id] = 0; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
foreach ($restOfCart as $item) { |
132
|
|
|
// if its a totally different item |
133
|
|
|
// just add it to the rest of our cart |
134
|
|
|
if (!in_array($item->getId(), $lineItemsToRemoveIDs, true)) { |
135
|
|
|
$newRestOfCart->add($item); |
136
|
|
|
} else { |
137
|
|
|
// we have an item that should be removed |
138
|
|
|
// now we have to calculate how many of the item position (qty diff) |
139
|
|
|
// or if we have even reached our max amount of quantities to remove for this item |
140
|
|
|
$maxRemoveMeta = $lineItemsToRemove[$item->getId()]->getQuantity(); |
141
|
|
|
|
142
|
|
|
$alreadyDeletedCount = $deleteBuffer[$item->getId()]; |
143
|
|
|
|
144
|
|
|
// now check if we can remove our current item completely |
145
|
|
|
// or if we have a sub quantity that still needs to be |
146
|
|
|
// added to the rest of the cart |
147
|
|
|
if ($alreadyDeletedCount + $item->getQuantity() <= $maxRemoveMeta) { |
148
|
|
|
// remove completely |
149
|
|
|
$deleteBuffer[$item->getId()] += $item->getQuantity(); |
150
|
|
|
} else { |
151
|
|
|
$toDeleteCount = $maxRemoveMeta - $alreadyDeletedCount; |
152
|
|
|
$keepCount = $item->getQuantity() - $toDeleteCount; |
153
|
|
|
|
154
|
|
|
// mark our diff as "deleted" |
155
|
|
|
$deleteBuffer[$item->getId()] += $toDeleteCount; |
156
|
|
|
|
157
|
|
|
// add the keep count to our item |
158
|
|
|
// and the item to the rest of our cart |
159
|
|
|
$item->setQuantity($keepCount); |
160
|
|
|
$newRestOfCart->add($item); |
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
return $newRestOfCart; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
private function getCartProducts(Cart $cart): LineItemCollection |
169
|
|
|
{ |
170
|
|
|
return $cart->getLineItems()->filterType(LineItem::PRODUCT_LINE_ITEM_TYPE); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* @throws InvalidQuantityException |
175
|
|
|
* @throws LineItemNotStackableException |
176
|
|
|
*/ |
177
|
|
|
private function splitQuantities(LineItemCollection $cartItems, SalesChannelContext $context): LineItemFlatCollection |
178
|
|
|
{ |
179
|
|
|
$items = []; |
180
|
|
|
|
181
|
|
|
foreach ($cartItems as $item) { |
182
|
|
|
for ($i = 1; $i <= $item->getQuantity(); ++$i) { |
183
|
|
|
$tmpItem = $this->quantitySplitter->split($item, 1, $context); |
184
|
|
|
|
185
|
|
|
$items[] = $tmpItem; |
186
|
|
|
} |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
return new LineItemFlatCollection($items); |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
|