PluginProbe
Packeta / trunk
Packeta vtrunk
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / src / Packetery / Module / Checkout / CartService.php

CartService.php in Packeta trunk, at src/Packetery/Module/Checkout/CartService.php

313 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare( strict_types=1 );
4
5 namespace Packetery\Module\Checkout;
6
7 use Packetery\Module\Carrier;
8 use Packetery\Module\Exception\ProductNotFoundException;
9 use Packetery\Module\Framework\WcAdapter;
10 use Packetery\Module\Framework\WpAdapter;
11 use Packetery\Module\Options\OptionsProvider;
12 use Packetery\Module\Product\ProductEntityFactory;
13 use Packetery\Module\ProductCategory\ProductCategoryEntityFactory;
14 use WC_Product;
15
16 class CartService {
17
18 private const CRITERIA_BY_LENGTH = 1;
19 public const CRITERIA_BY_SUM = 2;
20
21 /**
22 * @var WpAdapter
23 */
24 private $wpAdapter;
25
26 /**
27 * @var WcAdapter
28 */
29 private $wcAdapter;
30
31 /**
32 * @var ProductEntityFactory
33 */
34 private $productEntityFactory;
35
36 /**
37 * @var ProductCategoryEntityFactory
38 */
39 private $productCategoryEntityFactory;
40
41 /**
42 * @var OptionsProvider
43 */
44 private $optionsProvider;
45
46 public function __construct(
47 WpAdapter $wpAdapter,
48 WcAdapter $wcAdapter,
49 ProductEntityFactory $productEntityFactory,
50 ProductCategoryEntityFactory $productCategoryEntityFactory,
51 OptionsProvider $optionsProvider
52 ) {
53 $this->wpAdapter = $wpAdapter;
54 $this->wcAdapter = $wcAdapter;
55 $this->productEntityFactory = $productEntityFactory;
56 $this->productCategoryEntityFactory = $productCategoryEntityFactory;
57 $this->optionsProvider = $optionsProvider;
58 }
59
60 /**
61 * @throws ProductNotFoundException
62 */
63 public function isAgeVerificationRequired(): bool {
64 if ( $this->wpAdapter->didAction( 'wp_loaded' ) === 0 ) {
65 return false;
66 }
67
68 $products = $this->wcAdapter->cartGetCartContent();
69
70 foreach ( $products as $product ) {
71 $productEntity = $this->productEntityFactory->fromPostId( $product['product_id'] );
72 if ( $productEntity->isPhysical() && $productEntity->isAgeVerificationRequired() ) {
73 return true;
74 }
75 }
76
77 return false;
78 }
79
80 public function getCartWeightKg(): float {
81 if ( $this->wpAdapter->didAction( 'wp_loaded' ) === 0 ) {
82 return 0.0;
83 }
84
85 $weight = $this->wcAdapter->cartGetCartContentsWeight();
86 $weightKg = $this->wcAdapter->getWeight( $weight, 'kg' );
87
88 if ( $weightKg !== 0.0 ) {
89 $weightKg += $this->optionsProvider->getPackagingWeight();
90 }
91
92 return $weightKg;
93 }
94
95 public function getTotalCartProductValue(): float {
96 $totalProductPrice = 0.0;
97
98 foreach ( $this->wcAdapter->cartGetCartContent() as $cartItem ) {
99 $totalProductPrice += (float) $cartItem['data']->get_price( 'raw' ) * (float) $cartItem['quantity'];
100 }
101
102 return $totalProductPrice;
103 }
104
105 /**
106 * @throws ProductNotFoundException
107 */
108 public function getDisallowedShippingRateIds(): array {
109 $cartProducts = $this->wcAdapter->cartGetCartContent();
110
111 $disallowedShippingRateIds = [];
112 foreach ( $cartProducts as $cartProduct ) {
113 $productEntity = $this->productEntityFactory->fromPostId( $cartProduct['product_id'] );
114
115 if ( $productEntity->isPhysical() === false ) {
116 continue;
117 }
118
119 $disallowedShippingRateIds[] = $productEntity->getDisallowedShippingRateIds();
120 }
121
122 return array_unique( array_merge( [], ...$disallowedShippingRateIds ) );
123 }
124
125 /**
126 * Returns tax_class with the highest tax_rate of cart products, false if no product is taxable.
127 *
128 * @return string|null
129 * @throws ProductNotFoundException
130 */
131 public function getTaxClassWithMaxRate(): ?string {
132 $products = $this->wcAdapter->cartGetCartContent();
133 $taxClasses = [];
134
135 foreach ( $products as $cartProduct ) {
136 $product = $this->wcAdapter->productFactoryGetProduct( $cartProduct['product_id'] );
137 if ( ! ( $product instanceof WC_Product ) ) {
138 throw new ProductNotFoundException( "Product {$cartProduct['product_id']} not found." );
139 }
140 if ( $product->is_taxable() && is_string( $product->get_tax_class() ) ) {
141 $taxClasses[] = $product->get_tax_class();
142 }
143 }
144
145 if ( count( $taxClasses ) === 0 ) {
146 return null;
147 }
148
149 $taxClasses = array_unique( $taxClasses );
150 if ( count( $taxClasses ) === 1 ) {
151 return $taxClasses[0];
152 }
153
154 $taxRates = [];
155 $customer = $this->wcAdapter->cartGetCustomer();
156 foreach ( $taxClasses as $taxClass ) {
157 $taxRates[ $taxClass ] = $this->wcAdapter->taxGetRates( $taxClass, $customer );
158 }
159
160 $maxRate = 0;
161 $resultTaxClass = null;
162 foreach ( $taxRates as $taxClassName => $taxClassRates ) {
163 foreach ( $taxClassRates as $rate ) {
164 if ( $rate['rate'] > $maxRate ) {
165 $maxRate = $rate['rate'];
166 $resultTaxClass = $taxClassName;
167 }
168 }
169 }
170
171 return $resultTaxClass;
172 }
173
174 public function getCartContentsTotalIncludingTax(): float {
175 return $this->wcAdapter->cartGetCartContentsTotal() + $this->wcAdapter->cartGetCartContentsTax();
176 }
177
178 /**
179 * @throws ProductNotFoundException
180 */
181 public function isShippingRateRestrictedByProductsCategory( string $shippingRate, array $cartProducts ): bool {
182 if ( count( $cartProducts ) === 0 ) {
183 return false;
184 }
185
186 foreach ( $cartProducts as $cartProduct ) {
187 if ( ! isset( $cartProduct['product_id'] ) ) {
188 continue;
189 }
190 $product = $this->wcAdapter->productFactoryGetProduct( $cartProduct['product_id'] );
191 if ( ! ( $product instanceof WC_Product ) ) {
192 throw new ProductNotFoundException( "Product {$cartProduct['product_id']} not found." );
193 }
194 $productCategoryIds = $product->get_category_ids();
195
196 foreach ( $productCategoryIds as $productCategoryId ) {
197 $productCategoryEntity = $this->productCategoryEntityFactory->fromTermId( (int) $productCategoryId );
198 $disallowedCategoryShippingRates = $productCategoryEntity->getDisallowedShippingRateIds();
199 if ( in_array( $shippingRate, $disallowedCategoryShippingRates, true ) ) {
200 return true;
201 }
202 }
203 }
204
205 return false;
206 }
207
208 public function getBiggestProductSize( int $mode = self::CRITERIA_BY_LENGTH ): ?array {
209 if ( $this->wpAdapter->didAction( 'wp_loaded' ) === 0 ) {
210 return null;
211 }
212
213 $products = $this->wcAdapter->cartGetCartContent();
214 $maxSizes = [
215 'length' => 0,
216 'width' => 0,
217 'depth' => 0,
218 ];
219
220 foreach ( $products as $product ) {
221 $productEntity = $this->productEntityFactory->fromPostId( $product['product_id'] );
222 if ( $productEntity->isPhysical() && (
223 $productEntity->getLengthInCm( $this->wpAdapter ) > 0 ||
224 $productEntity->getWidthInCm( $this->wpAdapter ) > 0 ||
225 $productEntity->getHeightInCm( $this->wpAdapter ) > 0
226 )
227 ) {
228 if ( $mode === self::CRITERIA_BY_SUM ) {
229 $productSizeSum =
230 $productEntity->getLengthInCm( $this->wpAdapter ) +
231 $productEntity->getWidthInCm( $this->wpAdapter ) +
232 $productEntity->getHeightInCm( $this->wpAdapter );
233 if ( $productSizeSum > array_sum( $maxSizes ) ) {
234 $maxSizes = [
235 'length' => $productEntity->getLengthInCm( $this->wpAdapter ),
236 'width' => $productEntity->getWidthInCm( $this->wpAdapter ),
237 'depth' => $productEntity->getHeightInCm( $this->wpAdapter ),
238 ];
239 }
240 } else {
241 $productSizes = [
242 $productEntity->getLengthInCm( $this->wpAdapter ),
243 $productEntity->getWidthInCm( $this->wpAdapter ),
244 $productEntity->getHeightInCm( $this->wpAdapter ),
245 ];
246 rsort( $productSizes, SORT_NUMERIC );
247 if ( $productSizes[0] > $maxSizes['length'] ) {
248 $maxSizes = [
249 'length' => $productSizes[0],
250 'width' => $productSizes[1],
251 'depth' => $productSizes[2],
252 ];
253 }
254 }
255 }
256 }
257
258 if ( $maxSizes['length'] === 0 ) {
259 return null;
260 }
261
262 return $maxSizes;
263 }
264
265 public function cartContainsProductOversizedForCarrier( Carrier\Options $carrierOptions ): bool {
266 $sizeRestrictions = $carrierOptions->getSizeRestrictions();
267 if ( $sizeRestrictions === null ) {
268 return false;
269 }
270 $biggestProductSizeBySum = $this->getBiggestProductSize( self::CRITERIA_BY_SUM );
271 $biggestProductSizeByLength = $this->getBiggestProductSize();
272 if ( $biggestProductSizeBySum === null || $biggestProductSizeByLength === null ) {
273 return false;
274 }
275
276 if ( isset( $sizeRestrictions['maximum_length'] ) && is_numeric( trim( (string) $sizeRestrictions['maximum_length'] ) ) ) {
277 $productMax = max( $biggestProductSizeByLength );
278 if ( $productMax > $sizeRestrictions['maximum_length'] ) {
279 return true;
280 }
281 }
282 if ( isset( $sizeRestrictions['dimensions_sum'] ) && is_numeric( trim( (string) $sizeRestrictions['dimensions_sum'] ) ) ) {
283 $productSum = array_sum( $biggestProductSizeBySum );
284 if ( $productSum > $sizeRestrictions['dimensions_sum'] ) {
285 return true;
286 }
287 }
288
289 if (
290 isset( $sizeRestrictions['length'], $sizeRestrictions['width'], $sizeRestrictions['height'] )
291 && is_numeric( trim( (string) $sizeRestrictions['length'] ) )
292 && is_numeric( trim( (string) $sizeRestrictions['width'] ) )
293 && is_numeric( trim( (string) $sizeRestrictions['height'] ) )
294 ) {
295 $dimensions = [
296 $sizeRestrictions['length'],
297 $sizeRestrictions['width'],
298 $sizeRestrictions['height'],
299 ];
300 rsort( $dimensions, SORT_NUMERIC );
301 rsort( $biggestProductSizeByLength, SORT_NUMERIC );
302
303 foreach ( $dimensions as $index => $dimension ) {
304 if ( $biggestProductSizeByLength[ $index ] > $dimension ) {
305 return true;
306 }
307 }
308 }
309
310 return false;
311 }
312 }
313