Completed
Push — master ( ee2c71...0751df )
by Aimeos
03:00
created

Standard::addProduct()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 43
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 43
rs 8.8571
cc 3
eloc 28
nc 3
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
 * Default implementation of the basket frontend controller.
17
 *
18
 * @package Controller
19
 * @subpackage Frontend
20
 */
21
class Standard extends Base implements Iface, \Aimeos\Controller\Frontend\Common\Iface
22
{
23
	private $basket;
24
	private $domainManager;
25
26
27
	/**
28
	 * Initializes the frontend controller.
29
	 *
30
	 * @param \Aimeos\MShop\Context\Item\Iface $context Object storing the required instances for manaing databases
31
	 *  connections, logger, session, etc.
32
	 */
33
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
34
	{
35
		parent::__construct( $context );
36
37
		$this->domainManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base' );
38
		$this->basket = $this->domainManager->getSession();
39
40
		$this->checkLocale();
41
	}
42
43
44
	/**
45
	 * Empties the basket and removing all products, addresses, services, etc.
46
	 */
47
	public function clear()
48
	{
49
		$this->basket = $this->domainManager->createItem();
50
		$this->domainManager->setSession( $this->basket );
51
	}
52
53
54
	/**
55
	 * Returns the basket object.
56
	 *
57
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Basket holding products, addresses and delivery/payment options
58
	 */
59
	public function get()
60
	{
61
		return $this->basket;
62
	}
63
64
65
	/**
66
	 * Adds a categorized product to the basket of the user stored in the session.
67
	 *
68
	 * @param string $prodid ID of the base product to add
69
	 * @param integer $quantity Amount of products that should by added
70
	 * @param array $options Possible options are: 'stock'=>true|false and 'variant'=>true|false
71
	 * 	The 'stock'=>false option allows adding products without being in stock.
72
	 * 	The 'variant'=>false option allows adding the selection product to the basket
73
	 * 	instead of the specific sub-product if the variant-building attribute IDs
74
	 * 	doesn't match a specific sub-product or if the attribute IDs are missing.
75
	 * @param array $variantAttributeIds List of variant-building attribute IDs that identify a specific product
76
	 * 	in a selection products
77
	 * @param array $configAttributeIds  List of attribute IDs that doesn't identify a specific product in a
78
	 * 	selection of products but are stored together with the product (e.g. for configurable products)
79
	 * @param array $hiddenAttributeIds List of attribute IDs that should be stored along with the product in the order
80
	 * @param array $customAttributeValues Associative list of attribute IDs and arbitrary values that should be stored
81
	 * 	along with the product in the order
82
	 * @param string $warehouse Unique code of the warehouse to deliver the products from
83
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the product isn't available
84
	 */
85
	public function addProduct( $prodid, $quantity = 1, array $options = array(), array $variantAttributeIds = array(),
86
		array $configAttributeIds = array(), array $hiddenAttributeIds = array(), array $customAttributeValues = array(),
87
		$warehouse = 'default' )
88
	{
89
		$context = $this->getContext();
90
91
		$productItem = $this->getDomainItem( 'product', 'product.id', $prodid, array( 'media', 'supplier', 'price', 'product', 'text' ) );
92
93
		$orderBaseProductItem = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product' )->createItem();
94
		$orderBaseProductItem->copyFrom( $productItem );
95
		$orderBaseProductItem->setQuantity( $quantity );
96
		$orderBaseProductItem->setWarehouseCode( $warehouse );
97
98
		$attr = array();
99
		$prices = $productItem->getRefItems( 'price', 'default', 'default' );
100
101
		switch( $productItem->getType() )
102
		{
103
			case 'select':
104
				$attr = $this->getVariantDetails( $orderBaseProductItem, $productItem, $prices, $variantAttributeIds, $options );
105
				break;
106
			case 'bundle':
107
				$this->addBundleProducts( $orderBaseProductItem, $productItem, $variantAttributeIds, $warehouse );
108
				break;
109
		}
110
111
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
112
		$price = $priceManager->getLowestPrice( $prices, $quantity );
113
114
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $configAttributeIds, 'config' ) );
115
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $hiddenAttributeIds, 'hidden' ) );
116
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, array_keys( $customAttributeValues ), 'custom', $customAttributeValues ) );
117
118
		// remove product rebate of original price in favor to rebates granted for the order
