Completed
Push — master ( ac3c49...e09568 )
by Aimeos
02:38
created

Base::copyAddresses()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 11
nc 4
nop 3
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( $this->getContext(), '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
				throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Invalid price value "%1$s"', $amount ) );
55
			}
56
57
			$price->setValue( $amount );
58
		}
59
60
		// add prices of (optional) attributes
61
		foreach( $this->getAttributeItems( $product->getAttributes() ) as $attrItem )
62
		{
63
			$prices = $attrItem->getRefItems( 'price', 'default' );
64
65
			if( count( $prices ) > 0 )
66
			{
67
				$attrPrice = $priceManager->getLowestPrice( $prices, $quantity );
68
				$price->addItem( $attrPrice );
69
			}
70
		}
71
72
		// remove product rebate of original price in favor to rebates granted for the order
73
		$price->setRebate( '0.00' );
74
75
		return $price;
76
	}
77
78
79
	/**
80
	 * Checks if the reference IDs are really associated to the product
81
	 *
82
	 * @param string|array $prodId Unique ID of the product or list of product IDs
83
	 * @param string $domain Domain the references must be of
84
	 * @param array $refMap Associative list of list type codes as keys and lists of reference IDs as values
85
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If one or more of the IDs are not associated
86
	 */
87
	protected function checkListRef( $prodId, $domain, array $refMap )
88
	{
89
		if( empty( $refMap ) ) {
90
			return;
91
		}
92
93
		$productManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product' );
94
		$search = $productManager->createSearch( true );
95
96
		$expr = array(
97
			$search->compare( '==', 'product.id', $prodId ),
98
			$search->getConditions(),
99
		);
100
101
		foreach( $refMap as $listType => $refIds )
102
		{
103
			if( empty( $refIds ) ) {
104
				continue;
105
			}
106
107
			foreach( $refIds as $key => $refId ) {
108
				$refIds[$key] = (string) $refId;
109
			}
110
111
			$param = array( $domain, $this->getProductListTypeItem( $domain, $listType )->getId(), $refIds );
112
			$cmpfunc = $search->createFunction( 'product.contains', $param );
113
114
			$expr[] = $search->compare( '==', $cmpfunc, count( $refIds ) );
115
		}
116
117
		$search->setConditions( $search->combine( '&&', $expr ) );
118
119
		if( count( $productManager->searchItems( $search, [] ) ) === 0 )
120
		{
121
			$msg = sprintf( 'Invalid "%1$s" references for product with ID %2$s', $domain, json_encode( $prodId ) );
122
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
123
		}
124
	}
125
126
127
	/**
128
	 * Checks if the IDs of the given items are really associated to the product.
129
	 *
130
	 * @param string|array $prodId Unique ID of the product or list of product IDs
131
	 * @param string $domain Domain the references must be of
132
	 * @param integer $listTypeId ID of the list type the referenced items must be
133
	 * @param array $refIds List of IDs that must be associated to the product
134
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If one or more of the IDs are not associated
135
	 * @deprecated Use checkListRef() instead
136
	 */
137
	protected function checkReferences( $prodId, $domain, $listTypeId, array $refIds )
138
	{
139
		$productManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product' );
140
		$search = $productManager->createSearch( true );
141
142
		$expr = array(
143
			$search->compare( '==', 'product.id', $prodId ),
144
			$search->getConditions(),
145
		);
146
147
		if( count( $refIds ) > 0 )
148
		{
149
			foreach( $refIds as $key => $refId ) {
150
				$refIds[$key] = (string) $refId;
151
			}
152
153
			$param = array( $domain, $listTypeId, $refIds );
154
			$cmpfunc = $search->createFunction( 'product.contains', $param );
155
156
			$expr[] = $search->compare( '==', $cmpfunc, count( $refIds ) );
157
		}
158
159
		$search->setConditions( $search->combine( '&&', $expr ) );
160
161
		if( count( $productManager->searchItems( $search, [] ) ) === 0 )
162
		{
163
			$msg = sprintf( 'Invalid "%1$s" references for product with ID %2$s', $domain, json_encode( $prodId ) );
164
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
165
		}
166
	}
167
168
169
	/**
170
	 * Checks for a locale mismatch and migrates the products to the new basket if necessary.
171
	 *
172
	 * @param string $type Basket type
173
	 */
174
	protected function checkLocale( $type )
