Completed
Push — master ( 3b492e...e328b7 )
by Aimeos
02:04
created

Standard::addProduct()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 43
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 43
rs 8.8571
c 0
b 0
f 0
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
22
	extends Base
2 ignored issues
show
Coding Style introduced by
The extends keyword must be on the same line as the class name
Loading history...
Coding Style introduced by
Expected 0 spaces between "Base" and comma; 1 found
Loading history...
23
	implements Iface, \Aimeos\Controller\Frontend\Common\Iface
1 ignored issue
show
Coding Style introduced by
The implements keyword must be on the same line as the class name
Loading history...
24
{
25
	private $basket;
26
	private $domainManager;
27
28
29
	/**
30
	 * Initializes the frontend controller.
31
	 *
32
	 * @param \Aimeos\MShop\Context\Item\Iface $context Object storing the required instances for manaing databases
33
	 *  connections, logger, session, etc.
34
	 */
35
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
36
	{
37
		parent::__construct( $context );
38
39
		$this->domainManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base' );
40
		$this->basket = $this->domainManager->getSession();
41
42
		$this->checkLocale();
43
	}
44
45
46
	/**
47
	 * Empties the basket and removing all products, addresses, services, etc.
48
	 */
49
	public function clear()
50
	{
51
		$this->basket = $this->domainManager->createItem();
52
		$this->domainManager->setSession( $this->basket );
53
	}
54
55
56
	/**
57
	 * Returns the basket object.
58
	 *
59
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Basket holding products, addresses and delivery/payment options
60
	 */
61
	public function get()
62
	{
63
		return $this->basket;
64
	}
65
66
67
	/**
68
	 * Explicitely persists the basket content
69
	 */
70
	public function save()
71
	{
72
		if( $this->basket->isModified() ) {
73
			$this->domainManager->setSession( $this->basket );
74
		}
75
	}
76
77
78
	/**
79
	 * Adds a categorized product to the basket of the user stored in the session.
80
	 *
81
	 * @param string $prodid ID of the base product to add
82
	 * @param integer $quantity Amount of products that should by added
83
	 * @param array $options Possible options are: 'stock'=>true|false and 'variant'=>true|false
84
	 * 	The 'stock'=>false option allows adding products without being in stock.
85
	 * 	The 'variant'=>false option allows adding the selection product to the basket
86
	 * 	instead of the specific sub-product if the variant-building attribute IDs
87
	 * 	doesn't match a specific sub-product or if the attribute IDs are missing.
88
	 * @param array $variantAttributeIds List of variant-building attribute IDs that identify a specific product
89
	 * 	in a selection products
90
	 * @param array $configAttributeIds  List of attribute IDs that doesn't identify a specific product in a
91
	 * 	selection of products but are stored together with the product (e.g. for configurable products)
92
	 * @param array $hiddenAttributeIds List of attribute IDs that should be stored along with the product in the order
93
	 * @param array $customAttributeValues Associative list of attribute IDs and arbitrary values that should be stored
94
	 * 	along with the product in the order
95
	 * @param string $stocktype Unique code of the stock type to deliver the products from
96
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the product isn't available
97
	 */
98
	public function addProduct( $prodid, $quantity = 1, array $options = array(), array $variantAttributeIds = array(),
99
		array $configAttributeIds = array(), array $hiddenAttributeIds = array(), array $customAttributeValues = array(),
100
		$stocktype = 'default' )
101
	{
102
		$context = $this->getContext();
103
104
		$productItem = $this->getDomainItem( 'product', 'product.id', $prodid, array( 'media', 'supplier', 'price', 'product', 'text' ) );
105
106
		$orderBaseProductItem = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product' )->createItem();
107
		$orderBaseProductItem->copyFrom( $productItem );
108
		$orderBaseProductItem->setQuantity( $quantity );
109
		$orderBaseProductItem->setStockType( $stocktype );
110
111
		$attr = array();
112
		$prices = $productItem->getRefItems( 'price', 'default', 'default' );
113
114
		switch( $productItem->getType() )
115
		{
116
			case 'select':
117
				$attr = $this->getVariantDetails( $orderBaseProductItem, $productItem, $prices, $variantAttributeIds, $options );
118
				break;
119
			case 'bundle':
120
				$this->addBundleProducts( $orderBaseProductItem, $productItem, $variantAttributeIds, $stocktype );
121
				break;
122
		}
123
124
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
125
		$price = $priceManager->getLowestPrice( $prices, $quantity );
126
127
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $configAttributeIds, 'config' ) );
128
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $hiddenAttributeIds, 'hidden' ) );
129
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, array_keys( $customAttributeValues ), 'custom', $customAttributeValues ) );
130
131
		// remove product rebate of original price in favor to rebates granted for the order
