1<?php
2/**
3 * Copyright since 2007 PrestaShop SA and Contributors
4 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
5 *
6 * NOTICE OF LICENSE
7 *
8 * This source file is subject to the Open Software License (OSL 3.0)
9 * that is bundled with this package in the file LICENSE.md.
10 * It is also available through the world-wide-web at this URL:
11 * https://opensource.org/licenses/OSL-3.0
12 * If you did not receive a copy of the license and are unable to
13 * obtain it through the world-wide-web, please send an email
14 * to license@prestashop.com so we can send you a copy immediately.
15 *
16 * DISCLAIMER
17 *
18 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
19 * versions in the future. If you wish to customize PrestaShop for your
20 * needs please refer to https://devdocs.prestashop.com/ for more information.
21 *
22 * @author    PrestaShop SA and Contributors <contact@prestashop.com>
23 * @copyright Since 2007 PrestaShop SA and Contributors
24 * @license   https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
25 */
26
27declare(strict_types=1);
28
29namespace PrestaShop\PrestaShop\Adapter\Cart\Comparator;
30
31use Cart;
32
33/**
34 * This class saves a cart's products when it's created, you can then ask for the difference
35 * that happened on this cart.
36 */
37class CartProductsComparator
38{
39    /**
40     * @var Cart
41     */
42    private $cart;
43
44    /**
45     * @var array
46     */
47    private $savedProducts;
48
49    /**
50     * @var CartProductUpdate[]
51     */
52    private $knownUpdates = [];
53
54    /**
55     * @param Cart $cart
56     */
57    public function __construct(Cart $cart)
58    {
59        $this->cart = $cart;
60        $this->savedProducts = $cart->getProducts(true);
61    }
62
63    /**
64     * @param array $knownUpdates
65     *
66     * @return CartProductsComparator
67     */
68    public function setKnownUpdates(array $knownUpdates): self
69    {
70        $this->knownUpdates = $knownUpdates;
71
72        return $this;
73    }
74
75    /**
76     * Returns a list of products that were strictly updated (not created) compared to the state of the cart
77     * when this object was created, it removes the already known modified products provided as argument.
78     *
79     * @return CartProductUpdate[]
80     */
81    public function getUpdatedProducts(): array
82    {
83        $newProducts = $this->cart->getProducts(true);
84        $allUpdateProducts = $this->getAllUpdatedProducts($newProducts);
85
86        return $this->filterKnownUpdates($allUpdateProducts);
87    }
88
89    /**
90     * Returns a list of products that were strictly created (not updated) compared to the state of the cart
91     * when this object was created, it removes the already known modified products provided as argument.
92     *
93     * @return CartProductUpdate[]
94     */
95    public function getAdditionalProducts(): array
96    {
97        $newProducts = $this->cart->getProducts(true);
98        $allAdditionalProducts = $this->getAllAdditionalProducts($newProducts);
99
100        return $this->filterKnownUpdates($allAdditionalProducts);
101    }
102
103    /**
104     * Returns a list of products that were modified (created and/or updated) compared to the state of the cart
105     * when this object was created, it removes the already known modified products provided as argument.
106     *
107     * @return CartProductUpdate[]
108     */
109    public function getModifiedProducts(): array
110    {
111        $newProducts = $this->cart->getProducts(true);
112        $modifiedProducts = array_merge($this->getAllUpdatedProducts($newProducts), $this->getAllAdditionalProducts($newProducts));
113
114        return $this->filterKnownUpdates($modifiedProducts);
115    }
116
117    /**
118     * Returns the list of updates for products that were not in the cart previously
119     *
120     * @param array[] $newProducts
121     *
122     * @return array
123     */
124    private function getAllAdditionalProducts(array $newProducts): array
125    {
126        $additionalProducts = [];
127        foreach ($newProducts as $newProduct) {
128            // Then try and find the product in new products
129            $oldProduct = $this->getMatchingProduct($this->savedProducts, $newProduct);
130            if (null === $oldProduct) {
131                $additionalProducts[] = new CartProductUpdate(
132                    (int) $newProduct['id_product'],
133                    (int) $newProduct['id_product_attribute'],
134                    (int) $newProduct['cart_quantity'],
135                    true,
136                    (int) $newProduct['id_customization']
137                );
138            }
139        }
140
141        return $additionalProducts;
142    }
143
144    /**
145     * Returns a list of all products that were updated compared to the creation of this object.
146     *
147     * @param array[] $newProducts
148     *
149     * @return CartProductUpdate[]
150     */
151    private function getAllUpdatedProducts(array $newProducts): array
152    {
153        $updatedProducts = [];
154        foreach ($this->savedProducts as $oldProduct) {
155            // Then try and find the product in new products
156            $newProduct = $this->getMatchingProduct($newProducts, $oldProduct);
157            if (null === $newProduct) {
158                $deltaQuantity = -(int) $oldProduct['cart_quantity'];
159            } else {
160                $deltaQuantity = (int) $newProduct['cart_quantity'] - (int) $oldProduct['cart_quantity'];
161            }
162
163            if ($deltaQuantity) {
164                $updatedProducts[] = new CartProductUpdate(
165                    (int) $oldProduct['id_product'],
166                    (int) $oldProduct['id_product_attribute'],
167                    $deltaQuantity,
168                    false,
169                    (int) $oldProduct['id_customization']
170                );
171            }
172        }
173
174        return $updatedProducts;
175    }
176
177    /**
178     * @param CartProductUpdate[] $updates
179     *
180     * @return CartProductUpdate[]
181     */
182    private function filterKnownUpdates(array $updates): array
183    {
184        $filteredUpdates = [];
185        foreach ($updates as $updateProduct) {
186            foreach ($this->knownUpdates as $knownUpdate) {
187                if ($knownUpdate->productMatches($updateProduct)) {
188                    $updateProduct->setDeltaQuantity(
189                        $updateProduct->getDeltaQuantity() - $knownUpdate->getDeltaQuantity()
190                    );
191
192                    break;
193                }
194            }
195            if (0 !== $updateProduct->getDeltaQuantity()) {
196                $filteredUpdates[] = $updateProduct;
197            }
198        }
199
200        return $filteredUpdates;
201    }
202
203    /**
204     * @param array $products
205     * @param array $searchedProduct
206     *
207     * @return array|null
208     */
209    private function getMatchingProduct(array $products, array $searchedProduct): ?array
210    {
211        return array_reduce($products, function ($carry, $item) use ($searchedProduct) {
212            if (null !== $carry) {
213                return $carry;
214            }
215
216            $productMatch = $item['id_product'] == $searchedProduct['id_product'];
217            $combinationMatch = $item['id_product_attribute'] == $searchedProduct['id_product_attribute'];
218            $customizationMatch = $item['id_customization'] == $searchedProduct['id_customization'];
219
220            return $productMatch && $combinationMatch && $customizationMatch ? $item : null;
221        });
222    }
223}
224