175
	{
176
		$errors = [];
177
		$context = $this->getContext();
178
		$session = $context->getSession();
179
		$locale = $this->get()->getLocale();
180
181
		$localeStr = $session->get( 'aimeos/basket/locale' );
182
		$localeKey = $locale->getSite()->getCode() . '|' . $locale->getLanguageId() . '|' . $locale->getCurrencyId();
183
184
		if( $localeStr !== null && $localeStr !== $localeKey )
185
		{
186
			$locParts = explode( '|', $localeStr );
187
			$locSite = ( isset( $locParts[0] ) ? $locParts[0] : '' );
188
			$locLanguage = ( isset( $locParts[1] ) ? $locParts[1] : '' );
189
			$locCurrency = ( isset( $locParts[2] ) ? $locParts[2] : '' );
190
191
			$localeManager = \Aimeos\MShop\Factory::createManager( $context, 'locale' );
192
			$locale = $localeManager->bootstrap( $locSite, $locLanguage, $locCurrency, false );
193
194
			$context = clone $context;
195
			$context->setLocale( $locale );
196
197
			$manager = \Aimeos\MShop\Order\Manager\Factory::createManager( $context )->getSubManager( 'base' );
198
			$basket = $manager->getSession( $type );
199
200
			$this->copyAddresses( $basket, $errors, $localeKey );
201
			$this->copyServices( $basket, $errors );
202
			$this->copyProducts( $basket, $errors, $localeKey );
203
			$this->copyCoupons( $basket, $errors, $localeKey );
204
205
			$manager->setSession( $basket, $type );
206
		}
207
208
		$session->set( 'aimeos/basket/locale', $localeKey );
209
	}
210
211
212
	/**
213
	 * Migrates the addresses from the old basket to the current one.
214
	 *
215
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
216
	 * @param array $errors Associative list of previous errors
217
	 * @param string $localeKey Unique identifier of the site, language and currency
218
	 * @return array Associative list of errors occured
219
	 */
220
	protected function copyAddresses( \Aimeos\MShop\Order\Item\Base\Iface $basket, array $errors, $localeKey )
221
	{
222
		foreach( $basket->getAddresses() as $type => $item )
223
		{
224
			try
225
			{
226
				$this->setAddress( $type, $item->toArray() );
227
				$basket->deleteAddress( $type );
228
			}
229
			catch( \Exception $e )
230
			{
231
				$logger = $this->getContext()->getLogger();
232
				$str = 'Error migrating address with type "%1$s" in basket to locale "%2$s": %3$s';
233
				$logger->log( sprintf( $str, $type, $localeKey, $e->getMessage() ), \Aimeos\MW\Logger\Base::INFO );
234
				$errors['address'][$type] = $e->getMessage();
235
			}
236
		}
237
238
		return $errors;
239
	}
240
241
242
	/**
243
	 * Migrates the coupons from the old basket to the current one.
244
	 *
245
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
246
	 * @param array $errors Associative list of previous errors
247
	 * @param string $localeKey Unique identifier of the site, language and currency
248
	 * @return array Associative list of errors occured
249
	 */
250
	protected function copyCoupons( \Aimeos\MShop\Order\Item\Base\Iface $basket, array $errors, $localeKey )
251
	{
252
		foreach( $basket->getCoupons() as $code => $list )
253
		{
254
			try
255
			{
256
				$this->addCoupon( $code );
257
				$basket->deleteCoupon( $code, true );
258
			}
259
			catch( \Exception $e )
260
			{
261
				$logger = $this->getContext()->getLogger();
262
				$str = 'Error migrating coupon with code "%1$s" in basket to locale "%2$s": %3$s';
263
				$logger->log( sprintf( $str, $code, $localeKey, $e->getMessage() ), \Aimeos\MW\Logger\Base::INFO );
264
				$errors['coupon'][$code] = $e->getMessage();
265
			}
266
		}
267
268
		return $errors;
269
	}
270
271
272
	/**
273
	 * Migrates the products from the old basket to the current one.
274
	 *
275
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
276
	 * @param array $errors Associative list of previous errors
277
	 * @param string $localeKey Unique identifier of the site, language and currency
278
	 * @return array Associative list of errors occured
279
	 */
280
	protected function copyProducts( \Aimeos\MShop\Order\Item\Base\Iface $basket, array $errors, $localeKey )