119
		$price->setRebate( '0.00' );
120
121
		$orderBaseProductItem->setPrice( $price );
122
		$orderBaseProductItem->setAttributes( $attr );
123
124
		$this->addProductInStock( $orderBaseProductItem, $productItem->getId(), $quantity, $options, $warehouse );
125
126
		$this->domainManager->setSession( $this->basket );
127
	}
128
129
130
	/**
131
	 * Deletes a product item from the basket.
132
	 *
133
	 * @param integer $position Position number (key) of the order product item
134
	 */
135
	public function deleteProduct( $position )
136
	{
137
		$product = $this->basket->getProduct( $position );
138
139
		if( $product->getFlags() === \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
140
		{
141
			$msg = sprintf( 'Basket item at position "%1$d" cannot be deleted manually', $position );
142
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
143
		}
144
145
		$this->basket->deleteProduct( $position );
146
		$this->domainManager->setSession( $this->basket );
147
	}
148
149
150
	/**
151
	 * Edits the quantity of a product item in the basket.
152
	 *
153
	 * @param integer $position Position number (key) of the order product item
154
	 * @param integer $quantity New quantiy of the product item
155
	 * @param array $options Possible options are: 'stock'=>true|false
156
	 * 	The 'stock'=>false option allows adding products without being in stock.
157
	 * @param string[] $configAttributeCodes Codes of the product config attributes that should be REMOVED
158
	 */
159
	public function editProduct( $position, $quantity, array $options = array(),
160
		array $configAttributeCodes = array() )
161
	{
162
		$product = $this->basket->getProduct( $position );
163
		$product->setQuantity( $quantity ); // Enforce check immediately
164
165
		if( $product->getFlags() & \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
166
		{
167
			$msg = sprintf( 'Basket item at position "%1$d" cannot be changed', $position );
168
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
169
		}
170
171
		$attributes = $product->getAttributes();
172
		foreach( $attributes as $key => $attribute )
173
		{
174
			if( in_array( $attribute->getCode(), $configAttributeCodes ) ) {
175
				unset( $attributes[$key] );
176
			}
177
		}
178
		$product->setAttributes( $attributes );
179
180
		$productItem = $this->getDomainItem( 'product', 'product.code', $product->getProductCode(), array( 'price', 'text' ) );
181
		$prices = $productItem->getRefItems( 'price', 'default' );
182
183
		$product->setPrice( $this->calcPrice( $product, $prices, $quantity ) );
184
185
		$this->editProductInStock( $product, $productItem, $quantity, $position, $options );
186
187
		$this->domainManager->setSession( $this->basket );
188
	}
189
190
191
	/**
192
	 * Adds the given coupon code and updates the basket.
193
	 *
194
	 * @param string $code Coupon code entered by the user
195
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid or not allowed
196
	 */
197
	public function addCoupon( $code )
198
	{
199
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon' );
200
		$codeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon/code' );
201
202
203
		$search = $codeManager->createSearch( true );
204
		$expr = array(
205
			$search->compare( '==', 'coupon.code.code', $code ),
206
			$search->getConditions(),
207
		);
208
		$search->setConditions( $search->combine( '&&', $expr ) );
209
		$search->setSlice( 0, 1 );
210
211
		$result = $codeManager->searchItems( $search );
212
213
		if( ( $codeItem = reset( $result ) ) === false ) {
214
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon code "%1$s" is invalid or not available any more', $code ) );
215
		}
216
217
218
		$search = $manager->createSearch( true );
219
		$expr = array(
220
			$search->compare( '==', 'coupon.id', $codeItem->getParentId() ),
221
			$search->getConditions(),
222
		);
223
		$search->setConditions( $search->combine( '&&', $expr ) );
224
		$search->setSlice( 0, 1 );
225
226
		$result = $manager->searchItems( $search );
227
228
		if( ( $item = reset( $result ) ) === false ) {
229
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon for code "%1$s" is not available any more', $code ) );
230
		}
231
232
233
		$provider = $manager->getProvider( $item, $code );
234
235
		if( $provider->isAvailable( $this->basket ) !== true ) {
236
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Requirements for coupon code "%1$s" aren\'t met', $code ) );
237
		}
238
239
		$provider->addCoupon( $this->basket );
240
		$this->domainManager->setSession( $this->basket );
241
	}