132
		$price->setRebate( '0.00' );
133
134
		$orderBaseProductItem->setPrice( $price );
135
		$orderBaseProductItem->setAttributes( $attr );
136
137
		$this->basket->addProduct( $orderBaseProductItem );
138
139
		$this->domainManager->setSession( $this->basket );
140
	}
141
142
143
	/**
144
	 * Deletes a product item from the basket.
145
	 *
146
	 * @param integer $position Position number (key) of the order product item
147
	 */
148
	public function deleteProduct( $position )
149
	{
150
		$product = $this->basket->getProduct( $position );
151
152
		if( $product->getFlags() === \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
153
		{
154
			$msg = sprintf( 'Basket item at position "%1$d" cannot be deleted manually', $position );
155
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
156
		}
157
158
		$this->basket->deleteProduct( $position );
159
		$this->domainManager->setSession( $this->basket );
160
	}
161
162
163
	/**
164
	 * Edits the quantity of a product item in the basket.
165
	 *
166
	 * @param integer $position Position number (key) of the order product item
167
	 * @param integer $quantity New quantiy of the product item
168
	 * @param array $options Possible options are: 'stock'=>true|false
169
	 * 	The 'stock'=>false option allows adding products without being in stock.
170
	 * @param string[] $configAttributeCodes Codes of the product config attributes that should be REMOVED
171
	 */
172
	public function editProduct( $position, $quantity, array $options = array(),
173
		array $configAttributeCodes = array() )
174
	{
175
		$product = $this->basket->getProduct( $position );
176
177
		if( $product->getFlags() & \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
178
		{
179
			$msg = sprintf( 'Basket item at position "%1$d" cannot be changed', $position );
180
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
181
		}
182
183
		$product->setQuantity( $quantity );
184
185
		$attributes = $product->getAttributes();
186
		foreach( $attributes as $key => $attribute )
187
		{
188
			if( in_array( $attribute->getCode(), $configAttributeCodes ) ) {
189
				unset( $attributes[$key] );
190
			}
191
		}
192
		$product->setAttributes( $attributes );
193
194
		$productItem = $this->getDomainItem( 'product', 'product.code', $product->getProductCode(), array( 'price', 'text' ) );
195
		$prices = $productItem->getRefItems( 'price', 'default' );
196
		$product->setPrice( $this->calcPrice( $product, $prices, $quantity ) );
197
198
		$this->basket->deleteProduct( $position );
199
		$this->basket->addProduct( $product, $position );
200
201
		$this->domainManager->setSession( $this->basket );
202
	}
203
204
205
	/**
206
	 * Adds the given coupon code and updates the basket.
207
	 *
208
	 * @param string $code Coupon code entered by the user
209
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid or not allowed
210
	 */
211
	public function addCoupon( $code )
212
	{
213
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon' );
214
		$codeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon/code' );
215
216
217
		$search = $codeManager->createSearch( true );
218
		$expr = array(
219
			$search->compare( '==', 'coupon.code.code', $code ),
220
			$search->getConditions(),
221
		);
222
		$search->setConditions( $search->combine( '&&', $expr ) );
223
		$search->setSlice( 0, 1 );
224
225
		$result = $codeManager->searchItems( $search );
226
227
		if( ( $codeItem = reset( $result ) ) === false ) {
228
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon code "%1$s" is invalid or not available any more', $code ) );
229
		}
230
231
232
		$search = $manager->createSearch( true );
233
		$expr = array(
234
			$search->compare( '==', 'coupon.id', $codeItem->getParentId() ),
235
			$search->getConditions(),
236
		);
237
		$search->setConditions( $search->combine( '&&', $expr ) );
238
		$search->setSlice( 0, 1 );
239
240
		$result = $manager->searchItems( $search );
241
242
		if( ( $item = reset( $result ) ) === false ) {
243
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon for code "%1$s" is not available any more', $code ) );
244
		}
245
246
247
		$provider = $manager->getProvider( $item, $code );
248
249
		if( $provider->isAvailable( $this->basket ) !== true ) {
250
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Requirements for coupon code "%1$s" aren\'t met', $code ) );
251
		}
252
253
		$provider->addCoupon( $this->basket );
254
		$this->domainManager->setSession( $this->basket );
255
	}