281
	{
282
		foreach( $basket->getProducts() as $pos => $product )
283
		{
284
			if( $product->getFlags() & \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE ) {
285
				continue;
286
			}
287
288
			try
289
			{
290
				$attrIds = [];
291
292
				foreach( $product->getAttributes() as $attrItem ) {
293
					$attrIds[$attrItem->getType()][] = $attrItem->getAttributeId();
294
				}
295
296
				$this->addProduct(
297
					$product->getProductId(),
298
					$product->getQuantity(),
299
					[],
300
					$this->getValue( $attrIds, 'variant', [] ),
301
					$this->getValue( $attrIds, 'config', [] ),
302
					$this->getValue( $attrIds, 'hidden', [] ),
303
					$this->getValue( $attrIds, 'custom', [] ),
304
					$product->getStockType()
305
				);
306
307
				$basket->deleteProduct( $pos );
308
			}
309
			catch( \Exception $e )
310
			{
311
				$code = $product->getProductCode();
312
				$logger = $this->getContext()->getLogger();
313
				$errors['product'][$pos] = $e->getMessage();
314
315
				$str = 'Error migrating product with code "%1$s" in basket to locale "%2$s": %3$s';
316
				$logger->log( sprintf( $str, $code, $localeKey, $e->getMessage() ), \Aimeos\MW\Logger\Base::INFO );
317
			}
318
		}
319
320
		return $errors;
321
	}
322
323
324
	/**
325
	 * Migrates the services from the old basket to the current one.
326
	 *
327
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
328
	 * @param array $errors Associative list of previous errors
329
	 * @return array Associative list of errors occured
330
	 */
331
	protected function copyServices( \Aimeos\MShop\Order\Item\Base\Iface $basket, array $errors )
332
	{
333
		foreach( $basket->getServices() as $type => $list )
334
		{
335
			foreach( $list as $item )
336
			{
337
				try
338
				{
339
					$attributes = [];
340
341
					foreach( $item->getAttributes() as $attrItem ) {
342
						$attributes[$attrItem->getCode()] = $attrItem->getValue();
343
					}
344
345
					$this->addService( $type, $item->getServiceId(), $attributes );
346
					$basket->deleteService( $type );
347
				}
348
				catch( \Exception $e ) { ; } // Don't notify the user as appropriate services can be added automatically
349
			}
350
		}
351
352
		return $errors;
353
	}
354
355
356
	/**
357
	 * Creates the order product attribute items from the given attribute IDs and updates the price item if necessary.
358
	 *
359
	 * @param \Aimeos\MShop\Price\Item\Iface $price Price item of the ordered product
360
	 * @param string|array $prodid Unique product ID or list of product IDs where the given attributes must be attached to
361
	 * @param integer $quantity Number of products that should be added to the basket
362
	 * @param array $attributeIds List of attributes IDs of the given type
363
	 * @param string $type Attribute type
364
	 * @param array $attributeValues Associative list of attribute IDs as keys and their codes as values
365
	 * @return array List of items implementing \Aimeos\MShop\Order\Item\Product\Attribute\Iface
366
	 * @deprecated Use getOrderProductAttributes(), checkReferences() and calcPrice() instead
367
	 */
368
	protected function createOrderProductAttributes( \Aimeos\MShop\Price\Item\Iface $price, $prodid, $quantity,
369
			array $attributeIds, $type, array $attributeValues = [] )
370
	{
371
		if( empty( $attributeIds ) ) {
372
			return [];
373
		}
374
375
		$attrTypeId = $this->getProductListTypeItem( 'attribute', $type )->getId();
376
		$this->checkReferences( $prodid, 'attribute', $attrTypeId, $attributeIds );
0 ignored issues
show
Deprecated Code introduced by
The method Aimeos\Controller\Fronte...Base::checkReferences() has been deprecated with message: Use checkListRef() instead

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

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

Loading history...
377
378
		$list = [];
379
		$context = $this->getContext();
380
381
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
382
		$orderProductAttributeManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product/attribute' );
383
384
		foreach( $this->getAttributes( $attributeIds ) as $id => $attrItem )
385
		{
386
			$prices = $attrItem->getRefItems( 'price', 'default', 'default' );
387
388
			if( !empty( $prices ) ) {
389
				$price->addItem( $priceManager->getLowestPrice( $prices, $quantity ) );
390
			}
391
392
			$item = $orderProductAttributeManager->createItem();
393
			$item->copyFrom( $attrItem );
394
			$item->setType( $type );
395
396
			if( isset( $attributeValues[$id] ) ) {
397
				$item->setValue( $attributeValues[$id] );
398
			}
399
400
			$list[] = $item;
401
		}
402
403
		return $list;
404
	}