242
243
244
	/**
245
	 * Removes the given coupon code and its effects from the basket.
246
	 *
247
	 * @param string $code Coupon code entered by the user
248
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid
249
	 */
250
	public function deleteCoupon( $code )
251
	{
252
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon' );
253
254
		$search = $manager->createSearch();
255
		$search->setConditions( $search->compare( '==', 'coupon.code.code', $code ) );
256
		$search->setSlice( 0, 1 );
257
258
		$result = $manager->searchItems( $search );
259
260
		if( ( $item = reset( $result ) ) === false ) {
261
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon code "%1$s" is invalid', $code ) );
262
		}
263
264
		$manager->getProvider( $item, $code )->deleteCoupon( $this->basket );
265
		$this->domainManager->setSession( $this->basket );
266
	}
267
268
269
	/**
270
	 * Sets the address of the customer in the basket.
271
	 *
272
	 * @param string $type Address type constant from \Aimeos\MShop\Order\Item\Base\Address\Base
273
	 * @param \Aimeos\MShop\Common\Item\Address\Iface|array|null $value Address object or array with key/value pairs of address or null to remove address from basket
274
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the billing or delivery address is not of any required type of
275
	 * 	if one of the keys is invalid when using an array with key/value pairs
276
	 */
277
	public function setAddress( $type, $value )
278
	{
279
		$address = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/address' )->createItem();
280
		$address->setType( $type );
281
282
		if( $value instanceof \Aimeos\MShop\Common\Item\Address\Iface )
283
		{
284
			$address->copyFrom( $value );
285
			$this->basket->setAddress( $address, $type );
286
		}
287
		else if( is_array( $value ) )
288
		{
289
			$this->setAddressFromArray( $address, $value );
290
			$this->basket->setAddress( $address, $type );
291
		}
292
		else if( $value === null )
293
		{
294
			$this->basket->deleteAddress( $type );
295
		}
296
		else
297
		{
298
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Invalid value for address type "%1$s"', $type ) );
299
		}
300
301
		$this->domainManager->setSession( $this->basket );
302
	}
303
304
305
	/**
306
	 * Sets the delivery/payment service item based on the service ID.
307
	 *
308
	 * @param string $type Service type code like 'payment' or 'delivery'
309
	 * @param string $id Unique ID of the service item
310
	 * @param array $attributes Associative list of key/value pairs containing the attributes selected or
311
	 * 	entered by the customer when choosing one of the delivery or payment options
312
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there is no price to the service item attached
313
	 */
314
	public function setService( $type, $id, array $attributes = array() )
315
	{
316
		$context = $this->getContext();
317
318
		$serviceManager = \Aimeos\MShop\Factory::createManager( $context, 'service' );
319
		$serviceItem = $this->getDomainItem( 'service', 'service.id', $id, array( 'media', 'price', 'text' ) );
320
321
		$provider = $serviceManager->getProvider( $serviceItem );
322
		$result = $provider->checkConfigFE( $attributes );
323
		$unknown = array_diff_key( $attributes, $result );
324
325
		if( count( $unknown ) > 0 )
326
		{
327
			$msg = sprintf( 'Unknown attributes "%1$s"', implode( '","', array_keys( $unknown ) ) );
328
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
329
		}
330
331
		foreach( $result as $key => $value )
332
		{
333
			if( $value !== null ) {
334
				throw new \Aimeos\Controller\Frontend\Basket\Exception( $value );
335
			}
336
		}
337
338
		$orderBaseServiceManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/service' );
339
		$orderServiceItem = $orderBaseServiceManager->createItem();
340
		$orderServiceItem->copyFrom( $serviceItem );
341
342
		$price = $provider->calcPrice( $this->basket );
343
		// remove service rebate of original price
344
		$price->setRebate( '0.00' );
345
		$orderServiceItem->setPrice( $price );
346
347
		$provider->setConfigFE( $orderServiceItem, $attributes );
348
349
		$this->basket->setService( $orderServiceItem, $type );
350
		$this->domainManager->setSession( $this->basket );
351
	}