256
257
258
	/**
259
	 * Removes the given coupon code and its effects from the basket.
260
	 *
261
	 * @param string $code Coupon code entered by the user
262
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid
263
	 */
264
	public function deleteCoupon( $code )
265
	{
266
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon' );
267
268
		$search = $manager->createSearch();
269
		$search->setConditions( $search->compare( '==', 'coupon.code.code', $code ) );
270
		$search->setSlice( 0, 1 );
271
272
		$result = $manager->searchItems( $search );
273
274
		if( ( $item = reset( $result ) ) === false ) {
275
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon code "%1$s" is invalid', $code ) );
276
		}
277
278
		$manager->getProvider( $item, $code )->deleteCoupon( $this->basket );
279
		$this->domainManager->setSession( $this->basket );
280
	}
281
282
283
	/**
284
	 * Sets the address of the customer in the basket.
285
	 *
286
	 * @param string $type Address type constant from \Aimeos\MShop\Order\Item\Base\Address\Base
287
	 * @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
288
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the billing or delivery address is not of any required type of
289
	 * 	if one of the keys is invalid when using an array with key/value pairs
290
	 */
291
	public function setAddress( $type, $value )
292
	{
293
		$address = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/address' )->createItem();
294
		$address->setType( $type );
295
296
		if( $value instanceof \Aimeos\MShop\Common\Item\Address\Iface )
297
		{
298
			$address->copyFrom( $value );
299
			$this->basket->setAddress( $address, $type );
300
		}
301
		else if( is_array( $value ) )
302
		{
303
			$this->setAddressFromArray( $address, $value );
304
			$this->basket->setAddress( $address, $type );
305
		}
306
		else if( $value === null )
307
		{
308
			$this->basket->deleteAddress( $type );
309
		}
310
		else
311
		{
312
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Invalid value for address type "%1$s"', $type ) );
313
		}
314
315
		$this->domainManager->setSession( $this->basket );
316
	}
317
318
319
	/**
320
	 * Sets the delivery/payment service item based on the service ID.
321
	 *
322
	 * @param string $type Service type code like 'payment' or 'delivery'
323
	 * @param string $id Unique ID of the service item
324
	 * @param array $attributes Associative list of key/value pairs containing the attributes selected or
325
	 * 	entered by the customer when choosing one of the delivery or payment options
326
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there is no price to the service item attached
327
	 */
328
	public function setService( $type, $id, array $attributes = array() )
329
	{
330
		$context = $this->getContext();
331
332
		$serviceManager = \Aimeos\MShop\Factory::createManager( $context, 'service' );
333
		$serviceItem = $this->getDomainItem( 'service', 'service.id', $id, array( 'media', 'price', 'text' ) );
334
335
		$provider = $serviceManager->getProvider( $serviceItem );
336
		$result = $provider->checkConfigFE( $attributes );
337
		$unknown = array_diff_key( $attributes, $result );
338
339
		if( count( $unknown ) > 0 )
340
		{
341
			$msg = sprintf( 'Unknown attributes "%1$s"', implode( '","', array_keys( $unknown ) ) );
342
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
343
		}
344
345
		foreach( $result as $key => $value )
346
		{
347
			if( $value !== null ) {
348
				throw new \Aimeos\Controller\Frontend\Basket\Exception( $value );
349
			}
350
		}
351
352
		$orderBaseServiceManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/service' );
353
		$orderServiceItem = $orderBaseServiceManager->createItem();
354
		$orderServiceItem->copyFrom( $serviceItem );
355
356
		$price = $provider->calcPrice( $this->basket );
357
		// remove service rebate of original price
358
		$price->setRebate( '0.00' );
359
		$orderServiceItem->setPrice( $price );
360
361
		$provider->setConfigFE( $orderServiceItem, $attributes );
362
363
		$this->basket->setService( $orderServiceItem, $type );
364
		$this->domainManager->setSession( $this->basket );
365
	}
