Passed
Push — master ( b7775a...4e1b50 )
by Aimeos
03:15
created

Standard::product()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 3
nc 2
nop 1
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2017-2020
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 $sort = [];
27
	private $filter;
28
	private $manager;
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->filter( true );
42
		$this->conditions[] = $this->filter->compare( '!=', 'index.catalog.id', null );
0 ignored issues
show
Bug introduced by
The method compare() does not exist on Aimeos\MW\Criteria\Iface. It seems like you code against a sub-type of said class. However, the method does not exist in Aimeos\MW\Criteria\Base. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

42
		/** @scrutinizer ignore-call */ 
43
  $this->conditions[] = $this->filter->compare( '!=', 'index.catalog.id', null );
Loading history...
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
	 * @param string|null $value Search key for aggregating the value column
61
	 * @param string|null $type Type of the aggregation, empty string for count or "sum" or "avg" (average)
62
	 * @return \Aimeos\Map Associative list of key values as key and the product count for this key as value
63
	 * @since 2019.04
64
	 */
65
	public function aggregate( string $key, string $value = null, string $type = null ) : \Aimeos\Map
66
	{
67
		$this->filter->setConditions( $this->filter->combine( '&&', $this->conditions ) );
0 ignored issues
show
Bug introduced by
The method setConditions() does not exist on Aimeos\MW\Criteria\Iface. It seems like you code against a sub-type of said class. However, the method does not exist in Aimeos\MW\Criteria\Base. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

67
		$this->filter->/** @scrutinizer ignore-call */ 
68
                 setConditions( $this->filter->combine( '&&', $this->conditions ) );
Loading history...
Bug introduced by
The method combine() does not exist on Aimeos\MW\Criteria\Iface. It seems like you code against a sub-type of said class. However, the method does not exist in Aimeos\MW\Criteria\Base. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

67
		$this->filter->setConditions( $this->filter->/** @scrutinizer ignore-call */ combine( '&&', $this->conditions ) );
Loading history...
68
		return $this->manager->aggregate( $this->filter, $key, $value, $type );
69
	}
70
71
72
	/**
73
	 * Adds attribute IDs for filtering where products must reference all IDs
74
	 *
75
	 * @param array|string $attrIds Attribute ID or list of IDs
76
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
77
	 * @since 2019.04
78
	 */
79
	public function allOf( $attrIds ) : Iface
80
	{
81
		if( !empty( $attrIds ) && ( $ids = $this->validateIds( (array) $attrIds ) ) !== [] )
82
		{
83
			$func = $this->filter->make( 'index.attribute:allof', [$ids] );
84
			$this->conditions[] = $this->filter->compare( '!=', $func, null );
85
		}
86
87
		return $this;
88
	}
89
90
91
	/**
92
	 * Adds catalog IDs for filtering
93
	 *
94
	 * @param array|string $catIds Catalog ID or list of IDs
95
	 * @param string $listtype List type of the products referenced by the categories
96
	 * @param int $level Constant from \Aimeos\MW\Tree\Manager\Base if products in subcategories are matched too
97
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
98
	 * @since 2019.04
99
	 */
100
	public function category( $catIds, string $listtype = 'default', int $level = \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE ) : Iface