352
353
354
	/**
355
	 * Adds the bundled products to the order product item.
356
	 *
357
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem Order product item
358
	 * @param \Aimeos\MShop\Product\Item\Iface $productItem Bundle product item
359
	 * @param array $variantAttributeIds List of product variant attribute IDs
360
	 * @param string $warehouse
361
	 */
362
	protected function addBundleProducts( \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem,
363
		\Aimeos\MShop\Product\Item\Iface $productItem, array $variantAttributeIds, $warehouse )
364
	{
365
		$quantity = $orderBaseProductItem->getQuantity();
366
		$products = $subProductIds = $orderProducts = array();
367
		$orderProductManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/product' );
368
369
		foreach( $productItem->getRefItems( 'product', null, 'default' ) as $item ) {
370
			$subProductIds[] = $item->getId();
371
		}
372
373
		if( count( $subProductIds ) > 0 )
374
		{
375
			$productManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product' );
376
377
			$search = $productManager->createSearch( true );
378
			$expr = array(
379
				$search->compare( '==', 'product.id', $subProductIds ),
380
				$search->getConditions(),
381
			);
382
			$search->setConditions( $search->combine( '&&', $expr ) );
383
384
			$products = $productManager->searchItems( $search, array( 'attribute', 'media', 'price', 'text' ) );
385
		}
386
387
		foreach( $products as $product )
388
		{
389
			$prices = $product->getRefItems( 'price', 'default', 'default' );
390
391
			$orderProduct = $orderProductManager->createItem();
392
			$orderProduct->copyFrom( $product );
393
			$orderProduct->setWarehouseCode( $warehouse );
394
			$orderProduct->setPrice( $this->calcPrice( $orderProduct, $prices, $quantity ) );
395
396
			$orderProducts[] = $orderProduct;
397
		}
398
399
		$orderBaseProductItem->setProducts( $orderProducts );
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Order\Item\Base\Product\Iface as the method setProducts() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Product\Standard.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
400
	}
401
402
403
	/**
404
	 * Edits the changed product to the basket if it's in stock.
405
	 *
406
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem Old order product from basket
407
	 * @param string $productId Unique ID of the product item that belongs to the order product
408
	 * @param integer $quantity Number of products to add to the basket
409
	 * @param array $options Associative list of options
410
	 * @param string $warehouse Warehouse code for retrieving the stock level
411
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there's not enough stock available
412
	 */
413
	protected function addProductInStock( \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem,
414
			$productId, $quantity, array $options, $warehouse )
415
	{
416
		$stocklevel = null;
417
		if( !isset( $options['stock'] ) || $options['stock'] != false ) {
418
			$stocklevel = $this->getStockLevel( $productId, $warehouse );
419
		}
420
421
		if( $stocklevel === null || $stocklevel > 0 )
422
		{
423
			$position = $this->get()->addProduct( $orderBaseProductItem );
424
			$orderBaseProductItem = clone $this->get()->getProduct( $position );
425
			$quantity = $orderBaseProductItem->getQuantity();
426
427
			if( $stocklevel > 0 && $stocklevel < $quantity )
428
			{
429
				$this->get()->deleteProduct( $position );
430
				$orderBaseProductItem->setQuantity( $stocklevel );
431
				$this->get()->addProduct( $orderBaseProductItem, $position );
432
			}
433
		}
434
435
		if( $stocklevel !== null && $stocklevel < $quantity )
436
		{
437
			$msg = sprintf( 'There are not enough products "%1$s" in stock', $orderBaseProductItem->getName() );
438
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
439
		}
440
	}
