Completed
Push — master ( 4ffab1...137f58 )
by Aimeos
07:59
created

Base::checkReferences()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 30
rs 8.5806
cc 4
eloc 16
nc 4
nop 4
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2012
6
 * @copyright Aimeos (aimeos.org), 2015-2016
7
 * @package Controller
8
 * @subpackage Frontend
9
 */
10
11
12
namespace Aimeos\Controller\Frontend\Basket;
13
14
15
/**
16
 * Base class for the basket frontend controller
17
 *
18
 * @package Controller
19
 * @subpackage Frontend
20
 */
21
abstract class Base extends \Aimeos\Controller\Frontend\Base
22
{
23
	private $listTypeAttributes = array();
24
25
26
	/**
27
	 * Calculates and returns the current price for the given order product and product prices.
28
	 *
29
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $product Ordered product item
30
	 * @param \Aimeos\MShop\Price\Item\Iface[] $prices List of price items
31
	 * @param integer $quantity New product quantity
32
	 * @return \Aimeos\MShop\Price\Item\Iface Price item with calculated price
33
	 */
34
	protected function calcPrice( \Aimeos\MShop\Order\Item\Base\Product\Iface $product, array $prices, $quantity )
35
	{
36
		$context = $this->getContext();
37
38
		if( empty( $prices ) )
39
		{
40
			$parentItem = $this->getDomainItem( 'product', 'product.id', $product->getProductId(), array( 'price' ) );
41
			$prices = $parentItem->getRefItems( 'price', 'default' );
42
		}
43
44
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
45
		$price = $priceManager->getLowestPrice( $prices, $quantity );
46
47
		foreach( $this->getAttributeItems( $product->getAttributes() ) as $attrItem )
48
		{
49
			$prices = $attrItem->getRefItems( 'price', 'default' );
50
51
			if( count( $prices ) > 0 )
52
			{
53
				$attrPrice = $priceManager->getLowestPrice( $prices, $quantity );
54
				$price->addItem( $attrPrice );
55
			}
56
		}
57
58
		// remove product rebate of original price in favor to rebates granted for the order
59
		$price->setRebate( '0.00' );
60
61
		return $price;
62
	}
63
64
65
	/**
66
	 * Checks if the IDs of the given items are really associated to the product.
67
	 *
68
	 * @param string $prodId Unique ID of the product
69
	 * @param string $domain Domain the references must be of
70
	 * @param integer $listTypeId ID of the list type the referenced items must be
71
	 * @param array $refIds List of IDs that must be associated to the product
72
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If one or more of the IDs are not associated
73
	 */
74
	protected function checkReferences( $prodId, $domain, $listTypeId, array $refIds )
75
	{
76
		$productManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product' );
77
		$search = $productManager->createSearch( true );
78
79
		$expr = array(
80
			$search->compare( '==', 'product.id', $prodId ),
81
			$search->getConditions(),
82
		);
83
84
		if( count( $refIds ) > 0 )
85
		{
86
			foreach( $refIds as $key => $refId ) {
87
				$refIds[$key] = (string) $refId;
88
			}
89
90
			$param = array( $domain, $listTypeId, $refIds );
91
			$cmpfunc = $search->createFunction( 'product.contains', $param );
92
93
			$expr[] = $search->compare( '==', $cmpfunc, count( $refIds ) );
94
		}
95
96
		$search->setConditions( $search->combine( '&&', $expr ) );
97
98
		if( count( $productManager->searchItems( $search, array() ) ) === 0 )
99
		{
100
			$msg = sprintf( 'Invalid "%1$s" references for product with ID "%2$s"', $domain, $prodId );
101
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
102
		}
103
	}
104
105
106
	/**
107
	 * Creates the order product attribute items from the given attribute IDs and updates the price item if necessary.
108
	 *
109
	 * @param \Aimeos\MShop\Price\Item\Iface $price Price item of the ordered product
110
	 * @param string $prodid Unique product ID where the given attributes must be attached to
111
	 * @param integer $quantity Number of products that should be added to the basket
112
	 * @param array $attributeIds List of attributes IDs of the given type
113
	 * @param string $type Attribute type
114
	 * @param array $attributeValues Associative list of attribute IDs as keys and their codes as values
115
	 * @return array List of items implementing \Aimeos\MShop\Order\Item\Product\Attribute\Iface
116
	 */
117
	protected function createOrderProductAttributes( \Aimeos\MShop\Price\Item\Iface $price, $prodid, $quantity,
118
			array $attributeIds, $type, array $attributeValues = array() )
119
	{
120
		if( empty( $attributeIds ) ) {
121
			return array();
122
		}
123
124
		$attrTypeId = $this->getProductListTypeItem( 'attribute', $type )->getId();
125
		$this->checkReferences( $prodid, 'attribute', $attrTypeId, $attributeIds );
126
127
		$list = array();
128
		$context = $this->getContext();
129
130
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
131
		$orderProductAttributeManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product/attribute' );
132
133
		foreach( $this->getAttributes( $attributeIds ) as $id => $attrItem )
134
		{
135
			$prices = $attrItem->getRefItems( 'price', 'default', 'default' );
136
137
			if( !empty( $prices ) ) {
138
				$price->addItem( $priceManager->getLowestPrice( $prices, $quantity ) );
139
			}
140
141
			$item = $orderProductAttributeManager->createItem();
142
			$item->copyFrom( $attrItem );
143
			$item->setType( $type );
144
145
			if( isset( $attributeValues[$id] ) ) {
146
				$item->setValue( $attributeValues[$id] );
147
			}
148
149
			$list[] = $item;
150
		}
151
152
		return $list;
153
	}
154
155
156
	/**
157
	 * Returns the attribute items for the given attribute IDs.
158
	 *
159
	 * @param array $attributeIds List of attribute IDs
160
	 * @param string[] $domains Names of the domain items that should be fetched too
161
	 * @return array List of items implementing \Aimeos\MShop\Attribute\Item\Iface
162
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the actual attribute number doesn't match the expected one
163
	 */
164
	protected function getAttributes( array $attributeIds, array $domains = array( 'price', 'text' ) )
165
	{
166
		if( empty( $attributeIds ) ) {
167
			return array();
168
		}
169
170
		$attributeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'attribute' );
171
172
		$search = $attributeManager->createSearch( true );
173
		$expr = array(
174
				$search->compare( '==', 'attribute.id', $attributeIds ),
175
				$search->getConditions(),
176
		);
177
		$search->setConditions( $search->combine( '&&', $expr ) );
178
		$search->setSlice( 0, 0x7fffffff );
179
180
		$attrItems = $attributeManager->searchItems( $search, $domains );
181
182
		if( count( $attrItems ) !== count( $attributeIds ) )
183
		{
184
			$expected = implode( ',', $attributeIds );
185
			$actual = implode( ',', array_keys( $attrItems ) );
186
			$msg = sprintf( 'Available attribute IDs "%1$s" do not match the given attribute IDs "%2$s"', $actual, $expected );
187
188
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
189
		}
190
191
		return $attrItems;
192
	}