101
	{
102
		if( !empty( $catIds ) && ( $ids = $this->validateIds( (array) $catIds ) ) !== [] )
103
		{
104
			if( $level != \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE )
105
			{
106
				$list = map();
107
				$cntl = \Aimeos\Controller\Frontend::create( $this->getContext(), 'catalog' );
108
109
				foreach( $ids as $catId ) {
110
					$list->union( $cntl->root( $catId )->getTree( $level )->toList() );
111
				}
112
113
				$ids = $this->validateIds( $list->keys()->toArray() );
114
			}
115
116
			$func = $this->filter->make( 'index.catalog:position', [$listtype, $ids] );
117
118
			$this->conditions[] = $this->filter->compare( '==', 'index.catalog.id', $ids );
119
			$this->conditions[] = $this->filter->compare( '>=', $func, 0 );
120
121
			$func = $this->filter->make( 'sort:index.catalog:position', [$listtype, $ids] );
122
			$this->sort[] = $this->filter->sort( '+', $func );
0 ignored issues
show
Bug introduced by
The method sort() does not exist on Aimeos\MW\Criteria\Iface. It seems like you code against a sub-type of said class. However, the method does not exist in Aimeos\MW\Criteria\Base. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

122
			/** @scrutinizer ignore-call */ 
123
   $this->sort[] = $this->filter->sort( '+', $func );
Loading history...
123
			$this->sort[] = $this->filter->sort( '+', 'product.id' ); // prevent flaky order if products have same position
124
		}
125
126
		return $this;
127
	}
128
129
130
	/**
131
	 * Adds generic condition for filtering products
132
	 *
133
	 * @param string $operator Comparison operator, e.g. "==", "!=", "<", "<=", ">=", ">", "=~", "~="
134
	 * @param string $key Search key defined by the product manager, e.g. "product.status"
135
	 * @param array|string $value Value or list of values to compare to
136
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
137
	 * @since 2019.04
138
	 */
139
	public function compare( string $operator, string $key, $value ) : Iface
140
	{
141
		$this->conditions[] = $this->filter->compare( $operator, $key, $value );
142
		return $this;
143
	}
144
145
146
	/**
147
	 * Returns the product for the given product code
148
	 *
149
	 * @param string $code Unique product code
150
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
151
	 * @since 2019.04
152
	 */
153
	public function find( string $code ) : \Aimeos\MShop\Product\Item\Iface
154
	{
155
		return $this->manager->find( $code, $this->domains, 'product', null, true );
156
	}
157
158
159
	/**
160
	 * Creates a search function string for the given name and parameters
161
	 *
162
	 * @param string $name Name of the search function without parenthesis, e.g. "product:has"
163
	 * @param array $params List of parameters for the search function with numeric keys starting at 0
164
	 * @return string Search function string that can be used in compare()
165
	 */
166
	public function function( string $name, array $params ) : string
167
	{
168
		return $this->filter->make( $name, $params );
169
	}
170
171
172
	/**
173
	 * Returns the product for the given product ID
174
	 *
175
	 * @param string $id Unique product ID
176
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
177
	 * @since 2019.04
178
	 */
179
	public function get( string $id ) : \Aimeos\MShop\Product\Item\Iface
180
	{
181
		return $this->manager->get( $id, $this->domains, true );
182
	}
183
184
185
	/**
186
	 * Adds a filter to return only items containing a reference to the given ID
187
	 *
188
	 * @param string $domain Domain name of the referenced item, e.g. "attribute"
189
	 * @param string|null $type Type code of the reference, e.g. "variant" or null for all types
190
	 * @param string|null $refId ID of the referenced item of the given domain or null for all references
191
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
192
	 * @since 2019.04
193
	 */
194
	public function has( string $domain, string $type = null, string $refId = null ) : Iface
195
	{
196
		$params = [$domain];
197
		!$type ?: $params[] = $type;
198
		!$refId ?: $params[] = $refId;
199
200
		$func = $this->filter->make( 'product:has', $params );
201
		$this->conditions[] = $this->filter->compare( '!=', $func, null );
202
		return $this;
203
	}
204
205
206
	/**
207
	 * Adds attribute IDs for filtering where products must reference at least one ID
208
	 *
209
	 * If an array of ID lists is given, each ID list is added separately as condition.
210
	 *
211
	 * @param array|string $attrIds Attribute ID, list of IDs or array of lists with IDs
212
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
213
	 * @since 2019.04
214
	 */
215
	public function oneOf( $attrIds ) : Iface
