Passed
Push — master ( fc01fb...26d801 )
by Aimeos
28:48 queued 11:51
created

Base::getCoupons()   B

Complexity

Conditions 8
Paths 25

Size

Total Lines 31
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 31
rs 8.4444
cc 8
nc 25
nop 2
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2023
7
 * @package MShop
8
 * @subpackage Order
9
 */
10
11
12
namespace Aimeos\MShop\Order\Manager;
13
14
15
/**
16
 * Basic methods and constants for order items (shopping basket).
17
 *
18
 * @package MShop
19
 * @subpackage Order
20
 */
21
abstract class Base extends \Aimeos\MShop\Common\Manager\Base
22
{
23
	/**
24
	 * Unlock basket.
25
	 * Disable the lock for the serialized basket in the session so
26
	 * modifications of the basket content are allowed again. Note that the
27
	 * locks are advisory locks that can't be enforced if code doesn't care
28
	 * about the lock.
29
	 */
30
	const LOCK_DISABLE = 0;
31
32
	/**
33
	 * Lock basket.
34
	 * Enable the lock for the serialized basket in the session so
35
	 * modifications of the basket content are not allowed any more. Note that
36
	 * the locks are advisory locks that can't be enforced if code doesn't care
37
	 * about the lock.
38
	 */
39
	const LOCK_ENABLE = 1;
40
41
42
	/**
43
	 * Returns a new and empty order item (shopping basket).
44
	 *
45
	 * @param \Aimeos\MShop\Price\Item\Iface $price Default price of the basket (usually 0.00)
46
	 * @param \Aimeos\MShop\Locale\Item\Iface $locale Locale item containing the site, language and currency
47
	 * @param array $values Associative list of key/value pairs containing, e.g. the order or user ID
48
	 * @param \Aimeos\MShop\Order\Item\Product\Iface[] $products List of ordered product items
49
	 * @param \Aimeos\MShop\Order\Item\Address\Iface[] $addresses List of order address items
50
	 * @param \Aimeos\MShop\Order\Item\Service\Iface[] $services List of order serviceitems
51
	 * @param \Aimeos\MShop\Order\Item\Product\Iface[] $coupons Associative list of coupon codes as keys and items as values
52
	 * @param \Aimeos\MShop\Customer\Item\Iface|null $custItem Customer item object if requested
53
	 * @return \Aimeos\MShop\Order\Item\Iface Order object
54
	 */
55
	abstract protected function createItemBase( \Aimeos\MShop\Price\Item\Iface $price, \Aimeos\MShop\Locale\Item\Iface $locale,
56
		array $values = [], array $products = [], array $addresses = [], array $services = [], array $coupons = [],
57
		?\Aimeos\MShop\Customer\Item\Iface $custItem = null ) : \Aimeos\MShop\Order\Item\Iface;
58
59
60
	/**
61
	 * Returns the current basket of the customer.
62
	 *
63
	 * @param string $type Basket type if a customer can have more than one basket
64
	 * @return \Aimeos\MShop\Order\Item\Iface Shopping basket
65
	 */
66
	public function getSession( string $type = 'default' ) : \Aimeos\MShop\Order\Item\Iface
67
	{
68
		$context = $this->context();
69
		$token = $context->token();
70
		$locale = $context->locale();
71
		$currency = $locale->getCurrencyId();
72
		$language = $locale->getLanguageId();
73
		$sitecode = $locale->getSiteItem()->getCode();
74
75
		$key = $token . '-' . $sitecode . '-' . $language . '-' . $currency . '-' . $type;
76
77
		try
78
		{
79
			if( ( $order = \Aimeos\MShop::create( $context, 'order/basket' )->get( $key )->getItem() ) === null ) {
80
				return $this->object()->create();
81
			}
82
83
			\Aimeos\MShop::create( $context, 'plugin' )->register( $order, 'order' );
84
		}
85
		catch( \Exception $e )
86
		{
87
			return $this->object()->create();
88
		}
89
90
		return $order;
91
	}
92
93
94
	/**
95
	 * Returns the current lock status of the basket.
96
	 *
97
	 * @param string $type Basket type if a customer can have more than one basket
98
	 * @return int Lock status (@see \Aimeos\MShop\Order\Manager\Base)
99
	 */
100
	public function getSessionLock( string $type = 'default' ) : int
101
	{
102
		$context = $this->context();
103
		$session = $context->session();
104
		$locale = $context->locale();
105
		$currency = $locale->getCurrencyId();
106
		$language = $locale->getLanguageId();
107
		$sitecode = $locale->getSiteItem()->getCode();
108
		$key = 'aimeos/basket/lock-' . $sitecode . '-' . $language . '-' . $currency . '-' . strval( $type );
109
110
		if( ( $value = $session->get( $key ) ) !== null ) {
111
			return (int) $value;
112
		}
113
114
		return \Aimeos\MShop\Order\Manager\Base::LOCK_DISABLE;
115
	}
116
117
118
	/**
119
	 * Saves the current shopping basket of the customer.
120
	 *
121
	 * @param \Aimeos\MShop\Order\Item\Iface $order Shopping basket
122
	 * @param string $type Order type if a customer can have more than one order at once
123
	 * @return \Aimeos\MShop\Order\Manager\Iface Manager object for chaining method calls
124
	 */
125
	public function setSession( \Aimeos\MShop\Order\Item\Iface $order, string $type = 'default' ) : \Aimeos\MShop\Order\Manager\Iface
126
	{
127
		$context = $this->context();
128
		$token = $context->token();
129
		$locale = $context->locale();
130
		$currency = $locale->getCurrencyId();
131
		$language = $locale->getLanguageId();
132
		$sitecode = $locale->getSiteItem()->getCode();
133
134
		$key = $token . '-' . $sitecode . '-' . $language . '-' . $currency . '-' . strval( $type );
135
136
		$session = $context->session();
137
138
		$list = $session->get( 'aimeos/basket/list', [] );
139
		$list[$key] = $key;
140
141
		$session->set( 'aimeos/basket/list', $list );
142
143
		$manager = \Aimeos\MShop::create( $context, 'order/basket' );
144
		$manager->save( $manager->create()->setId( $key )->setCustomerId( $context->user() )->setItem( clone $order ) );
145
146
		return $this;
147
	}
148
149
150
	/**
151
	 * Locks or unlocks the session by setting the lock value.
152
	 * The lock is a cooperative lock and you have to check the lock value before you proceed.
153
	 *
154
	 * @param int $lock Lock value (@see \Aimeos\MShop\Order\Manager\Base)
155
	 * @param string $type Order type if a customer can have more than one order at once
156
	 * @return \Aimeos\MShop\Order\Manager\Iface Manager object for chaining method calls
157
	 */
158
	public function setSessionLock( int $lock, string $type = 'default' ) : \Aimeos\MShop\Order\Manager\Iface
159
	{
160
		$this->checkLock( $lock );
161
162
		$context = $this->context();
163
		$session = $context->session();
164
		$locale = $context->locale();
165
		$currency = $locale->getCurrencyId();
166
		$language = $locale->getLanguageId();
167
		$sitecode = $locale->getSiteItem()->getCode();
168
		$key = 'aimeos/basket/lock-' . $sitecode . '-' . $language . '-' . $currency . '-' . strval( $type );
169
170
		$session->set( $key, strval( $lock ) );
171
172
		return $this;
173
	}
174
175
176
	/**
177
	 * Checks if the lock value is a valid constant.
178
	 *
179
	 * @param int $value Lock constant
180
	 * @return \Aimeos\MShop\Order\Manager\Iface Manager object for chaining method calls
181
	 * @throws \Aimeos\MShop\Order\Exception If given value is invalid
182
	 */
183
	protected function checkLock( int $value ) : \Aimeos\MShop\Order\Manager\Iface
184
	{
185
		switch( $value )
186
		{
187
			case \Aimeos\MShop\Order\Manager\Base::LOCK_DISABLE:
188
			case \Aimeos\MShop\Order\Manager\Base::LOCK_ENABLE:
189
				return $this;
190
		}
191
192
		$msg = $this->context()->translate( 'mshop', 'Lock flag "%1$d" not within allowed range' );
193
		throw new \Aimeos\MShop\Order\Exception( sprintf( $msg, $value ) );
194
	}
195
196
197
	/**
198
	 * Returns the address item map for the given order IDs
199
	 *
200
	 * @param string[] $ids List of order IDs
201
	 * @return array Multi-dimensional associative list of order IDs as keys and order address type/item pairs as values
202
	 */
203
	protected function getAddresses( array $ids ) : array
204
	{
205
		$items = [];
206
		$manager = $this->object()->getSubManager( 'address' );
207
208
		$criteria = $manager->filter()->slice( 0, 0x7fffffff );
209
		$criteria->setConditions( $criteria->compare( '==', 'order.address.parentid', $ids ) );
210
211
		foreach( $manager->search( $criteria ) as $item ) {
212
			$items[$item->getParentId()][] = $item;
213
		}
214
215
		return $items;
216
	}
217
218
219
	/**
220
	 * Returns the coupon map for the given order IDs
221
	 *
222
	 * @param string[] $ids List of order IDs
223
	 * @param array $products Associative list of IDs and order product ID/item pairs as values
224
	 * @return array Multi-dimensional associative list of order IDs as keys and coupons with product items as values
225
	 */
226
	protected function getCoupons( array $ids, array $products = [] ) : array
227
	{
228
		$map = $productMap = [];
229
		$manager = $this->object()->getSubManager( 'coupon' );
230
231
		foreach( $products as $id => $list )
232
		{
233
			if( !isset( $productMap[$id] ) ) {
234
				$productMap[$id] = [];
235
			}
236
237
			foreach( $list as $key => $product ) {
238
				$productMap[$id][$product->getId()] = $product;
239
			}
240
		}
241
242
		$criteria = $manager->filter()->slice( 0, 0x7fffffff );
243
		$criteria->setConditions( $criteria->compare( '==', 'order.coupon.parentid', $ids ) );
244
245
		foreach( $manager->search( $criteria ) as $item )
246
		{
247
			if( !isset( $map[$item->getParentId()][$item->getCode()] ) ) {
248
				$map[$item->getParentId()][$item->getCode()] = [];
249
			}
250
251
			if( $item->getProductId() !== null && isset( $productMap[$item->getParentId()][$item->getProductId()] ) ) {
252
				$map[$item->getParentId()][$item->getCode()][] = $productMap[$item->getParentId()][$item->getProductId()];
253
			}
254
		}
255
256
		return $map;
257
	}
258
259
260
	/**
261
	 * Retrieves the ordered products from the storage.
262
	 *
263
	 * @param string[] $ids List of order IDs
264
	 * @return array Multi-dimensional associative list of order IDs as keys and order product
265
	 *	IDs/items pairs in reversed order as values
266
	 */
267
	protected function getProducts( array $ids ) : array
268
	{
269
		$map = $attributes = $subProducts = [];
270
		$manager = $this->object()->getSubManager( 'product' );
271
		$attrManager = $manager->getSubManager( 'attribute' );
272
273
		$criteria = $manager->filter()->slice( 0, 0x7fffffff );
274
		$criteria->setConditions( $criteria->compare( '==', 'order.product.parentid', $ids ) );
275
		$items = $manager->search( $criteria )->reverse();
276
277
		$search = $attrManager->filter()->slice( 0, 0x7fffffff );
278
		$search->setConditions( $search->compare( '==', 'order.product.attribute.parentid', $items->keys()->toArray() ) );
279
280
		foreach( $attrManager->search( $search ) as $id => $attribute ) {
281
			$attributes[$attribute->getParentId()][$id] = $attribute;
282
		}
283
284
		foreach( $items as $id => $item )
285
		{
286
			if( isset( $attributes[$id] ) ) {
287
				$item->setAttributeItems( $attributes[$id] );
288
			}
289
290
			if( $item->getOrderProductId() === null )
291
			{
292
				ksort( $subProducts ); // bring the array into the right order because it's reversed
293
				$item->setProducts( $subProducts );
294
				$map[$item->getParentId()][$item->getPosition()] = $item;
295
296
				$subProducts = [];
297
			}
298
			else
299
			{	// in case it's a sub-product
300
				$subProducts[$item->getPosition()] = $item;
301
			}
302
		}
303
304
		foreach( $map as $key => $list ) {
305
			ksort( $map[$key] );
306
		}
307
308
		return $map;
309
	}
310
311
312
	/**
313
	 * Retrieves the order services from the storage.
314
	 *
315
	 * @param string[] $ids List of order IDs
316
	 * @return array Multi-dimensional associative list of order IDs as keys and service type/items pairs as values
317
	 */
318
	protected function getServices( array $ids ) : array
319
	{
320
		$map = [];
321
		$manager = $this->object()->getSubManager( 'service' );
322
323
		$criteria = $manager->filter()->slice( 0, 0x7fffffff );
324
		$criteria->setConditions( $criteria->compare( '==', 'order.service.parentid', $ids ) );
325
326
		foreach( $manager->search( $criteria ) as $item ) {
327
			$map[$item->getParentId()][] = $item;
328
		}
329
330
		return $map;
331
	}
332
333
334
	/**
335
	 * Saves the addresses of the order to the storage.
336
	 *
337
	 * @param \Aimeos\MShop\Order\Item\Iface $basket Basket containing address items
338
	 * @return \Aimeos\MShop\Order\Manager\Iface Manager object for chaining method calls
339
	 */
340
	protected function saveAddresses( \Aimeos\MShop\Order\Item\Iface $basket ) : \Aimeos\MShop\Order\Manager\Iface
341
	{
342
		$addresses = $basket->getAddresses()->flat( 1 );
343
344
		foreach( $addresses as $address )
345
		{
346
			if( $address->getParentId() != $basket->getId() ) {
347
				$address->setId( null ); // create new item if copied
348
			}
349
350
			$address->setParentId( $basket->getId() );
351
		}
352
353
		$this->object()->getSubManager( 'address' )->save( $addresses );
354
355
		return $this;
356
	}
357
358
359
	/**
360
	 * Saves the coupons of the order to the storage.
361
	 *
362
	 * @param \Aimeos\MShop\Order\Item\Iface $basket Basket containing coupon items
363
	 * @return \Aimeos\MShop\Order\Manager\Iface Manager object for chaining method calls
364
	 */
365
	protected function saveCoupons( \Aimeos\MShop\Order\Item\Iface $basket ) : \Aimeos\MShop\Order\Manager\Iface
366
	{
367
		$manager = $this->object()->getSubManager( 'coupon' );
368
		$filter = $manager->filter()->add( 'order.coupon.parentid', '==', $basket->getId() )->slice( 0, 0x7fffffff );
369
		$items = $manager->search( $filter )->groupBy( 'order.coupon.code' );
370
371
		foreach( $basket->getCoupons() as $code => $products )
372
		{
373
			if( empty( $products ) )
374
			{
375
				$item = !empty( $items[$code] ) ? current( $items[$code] ) : $manager->create()->setParentId( $basket->getId() );
0 ignored issues
show
Bug introduced by
It seems like $items[$code] can also be of type null; however, parameter $array of current() does only seem to accept array|object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

375
				$item = !empty( $items[$code] ) ? current( /** @scrutinizer ignore-type */ $items[$code] ) : $manager->create()->setParentId( $basket->getId() );
Loading history...
376
				$manager->save( $item->setCode( $code ) );
377
				continue;
378
			}
379
380
			foreach( $products as $product )
381
			{
382
				foreach( $items[$code] ?? [] as $prodItem )
383
				{
384
					if( $product->getId() === $prodItem->getId() ) {
385
						continue 2;
386
					}
387
				}
388
389
				$manager->save( $manager->create()->setParentId( $basket->getId() )->setCode( $code )->setProductId( $product->getId() ) );
390
			}
391
		}
392
393
		return $this;
394
	}
395
396
397
	/**
398
	 * Saves the ordered products to the storage.
399
	 *
400
	 * @param \Aimeos\MShop\Order\Item\Iface $basket Basket containing ordered products or bundles
401
	 * @return \Aimeos\MShop\Order\Manager\Iface Manager object for chaining method calls
402
	 */
403
	protected function saveProducts( \Aimeos\MShop\Order\Item\Iface $basket ) : \Aimeos\MShop\Order\Manager\Iface
404
	{
405
		$products = $basket->getProducts();
406
		$pos = (int) $products->merge( $products->getProducts()->flat( 1 ) )->max( 'order.product.position' );
407
408
		foreach( $products as $product )
409
		{
410
			if( $product->getParentId() != $basket->getId() ) {
411
				$product->setId( null ); // create new item if copied
412
			}
413
414
			if( !$product->getPosition() ) {
415
				$product->setPosition( ++$pos );
416
			}
417
418
			$product->setParentId( $basket->getId() );
419
420
			foreach( $product->getProducts() as $subProduct )
421
			{
422
				if( $subProduct->getParentId() != $basket->getId() ) {
423
					$subProduct->setId( null ); // create new item if copied
424
				}
425
426
				if( !$subProduct->getPosition() ) {
427
					$subProduct->setPosition( ++$pos );
428
				}
429
430
				$subProduct->setParentId( $basket->getId() );
431
			}
432
		}
433
434
		$this->object()->getSubManager( 'product' )->save( $products );
435
436
		return $this;
437
	}
438
439
440
	/**
441
	 * Saves the services of the order to the storage.
442
	 *
443
	 * @param \Aimeos\MShop\Order\Item\Iface $basket Basket containing service items
444
	 * @return \Aimeos\MShop\Order\Manager\Iface Manager object for chaining method calls
445
	 */
446
	protected function saveServices( \Aimeos\MShop\Order\Item\Iface $basket ) : \Aimeos\MShop\Order\Manager\Iface
447
	{
448
		$services = $basket->getServices()->flat( 1 );
449
450
		foreach( $services as $service )
451
		{
452
			if( $service->getParentId() != $basket->getId() ) {
453
				$service->setId( null ); // create new item if copied
454
			}
455
456
			$service->setParentId( $basket->getId() );
457
		}
458
459
		$this->object()->getSubManager( 'service' )->save( $services );
460
461
		return $this;
462
	}
463
}
464