Completed
Push — master ( 94eb6c...a336e2 )
by Aimeos
02:13
created

Standard::function()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 2
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 $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->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( string $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 ) : Iface
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 int $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, string $listtype = 'default', int $level = \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE ) : Iface
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 = $this->validateIds( 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( string $operator, string $key, $value ) : Iface
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( string $code ) : \Aimeos\MShop\Product\Item\Iface
151
	{
152
		return $this->manager->findItem( $code, $this->domains, 'product', null, true );
153
	}
154
155
156
	/**
157
	 * Creates a search function string for the given name and parameters
158
	 *
159
	 * @param string $name Name of the search function without parenthesis, e.g. "product:has"
160
	 * @param array $params List of parameters for the search function with numeric keys starting at 0
161
	 * @param string Search function string that can be used in compare()
0 ignored issues
show
Bug introduced by
The type Aimeos\Controller\Frontend\Product\Search 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...
162
	 */
163
	public function function( string $name, array $params ) : string
164
	{
165
		return $this->filter->createFunction( $name, $params );
166
	}
167
168
169
	/**
170
	 * Returns the product for the given product ID
171
	 *
172
	 * @param string $id Unique product ID
173
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
174
	 * @since 2019.04
175
	 */
176
	public function get( string $id ) : \Aimeos\MShop\Product\Item\Iface
177
	{
178
		return $this->manager->getItem( $id, $this->domains, true );
179
	}
180
181
182
	/**
183
	 * Adds a filter to return only items containing a reference to the given ID
184
	 *
185
	 * @param string $domain Domain name of the referenced item, e.g. "attribute"
186
	 * @param string|null $type Type code of the reference, e.g. "variant" or null for all types
187
	 * @param string|null $refId ID of the referenced item of the given domain or null for all references
188
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
189
	 * @since 2019.04
190
	 */
191
	public function has( string $domain, string $type = null, string $refId = null ) : Iface
192
	{
193
		$params = [$domain];
194
		!$type ?: $params[] = $type;
195
		!$refId ?: $params[] = $refId;
196
197
		$func = $this->filter->createFunction( 'product:has', $params );
198
		$this->conditions[] = $this->filter->compare( '!=', $func, null );
199
		return $this;
200
	}
201
202
203
	/**
204
	 * Adds attribute IDs for filtering where products must reference at least one ID
205
	 *
206
	 * If an array of ID lists is given, each ID list is added separately as condition.
207
	 *
208
	 * @param array|string $attrIds Attribute ID, list of IDs or array of lists with IDs
209
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
210
	 * @since 2019.04
211
	 */
212
	public function oneOf( $attrIds ) : Iface
213
	{
214
		$attrIds = (array) $attrIds;
215
216
		foreach( $attrIds as $key => $entry )
217
		{
218
			if( is_array( $entry ) && ( $ids = array_unique( $this->validateIds( $entry ) ) ) !== [] )
219
			{
220
				$func = $this->filter->createFunction( 'index.attribute:oneof', [$ids] );
221
				$this->conditions[] = $this->filter->compare( '!=', $func, null );
222
				unset( $attrIds[$key] );
223
			}
224
		}
225
226
		if( ( $ids = array_unique( $this->validateIds( $attrIds ) ) ) !== [] )
227
		{
228
			$func = $this->filter->createFunction( 'index.attribute:oneof', [$ids] );
229
			$this->conditions[] = $this->filter->compare( '!=', $func, null );
230
		}
231
232
		return $this;
233
	}
234
235
236
	/**
237
	 * Parses the given array and adds the conditions to the list of conditions
238
	 *
239
	 * @param array $conditions List of conditions, e.g. [['>' => ['product.status' => 0]], ['==' => ['product.type' => 'default']]]
240
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
241
	 * @since 2019.04
242
	 */
243
	public function parse( array $conditions ) : Iface
244
	{
245
		if( ( $cond = $this->filter->toConditions( $conditions ) ) !== null ) {
246
			$this->conditions[] = $cond;
247
		}
248
249
		return $this;
250
	}
251
252
253
	/**
254
	 * Adds product IDs for filtering
255
	 *
256
	 * @param array|string $prodIds Product ID or list of IDs
257
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
258
	 * @since 2019.04
259
	 */
260
	public function product( $prodIds ) : Iface
261
	{
262
		if( !empty( $prodIds ) && ( $ids = array_unique( $this->validateIds( (array) $prodIds ) ) ) !== [] ) {
263
			$this->conditions[] = $this->filter->compare( '==', 'product.id', $ids );
264
		}
265
266
		return $this;
267
	}
268
269
270
	/**
271
	 * Adds a filter to return only items containing the property
272
	 *
273
	 * @param string $type Type code of the property, e.g. "isbn"
274
	 * @param string|null $value Exact value of the property
275
	 * @param string|null $langid ISO country code (en or en_US) or null if not language specific
276
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
277
	 * @since 2019.04
278
	 */
279
	public function property( string $type, string $value = null, string $langid = null ) : Iface
280
	{
281
		$func = $this->filter->createFunction( 'product:prop', [$type, $langid, $value] );
282
		$this->conditions[] = $this->filter->compare( '!=', $func, null );
283
		return $this;
284
	}
285
286
287
	/**
288
	 * Returns the product for the given product URL name
289
	 *
290
	 * @param string $name Product URL name
291
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
292
	 * @since 2019.04
293
	 */
294
	public function resolve( string $name ) : \Aimeos\MShop\Product\Item\Iface
295
	{
296
		$langid = $this->getContext()->getLocale()->getLanguageId();
297
298
		$search = $this->manager->createSearch();
299
		$func = $search->createFunction( 'index.text:url', [$langid] );
300
		$search->setConditions( $search->compare( '==', $func, $name ) );
301
302
		$items = $this->manager->searchItems( $search, $this->domains );
303
304
		if( ( $item = reset( $items ) ) !== false ) {
305
			return $item;
306
		}
307
308
		$msg = $this->getContext()->getI18n()->dt( 'controller/frontend', 'Unable to find product "%1$s"' );
309
		throw new \Aimeos\Controller\Frontend\Product\Exception( sprintf( $msg, $name ) );
310
	}
311
312
313
	/**
314
	 * Returns the products filtered by the previously assigned conditions
315
	 *
316
	 * @param int &$total Parameter where the total number of found products will be stored in
317
	 * @return \Aimeos\MShop\Product\Item\Iface[] Ordered list of product items
318
	 * @since 2019.04
319
	 */
320
	public function search( int &$total = null )
321
	{
322
		$this->filter->setSortations( $this->sort );
323
		$this->filter->setConditions( $this->filter->combine( '&&', $this->conditions ) );
324
		return $this->manager->searchItems( $this->filter, $this->domains, $total );
325
	}
326
327
328
	/**
329
	 * Sets the start value and the number of returned products for slicing the list of found products
330
	 *
331
	 * @param int $start Start value of the first product in the list
332
	 * @param int $limit Number of returned products
333
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
334
	 * @since 2019.04
335
	 */
336
	public function slice( int $start, int $limit ) : Iface
337
	{
338
		$this->filter->setSlice( $start, $limit );
339
		return $this;
340
	}
341
342
343
	/**
344
	 * Sets the sorting of the result list
345
	 *
346
	 * @param string|null $key Sorting of the result list like "name", "-name", "price", "-price", "code", "-code",
347
	 * 	"ctime, "-ctime", "relevance" or comma separated combinations and null for no sorting
348
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
349
	 * @since 2019.04
350
	 */
351
	public function sort( string $key = null ) : Iface
352
	{
353
		$list = ( $key ? explode( ',', $key ) : $this->sort = [] );
354
355
		foreach( $list as $sortkey )
356
		{
357
			$direction = ( $sortkey[0] === '-' ? '-' : '+' );
358
			$sortkey = ltrim( $sortkey, '+-' );
359
360
			switch( $sortkey )
361
			{
362
				case 'relevance':
363
					break;
364
365
				case 'code':
366
					$this->sort[] = $this->filter->sort( $direction, 'product.code' );
367
					break;
368
369
				case 'ctime':
370
					$this->sort[] = $this->filter->sort( $direction, 'product.ctime' );
371
					break;
372
373
				case 'name':
374
					$langid = $this->getContext()->getLocale()->getLanguageId();
375
376
					$cmpfunc = $this->filter->createFunction( 'index.text:name', [$langid] );
377
					$this->conditions[] = $this->filter->compare( '!=', $cmpfunc, null );
378
379
					$sortfunc = $this->filter->createFunction( 'sort:index.text:name', [$langid] );
380
					$this->sort[] = $this->filter->sort( $direction, $sortfunc );
381
					break;
382
383
				case 'price':
384
					$expr = [];
385
					$context = $this->getContext();
386
387
					/** controller/frontend/product/price-types
388
					 * Use different product prices types for sorting by price
389
					 *
390
					 * In some cases, prices are stored with different types, eg. price per kg.
391
					 * This configuration option defines which types are incorporated when sorting
392
					 * the product list by price.
393
					 *
394
					 * @param array List of price types codes
395
					 * @since 2018.10
396
					 * @category Developer
397
					 */
398
					$types = $context->getConfig()->get( 'controller/frontend/product/price-types', ['default'] );
399
					$currencyid = $context->getLocale()->getCurrencyId();
400
401
					foreach( $types as $type )
402
					{
403
						$cmpfunc = $this->filter->createFunction( 'index.price:value', [$currencyid] );
404
						$expr[] = $this->filter->compare( '!=', $cmpfunc, null );
405
					}
406
407
					$this->conditions[] = $this->filter->combine( '||', $expr );
408
409
					$sortfunc = $this->filter->createFunction( 'sort:index.price:value', [$currencyid] );
410
					$this->sort[] = $this->filter->sort( $direction, $sortfunc );
411
					break;
412
413
				default:
414
					$this->sort[] = $this->filter->sort( $direction, $sortkey );
415
			}
416
		}
417
418
		$this->filter->setSortations( $this->sort );
419
		return $this;
420
	}
421
422
423
	/**
424
	 * Adds supplier IDs for filtering
425
	 *
426
	 * @param array|string $supIds Supplier ID or list of IDs
427
	 * @param string $listtype List type of the products referenced by the suppliers
428
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
429
	 * @since 2019.04
430
	 */
431
	public function supplier( $supIds, string $listtype = 'default' ) : Iface
432
	{
433
		if( !empty( $supIds ) && ( $ids = array_unique( $this->validateIds( (array) $supIds ) ) ) !== [] )
434
		{
435
			$func = $this->filter->createFunction( 'index.supplier:position', [$listtype, $ids] );
436
437
			$this->conditions[] = $this->filter->compare( '==', 'index.supplier.id', $ids );
438
			$this->conditions[] = $this->filter->compare( '>=', $func, 0 );
439
440
			$func = $this->filter->createFunction( 'sort:index.supplier:position', [$listtype, $ids] );
441
			$this->sort[] = $this->filter->sort( '+', $func );
442
		}
443
444
		return $this;
445
	}
446
447
448
	/**
449
	 * Adds input string for full text search
450
	 *
451
	 * @param string|null $text User input for full text search
452
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
453
	 * @since 2019.04
454
	 */
455
	public function text( string $text = null ) : Iface
456
	{
457
		if( !empty( $text ) )
458
		{
459
			$langid = $this->getContext()->getLocale()->getLanguageId();
460
			$func = $this->filter->createFunction( 'index.text:relevance', [$langid, $text] );
461
462
			$this->conditions[] = $this->filter->compare( '>', $func, 0 );
463
		}
464
465
		return $this;
466
	}
467
468
469
	/**
470
	 * Sets the referenced domains that will be fetched too when retrieving items
471
	 *
472
	 * @param array $domains Domain names of the referenced items that should be fetched too
473
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
474
	 * @since 2019.04
475
	 */
476
	public function uses( array $domains ) : Iface
477
	{
478
		$this->domains = $domains;
479
		return $this;
480
	}
481
482
483
	/**
484
	 * Returns the list of catalog IDs for the given catalog tree
485
	 *
486
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Catalog item with children
487
	 * @return array List of catalog IDs
488
	 */
489
	protected function getCatalogIdsFromTree( \Aimeos\MShop\Catalog\Item\Iface $item ) : array
490
	{
491
		if( $item->getStatus() < 1 ) {
492
			return [];
493
		}
494
495
		$list = [$item->getId()];
496
497
		foreach( $item->getChildren() as $child ) {
498
			$list = array_merge( $list, $this->getCatalogIdsFromTree( $child ) );
499
		}
500
501
		return $list;
502
	}
503
504
505
	/**
506
	 * Validates the given IDs as integers
507
	 *
508
	 * @param array $ids List of IDs to validate
509
	 * @return array List of validated IDs
510
	 */
511
	protected function validateIds( array $ids ) : array
512
	{
513
		$list = [];
514
515
		foreach( $ids as $id )
516
		{
517
			if( $id != '' && preg_match( '/^[A-Za-z0-9\-\_]+$/', $id ) === 1 ) {
518
				$list[] = (string) $id;
519
			}
520
		}
521
522
		return $list;
523
	}
524
}
525