216
	{
217
		$attrIds = (array) $attrIds;
218
219
		foreach( $attrIds as $key => $entry )
220
		{
221
			if( is_array( $entry ) && ( $ids = $this->validateIds( $entry ) ) !== [] )
222
			{
223
				$func = $this->filter->make( 'index.attribute:oneof', [$ids] );
224
				$this->conditions[] = $this->filter->compare( '!=', $func, null );
225
				unset( $attrIds[$key] );
226
			}
227
		}
228
229
		if( ( $ids = $this->validateIds( $attrIds ) ) !== [] )
230
		{
231
			$func = $this->filter->make( 'index.attribute:oneof', [$ids] );
232
			$this->conditions[] = $this->filter->compare( '!=', $func, null );
233
		}
234
235
		return $this;
236
	}
237
238
239
	/**
240
	 * Parses the given array and adds the conditions to the list of conditions
241
	 *
242
	 * @param array $conditions List of conditions, e.g. [['>' => ['product.status' => 0]], ['==' => ['product.type' => 'default']]]
243
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
244
	 * @since 2019.04
245
	 */
246
	public function parse( array $conditions ) : Iface
247
	{
248
		if( ( $cond = $this->filter->parse( $conditions ) ) !== null ) {
249
			$this->conditions[] = $cond;
250
		}
251
252
		return $this;
253
	}
254
255
256
	/**
257
	 * Adds price restrictions for filtering
258
	 *
259
	 * @param array|string $value Upper price limit, list of lower and upper price or NULL for no restrictions
260
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
261
	 * @since 2020.10
262
	 */
263
	public function price( $value = null ) : Iface
264
	{
265
		if( $value )
266
		{
267
			$value = (array) $value;
268
			$func = $this->filter->make( 'index.price:value', [$this->getContext()->getLocale()->getCurrencyId()] );
269
270
			$this->conditions['price-high'] = $this->filter->compare( '<=', $func, sprintf( '%013.2F', end( $value ) ) );
271
272
			if( count( $value ) > 1 ) {
273
				$this->conditions['price-low'] = $this->filter->compare( '>=', $func, sprintf( '%013.2F', reset( $value ) ) );
274
			}
275
		}
276
277
		return $this;
278
	}
279
280
281
	/**
282
	 * Adds product IDs for filtering
283
	 *
284
	 * @param array|string $prodIds Product ID or list of IDs
285
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
286
	 * @since 2019.04
287
	 */
288
	public function product( $prodIds ) : Iface
289
	{
290
		if( !empty( $prodIds ) && ( $ids = array_unique( $this->validateIds( (array) $prodIds ) ) ) !== [] ) {
291
			$this->conditions[] = $this->filter->compare( '==', 'product.id', $ids );
292
		}
293
294
		return $this;
295
	}
296
297
298
	/**
299
	 * Adds a filter to return only items containing the property
300
	 *
301
	 * @param string $type Type code of the property, e.g. "isbn"
302
	 * @param string|null $value Exact value of the property
303
	 * @param string|null $langid ISO country code (en or en_US) or null if not language specific
304
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
305
	 * @since 2019.04
306
	 */
307
	public function property( string $type, string $value = null, string $langid = null ) : Iface
308
	{
309
		$func = $this->filter->make( 'product:prop', [$type, $langid, $value] );
310
		$this->conditions[] = $this->filter->compare( '!=', $func, null );
311
		return $this;
312
	}
313
314
315
	/**
316
	 * Returns the product for the given product URL name
317
	 *
318
	 * @param string $name Product URL name
319
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
320
	 * @since 2019.04
321
	 */
322
	public function resolve( string $name ) : \Aimeos\MShop\Product\Item\Iface
323
	{
324
		$search = $this->manager->filter()->slice( 0, 1 );
325
		$search->setConditions( $search->compare( '==', 'index.text:url()', $name ) );
326
327
		if( ( $item = $this->manager->search( $search, $this->domains )->first() ) === null )
328
		{
329
			$msg = $this->getContext()->getI18n()->dt( 'controller/frontend', 'Unable to find product "%1$s"' );
330
			throw new \Aimeos\Controller\Frontend\Product\Exception( sprintf( $msg, $name ) );
331
		}
332
333
		return $item;
334
	}