405
406
407
	/**
408
	 * Returns the attribute items for the given attribute IDs.
409
	 *
410
	 * @param array $attributeIds List of attribute IDs
411
	 * @param string[] $domains Names of the domain items that should be fetched too
412
	 * @return array List of items implementing \Aimeos\MShop\Attribute\Item\Iface
413
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the actual attribute number doesn't match the expected one
414
	 */
415
	protected function getAttributes( array $attributeIds, array $domains = array( 'price', 'text' ) )
416
	{
417
		if( empty( $attributeIds ) ) {
418
			return [];
419
		}
420
421
		$attributeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'attribute' );
422
423
		$search = $attributeManager->createSearch( true );
424
		$expr = array(
425
			$search->compare( '==', 'attribute.id', $attributeIds ),
426
			$search->getConditions(),
427
		);
428
		$search->setConditions( $search->combine( '&&', $expr ) );
429
		$search->setSlice( 0, 0x7fffffff );
430
431
		$attrItems = $attributeManager->searchItems( $search, $domains );
432
433
		if( count( $attrItems ) !== count( $attributeIds ) )
434
		{
435
			$expected = implode( ',', $attributeIds );
436
			$actual = implode( ',', array_keys( $attrItems ) );
437
			$msg = sprintf( 'Available attribute IDs "%1$s" do not match the given attribute IDs "%2$s"', $actual, $expected );
438
439
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
440
		}
441
442
		return $attrItems;
443
	}
444
445
446
	/**
447
	 * Returns the attribute items using the given order attribute items.
448
	 *
449
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Attribute\Item[] $orderAttributes List of order product attribute items
450
	 * @return \Aimeos\MShop\Attribute\Item\Iface[] Associative list of attribute IDs as key and attribute items as values
451
	 */
452
	protected function getAttributeItems( array $orderAttributes )
453
	{
454
		if( empty( $orderAttributes ) ) {
455
			return [];
456
		}
457
458
		$attributeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'attribute' );
459
		$search = $attributeManager->createSearch( true );
460
		$expr = [];
461
462
		foreach( $orderAttributes as $item )
463
		{
464
			$tmp = array(
465
				$search->compare( '==', 'attribute.domain', 'product' ),
466
				$search->compare( '==', 'attribute.code', $item->getValue() ),
467
				$search->compare( '==', 'attribute.type.domain', 'product' ),
468
				$search->compare( '==', 'attribute.type.code', $item->getCode() ),
469
				$search->compare( '>', 'attribute.type.status', 0 ),
470
				$search->getConditions(),
471
			);
472
			$expr[] = $search->combine( '&&', $tmp );
473
		}
474
475
		$search->setConditions( $search->combine( '||', $expr ) );
476
		return $attributeManager->searchItems( $search, array( 'price' ) );
477
	}
478
479
480
	/**
481
	 * Retrieves the domain item specified by the given key and value.
482
	 *
483
	 * @param string $domain Product manager search key
484
	 * @param string $key Domain manager search key
485
	 * @param string $value Unique domain identifier
486
	 * @param string[] $ref List of referenced items that should be fetched too
487
	 * @return \Aimeos\MShop\Common\Item\Iface Domain item object
488
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception
489
	 * @deprecated Use getItem() or findItem() instead
490
	 */
491
	protected function getDomainItem( $domain, $key, $value, array $ref )
492
	{
493
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $domain );
494
495
		$search = $manager->createSearch( true );
496
		$expr = array(
497
			$search->compare( '==', $key, $value ),
498
			$search->getConditions(),
499
		);
500
		$search->setConditions( $search->combine( '&&', $expr ) );
501
502
		$result = $manager->searchItems( $search, $ref );
503
504
		if( ( $item = reset( $result ) ) === false )
505
		{
506
			$msg = sprintf( 'No item for "%1$s" (%2$s) found', $value, $key );
507
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
508
		}
509
510
		return $item;
511
	}
512
513
514
	/**
515
	 * Returns the order product attribute items for the given IDs and values
516
	 *
517
	 * @param string $type Attribute type code
518
	 * @param array $attributeIds List of attributes IDs of the given type
519
	 * @param array $attributeValues Associative list of attribute IDs as keys and their codes as values
520
	 * @return array List of items implementing \Aimeos\MShop\Order\Item\Product\Attribute\Iface
521
	 */