441
442
443
	/**
444
	 * Creates the order product attribute items from the given attribute IDs and updates the price item if necessary.
445
	 *
446
	 * @param \Aimeos\MShop\Price\Item\Iface $price Price item of the ordered product
447
	 * @param string $prodid Unique product ID where the given attributes must be attached to
448
	 * @param integer $quantity Number of products that should be added to the basket
449
	 * @param array $attributeIds List of attributes IDs of the given type
450
	 * @param string $type Attribute type
451
	 * @param array $attributeValues Associative list of attribute IDs as keys and their codes as values
452
	 * @return array List of items implementing \Aimeos\MShop\Order\Item\Product\Attribute\Iface
453
	 */
454
	protected function createOrderProductAttributes( \Aimeos\MShop\Price\Item\Iface $price, $prodid, $quantity,
455
			array $attributeIds, $type, array $attributeValues = array() )
456
	{
457
		if( empty( $attributeIds ) ) {
458
			return array();
459
		}
460
461
		$attrTypeId = $this->getProductListTypeItem( 'attribute', $type )->getId();
462
		$this->checkReferences( $prodid, 'attribute', $attrTypeId, $attributeIds );
463
464
		$list = array();
465
		$context = $this->getContext();
466
467
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
468
		$orderProductAttributeManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product/attribute' );
469
470
		foreach( $this->getAttributes( $attributeIds ) as $id => $attrItem )
471
		{
472
			$prices = $attrItem->getRefItems( 'price', 'default', 'default' );
473
474
			if( !empty( $prices ) ) {
475
				$price->addItem( $priceManager->getLowestPrice( $prices, $quantity ) );
476
			}
477
478
			$item = $orderProductAttributeManager->createItem();
479
			$item->copyFrom( $attrItem );
480
			$item->setType( $type );
481
482
			if( isset( $attributeValues[$id] ) ) {
483
				$item->setValue( $attributeValues[$id] );
484
			}
485
486
			$list[] = $item;
487
		}
488
489
		return $list;
490
	}
491
492
493
	/**
494
	 * Edits the changed product to the basket if it's in stock.
495
	 *
496
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $product Old order product from basket
497
	 * @param \Aimeos\MShop\Product\Item\Iface $productItem Product item that belongs to the order product
498
	 * @param integer $quantity New product quantity
499
	 * @param integer $position Position of the old order product in the basket
500
	 * @param array Associative list of options
501
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there's not enough stock available
502
	 */
503
	protected function editProductInStock( \Aimeos\MShop\Order\Item\Base\Product\Iface $product,
504
			\Aimeos\MShop\Product\Item\Iface $productItem, $quantity, $position, array $options )
505
	{
506
		$stocklevel = null;
507
		if( !isset( $options['stock'] ) || $options['stock'] != false ) {
508
			$stocklevel = $this->getStockLevel( $productItem->getId(), $product->getWarehouseCode() );
509
		}
510
511
		$product->setQuantity( ( $stocklevel !== null && $stocklevel > 0 ? min( $stocklevel, $quantity ) : $quantity ) );
512
513
		$this->get()->deleteProduct( $position );
514
515
		if( $stocklevel === null || $stocklevel > 0 ) {
516
			$this->get()->addProduct( $product, $position );
517
		}
518
519
		if( $stocklevel !== null && $stocklevel < $quantity )
520
		{
521
			$msg = sprintf( 'There are not enough products "%1$s" in stock', $productItem->getName() );
522
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
523
		}
524
	}
525
526
527
	/**
528
	 * Retrieves the domain item specified by the given key and value.
529
	 *
530
	 * @param string $domain Product manager search key
531
	 * @param string $key Domain manager search key
532
	 * @param string $value Unique domain identifier
533
	 * @param string[] $ref List of referenced items that should be fetched too
534
	 * @return \Aimeos\MShop\Common\Item\Iface Domain item object
535
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception
536
	 */