366
367
368
	/**
369
	 * Adds the bundled products to the order product item.
370
	 *
371
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem Order product item
372
	 * @param \Aimeos\MShop\Product\Item\Iface $productItem Bundle product item
373
	 * @param array $variantAttributeIds List of product variant attribute IDs
374
	 * @param string $stocktype
375
	 */
376
	protected function addBundleProducts( \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem,
377
		\Aimeos\MShop\Product\Item\Iface $productItem, array $variantAttributeIds, $stocktype )
378
	{
379
		$quantity = $orderBaseProductItem->getQuantity();
380
		$products = $subProductIds = $orderProducts = array();
381
		$orderProductManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/product' );
382
383
		foreach( $productItem->getRefItems( 'product', null, 'default' ) as $item ) {
384
			$subProductIds[] = $item->getId();
385
		}
386
387
		if( count( $subProductIds ) > 0 )
388
		{
389
			$productManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product' );
390
391
			$search = $productManager->createSearch( true );
392
			$expr = array(
393
				$search->compare( '==', 'product.id', $subProductIds ),
394
				$search->getConditions(),
395
			);
396
			$search->setConditions( $search->combine( '&&', $expr ) );
397
398
			$products = $productManager->searchItems( $search, array( 'attribute', 'media', 'price', 'text' ) );
399
		}
400
401
		foreach( $products as $product )
402
		{
403
			$prices = $product->getRefItems( 'price', 'default', 'default' );
404
405
			$orderProduct = $orderProductManager->createItem();
406
			$orderProduct->copyFrom( $product );
407
			$orderProduct->setStockType( $stocktype );
408
			$orderProduct->setPrice( $this->calcPrice( $orderProduct, $prices, $quantity ) );
409
410
			$orderProducts[] = $orderProduct;
411
		}
412
413
		$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...
414
	}
415
416
417
	/**
418
	 * Creates the order product attribute items from the given attribute IDs and updates the price item if necessary.
419
	 *
420
	 * @param \Aimeos\MShop\Price\Item\Iface $price Price item of the ordered product
421
	 * @param string $prodid Unique product ID where the given attributes must be attached to
422
	 * @param integer $quantity Number of products that should be added to the basket
423
	 * @param array $attributeIds List of attributes IDs of the given type
424
	 * @param string $type Attribute type
425
	 * @param array $attributeValues Associative list of attribute IDs as keys and their codes as values
426
	 * @return array List of items implementing \Aimeos\MShop\Order\Item\Product\Attribute\Iface
427
	 */
428
	protected function createOrderProductAttributes( \Aimeos\MShop\Price\Item\Iface $price, $prodid, $quantity,
429
			array $attributeIds, $type, array $attributeValues = array() )
430
	{
431
		if( empty( $attributeIds ) ) {
432
			return array();
433
		}
434
435
		$attrTypeId = $this->getProductListTypeItem( 'attribute', $type )->getId();
436
		$this->checkReferences( $prodid, 'attribute', $attrTypeId, $attributeIds );
437
438
		$list = array();
439
		$context = $this->getContext();
440
441
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
442
		$orderProductAttributeManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product/attribute' );
443
444
		foreach( $this->getAttributes( $attributeIds ) as $id => $attrItem )
445
		{
446
			$prices = $attrItem->getRefItems( 'price', 'default', 'default' );
447
448
			if( !empty( $prices ) ) {
449
				$price->addItem( $priceManager->getLowestPrice( $prices, $quantity ) );
450
			}
451
452
			$item = $orderProductAttributeManager->createItem();
453
			$item->copyFrom( $attrItem );
454
			$item->setType( $type );
455
456
			if( isset( $attributeValues[$id] ) ) {
457
				$item->setValue( $attributeValues[$id] );
458
			}
459
460
			$list[] = $item;
461
		}
462
463
		return $list;
464
	}
465
466
467
	/**
468
	 * Retrieves the domain item specified by the given key and value.
469
	 *
470
	 * @param string $domain Product manager search key
471
	 * @param string $key Domain manager search key
472
	 * @param string $value Unique domain identifier
473
	 * @param string[] $ref List of referenced items that should be fetched too
474
	 * @return \Aimeos\MShop\Common\Item\Iface Domain item object
475
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception
476
	 */
