Completed
Push — master ( 7e7dfb...1f41d8 )
by Aimeos
01:49
created

Base   F

Complexity

Total Complexity 67

Size/Duplication

Total Lines 539
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 0
Metric Value
wmc 67
lcom 1
cbo 17
dl 0
loc 539
rs 3.04
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
B calcPrice() 0 49 8
B checkListRef() 0 39 6
B checkLocale() 0 35 6
A copyAddresses() 0 21 3
A copyCoupons() 0 21 3
B copyProducts() 0 42 8
A copyServices() 0 23 5
B createSubscriptions() 0 25 6
A getAttributes() 0 30 3
A getAttributeItems() 0 26 3
A getOrderProductAttributes() 0 25 5
A getProductListTypeItem() 0 21 4
A getProductVariants() 0 38 5
A getValue() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like Base often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Base, and based on these observations, apply Extract Interface, too.

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-2017
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 implements Iface
22
{
23
	private $listTypeItems = [];
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
			$manager = \Aimeos\MShop\Factory::createManager( $context, 'product' );
41
			$prices = $manager->getItem( $product->getProductId(), array( 'price' ) )->getRefItems( 'price', 'default' );
42
		}
43
44
45
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
46
		$price = $priceManager->getLowestPrice( $prices, $quantity );
47
48
		// customers can pay what they would like to pay
49
		if( ( $attr = $product->getAttributeItem( 'price', 'custom' ) ) !== null )
50
		{
51
			$amount = $attr->getValue();
52
53
			if( preg_match( '/^[0-9]*(\.[0-9]+)?$/', $amount ) !== 1 || ((double) $amount) < 0.01 )
54
			{
55
				$msg = $context->getI18n()->dt( 'controller/frontend', 'Invalid price value "%1$s"' );
56
				throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( $msg, $amount ) );
57
			}
58
59
			$price->setValue( $amount );
60
		}
61
62
		$orderAttributes = $product->getAttributes();
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on Aimeos\MShop\Order\Item\Base\Product\Iface. Did you maybe mean getAttribute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
63
		$attrItems = $this->getAttributeItems( $orderAttributes );
64
65
		// add prices of (optional) attributes
66
		foreach( $orderAttributes as $orderAttrItem )
67
		{
68
			$attrId = $orderAttrItem->getAttributeId();
69
70
			if( isset( $attrItems[$attrId] )
71
				&& ( $prices = $attrItems[$attrId]->getRefItems( 'price', 'default' ) ) !== []
72
			) {
73
				$attrPrice = $priceManager->getLowestPrice( $prices, $orderAttrItem->getQuantity() );
74
				$price->addItem( $attrPrice, $orderAttrItem->getQuantity() );
75
			}
76
		}
77
78
		// remove product rebate of original price in favor to rebates granted for the order
79
		$price->setRebate( '0.00' );
80
81
		return $price;
82
	}
83
84
85
	/**
86
	 * Checks if the reference IDs are really associated to the product
87
	 *
88
	 * @param string|array $prodId Unique ID of the product or list of product IDs
89
	 * @param string $domain Domain the references must be of
90
	 * @param array $refMap Associative list of list type codes as keys and lists of reference IDs as values
91
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If one or more of the IDs are not associated
92
	 */
93
	protected function checkListRef( $prodId, $domain, array $refMap )
94
	{
95
		if( empty( $refMap ) ) {
96
			return;
97
		}
98
99
		$context = $this->getContext();
100
		$productManager = \Aimeos\MShop\Factory::createManager( $context, 'product' );
101
		$search = $productManager->createSearch( true );
102
103
		$expr = array(
104
			$search->compare( '==', 'product.id', $prodId ),
105
			$search->getConditions(),
106
		);
107
108
		foreach( $refMap as $listType => $refIds )
109
		{
110
			if( empty( $refIds ) ) {
111
				continue;
112
			}
113
114
			foreach( $refIds as $key => $refId ) {
115
				$refIds[$key] = (string) $refId;
116
			}
117
118
			$param = array( $domain, $this->getProductListTypeItem( $domain, $listType )->getId(), $refIds );
119
			$cmpfunc = $search->createFunction( 'product.contains', $param );
120
121
			$expr[] = $search->compare( '==', $cmpfunc, count( $refIds ) );
122
		}
123
124
		$search->setConditions( $search->combine( '&&', $expr ) );
125
126
		if( count( $productManager->searchItems( $search, [] ) ) === 0 )
127
		{
128
			$msg = $context->getI18n()->dt( 'controller/frontend', 'Invalid "%1$s" references for product with ID %2$s' );
129
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( $msg, $domain, json_encode( $prodId ) ) );
130
		}