522
	protected function getOrderProductAttributes( $type, array $attributeIds, array $attributeValues = [] )
523
	{
524
		if( empty( $attributeIds ) ) {
525
			return [];
526
		}
527
528
		foreach( $attributeValues as $key => $value ) {
529
			$attributeValues[(string) $key] = $value; // Workaround for PHP bug #74739
530
		}
531
532
		$list = [];
533
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/product/attribute' );
534
535
		foreach( $this->getAttributes( $attributeIds ) as $id => $attrItem )
536
		{
537
			$item = $manager->createItem();
538
			$item->copyFrom( $attrItem );
539
			$item->setType( $type );
540
541
			if( isset( $attributeValues[$id] ) ) {
542
				$item->setValue( $attributeValues[$id] );
543
			}
544
545
			$list[] = $item;
546
		}
547
548
		return $list;
549
	}
550
551
552
	/**
553
	 * Returns the list type item for the given domain and code.
554
	 *
555
	 * @param string $domain Domain name of the list type
556
	 * @param string $code Code of the list type
557
	 * @return \Aimeos\MShop\Common\Item\Type\Iface List type item
558
	 */
559
	protected function getProductListTypeItem( $domain, $code )
560
	{
561
		if( empty( $this->listTypeItems ) )
562
		{
563
			$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product/lists/type' );
564
565
			foreach( $manager->searchItems( $manager->createSearch( true ) ) as $item ) {
566
				$this->listTypeItems[ $item->getDomain() ][ $item->getCode() ] = $item;
567
			}
568
		}
569
570
		if( !isset( $this->listTypeItems[$domain][$code] ) )
571
		{
572
			$msg = sprintf( 'List type for domain "%1$s" and code "%2$s" not found', $domain, $code );
573
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
574
		}
575
576
		return $this->listTypeItems[$domain][$code];
577
	}
578
579
580
	/**
581
	 * Returns the product variants of a selection product that match the given attributes.
582
	 *
583
	 * @param \Aimeos\MShop\Product\Item\Iface $productItem Product item including sub-products
584
	 * @param array $variantAttributeIds IDs for the variant-building attributes
585
	 * @param array $domains Names of the domain items that should be fetched too
586
	 * @return array List of products matching the given attributes
587
	 */
588
	protected function getProductVariants( \Aimeos\MShop\Product\Item\Iface $productItem, array $variantAttributeIds,
589
			array $domains = array( 'attribute', 'media', 'price', 'text' ) )
590
	{
591
		$subProductIds = [];
592
		foreach( $productItem->getRefItems( 'product', 'default', 'default' ) as $item ) {
593
			$subProductIds[] = $item->getId();
594
		}
595
596
		if( count( $subProductIds ) === 0 ) {
597
			return [];
598
		}
599
600
		$productManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product' );
601
		$search = $productManager->createSearch( true );
602
603
		$expr = array(
604
			$search->compare( '==', 'product.id', $subProductIds ),
605
			$search->getConditions(),
606
		);
607
608
		if( count( $variantAttributeIds ) > 0 )
609
		{
610
			foreach( $variantAttributeIds as $key => $id ) {
611
				$variantAttributeIds[$key] = (string) $id;
612
			}
613
614
			$listTypeItem = $this->getProductListTypeItem( 'attribute', 'variant' );
615
616
			$param = array( 'attribute', $listTypeItem->getId(), $variantAttributeIds );
617
			$cmpfunc = $search->createFunction( 'product.contains', $param );
618
619
			$expr[] = $search->compare( '==', $cmpfunc, count( $variantAttributeIds ) );
620
		}
621
622
		$search->setConditions( $search->combine( '&&', $expr ) );
623
624
		return $productManager->searchItems( $search, $domains );
625
	}
626
627
628
	/**
629
	 * Returns the value of an array or the default value if it's not available.
630
	 *
631
	 * @param array $values Associative list of key/value pairs
632
	 * @param string $name Name of the key to return the value for
633
	 * @param mixed $default Default value if no value is available for the given name
634
	 * @return mixed Value from the array or default value
635
	 */
636
	protected function getValue( array $values, $name, $default = null )
637
	{
638
		if( isset( $values[$name] ) ) {
639
			return $values[$name];
640
		}
641
642
		return $default;
643
	}
644
}
645