Passed
Push — master ( 517d52...c8a2c5 )
by Aimeos
02:30
created

Standard::uses()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2017-2019
6
 * @package Controller
7
 * @subpackage Frontend
8
 */
9
10
11
namespace Aimeos\Controller\Frontend\Product;
12
13
14
/**
15
 * Default implementation of the product frontend controller.
16
 *
17
 * @package Controller
18
 * @subpackage Frontend
19
 */
20
class Standard
21
	extends \Aimeos\Controller\Frontend\Base
22
	implements Iface, \Aimeos\Controller\Frontend\Common\Iface
23
{
24
	private $conditions = [];
25
	private $domains = [];
26
	private $filter;
27
	private $manager;
28
	private $sort;
29
30
31
	/**
32
	 * Common initialization for controller classes
33
	 *
34
	 * @param \Aimeos\MShop\Context\Item\Iface $context Common MShop context object
35
	 */
36
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
37
	{
38
		parent::__construct( $context );
39
40
		$this->manager = \Aimeos\MShop::create( $context, 'index' );
41
		$this->filter = $this->manager->createSearch( true );
42
		$this->conditions[] = $this->filter->compare( '!=', 'index.catalog.id', null );
43
		$this->conditions[] = $this->filter->getConditions();
44
	}
45
46
47
	/**
48
	 * Clones objects in controller and resets values
49
	 */
50
	public function __clone()
51
	{
52
		$this->filter = clone $this->filter;
53
	}
54
55
56
	/**
57
	 * Returns the aggregated count of products for the given key.
58
	 *
59
	 * @param string $key Search key to aggregate for, e.g. "index.attribute.id"
60
	 * @return array Associative list of key values as key and the product count for this key as value
61
	 * @since 2019.04
62
	 */
63
	public function aggregate( $key )
64
	{
65
		$this->filter->setConditions( $this->filter->combine( '&&', $this->conditions ) );
66
		return $this->manager->aggregate( $this->filter, $key );
67
	}
68
69
70
	/**
71
	 * Adds attribute IDs for filtering where products must reference all IDs
72
	 *
73
	 * @param array|string $attrIds Attribute ID or list of IDs
74
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
75
	 * @since 2019.04
76
	 */
77
	public function allOf( $attrIds )
78
	{
79
		if( !empty( $attrIds ) && ( $ids = array_unique( $this->validateIds( (array) $attrIds ) ) ) !== [] )
80
		{
81
			$func = $this->filter->createFunction( 'index.attribute:allof', [$ids] );
82
			$this->conditions[] = $this->filter->compare( '!=', $func, null );
83
		}
84
85
		return $this;
86
	}
87
88
89
	/**
90
	 * Adds catalog IDs for filtering
91
	 *
92
	 * @param array|string $catIds Catalog ID or list of IDs
93
	 * @param string $listtype List type of the products referenced by the categories
94
	 * @param integer $level Constant from \Aimeos\MW\Tree\Manager\Base if products in subcategories are matched too
95
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
96
	 * @since 2019.04
97
	 */
98
	public function category( $catIds, $listtype = 'default', $level = \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE )
99
	{
100
		if( !empty( $catIds ) && ( $ids = $this->validateIds( (array) $catIds ) ) !== [] )
101
		{
102
			if( $level != \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE )
103
			{
104
				$list = [];
105
				$cntl = \Aimeos\Controller\Frontend::create( $this->getContext(), 'catalog' );
106
107
				foreach( $ids as $catId ) {
108
					$list += $cntl->root( $catId )->getTree( $level )->toList();
109
				}
110
111
				$ids = array_keys( $list );
112
			}
113
114
			$func = $this->filter->createFunction( 'index.catalog:position', [$listtype, $ids] );
115
116
			$this->conditions[] = $this->filter->compare( '==', 'index.catalog.id', $ids );
117
			$this->conditions[] = $this->filter->compare( '>=', $func, 0 );
118
119
			$func = $this->filter->createFunction( 'sort:index.catalog:position', [$listtype, $ids] );
120
			$this->sort = $this->filter->sort( '+', $func );
121
		}
122
123
		return $this;
124
	}
125
126
127
	/**
128
	 * Adds generic condition for filtering products
129
	 *
130
	 * @param string $operator Comparison operator, e.g. "==", "!=", "<", "<=", ">=", ">", "=~", "~="
131
	 * @param string $key Search key defined by the product manager, e.g. "product.status"
132
	 * @param array|string $value Value or list of values to compare to
133
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
134
	 * @since 2019.04
135
	 */
136
	public function compare( $operator, $key, $value )
137
	{
138
		$this->conditions[] = $this->filter->compare( $operator, $key, $value );
139
		return $this;
140
	}
141
142
143
	/**
144
	 * Returns the product for the given product code
145
	 *
146
	 * @param string $code Unique product code
147
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
148
	 * @since 2019.04
149
	 */
150
	public function find( $code )
151
	{
152
		return $this->manager->findItem( $code, $this->domains, 'product', null, true );
153
	}
154
155
156
	/**
157
	 * Returns the product for the given product ID
158
	 *
159
	 * @param string $id Unique product ID
160
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
161
	 * @since 2019.04
162
	 */
163
	public function get( $id )
164
	{
165
		return $this->manager->getItem( $id, $this->domains, true );
166
	}
167
168
169
	/**
170
	 * Adds a filter to return only items containing a reference to the given ID
171
	 *
172
	 * @param string $domain Domain name of the referenced item, e.g. "attribute"
173
	 * @param string|null $type Type code of the reference, e.g. "variant" or null for all types
174
	 * @param string|null $refId ID of the referenced item of the given domain or null for all references
175
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
176
	 * @since 2019.04
177
	 */
178
	public function has( $domain, $type = null, $refId = null )
179
	{
180
		$params = [$domain];
181
		!$type ?: $params[] = $type;
182
		!$refId ?: $params[] = $refId;
183
184
		$func = $this->filter->createFunction( 'product:has', $params );
185
		$this->conditions[] = $this->filter->compare( '!=', $func, null );
186
		return $this;
187
	}
188
189
190
	/**
191
	 * Adds attribute IDs for filtering where products must reference at least one ID
192
	 *
193
	 * If an array of ID lists is given, each ID list is added separately as condition.
194
	 *
195
	 * @param array|string $attrIds Attribute ID, list of IDs or array of lists with IDs
196
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
197
	 * @since 2019.04
198
	 */
199
	public function oneOf( $attrIds )
200
	{
201
		$attrIds = (array) $attrIds;
202
203
		foreach( $attrIds as $key => $entry )
204
		{
205
			if( is_array( $entry ) && ( $ids = array_unique( $this->validateIds( $entry ) ) ) !== [] )
206
			{
207
				$func = $this->filter->createFunction( 'index.attribute:oneof', [$ids] );
208
				$this->conditions[] = $this->filter->compare( '!=', $func, null );
209
				unset( $attrIds[$key] );
210
			}
211
		}
212
213
		if( ( $ids = array_unique( $this->validateIds( $attrIds ) ) ) !== [] )
214
		{
215
			$func = $this->filter->createFunction( 'index.attribute:oneof', [$ids] );
216
			$this->conditions[] = $this->filter->compare( '!=', $func, null );
217
		}
218
219
		return $this;
220
	}
221
222
223
	/**
224
	 * Parses the given array and adds the conditions to the list of conditions
225
	 *
226
	 * @param array $conditions List of conditions, e.g. [['>' => ['product.status' => 0]], ['==' => ['product.type' => 'default']]]
227
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
228
	 * @since 2019.04
229
	 */
230
	public function parse( array $conditions )
231
	{
232
		$this->conditions[] = $this->filter->toConditions( $conditions );
233
		return $this;
234
	}
235
236
237
	/**
238
	 * Adds product IDs for filtering
239
	 *
240
	 * @param array|string $prodIds Product ID or list of IDs
241
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
242
	 * @since 2019.04
243
	 */
244
	public function product( $prodIds )
245
	{
246
		if( !empty( $prodIds ) && ( $ids = array_unique( $this->validateIds( (array) $prodIds ) ) ) !== [] ) {
247
			$this->conditions[] = $this->filter->compare( '==', 'product.id', $ids );
248
		}
249
250
		return $this;
251
	}
252
253
254
	/**
255
	 * Adds a filter to return only items containing the property
256
	 *
257
	 * @param string $type Type code of the property, e.g. "isbn"
258
	 * @param string|null $value Exact value of the property
259
	 * @param string|null $langid ISO country code (en or en_US) or null if not language specific
260
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
261
	 * @since 2019.04
262
	 */
263
	public function property( $type, $value = null, $langid = null )
264
	{
265
		$func = $this->filter->createFunction( 'product:prop', [$type, $langid, $value] );
266
		$this->conditions[] = $this->filter->compare( '!=', $func, null );
267
		return $this;
268
	}
269
270
271
	/**
272
	 * Returns the product for the given product URL name
273
	 *
274
	 * @param string $name Product URL name
275
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
276
	 * @since 2019.04
277
	 */
278
	public function resolve( $name )
279
	{
280
		$langid = $this->getContext()->getLocale()->getLanguageId();
281
282
		$search = $this->manager->createSearch();
283
		$func = $search->createFunction( 'index.text:url', [$langid] );
284
		$search->setConditions( $search->compare( '==', $func, $name ) );
285
286
		$items = $this->manager->searchItems( $search, $this->domains );
287
288
		if( ( $item = reset( $items ) ) !== false ) {
289
			return $item;
290
		}
291
292
		throw new \Aimeos\Controller\Frontend\Product\Exception( sprintf( 'Unable to find product "%1$s"', $name ) );
293
	}
294
295
296
	/**
297
	 * Returns the products filtered by the previously assigned conditions
298
	 *
299
	 * @param integer &$total Parameter where the total number of found products will be stored in
300
	 * @return \Aimeos\MShop\Product\Item\Iface[] Ordered list of product items
301
	 * @since 2019.04
302
	 */
303
	public function search( &$total = null )
304
	{
305
		$this->filter->setConditions( $this->filter->combine( '&&', $this->conditions ) );
306
		return $this->manager->searchItems( $this->filter, $this->domains, $total );
307
	}
308
309
310
	/**
311
	 * Sets the start value and the number of returned products for slicing the list of found products
312
	 *
313
	 * @param integer $start Start value of the first product in the list
314
	 * @param integer $limit Number of returned products
315
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
316
	 * @since 2019.04
317
	 */
318
	public function slice( $start, $limit )
319
	{
320
		$this->filter->setSlice( $start, $limit );
321
		return $this;
322
	}
323
324
325
	/**
326
	 * Sets the sorting of the result list
327
	 *
328
	 * @param string|null $key Sorting of the result list like "name", "-name", "price", "-price", "code", "-code", "ctime, "-ctime" and "relevance", null for no sorting
329
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
330
	 * @since 2019.04
331
	 */
332
	public function sort( $key = null )
333
	{
334
		$direction = '+';
335
336
		if( $key != null && $key[0] === '-' )
337
		{
338
			$key = substr( $key, 1 );
339
			$direction = '-';
340
		}
341
342
		switch( $key )
343
		{
344
			case null:
345
				$this->sort = null;
346
				break;
347
348
			case 'relevance':
349
				break;
350
351
			case 'code':
352
				$this->sort = $this->filter->sort( $direction, 'product.code' );
353
				break;
354
355
			case 'ctime':
356
				$this->sort = $this->filter->sort( $direction, 'product.ctime' );
357
				break;
358
359
			case 'name':
360
				$langid = $this->getContext()->getLocale()->getLanguageId();
361
362
				$cmpfunc = $this->filter->createFunction( 'index.text:name', [$langid] );
363
				$this->conditions[] = $this->filter->compare( '!=', $cmpfunc, null );
364
365
				$sortfunc = $this->filter->createFunction( 'sort:index.text:name', [$langid] );
366
				$this->sort = $this->filter->sort( $direction, $sortfunc );
367
				break;
368
369
			case 'price':
370
				$expr = [];
371
				$context = $this->getContext();
372
373
				/** controller/frontend/product/price-types
374
				 * Use different product prices types for sorting by price
375
				 *
376
				 * In some cases, prices are stored with different types, eg. price per kg.
377
				 * This configuration option defines which types are incorporated when sorting
378
				 * the product list by price.
379
				 *
380
				 * @param array List of price types codes
381
				 * @since 2018.10
382
				 * @category Developer
383
				 */
384
				$types = $context->getConfig()->get( 'controller/frontend/product/price-types', ['default'] );
385
				$currencyid = $context->getLocale()->getCurrencyId();
386
387
				foreach( $types as $type )
388
				{
389
					$cmpfunc = $this->filter->createFunction( 'index.price:value', [$currencyid] );
390
					$expr[] = $this->filter->compare( '!=', $cmpfunc, null );
391
				}
392
393
				$this->conditions[] = $this->filter->combine( '||', $expr );
394
395
				$sortfunc = $this->filter->createFunction( 'sort:index.price:value', [$currencyid] );
396
				$this->sort = $this->filter->sort( $direction, $sortfunc );
397
				break;
398
399
			default:
400
				$this->sort = $this->filter->sort( $direction, $key );
401
		}
402
403
		if( $this->sort ) {
404
			$this->filter->setSortations( [$this->sort] );
405
		}
406
407
		return $this;
408
	}
409
410
411
	/**
412
	 * Adds supplier IDs for filtering
413
	 *
414
	 * @param array|string $supIds Supplier ID or list of IDs
415
	 * @param string $listtype List type of the products referenced by the suppliers
416
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
417
	 * @since 2019.04
418
	 */
419
	public function supplier( $supIds, $listtype = 'default' )
420
	{
421
		if( !empty( $supIds ) && ( $ids = array_unique( $this->validateIds( (array) $supIds ) ) ) !== [] )
422
		{
423
			$func = $this->filter->createFunction( 'index.supplier:position', [$listtype, $ids] );
424
425
			$this->conditions[] = $this->filter->compare( '==', 'index.supplier.id', $ids );
426
			$this->conditions[] = $this->filter->compare( '>=', $func, 0 );
427
428
			$func = $this->filter->createFunction( 'sort:index.supplier:position', [$listtype, $ids] );
429
			$this->sort = $this->filter->sort( '+', $func );
430
		}
431
432
		return $this;
433
	}
434
435
436
	/**
437
	 * Adds input string for full text search
438
	 *
439
	 * @param string|null $text User input for full text search
440
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
441
	 * @since 2019.04
442
	 */
443
	public function text( $text )
444
	{
445
		if( !empty( $text ) )
446
		{
447
			$langid = $this->getContext()->getLocale()->getLanguageId();
448
			$func = $this->filter->createFunction( 'index.text:relevance', [$langid, $text] );
449
450
			$this->conditions[] = $this->filter->compare( '>', $func, 0 );
451
		}
452
453
		return $this;
454
	}
455
456
457
	/**
458
	 * Sets the referenced domains that will be fetched too when retrieving items
459
	 *
460
	 * @param array $domains Domain names of the referenced items that should be fetched too
461
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
462
	 * @since 2019.04
463
	 */
464
	public function uses( array $domains )
465
	{
466
		$this->domains = $domains;
467
		return $this;
468
	}
469
470
471
	/**
472
	 * Returns the list of catalog IDs for the given catalog tree
473
	 *
474
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Catalog item with children
475
	 * @return array List of catalog IDs
476
	 */
477
	protected function getCatalogIdsFromTree( \Aimeos\MShop\Catalog\Item\Iface $item )
478
	{
479
		if( $item->getStatus() < 1 ) {
480
			return [];
481
		}
482
483
		$list = [ $item->getId() ];
484
485
		foreach( $item->getChildren() as $child ) {
486
			$list = array_merge( $list, $this->getCatalogIdsFromTree( $child ) );
487
		}
488
489
		return $list;
490
	}
491
492
493
	/**
494
	 * Validates the given IDs as integers
495
	 *
496
	 * @param array $ids List of IDs to validate
497
	 * @return array List of validated IDs
498
	 */
499
	protected function validateIds( array $ids )
500
	{
501
		$list = [];
502
503
		foreach( $ids as $id )
504
		{
505
			if( $id != '' && preg_match( '/^[A-Za-z0-9\-\_]+$/', $id ) === 1 ) {
506
				$list[] = (string) $id;
507
			}
508
		}
509
510
		return $list;
511
	}
512
}
513