Completed
Push — master ( 272a5c...1fc904 )
by Aimeos
08:57
created

lib/mshoplib/src/MShop/Order/Item/Base/Base.php (1 issue)

php_doc.return_type_not_inferrable

Documentation Minor

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2016
7
 * @package MShop
8
 * @subpackage Order
9
 */
10
11
12
namespace Aimeos\MShop\Order\Item\Base;
13
14
15
/**
16
 * Abstract order base class with necessary constants and basic methods.
17
 *
18
 * @package MShop
19
 * @subpackage Order
20
 */
21
abstract class Base
22
	extends \Aimeos\MW\Observer\Publisher\Base
23
	implements \Aimeos\MShop\Order\Item\Base\Iface
24
{
25
	/**
26
	 * Check no basket content.
27
	 * Don't check if the basket is ready for checkout or ordering.
28
	 */
29
	const PARTS_NONE = 0;
30
31
	/**
32
	 * Check basket for products.
33
	 * Checks if the basket complies to the product related requirements.
34
	 */
35
	const PARTS_PRODUCT = 1;
36
37
	/**
38
	 * Check basket for addresses.
39
	 * Checks if the basket complies to the address related requirements.
40
	 */
41
	const PARTS_ADDRESS = 2;
42
43
	/**
44
	 * Check basket for delivery/payment.
45
	 * Checks if the basket complies to the delivery/payment related
46
	 * requirements.
47
	 */
48
	const PARTS_SERVICE = 4;
49
50
	/**
51
	 * Check basket for all parts.
52
	 * This constant matches all other part constants.
53
	 */
54
	const PARTS_ALL = 7;
55
56
57
	protected $products;
58
	protected $addresses;
59
	protected $services;
60
	protected $coupons;
61
	private $modified = false;
62
63
64
	/**
65
	 * Initializes the basket object
66
	 *
67
	 * @param \Aimeos\MShop\Price\Item\Iface $price Default price of the basket (usually 0.00)
68
	 * @param \Aimeos\MShop\Locale\Item\Iface $locale Locale item containing the site, language and currency
69
	 * @param array $values Associative list of key/value pairs containing, e.g. the order or user ID
70
	 * @param array $products List of ordered products implementing \Aimeos\MShop\Order\Item\Base\Product\Iface
71
	 * @param array $addresses List of order addresses implementing \Aimeos\MShop\Order\Item\Base\Address\Iface
72
	 * @param array $services List of order services implementing \Aimeos\MShop\Order\Item\Base\Service\Iface
73
	 * @param array $coupons Associative list of coupon codes as keys and ordered products implementing \Aimeos\MShop\Order\Item\Base\Product\Iface as values
74
	 */
75
	public function __construct( \Aimeos\MShop\Price\Item\Iface $price, \Aimeos\MShop\Locale\Item\Iface $locale,
76
			array $values = [], array $products = [], array $addresses = [],
77
			array $services = [], array $coupons = [] )
78
	{
79
		\Aimeos\MW\Common\Base::checkClassList( '\Aimeos\MShop\Order\Item\Base\Product\Iface', $products );
80
		\Aimeos\MW\Common\Base::checkClassList( '\Aimeos\MShop\Order\Item\Base\Address\Iface', $addresses );
81
		\Aimeos\MW\Common\Base::checkClassList( '\Aimeos\MShop\Order\Item\Base\Service\Iface', $services );
82
83
		foreach( $coupons as $couponProducts ) {
84
			\Aimeos\MW\Common\Base::checkClassList( '\Aimeos\MShop\Order\Item\Base\Product\Iface', $couponProducts );
85
		}
86
87
		$this->products = $products;
88
		$this->addresses = $addresses;
89
		$this->services = $services;
90
		$this->coupons = $coupons;
91
	}
92
93
94
	/**
95
	 * Clones internal objects of the order base item.
96
	 */
97
	public function __clone()
98
	{
99
		foreach( $this->products as $key => $value ) {
100
			$this->products[$key] = $value;
101
		}
102
103
		foreach( $this->addresses as $key => $value ) {
104
			$this->addresses[$key] = $value;
105
		}
106
107
		foreach( $this->services as $key => $value ) {
108
			$this->services[$key] = $value;
109
		}
110
111
		foreach( $this->coupons as $key => $value ) {
112
			$this->coupons[$key] = $value;
113
		}
114
	}
115
116
117
	/**
118
	 * Returns the item type
119
	 *
120
	 * @return string Item type, subtypes are separated by slashes
121
	 */
122
	public function getResourceType()
123
	{
124
		return 'order/base';
125
	}
126
127
128
	/**
129
	 * Returns the product items that are or should be part of an (future) order.
130
	 *
131
	 * @return array Array of order product items implementing \Aimeos\MShop\Order\Item\Base\Product\Iface
132
	 */
133
	public function getProducts()
134
	{
135
		return $this->products;
136
	}
137
138
139
	/**
140
	 * Returns the product item of an (future) order specified by its key.
141
	 *
142
	 * @param integer $key Key returned by getProducts() identifying the requested product
143
	 * @return \Aimeos\MShop\Order\Item\Base\Product\Iface Product item of an order
144
	 */
145
	public function getProduct( $key )
146
	{
147
		if( !isset( $this->products[$key] ) ) {
148
			throw new \Aimeos\MShop\Order\Exception( sprintf( 'Product with array key "%1$d" not available', $key ) );
149
		}
150
151
		return $this->products[$key];
152
	}
153
154
155
	/**
156
	 * Adds an order product item to the (future) order.
157
	 * If a similar item is found, only the quantity is increased.
158
	 *
159
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $item Order product item to be added
160
	 * @param integer|null $position position of the new order product item
161
	 */
162
	public function addProduct( \Aimeos\MShop\Order\Item\Base\Product\Iface $item, $position = null )
163
	{
164
		$this->checkProduct( $item );
165
		$this->checkPrice( $item->getPrice() );
166
167
		$this->notifyListeners( 'addProduct.before', $item );
168
169
		if( ( $pos = $this->getSameProduct( $item, $this->products ) ) !== false )
170
		{
171
			$quantity = $item->getQuantity();
172
			$item = $this->products[$pos];
173
			$item->setQuantity( $item->getQuantity() + $quantity );
174
		}
175
		else if( $position !== null )
176
		{
177
			if( isset( $this->products[$position] ) )
178
			{
179
				$products = [];
180
181
				foreach( $this->products as $key => $product )
182
				{
183
					if( $key < $position ) {
184
						$products[$key] = $product;
185
					} else if( $key >= $position ) {
186
						$products[$key + 1] = $product;
187
					}
188
				}
189
190
				$products[$position] = $item;
191
				$this->products = $products;
192
			}
193
			else
194
			{
195
				$this->products[$position] = $item;
196
			}
197
		}
198
		else
199
		{
200
			$this->products[] = $item;
201
		}
202
203
		ksort( $this->products );
204
		$this->setModified();
205
206
		$this->notifyListeners( 'addProduct.after', $item );
207
	}
208
209
210
	/**
211
	 * Sets a modified order product item to the (future) order.
212
	 *
213
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $item Order product item to be added
214
	 * @param integer $position Position id of the order product item
215
	 */
216
	public function editProduct( \Aimeos\MShop\Order\Item\Base\Product\Iface $item, $pos )
217
	{
218
		if( !array_key_exists( $pos, $this->products ) ) {
219
			throw new \Aimeos\MShop\Order\Exception( sprintf( 'Product with array key "%1$d" not available', $pos ) );
220
		}
221
222
		$this->notifyListeners( 'editProduct.before', $item );
223
224
		if( ( $pos = $this->getSameProduct( $item, $this->products ) ) !== false )
225
		{
226
			$this->products[$pos] = $item;
227
			$this->setModified();
228
		}
229
		else
230
		{
231
			$this->products[$pos] = $item;
232
		}
233
234
		$this->notifyListeners( 'editProduct.after', $item );
235
	}
236
237
238
	/**
239
	 * Deletes an order product item from the (future) order.
240
	 *
241
	 * @param integer $position Position id of the order product item
242
	 */
243
	public function deleteProduct( $position )
244
	{
245
		if( !array_key_exists( $position, $this->products ) ) {
246
			return;
247
		}
248
249
		$product = $this->products[$position];
250
251
		$this->notifyListeners( 'deleteProduct.before', $product );
252
253
		unset( $this->products[$position] );
254
		$this->setModified();
255
256
		$this->notifyListeners( 'deleteProduct.after', $product );
257
	}
258
259
260
	/**
261
	 * Returns all addresses that are part of the basket.
262
	 *
263
	 * @return array Associative list of address items implementing
264
	 *  \Aimeos\MShop\Order\Item\Base\Address\Iface with "billing" or "delivery" as key
265
	 */
266
	public function getAddresses()
267
	{
268
		return $this->addresses;
269
	}
270
271
272
	/**
273
	 * Returns the billing or delivery address depending on the given type.
274
	 *
275
	 * @param string $type Address type, usually "billing" or "delivery"
276
	 * @return \Aimeos\MShop\Order\Item\Base\Address\Iface Order address item for the requested type
277
	 */
278
	public function getAddress( $type = \Aimeos\MShop\Order\Item\Base\Address\Base::TYPE_PAYMENT )
279
	{
280
		if( !isset( $this->addresses[$type] ) ) {
281
			throw new \Aimeos\MShop\Order\Exception( sprintf( 'Address for type "%1$s" not available', $type ) );
282
		}
283
284
		return $this->addresses[$type];
285
	}
286
287
288
	/**
289
	 * Sets a customer address as billing or delivery address for an order.
290
	 *
291
	 * @param \Aimeos\MShop\Order\Item\Base\Address\Iface $address Order address item for the given type
292
	 * @param string $type Address type, usually "billing" or "delivery"
293
	 */
294
	public function setAddress( \Aimeos\MShop\Order\Item\Base\Address\Iface $address, $type )
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
295
	{
296
		if( isset( $this->addresses[$type] ) && $this->addresses[$type] === $address ) { return $address; }
297
298
		$this->notifyListeners( 'setAddress.before', $address );
299
300
		$address = clone $address;
301
		$address->setType( $type ); // enforce that the type is the same as the given one
302
		$address->setId( null ); // enforce saving as new item
303
304
		$this->addresses[$type] = $address;
305
		$this->setModified();
306
307
		$this->notifyListeners( 'setAddress.after', $address );
308
	}
309
310
311
	/**
312
	 * Deleted a customer address for billing or delivery of an order.
313
	 *
314
	 * @param string $type Address type defined in \Aimeos\MShop\Order\Item\Base\Address\Base
315
	 */
316
	public function deleteAddress( $type )
317
	{
318
		if( !isset( $this->addresses[$type] ) ) {
319
			return;
320
		}
321
322
		$this->notifyListeners( 'deleteAddress.before', $type );
323
324
		$address = $this->addresses[$type];
325
		unset( $this->addresses[$type] );
326
		$this->setModified();
327
328
		$this->notifyListeners( 'deleteAddress.after', $address );
329
	}
330
331
332
	/**
333
	 * Returns all services that are part of the basket.
334
	 *
335
	 * @return array Associative list of service items implementing \Aimeos\MShop\Order\Service\Iface
336
	 *  with "delivery" or "payment" as key
337
	 */
338
	public function getServices()
339
	{
340
		return $this->services;
341
	}
342
343
344
	/**
345
	 * Returns the delivery or payment service depending on the given type.
346
	 *
347
	 * @param string $type Service type constant from \Aimeos\MShop\Order\Item\Service\Base
348
	 * @return \Aimeos\MShop\Order\Item\Base\Serive\Iface Order service item for the requested type
349
	 */
350
	public function getService( $type )
351
	{
352
		if( !isset( $this->services[$type] ) ) {
353
			throw new \Aimeos\MShop\Order\Exception( sprintf( 'Service of type "%1$s" not available', $type ) );
354
		}
355
356
		return $this->services[$type];
357
	}
358
359
360
	/**
361
	 * Sets a service as delivery or payment service for an order.
362
	 *
363
	 * @param \Aimeos\MShop\Order\Item\Base\Service\Iface $service Order service item for the given domain
364
	 * @param string $type Service type constant from \Aimeos\MShop\Order\Item\Service\Base
365
	 */
366
	public function setService( \Aimeos\MShop\Order\Item\Base\Service\Iface $service, $type )
367
	{
368
		$this->checkPrice( $service->getPrice() );
369
370
		$this->notifyListeners( 'setService.before', $service );
371
372
		$service = clone $service;
373
		$service->setType( $type ); // enforce that the type is the same as the given one
374
		$service->setId( null ); // enforce saving as new item
375
376
		$this->services[$type] = $service;
377
		$this->setModified();
378
379
		$this->notifyListeners( 'setService.after', $service );
380
	}
381
382
383
	/**
384
	 * Deletes the delivery or payment service from the basket.
385
	 *
386
	 * @param string $type Service type constant from \Aimeos\MShop\Order\Item\Service\Base
387
	 */
388
	public function deleteService( $type )
389
	{
390
		if( !isset( $this->services[$type] ) ) {
391
			return;
392
		}
393
394
		$this->notifyListeners( 'deleteService.before', $type );
395
396
		$service = $this->services[$type];
397
		unset( $this->services[$type] );
398
		$this->setModified();
399
400
		$this->notifyListeners( 'deleteService.after', $service );
401
	}
402
403
404
	/**
405
	 * Returns the available coupon codes and the lists of affected product items.
406
	 *
407
	 * @return array Associative array of codes and lists of product items
408
	 *  implementing \Aimeos\MShop\Order\Product\Iface
409
	 */
410
	public function getCoupons()
411
	{
412
		return $this->coupons;
413
	}
414
415
416
	/**
417
	 * Adds a coupon code entered by the customer and the given product item to the basket.
418
	 *
419
	 * @param string $code Coupon code
420
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface[] $products List of coupon products
421
	 */
422
	public function addCoupon( $code, array $products = [] )
423
	{
424
		if( isset( $this->coupons[$code] ) ) {
425
			throw new \Aimeos\MShop\Order\Exception( sprintf( 'Duplicate coupon code "%1$s"', $code ) );
426
		}
427
428
		foreach( $products as $product )
429
		{
430
			$this->checkProduct( $product );
431
			$this->checkPrice( $product->getPrice() );
432
		}
433
434
		$this->notifyListeners( 'addCoupon.before', $products );
435
436
		$this->coupons[$code] = $products;
437
438
		foreach( $products as $product ) {
439
			$this->products[] = $product;
440
		}
441
442
		$this->setModified();
443
444
		$this->notifyListeners( 'addCoupon.after', $code );
445
	}
446
447
448
	/**
449
	 * Removes a coupon and the related product items from the basket.
450
	 *
451
	 * @param string $code Coupon code
452
	 * @param boolean $removecode If the coupon code should also be removed
453
	 * @return array List of affected product items implementing \Aimeos\MShop\Order\Item\Base\Product\Iface
454
	 *  or an empty list if no products are affected by a coupon
455
	 */
456
	public function deleteCoupon( $code, $removecode = false )
457
	{
458
		$products = [];
459
460
		if( isset( $this->coupons[$code] ) )
461
		{
462
			$this->notifyListeners( 'deleteCoupon.before', $code );
463
464
			$products = $this->coupons[$code];
465
466
			foreach( $products as $product )
467
			{
468
				if( ( $key = array_search( $product, $this->products, true ) ) !== false ) {
469
					unset( $this->products[$key] );
470
				}
471
			}
472
473
			if( $removecode === true ) {
474
				unset( $this->coupons[$code] );
475
			} else {
476
				$this->coupons[$code] = [];
477
			}
478
479
			$this->setModified();
480
481
			$this->notifyListeners( 'deleteCoupon.after', $code );
482
		}
483
484
		return $products;
485
	}
486
487
488
	/**
489
	 * Tests if all necessary items are available to create the order.
490
	 *
491
	 * @param integer $what Test for the specific type of completeness
492
	 * @throws \Aimeos\MShop\Order\Exception if there are no products in the basket
493
	 */
494
	public function check( $what = self::PARTS_ALL )
495
	{
496
		$this->checkParts( $what );
497
498
		$this->notifyListeners( 'check.before', $what );
499
500
		if( ( $what & self::PARTS_PRODUCT ) && ( count( $this->products ) < 1 ) ) {
501
			throw new \Aimeos\MShop\Order\Exception( sprintf( 'Basket empty' ) );
502
		}
503
504
		$this->notifyListeners( 'check.after', $what );
505
	}
506
507
508
	/**
509
	 * Tests if the order object was modified.
510
	 *
511
	 * @return bool True if modified, false if not
512
	 */
513
	public function isModified()
514
	{
515
		return $this->modified;
516
	}
517
518
519
	/**
520
	 * Sets the modified flag of the object.
521
	 */
522
	public function setModified()
523
	{
524
		$this->modified = true;
525
	}
526
527
528
	/**
529
	 * Checks the constants for the different parts of the basket.
530
	 *
531
	 * @param integer $value Part constant
532
	 * @throws \Aimeos\MShop\Order\Exception If parts constant is invalid
533
	 */
534
	protected function checkParts( $value )
535
	{
536
		$value = (int) $value;
537
538
		if( $value < self::PARTS_NONE || $value > self::PARTS_ALL ) {
539
			throw new \Aimeos\MShop\Order\Exception( sprintf( 'Flags "%1$s" not within allowed range', $value ) );
540
		}
541
	}
542
543
544
	/**
545
	 * Checks if the price uses the same currency as the price in the basket.
546
	 *
547
	 * @param \Aimeos\MShop\Price\Item\Iface $item Price item
548
	 * @return null
549
	 */
550
	abstract protected function checkPrice( \Aimeos\MShop\Price\Item\Iface $item );
551
552
553
	/**
554
	 * Checks if a order product contains all required values.
555
	 *
556
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $item Order product item
557
	 * @throws \Aimeos\MShop\Exception if the price item or product code is missing
558
	 */
559
	protected function checkProduct( \Aimeos\MShop\Order\Item\Base\Product\Iface $item )
560
	{
561
		if( $item->getProductCode() === '' ) {
562
			throw new \Aimeos\MShop\Order\Exception( sprintf( 'Product does not contain all required values. Product code for item not available.' ) );
563
		}
564
	}
565
566
567
	/**
568
	 * Tests if the given product is similar to an existing one.
569
	 * Similarity is described by the equality of properties so the quantity of
570
	 * the existing product can be updated.
571
	 *
572
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $item Order product item
573
	 * @param array $products List of order product items to check against
574
	 * @return integer|false Positon of the same product in the product list of false if product is unique
575
	 * @throws \Aimeos\MShop\Order\Exception If no similar item was found
576
	 */
577
	protected function getSameProduct( \Aimeos\MShop\Order\Item\Base\Product\Iface $item, array $products )
578
	{
579
		$attributeMap = [];
580
581
		foreach( $item->getAttributes() as $attributeItem ) {
582
			$attributeMap[$attributeItem->getCode()] = $attributeItem;
583
		}
584
585
		foreach( $products as $position => $product )
586
		{
587
			if( $product->compare( $item ) === false ) {
588
				continue;
589
			}
590
591
			$prodAttributes = $product->getAttributes();
592
593
			if( count( $prodAttributes ) !== count( $attributeMap ) ) {
594
				continue;
595
			}
596
597
			foreach( $prodAttributes as $attribute )
598
			{
599
				if( array_key_exists( $attribute->getCode(), $attributeMap ) === false
600
					|| $attributeMap[$attribute->getCode()]->getValue() != $attribute->getValue() ) {
601
					continue 2; // jump to outer loop
602
				}
603
			}
604
605
			return $position;
606
		}
607
608
		return false;
609
	}
610
}
611