335
336
337
	/**
338
	 * Returns the products filtered by the previously assigned conditions
339
	 *
340
	 * @param int &$total Parameter where the total number of found products will be stored in
341
	 * @return \Aimeos\Map Ordered list of product items implementing \Aimeos\MShop\Product\Item\Iface
342
	 * @since 2019.04
343
	 */
344
	public function search( int &$total = null ) : \Aimeos\Map
345
	{
346
		$this->filter->setSortations( $this->sort );
0 ignored issues
show
Bug introduced by
The method setSortations() does not exist on Aimeos\MW\Criteria\Iface. It seems like you code against a sub-type of said class. However, the method does not exist in Aimeos\MW\Criteria\Base. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

346
		$this->filter->/** @scrutinizer ignore-call */ 
347
                 setSortations( $this->sort );
Loading history...
347
		$this->filter->setConditions( $this->filter->combine( '&&', $this->conditions ) );
348
349
		return $this->manager->search( $this->filter, $this->domains, $total );
350
	}
351
352
353
	/**
354
	 * Sets the start value and the number of returned products for slicing the list of found products
355
	 *
356
	 * @param int $start Start value of the first product in the list
357
	 * @param int $limit Number of returned products
358
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
359
	 * @since 2019.04
360
	 */
361
	public function slice( int $start, int $limit ) : Iface
362
	{
363
		$this->filter->slice( $start, $limit );
364
		return $this;
365
	}
366
367
368
	/**
369
	 * Sets the sorting of the result list
370
	 *
371
	 * @param string|null $key Sorting of the result list like "name", "-name", "price", "-price", "code", "-code",
372
	 * 	"ctime, "-ctime", "relevance" or comma separated combinations and null for no sorting
373
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
374
	 * @since 2019.04
375
	 */
376
	public function sort( string $key = null ) : Iface
377
	{
378
		$list = ( $key ? explode( ',', $key ) : $this->sort = [] );
379
380
		foreach( $list as $sortkey )
381
		{
382
			$direction = ( $sortkey[0] === '-' ? '-' : '+' );
383
			$sortkey = ltrim( $sortkey, '+-' );
384
385
			switch( $sortkey )
386
			{
387
				case 'relevance':
388
					break;
389
390
				case 'code':
391
					$this->sort[] = $this->filter->sort( $direction, 'product.code' );
392
					break;
393
394
				case 'ctime':
395
					$this->sort[] = $this->filter->sort( $direction, 'product.ctime' );
396
					break;
397
398
				case 'name':
399
					$langid = $this->getContext()->getLocale()->getLanguageId();
400
401
					$cmpfunc = $this->filter->make( 'index.text:name', [$langid] );
402
					$this->conditions[] = $this->filter->compare( '!=', $cmpfunc, null );
403
404
					$sortfunc = $this->filter->make( 'sort:index.text:name', [$langid] );
405
					$this->sort[] = $this->filter->sort( $direction, $sortfunc );
406
					break;
407
408
				case 'price':
409
					$currencyid = $this->getContext()->getLocale()->getCurrencyId();
410
					$sortfunc = $this->filter->make( 'sort:index.price:value', [$currencyid] );
411
412
					$cmpfunc = $this->filter->make( 'index.price:value', [$currencyid] );
413
					$this->conditions['price-sort'] = $this->filter->compare( '!=', $cmpfunc, null );
414
415
					$this->sort[] = $this->filter->sort( $direction, $sortfunc );
416
					break;
417
418
				default:
419
					$this->sort[] = $this->filter->sort( $direction, $sortkey );
420
			}
421
		}
422
423
		$this->filter->setSortations( $this->sort );
424
		return $this;
425
	}
426
427
428
	/**
429
	 * Adds minimum stock level for filtering
430
	 *
431
	 * @param string|int|float|null Minimum stock level
0 ignored issues
show
Bug introduced by
The type Aimeos\Controller\Frontend\Product\Minimum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
432
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
433
	 * @since 2021.01
434
	 */