477
	protected function getDomainItem( $domain, $key, $value, array $ref )
478
	{
479
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $domain );
480
481
		$search = $manager->createSearch( true );
482
		$expr = array(
483
				$search->compare( '==', $key, $value ),
484
				$search->getConditions(),
485
		);
486
		$search->setConditions( $search->combine( '&&', $expr ) );
487
488
		$result = $manager->searchItems( $search, $ref );
489
490
		if( ( $item = reset( $result ) ) === false )
491
		{
492
			$msg = sprintf( 'No item for "%1$s" (%2$s) found', $value, $key );
493
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
494
		}
495
496
		return $item;
497
	}
498
499
500
	/**
501
	 * Returns the variant attributes and updates the price list if necessary.
502
	 *
503
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem Order product item
504
	 * @param \Aimeos\MShop\Product\Item\Iface &$productItem Product item which is replaced if necessary
505
	 * @param array &$prices List of product prices that will be updated if necessary
506
	 * @param array $variantAttributeIds List of product variant attribute IDs
507
	 * @param array $options Associative list of options
508
	 * @return \Aimeos\MShop\Order\Item\Base\Product\Attribute\Iface[] List of order product attributes
509
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If no product variant is found
510
	 */
511
	protected function getVariantDetails( \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem,
512
		\Aimeos\MShop\Product\Item\Iface &$productItem, array &$prices, array $variantAttributeIds, array $options )
513
	{
514
		$attr = array();
515
		$productItems = $this->getProductVariants( $productItem, $variantAttributeIds );
516
517
		if( count( $productItems ) > 1 )
518
		{
519
			$msg = sprintf( 'No unique article found for selected attributes and product ID "%1$s"', $productItem->getId() );
520
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
521
		}
522
		else if( ( $result = reset( $productItems ) ) !== false ) // count == 1
523
		{
524
			$productItem = $result;
525
			$orderBaseProductItem->setProductCode( $productItem->getCode() );
526
527
			$subprices = $productItem->getRefItems( 'price', 'default', 'default' );
528
529
			if( !empty( $subprices ) ) {
530
				$prices = $subprices;
531
			}
532
533
			$submedia = $productItem->getRefItems( 'media', 'default', 'default' );
534
535
			if( ( $mediaItem = reset( $submedia ) ) !== false ) {
536
				$orderBaseProductItem->setMediaUrl( $mediaItem->getPreview() );
537
			}
538
539
			$orderProductAttrManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/product/attribute' );
540
			$variantAttributes = $productItem->getRefItems( 'attribute', null, 'variant' );
541
542
			foreach( $this->getAttributes( array_keys( $variantAttributes ), array( 'text' ) ) as $attrItem )
543
			{
544
				$orderAttributeItem = $orderProductAttrManager->createItem();
545
				$orderAttributeItem->copyFrom( $attrItem );
546
				$orderAttributeItem->setType( 'variant' );
547
548
				$attr[] = $orderAttributeItem;
549
			}
550
		}
551
		else if( !isset( $options['variant'] ) || $options['variant'] != false ) // count == 0
552
		{
553
			$msg = sprintf( 'No article found for selected attributes and product ID "%1$s"', $productItem->getId() );
554
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
555
		}
556
557
		return $attr;
558
	}
559
560
561
	/**
562
	 * Fills the order address object with the values from the array.
563
	 *
564
	 * @param \Aimeos\MShop\Order\Item\Base\Address\Iface $address Address item to store the values into
565
	 * @param array $map Associative array of key/value pairs. The keys must be the same as when calling toArray() from
566
	 * 	an address item.
567
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception
568
	 */
569
	protected function setAddressFromArray( \Aimeos\MShop\Order\Item\Base\Address\Iface $address, array $map )
570
	{
571
		foreach( $map as $key => $value ) {
572
			$map[$key] = strip_tags( $value ); // prevent XSS
573
		}
574
575
		$errors = $address->fromArray( $map );
576
577
		if( count( $errors ) > 0 )
578
		{
579
			$msg = sprintf( 'Invalid address properties, please check your input' );
580
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg, 0, null, $errors );
581
		}
582
	}
583
}
584