537
	protected function getDomainItem( $domain, $key, $value, array $ref )
538
	{
539
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $domain );
540
541
		$search = $manager->createSearch( true );
542
		$expr = array(
543
				$search->compare( '==', $key, $value ),
544
				$search->getConditions(),
545
		);
546
		$search->setConditions( $search->combine( '&&', $expr ) );
547
548
		$result = $manager->searchItems( $search, $ref );
549
550
		if( ( $item = reset( $result ) ) === false )
551
		{
552
			$msg = sprintf( 'No item for "%1$s" (%2$s) found', $value, $key );
553
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
554
		}
555
556
		return $item;
557
	}
558
559
560
	/**
561
	 * Returns the variant attributes and updates the price list if necessary.
562
	 *
563
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem Order product item
564
	 * @param \Aimeos\MShop\Product\Item\Iface &$productItem Product item which is replaced if necessary
565
	 * @param array &$prices List of product prices that will be updated if necessary
566
	 * @param array $variantAttributeIds List of product variant attribute IDs
567
	 * @param array $options Associative list of options
568
	 * @return \Aimeos\MShop\Order\Item\Base\Product\Attribute\Iface[] List of order product attributes
569
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If no product variant is found
570
	 */
571
	protected function getVariantDetails( \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem,
572
		\Aimeos\MShop\Product\Item\Iface &$productItem, array &$prices, array $variantAttributeIds, array $options )
573
	{
574
		$attr = array();
575
		$productItems = $this->getProductVariants( $productItem, $variantAttributeIds );
576
577
		if( count( $productItems ) > 1 )
578
		{
579
			$msg = sprintf( 'No unique article found for selected attributes and product ID "%1$s"', $productItem->getId() );
580
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
581
		}
582
		else if( ( $result = reset( $productItems ) ) !== false ) // count == 1
583
		{
584
			$productItem = $result;
585
			$orderBaseProductItem->setProductCode( $productItem->getCode() );
586
587
			$subprices = $productItem->getRefItems( 'price', 'default', 'default' );
588
589
			if( count( $subprices ) > 0 ) {
590
				$prices = $subprices;
591
			}
592
593
			$orderProductAttrManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/product/attribute' );
594
			$variantAttributes = $productItem->getRefItems( 'attribute', null, 'variant' );
595
596
			foreach( $this->getAttributes( array_keys( $variantAttributes ), array( 'text' ) ) as $attrItem )
597
			{
598
				$orderAttributeItem = $orderProductAttrManager->createItem();
599
				$orderAttributeItem->copyFrom( $attrItem );
600
				$orderAttributeItem->setType( 'variant' );
601
602
				$attr[] = $orderAttributeItem;
603
			}
604
		}
605
		else if( !isset( $options['variant'] ) || $options['variant'] != false ) // count == 0
606
		{
607
			$msg = sprintf( 'No article found for selected attributes and product ID "%1$s"', $productItem->getId() );
608
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
609
		}
610
611
		return $attr;
612
	}
613
614
615
	/**
616
	 * Fills the order address object with the values from the array.
617
	 *
618
	 * @param \Aimeos\MShop\Order\Item\Base\Address\Iface $address Address item to store the values into
619
	 * @param array $map Associative array of key/value pairs. The keys must be the same as when calling toArray() from
620
	 * 	an address item.
621
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception
622
	 */
623
	protected function setAddressFromArray( \Aimeos\MShop\Order\Item\Base\Address\Iface $address, array $map )
624
	{
625
		foreach( $map as $key => $value ) {
626
			$map[$key] = strip_tags( $value ); // prevent XSS
627
		}
628
629
		$errors = $address->fromArray( $map );
630
631
		if( count( $errors ) > 0 )
632
		{
633
			$msg = sprintf( 'Invalid address properties, please check your input' );
634
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg, 0, null, $errors );
635
		}
636
	}
637
}
638