435
	public function stock( $minvalue )
436
	{
437
		$this->conditions['stock'] = $this->filter->or( [
438
			$this->filter->is( 'stock.stocklevel', '>=', $minvalue ),
439
			$this->filter->is( 'stock.stocklevel', '==', null )
440
		] );
441
442
		return $this;
443
	}
444
445
446
	/**
447
	 * Adds supplier IDs for filtering
448
	 *
449
	 * @param array|string $supIds Supplier ID or list of IDs
450
	 * @param string $listtype List type of the products referenced by the suppliers
451
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
452
	 * @since 2019.04
453
	 */
454
	public function supplier( $supIds, string $listtype = 'default' ) : Iface
455
	{
456
		if( !empty( $supIds ) && ( $ids = array_unique( $this->validateIds( (array) $supIds ) ) ) !== [] )
457
		{
458
			$func = $this->filter->make( 'index.supplier:position', [$listtype, $ids] );
459
460
			$this->conditions[] = $this->filter->compare( '==', 'index.supplier.id', $ids );
461
			$this->conditions[] = $this->filter->compare( '>=', $func, 0 );
462
463
			$func = $this->filter->make( 'sort:index.supplier:position', [$listtype, $ids] );
464
			$this->sort[] = $this->filter->sort( '+', $func );
465
		}
466
467
		return $this;
468
	}
469
470
471
	/**
472
	 * Adds input string for full text search
473
	 *
474
	 * @param string|null $text User input for full text search
475
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
476
	 * @since 2019.04
477
	 */
478
	public function text( string $text = null ) : Iface
479
	{
480
		if( !empty( $text ) )
481
		{
482
			$langid = $this->getContext()->getLocale()->getLanguageId();
483
			$func = $this->filter->make( 'index.text:relevance', [$langid, $text] );
484
			$sortfunc = $this->filter->make( 'sort:index.text:relevance', [$langid, $text] );
485
486
			$this->conditions[] = $this->filter->compare( '>', $func, 0 );
487
			$this->sort[] = $this->filter->sort( '-', $sortfunc );
488
		}
489
490
		return $this;
491
	}
492
493
494
	/**
495
	 * Sets the referenced domains that will be fetched too when retrieving items
496
	 *
497
	 * @param array $domains Domain names of the referenced items that should be fetched too
498
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
499
	 * @since 2019.04
500
	 */
501
	public function uses( array $domains ) : Iface
502
	{
503
		$this->domains = $domains;
504
		return $this;
505
	}
506
507
508
	/**
509
	 * Returns the list of catalog IDs for the given catalog tree
510
	 *
511
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Catalog item with children
512
	 * @return array List of catalog IDs
513
	 */
514
	protected function getCatalogIdsFromTree( \Aimeos\MShop\Catalog\Item\Iface $item ) : array
515
	{
516
		if( $item->getStatus() < 1 ) {
517
			return [];
518
		}
519
520
		$list = [$item->getId()];
521
522
		foreach( $item->getChildren() as $child ) {
523
			$list = array_merge( $list, $this->getCatalogIdsFromTree( $child ) );
524
		}
525
526
		return $list;
527
	}
528
529
530
	/**
531
	 * Validates the given IDs as integers
532
	 *
533
	 * @param array $ids List of IDs to validate
534
	 * @return array List of validated IDs
535
	 */
536
	protected function validateIds( array $ids ) : array
537
	{
538
		$list = [];
539
540
		foreach( $ids as $id )
541
		{
542
			if( is_array( $id ) ) {
543
				$list[] = $this->validateIds( $id );
544
			} elseif( $id != '' && preg_match( '/^[A-Za-z0-9\-\_]+$/', $id ) === 1 ) {
545
				$list[] = (string) $id;
546
			}
547
		}
548
549
		return $list;
550
	}
551
}
552