131
	}
132
133
134
	/**
135
	 * Checks for a locale mismatch and migrates the products to the new basket if necessary.
136
	 *
137
	 * @param \Aimeos\MShop\Locale\Item\Iface $locale Locale object from current basket
138
	 * @param string $type Basket type
139
	 */
140
	protected function checkLocale( \Aimeos\MShop\Locale\Item\Iface $locale, $type )
141
	{
142
		$errors = [];
143
		$context = $this->getContext();
144
		$session = $context->getSession();
145
146
		$localeStr = $session->get( 'aimeos/basket/locale' );
147
		$localeKey = $locale->getSite()->getCode() . '|' . $locale->getLanguageId() . '|' . $locale->getCurrencyId();
148
149
		if( $localeStr !== null && $localeStr !== $localeKey )
150
		{
151
			$locParts = explode( '|', $localeStr );
152
			$locSite = ( isset( $locParts[0] ) ? $locParts[0] : '' );
153
			$locLanguage = ( isset( $locParts[1] ) ? $locParts[1] : '' );
154
			$locCurrency = ( isset( $locParts[2] ) ? $locParts[2] : '' );
155
156
			$localeManager = \Aimeos\MShop\Factory::createManager( $context, 'locale' );
157
			$locale = $localeManager->bootstrap( $locSite, $locLanguage, $locCurrency, false );
158
159
			$context = clone $context;
160
			$context->setLocale( $locale );
161
162
			$manager = \Aimeos\MShop\Factory::createManager( $context, 'order/base' );
163
			$basket = $manager->getSession( $type );
164
165
			$this->copyAddresses( $basket, $errors, $localeKey );
166
			$this->copyServices( $basket, $errors );
167
			$this->copyProducts( $basket, $errors, $localeKey );
168
			$this->copyCoupons( $basket, $errors, $localeKey );
169
170
			$manager->setSession( $basket, $type );
171
		}
172
173
		$session->set( 'aimeos/basket/locale', $localeKey );
174
	}
175
176
177
	/**
178
	 * Migrates the addresses from the old basket to the current one.
179
	 *
180
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
181
	 * @param array $errors Associative list of previous errors
182
	 * @param string $localeKey Unique identifier of the site, language and currency
183
	 * @return array Associative list of errors occured
184
	 */
185
	protected function copyAddresses( \Aimeos\MShop\Order\Item\Base\Iface $basket, array $errors, $localeKey )
186
	{
187
		foreach( $basket->getAddresses() as $type => $item )
188
		{
189
			try
190
			{
191
				$this->setAddress( $type, $item->toArray() );
192
				$basket->deleteAddress( $type );
193
			}
194
			catch( \Exception $e )
195
			{
196
				$logger = $this->getContext()->getLogger();
197
				$errors['address'][$type] = $e->getMessage();
198
199
				$str = 'Error migrating address with type "%1$s" in basket to locale "%2$s": %3$s';
200
				$logger->log( sprintf( $str, $type, $localeKey, $e->getMessage() ), \Aimeos\MW\Logger\Base::INFO );
201
			}
202
		}
203
204
		return $errors;
205
	}
206
207
208
	/**
209
	 * Migrates the coupons from the old basket to the current one.
210
	 *
211
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
212
	 * @param array $errors Associative list of previous errors
213
	 * @param string $localeKey Unique identifier of the site, language and currency
214
	 * @return array Associative list of errors occured
215
	 */
216
	protected function copyCoupons( \Aimeos\MShop\Order\Item\Base\Iface $basket, array $errors, $localeKey )
217
	{
218
		foreach( $basket->getCoupons() as $code => $list )
219
		{
220
			try
221
			{
222
				$this->addCoupon( $code );
223
				$basket->deleteCoupon( $code, true );
224
			}
225
			catch( \Exception $e )
226
			{
227
				$logger = $this->getContext()->getLogger();
228
				$errors['coupon'][$code] = $e->getMessage();
229
230
				$str = 'Error migrating coupon with code "%1$s" in basket to locale "%2$s": %3$s';
231
				$logger->log( sprintf( $str, $code, $localeKey, $e->getMessage() ), \Aimeos\MW\Logger\Base::INFO );
232
			}
233
		}
234
235
		return $errors;
236
	}