193
194
195
	/**
196
	 * Returns the attribute items using the given order attribute items.
197
	 *
198
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Attribute\Item[] $orderAttributes List of order product attribute items
199
	 * @return \Aimeos\MShop\Attribute\Item\Iface[] Associative list of attribute IDs as key and attribute items as values
200
	 */
201
	protected function getAttributeItems( array $orderAttributes )
202
	{
203
		if( empty( $orderAttributes ) ) {
204
			return array();
205
		}
206
207
		$attributeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'attribute' );
208
		$search = $attributeManager->createSearch( true );
209
		$expr = array();
210
211
		foreach( $orderAttributes as $item )
212
		{
213
			$tmp = array(
214
				$search->compare( '==', 'attribute.domain', 'product' ),
215
				$search->compare( '==', 'attribute.code', $item->getValue() ),
216
				$search->compare( '==', 'attribute.type.domain', 'product' ),
217
				$search->compare( '==', 'attribute.type.code', $item->getCode() ),
218
				$search->compare( '>', 'attribute.type.status', 0 ),
219
				$search->getConditions(),
220
			);
221
			$expr[] = $search->combine( '&&', $tmp );
222
		}
223
224
		$search->setConditions( $search->combine( '||', $expr ) );
225
		return $attributeManager->searchItems( $search, array( 'price' ) );
226
	}
227
228
229
	/**
230
	 * Retrieves the domain item specified by the given key and value.
231
	 *
232
	 * @param string $domain Product manager search key
233
	 * @param string $key Domain manager search key
234
	 * @param string $value Unique domain identifier
235
	 * @param string[] $ref List of referenced items that should be fetched too
236
	 * @return \Aimeos\MShop\Common\Item\Iface Domain item object
237
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception
238
	 */
