Completed
Push — master ( ad1128...ee2c71 )
by Aimeos
02:39
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
	 * Fills the order address object with the values from the array.
405
	 *
406
	 * @param \Aimeos\MShop\Order\Item\Base\Address\Iface $address Address item to store the values into
407
	 * @param array $map Associative array of key/value pairs. The keys must be the same as when calling toArray() from
408
	 * 	an address item.
409
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception
410
	 */
411
	protected function setAddressFromArray( \Aimeos\MShop\Order\Item\Base\Address\Iface $address, array $map )
412
	{
413
		foreach( $map as $key => $value ) {
414
			$map[$key] = strip_tags( $value ); // prevent XSS
415
		}
416
417
		$errors = $address->fromArray( $map );
418
419
		if( count( $errors ) > 0 )
420
		{
421
			$msg = sprintf( 'Invalid address properties, please check your input' );
422
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg, 0, null, $errors );
423
		}
424
	}
425
426
427
	/**
428
	 * Edits the changed product to the basket if it's in stock.
429
	 *
430
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem Old order product from basket
431
	 * @param string $productId Unique ID of the product item that belongs to the order product
432
	 * @param integer $quantity Number of products to add to the basket
433
	 * @param array $options Associative list of options
434
	 * @param string $warehouse Warehouse code for retrieving the stock level
435
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there's not enough stock available
436
	 */
437
	protected function addProductInStock( \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem,
438
			$productId, $quantity, array $options, $warehouse )
439
	{
440
		$stocklevel = null;
441
		if( !isset( $options['stock'] ) || $options['stock'] != false ) {
442
			$stocklevel = $this->getStockLevel( $productId, $warehouse );
443
		}
444
445
		if( $stocklevel === null || $stocklevel > 0 )
446
		{
447
			$position = $this->get()->addProduct( $orderBaseProductItem );
448
			$orderBaseProductItem = clone $this->get()->getProduct( $position );
449
			$quantity = $orderBaseProductItem->getQuantity();
450
451
			if( $stocklevel > 0 && $stocklevel < $quantity )
452
			{
453
				$this->get()->deleteProduct( $position );
454
				$orderBaseProductItem->setQuantity( $stocklevel );
455
				$this->get()->addProduct( $orderBaseProductItem, $position );
456
			}
457
		}
458
459
		if( $stocklevel !== null && $stocklevel < $quantity )
460
		{
461
			$msg = sprintf( 'There are not enough products "%1$s" in stock', $orderBaseProductItem->getName() );
462
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
463
		}
464
	}
465
466
467
	/**
468
	 * Edits the changed product to the basket if it's in stock.
469
	 *
470
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $product Old order product from basket
471
	 * @param \Aimeos\MShop\Product\Item\Iface $productItem Product item that belongs to the order product
472
	 * @param integer $quantity New product quantity
473
	 * @param integer $position Position of the old order product in the basket
474
	 * @param array Associative list of options
475
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there's not enough stock available
476
	 */
477
	protected function editProductInStock( \Aimeos\MShop\Order\Item\Base\Product\Iface $product,
478
			\Aimeos\MShop\Product\Item\Iface $productItem, $quantity, $position, array $options )
479
	{
480
		$stocklevel = null;
481
		if( !isset( $options['stock'] ) || $options['stock'] != false ) {
482
			$stocklevel = $this->getStockLevel( $productItem->getId(), $product->getWarehouseCode() );
483
		}
484
485
		$product->setQuantity( ( $stocklevel !== null && $stocklevel > 0 ? min( $stocklevel, $quantity ) : $quantity ) );
486
487
		$this->get()->deleteProduct( $position );
488
489
		if( $stocklevel === null || $stocklevel > 0 ) {
490
			$this->get()->addProduct( $product, $position );
491
		}
492
493
		if( $stocklevel !== null && $stocklevel < $quantity )
494
		{
495
			$msg = sprintf( 'There are not enough products "%1$s" in stock', $productItem->getName() );
496
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
497
		}
498
	}
499
500
501
	/**
502
	 * Returns the variant attributes and updates the price list if necessary.
503
	 *
504
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem Order product item
505
	 * @param \Aimeos\MShop\Product\Item\Iface &$productItem Product item which is replaced if necessary
506
	 * @param array &$prices List of product prices that will be updated if necessary
507
	 * @param array $variantAttributeIds List of product variant attribute IDs
508
	 * @param array $options Associative list of options
509
	 * @return \Aimeos\MShop\Order\Item\Base\Product\Attribute\Iface[] List of order product attributes
510
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If no product variant is found
511
	 */
512
	protected function getVariantDetails( \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem,
513
		\Aimeos\MShop\Product\Item\Iface &$productItem, array &$prices, array $variantAttributeIds, array $options )
514
	{
515
		$attr = array();
516
		$productItems = $this->getProductVariants( $productItem, $variantAttributeIds );
517
518
		if( count( $productItems ) > 1 )
519
		{
520
			$msg = sprintf( 'No unique article found for selected attributes and product ID "%1$s"', $productItem->getId() );
521
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
522
		}
523
		else if( ( $result = reset( $productItems ) ) !== false ) // count == 1
524
		{
525
			$productItem = $result;
526
			$orderBaseProductItem->setProductCode( $productItem->getCode() );
527
528
			$subprices = $productItem->getRefItems( 'price', 'default', 'default' );
529
530
			if( count( $subprices ) > 0 ) {
531
				$prices = $subprices;
532
			}
533
534
			$orderProductAttrManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/product/attribute' );
535
			$variantAttributes = $productItem->getRefItems( 'attribute', null, 'variant' );
536
537
			foreach( $this->getAttributes( array_keys( $variantAttributes ), array( 'text' ) ) as $attrItem )
538
			{
539
				$orderAttributeItem = $orderProductAttrManager->createItem();
540
				$orderAttributeItem->copyFrom( $attrItem );
541
				$orderAttributeItem->setType( 'variant' );
542
543
				$attr[] = $orderAttributeItem;
544
			}
545
		}
546
		else if( !isset( $options['variant'] ) || $options['variant'] != false ) // count == 0
547
		{
548
			$msg = sprintf( 'No article found for selected attributes and product ID "%1$s"', $productItem->getId() );
549
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
550
		}
551
552
		return $attr;
553
	}
554
}
555