237
238
239
	/**
240
	 * Migrates the products from the old basket to the current one.
241
	 *
242
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
243
	 * @param array $errors Associative list of previous errors
244
	 * @param string $localeKey Unique identifier of the site, language and currency
245
	 * @return array Associative list of errors occured
246
	 */
247
	protected function copyProducts( \Aimeos\MShop\Order\Item\Base\Iface $basket, array $errors, $localeKey )
248
	{
249
		foreach( $basket->getProducts() as $pos => $product )
250
		{
251
			if( $product->getFlags() & \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE ) {
252
				continue;
253
			}
254
255
			try
256
			{
257
				$variantIds = $configIds = $customIds = [];
258
259
				foreach( $product->getAttributeItems() as $attrItem )
260
				{
261
					switch( $attrItem->getType() )
262
					{
263
						case 'variant': $variantIds[] = $attrItem->getAttributeId(); break;
264
						case 'config': $configIds[$attrItem->getAttributeId()] = $attrItem->getQuantity(); break;
265
						case 'custom': $customIds[$attrItem->getAttributeId()] = $attrItem->getValue(); break;
266
					}
267
				}
268
269
				$this->addProduct(
270
					$product->getProductId(), $product->getQuantity(), $product->getStockType(),
271
					$variantIds, $configIds, [], $customIds
272
				);
273
274
				$basket->deleteProduct( $pos );
275
			}
276
			catch( \Exception $e )
277
			{
278
				$code = $product->getProductCode();
279
				$logger = $this->getContext()->getLogger();
280
				$errors['product'][$pos] = $e->getMessage();
281
282
				$str = 'Error migrating product with code "%1$s" in basket to locale "%2$s": %3$s';
283
				$logger->log( sprintf( $str, $code, $localeKey, $e->getMessage() ), \Aimeos\MW\Logger\Base::INFO );
284
			}
285
		}
286
287
		return $errors;
288
	}
289
290
291
	/**
292
	 * Migrates the services from the old basket to the current one.
293
	 *
294
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
295
	 * @param array $errors Associative list of previous errors
296
	 * @return array Associative list of errors occured
297
	 */
298
	protected function copyServices( \Aimeos\MShop\Order\Item\Base\Iface $basket, array $errors )
299
	{
300
		foreach( $basket->getServices() as $type => $list )
301
		{
302
			foreach( $list as $item )
303
			{
304
				try
305
				{
306
					$attributes = [];
307
308
					foreach( $item->getAttributes() as $attrItem ) {
309
						$attributes[$attrItem->getCode()] = $attrItem->getValue();
310
					}
311
312
					$this->addService( $type, $item->getServiceId(), $attributes );
313
					$basket->deleteService( $type );
314
				}
315
				catch( \Exception $e ) { ; } // Don't notify the user as appropriate services can be added automatically
316
			}
317
		}
318
319
		return $errors;
320
	}
321
322
323
	/**
324
	 * Creates the subscription entries for the ordered products with interval attributes
325
	 *
326
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
327
	 */
328
	protected function createSubscriptions( \Aimeos\MShop\Order\Item\Base\Iface $basket )
329
	{
330
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'subscription' );
331
332
		foreach( $basket->getProducts() as $orderProduct )
333
		{
334
			if( ( $interval = $orderProduct->getAttribute( 'interval', 'config' ) ) !== null )
335
			{
336
				$item = $manager->createItem();
337
				$item->setOrderBaseId( $basket->getId() );
0 ignored issues
show
Bug introduced by
The method setOrderBaseId() does not seem to exist on object<Aimeos\MShop\Attribute\Item\Iface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
338
				$item->setOrderProductId( $orderProduct->getId() );
0 ignored issues
show
Bug introduced by
The method setOrderProductId() does not seem to exist on object<Aimeos\MShop\Attribute\Item\Iface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
339
				$item->setInterval( $interval );
0 ignored issues
show
Bug introduced by
The method setInterval() does not seem to exist on object<Aimeos\MShop\Attribute\Item\Iface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
340
				$item->setStatus( 1 );
341
342
				if( ( $end = $orderProduct->getAttribute( 'intervalend', 'custom' ) ) !== null
343
					|| ( $end = $orderProduct->getAttribute( 'intervalend', 'config' ) ) !== null
344
					|| ( $end = $orderProduct->getAttribute( 'intervalend', 'hidden' ) ) !== null
345
				) {
346
					$item->setDateEnd( $end );
0 ignored issues
show
Bug introduced by
The method setDateEnd() does not seem to exist on object<Aimeos\MShop\Attribute\Item\Iface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
347
				}
348
349
				$manager->saveItem( $item, false );
350
			}
351
		}