239
	protected function getDomainItem( $domain, $key, $value, array $ref )
240
	{
241
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $domain );
242
243
		$search = $manager->createSearch( true );
244
		$expr = array(
245
			$search->compare( '==', $key, $value ),
246
			$search->getConditions(),
247
		);
248
		$search->setConditions( $search->combine( '&&', $expr ) );
249
250
		$result = $manager->searchItems( $search, $ref );
251
252
		if( ( $item = reset( $result ) ) === false )
253
		{
254
			$msg = sprintf( 'No item for "%1$s" (%2$s) found', $value, $key );
255
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
256
		}
257
258
		return $item;
259
	}
260
261
262
	/**
263
	 * Returns the list type item for the given domain and code.
264
	 *
265
	 * @param string $domain Domain name of the list type
266
	 * @param string $code Code of the list type
267
	 * @return \Aimeos\MShop\Common\Item\Type\Iface List type item
268
	 */
269
	protected function getProductListTypeItem( $domain, $code )
270
	{
271
		if( !isset( $this->listTypeAttributes[$domain][$code] ) )
272
		{
273
			$listTypeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product/lists/type' );
274
275
			$listTypeSearch = $listTypeManager->createSearch( true );
276
			$expr = array(
277
				$listTypeSearch->compare( '==', 'product.lists.type.domain', $domain ),
278
				$listTypeSearch->compare( '==', 'product.lists.type.code', $code ),
279
				$listTypeSearch->getConditions(),
280
			);
281
			$listTypeSearch->setConditions( $listTypeSearch->combine( '&&', $expr ) );
282
283
			$listTypeItems = $listTypeManager->searchItems( $listTypeSearch );
284
285
			if( ( $listTypeItem = reset( $listTypeItems ) ) === false )
286
			{
287
				$msg = sprintf( 'List type for domain "%1$s" and code "%2$s" not found', $domain, $code );
288
				throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
289
			}
290
291
			$this->listTypeAttributes[$domain][$code] = $listTypeItem;
292
		}
293
294
		return $this->listTypeAttributes[$domain][$code];
295
	}
296
297
298
	/**
299
	 * Returns the value of an array or the default value if it's not available.
300
	 *
301
	 * @param array $values Associative list of key/value pairs
302
	 * @param string $name Name of the key to return the value for
303
	 * @param mixed $default Default value if no value is available for the given name
304
	 * @return mixed Value from the array or default value
305
	 */
306
	protected function getValue( array $values, $name, $default = null )
307
	{
308
		if( isset( $values[$name] ) ) {
309
			return $values[$name];
310
		}
311
312
		return $default;
313
	}
314
315
316
	/**
317
	 * Checks if the product is part of at least one category in the product catalog.
318
	 *
319
	 * @param string $prodid Unique ID of the product
320
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If product is not associated to at least one category
321
	 * @deprecated 2016.05
322
	 */
323
	protected function checkCategory( $prodid )
324
	{
325
		$catalogListManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'catalog/lists' );
326
327
		$search = $catalogListManager->createSearch( true );
328
		$expr = array(
329
				$search->compare( '==', 'catalog.lists.refid', $prodid ),
330
				$search->getConditions()
331
		);
332
		$search->setConditions( $search->combine( '&&', $expr ) );
333
		$search->setSlice( 0, 1 );
334
335
		$result = $catalogListManager->searchItems( $search );
336
337
		if( reset( $result ) === false )
338
		{
339
			$msg = sprintf( 'Adding product with ID "%1$s" is not allowed', $prodid );
340
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
341
		}
342
	}
343
344
345
	/**
346
	 * Edits the changed product to the basket if it's in stock.
347
	 *
348
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem Old order product from basket
349
	 * @param string $productId Unique ID of the product item that belongs to the order product
350
	 * @param integer $quantity Number of products to add to the basket
351
	 * @param array $options Associative list of options
352
	 * @param string $warehouse Warehouse code for retrieving the stock level
353
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there's not enough stock available
354
	 * @deprecated 2016.05
355
	 */
356
	protected function addProductInStock( \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem,
357
			$productId, $quantity, array $options, $warehouse )