352
	}
353
354
355
	/**
356
	 * Returns the attribute items for the given attribute IDs.
357
	 *
358
	 * @param array $attributeIds List of attribute IDs
359
	 * @param string[] $domains Names of the domain items that should be fetched too
360
	 * @return array List of items implementing \Aimeos\MShop\Attribute\Item\Iface
361
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the actual attribute number doesn't match the expected one
362
	 */
363
	protected function getAttributes( array $attributeIds, array $domains = array( 'price', 'text' ) )
364
	{
365
		if( empty( $attributeIds ) ) {
366
			return [];
367
		}
368
369
		$attributeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'attribute' );
370
371
		$search = $attributeManager->createSearch( true );
372
		$expr = array(
373
			$search->compare( '==', 'attribute.id', $attributeIds ),
374
			$search->getConditions(),
375
		);
376
		$search->setConditions( $search->combine( '&&', $expr ) );
377
		$search->setSlice( 0, 0x7fffffff );
378
379
		$attrItems = $attributeManager->searchItems( $search, $domains );
380
381
		if( count( $attrItems ) !== count( $attributeIds ) )
382
		{
383
			$i18n = $this->getContext()->getI18n();
384
			$expected = implode( ',', $attributeIds );
385
			$actual = implode( ',', array_keys( $attrItems ) );
386
			$msg = $i18n->dt( 'controller/frontend', 'Available attribute IDs "%1$s" do not match the given attribute IDs "%2$s"' );
387
388
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( $msg, $actual, $expected ) );
389
		}
390
391
		return $attrItems;
392
	}
393
394
395
	/**
396
	 * Returns the attribute items using the given order attribute items.
397
	 *
398
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Attribute\Item[] $orderAttributes List of order product attribute items
399
	 * @return \Aimeos\MShop\Attribute\Item\Iface[] Associative list of attribute IDs as key and attribute items as values
400
	 */
401
	protected function getAttributeItems( array $orderAttributes )
402
	{
403
		if( empty( $orderAttributes ) ) {
404
			return [];
405
		}
406
407
		$attributeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'attribute' );
408
		$search = $attributeManager->createSearch( true );
409
		$expr = [];
410
411
		foreach( $orderAttributes as $item )
412
		{
413
			$tmp = array(
414
				$search->compare( '==', 'attribute.domain', 'product' ),
415
				$search->compare( '==', 'attribute.code', $item->getValue() ),
416
				$search->compare( '==', 'attribute.type.domain', 'product' ),
417
				$search->compare( '==', 'attribute.type.code', $item->getCode() ),
418
				$search->compare( '>', 'attribute.type.status', 0 ),
419
				$search->getConditions(),
420
			);
421
			$expr[] = $search->combine( '&&', $tmp );
422
		}
423
424
		$search->setConditions( $search->combine( '||', $expr ) );
425
		return $attributeManager->searchItems( $search, array( 'price' ) );
426
	}
427
428
429
	/**
430
	 * Returns the order product attribute items for the given IDs and values
431
	 *
432
	 * @param string $type Attribute type code
433
	 * @param array $ids List of attributes IDs of the given type
434
	 * @param array $values Associative list of attribute IDs as keys and their codes as values
435
	 * @param array $quantities Associative list of attribute IDs as keys and their quantities as values
436
	 * @return array List of items implementing \Aimeos\MShop\Order\Item\Product\Attribute\Iface
437
	 */
438
	protected function getOrderProductAttributes( $type, array $ids, array $values = [], array $quantities = [] )
439
	{
440
		if( empty( $ids ) ) {
441
			return [];
442
		}
443
444
		$list = [];
445
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/product/attribute' );
446
447
		foreach( $this->getAttributes( $ids ) as $id => $attrItem )
448
		{
449
			$item = $manager->createItem();
450
			$item->copyFrom( $attrItem );
0 ignored issues
show
Bug introduced by
The method copyFrom() does not seem to exist on object<Aimeos\MShop\Attribute\Item\Iface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
451
			$item->setType( $type );
0 ignored issues
show
Bug introduced by
The method setType() does not exist on Aimeos\MShop\Attribute\Item\Iface. Did you maybe mean setTypeId()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
452
			$item->setQuantity( isset( $quantities[$id] ) ? $quantities[$id] : 1 );
0 ignored issues
show
Bug introduced by
The method setQuantity() does not seem to exist on object<Aimeos\MShop\Attribute\Item\Iface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
453
454
			if( isset( $values[$id] ) ) {
455
				$item->setValue( $values[$id] );
0 ignored issues
show
Bug introduced by
The method setValue() does not seem to exist on object<Aimeos\MShop\Attribute\Item\Iface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
456
			}
457
458
			$list[] = $item;
459
		}
460
461
		return $list;
462
	}
463
464
465
	/**
466
	 * Returns the list type item for the given domain and code.
467
	 *
468
	 * @param string $domain Domain name of the list type
469
	 * @param string $code Code of the list type
470
	 * @return \Aimeos\MShop\Common\Item\Type\Iface List type item
471
	 */
472
	protected function getProductListTypeItem( $domain, $code )
473
	{
474
		$context = $this->getContext();
475
476
		if( empty( $this->listTypeItems ) )
477
		{
478
			$manager = \Aimeos\MShop\Factory::createManager( $context, 'product/lists/type' );
479
480
			foreach( $manager->searchItems( $manager->createSearch( true ) ) as $item ) {
481
				$this->listTypeItems[ $item->getDomain() ][ $item->getCode() ] = $item;
482
			}
483
		}
484
485
		if( !isset( $this->listTypeItems[$domain][$code] ) )
486
		{
487
			$msg = $context->getI18n()->dt( 'controller/frontend', 'List type for domain "%1$s" and code "%2$s" not found' );
488
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( $msg, $domain, $code ) );
489
		}
490
491
		return $this->listTypeItems[$domain][$code];
492
	}
493
494
495
	/**
496
	 * Returns the product variants of a selection product that match the given attributes.
497
	 *
498
	 * @param \Aimeos\MShop\Product\Item\Iface $productItem Product item including sub-products
499
	 * @param array $variantAttributeIds IDs for the variant-building attributes
500
	 * @param array $domains Names of the domain items that should be fetched too
501
	 * @return array List of products matching the given attributes
502
	 */
503
	protected function getProductVariants( \Aimeos\MShop\Product\Item\Iface $productItem, array $variantAttributeIds,
504
			array $domains = array( 'attribute', 'media', 'price', 'text' ) )
505
	{
506
		$subProductIds = [];
507
		foreach( $productItem->getRefItems( 'product', 'default', 'default' ) as $item ) {
508
			$subProductIds[] = $item->getId();
509
		}
510
511
		if( count( $subProductIds ) === 0 ) {
512
			return [];
513
		}
514
515
		$productManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product' );
516
		$search = $productManager->createSearch( true );
517
518
		$expr = array(
519
			$search->compare( '==', 'product.id', $subProductIds ),
520
			$search->getConditions(),
521
		);
522
523
		if( count( $variantAttributeIds ) > 0 )
524
		{
525
			foreach( $variantAttributeIds as $key => $id ) {
526
				$variantAttributeIds[$key] = (string) $id;
527
			}
528
529
			$listTypeItem = $this->getProductListTypeItem( 'attribute', 'variant' );
530
531
			$param = array( 'attribute', $listTypeItem->getId(), $variantAttributeIds );
532
			$cmpfunc = $search->createFunction( 'product.contains', $param );
533
534
			$expr[] = $search->compare( '==', $cmpfunc, count( $variantAttributeIds ) );
535
		}
536
537
		$search->setConditions( $search->combine( '&&', $expr ) );
538
539
		return $productManager->searchItems( $search, $domains );
540
	}
541
542
543
	/**
544
	 * Returns the value of an array or the default value if it's not available.
545
	 *
546
	 * @param array $values Associative list of key/value pairs
547
	 * @param string $name Name of the key to return the value for
548
	 * @param mixed $default Default value if no value is available for the given name
549
	 * @return mixed Value from the array or default value
550
	 */
551
	protected function getValue( array $values, $name, $default = null )
552
	{
553
		if( isset( $values[$name] ) ) {
554
			return $values[$name];
555
		}
556
557
		return $default;
558
	}
559
}
560