358
	{
359
		$stocklevel = null;
360
		if( !isset( $options['stock'] ) || $options['stock'] != false ) {
361
			$stocklevel = $this->getStockLevel( $productId, $warehouse );
0 ignored issues
show
Deprecated Code introduced by
The method Aimeos\Controller\Fronte...t\Base::getStockLevel() has been deprecated with message: 2016.04 Use basket stock decorator instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
362
		}
363
364
		if( $stocklevel === null || $stocklevel > 0 )
365
		{
366
			$position = $this->basket->addProduct( $orderBaseProductItem );
0 ignored issues
show
Bug introduced by
The property basket does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
367
			$orderBaseProductItem = clone $this->basket->getProduct( $position );
368
			$quantity = $orderBaseProductItem->getQuantity();
369
370
			if( $stocklevel > 0 && $stocklevel < $quantity )
371
			{
372
				$this->basket->deleteProduct( $position );
373
				$orderBaseProductItem->setQuantity( $stocklevel );
374
				$this->basket->addProduct( $orderBaseProductItem, $position );
375
			}
376
		}
377
378
		if( $stocklevel !== null && $stocklevel < $quantity )
379
		{
380
			$msg = sprintf( 'There are not enough products "%1$s" in stock', $orderBaseProductItem->getName() );
381
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
382
		}
383
	}
384
385
386
	/**
387
	 * Edits the changed product to the basket if it's in stock.
388
	 *
389
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $product Old order product from basket
390
	 * @param \Aimeos\MShop\Product\Item\Iface $productItem Product item that belongs to the order product
391
	 * @param integer $quantity New product quantity
392
	 * @param integer $position Position of the old order product in the basket
393
	 * @param array Associative list of options
394
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there's not enough stock available
395
	 * @deprecated 2016.05
396
	 */
397
	protected function editProductInStock( \Aimeos\MShop\Order\Item\Base\Product\Iface $product,
398
			\Aimeos\MShop\Product\Item\Iface $productItem, $quantity, $position, array $options )
399
	{
400
		$stocklevel = null;
401
		if( !isset( $options['stock'] ) || $options['stock'] != false ) {
402
			$stocklevel = $this->getStockLevel( $productItem->getId(), $product->getWarehouseCode() );
0 ignored issues
show
Deprecated Code introduced by
The method Aimeos\Controller\Fronte...t\Base::getStockLevel() has been deprecated with message: 2016.04 Use basket stock decorator instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
403
		}
404
405
		$product->setQuantity( ( $stocklevel !== null && $stocklevel > 0 ? min( $stocklevel, $quantity ) : $quantity ) );
406
407
		$this->basket->deleteProduct( $position );
408
409
		if( $stocklevel === null || $stocklevel > 0 ) {
410
			$this->basket->addProduct( $product, $position );
411
		}
412
413
		if( $stocklevel !== null && $stocklevel < $quantity )
414
		{
415
			$msg = sprintf( 'There are not enough products "%1$s" in stock', $productItem->getName() );
416
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
417
		}
418
	}
419
420
421
	/**
422
	 * Returns the highest stock level for the product.
423
	 *
424
	 * @param string $prodid Unique ID of the product
425
	 * @param string $warehouse Unique code of the warehouse
426
	 * @return integer|null Number of available items in stock (null for unlimited stock)
427
	 * @deprecated 2016.04 Use basket stock decorator instead
428
	 */
429
	protected function getStockLevel( $prodid, $warehouse )
430
	{
431
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product/stock' );
432
433
		$search = $manager->createSearch( true );
434
		$expr = array(
435
				$search->compare( '==', 'product.stock.parentid', $prodid ),
436
				$search->getConditions(),
437
				$search->compare( '==', 'product.stock.warehouse.code', $warehouse ),
438
		);
439
		$search->setConditions( $search->combine( '&&', $expr ) );
440
441
		$result = $manager->searchItems( $search );
442
443
		if( empty( $result ) )
444
		{
445
			$msg = sprintf( 'No stock for product ID "%1$s" and warehouse "%2$s" available', $prodid, $warehouse );
446
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
447
		}
448
449
		$stocklevel = null;
450
451
		foreach( $result as $item )
452
		{
453
			if( ( $stock = $item->getStockLevel() ) === null ) {
454
				return null;
455
			}
456
457
			$stocklevel = max( (int) $stocklevel, $item->getStockLevel() );
458
		}
459
460
		return $stocklevel;
461
	}
462
}
463