Passed
Push — master ( 15f9a9...e2b004 )
by Aimeos
02:40
created

Standard   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 515
Duplicated Lines 0 %

Importance

Changes 24
Bugs 0 Features 0
Metric Value
wmc 58
eloc 144
c 24
b 0
f 0
dl 0
loc 515
rs 4.5599

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __clone() 0 3 1
A allOf() 0 9 3
A compare() 0 4 1
A __construct() 0 9 1
A aggregate() 0 6 1
A category() 0 27 5
A find() 0 4 1
A validateIds() 0 14 5
A function() 0 3 1
A getCatalogIdsFromTree() 0 13 3
A oneOf() 0 21 5
A slice() 0 4 1
A parse() 0 7 2
A property() 0 5 1
B sort() 0 48 8
A uses() 0 4 1
A has() 0 9 3
A get() 0 4 1
A text() 0 13 2
A search() 0 7 1
A product() 0 7 3
A resolve() 0 11 2
A supplier() 0 14 3
A price() 0 15 3

How to fix   Complexity   

Complex Class

Complex classes like Standard 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.

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 Standard, 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 Aimeos (aimeos.org), 2017-2021
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 $domains = [];
25
	private $filter;
26
	private $manager;
27
	private $rules;
0 ignored issues
show
introduced by
The private property $rules is not used, and could be removed.
Loading history...
28
29
30
	/**
31
	 * Common initialization for controller classes
32
	 *
33
	 * @param \Aimeos\MShop\Context\Item\Iface $context Common MShop context object
34
	 */
35
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
36
	{
37
		parent::__construct( $context );
38
39
		$this->manager = \Aimeos\MShop::create( $context, 'index' );
40
		$this->filter = $this->manager->filter( true );
41
42
		$this->addExpression( $this->filter->compare( '!=', 'index.catalog.id', null ) );
43
		$this->addExpression( $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->and( $this->getConditions() ) );
68
		$this->filter->setSortations( $this->getSortations() );
69
70
		return $this->manager->aggregate( $this->filter, $key, $value, $type );
71
	}
72
73
74
	/**
75
	 * Adds attribute IDs for filtering where products must reference all IDs
76
	 *
77
	 * @param array|string $attrIds Attribute ID or list of IDs
78
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
79
	 * @since 2019.04
80
	 */
81
	public function allOf( $attrIds ) : Iface
82
	{
83
		if( !empty( $attrIds ) && ( $ids = $this->validateIds( (array) $attrIds ) ) !== [] )
84
		{
85
			$func = $this->filter->make( 'index.attribute:allof', [$ids] );
86
			$this->addExpression( $this->filter->compare( '!=', $func, null ) );
87
		}
88
89
		return $this;
90
	}
91
92
93
	/**
94
	 * Adds catalog IDs for filtering
95
	 *
96
	 * @param array|string $catIds Catalog ID or list of IDs
97
	 * @param string $listtype List type of the products referenced by the categories
98
	 * @param int $level Constant from \Aimeos\MW\Tree\Manager\Base if products in subcategories are matched too
99
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
100
	 * @since 2019.04
101
	 */
102
	public function category( $catIds, string $listtype = 'default', int $level = \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE ) : Iface
103
	{
104
		if( !empty( $catIds ) && ( $ids = $this->validateIds( (array) $catIds ) ) !== [] )
105
		{
106
			if( $level != \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE )
107
			{
108
				$list = map();
109
				$cntl = \Aimeos\Controller\Frontend::create( $this->getContext(), 'catalog' );
110
111
				foreach( $ids as $catId ) {
112
					$list->union( $cntl->root( $catId )->getTree( $level )->toList() );
113
				}
114
115
				$ids = $this->validateIds( $list->keys()->toArray() );
116
			}
117
118
			$func = $this->filter->make( 'index.catalog:position', [$listtype, $ids] );
119
120
			$this->addExpression( $this->filter->compare( '==', 'index.catalog.id', $ids ) );
121
			$this->addExpression( $this->filter->compare( '>=', $func, 0 ) );
122
123
			$func = $this->filter->make( 'sort:index.catalog:position', [$listtype, $ids] );
124
			$this->addExpression( $this->filter->sort( '+', $func ) );
125
			$this->addExpression( $this->filter->sort( '+', 'product.id' ) ); // prevent flaky order if products have same position
126
		}
127
128
		return $this;
129
	}
130
131
132
	/**
133
	 * Adds generic condition for filtering products
134
	 *
135
	 * @param string $operator Comparison operator, e.g. "==", "!=", "<", "<=", ">=", ">", "=~", "~="
136
	 * @param string $key Search key defined by the product manager, e.g. "product.status"
137
	 * @param array|string $value Value or list of values to compare to
138
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
139
	 * @since 2019.04
140
	 */
141
	public function compare( string $operator, string $key, $value ) : Iface
142
	{
143
		$this->addExpression( $this->filter->compare( $operator, $key, $value ) );
144
		return $this;
145
	}
146
147
148
	/**
149
	 * Returns the product for the given product code
150
	 *
151
	 * @param string $code Unique product code
152
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
153
	 * @since 2019.04
154
	 */
155
	public function find( string $code ) : \Aimeos\MShop\Product\Item\Iface
156
	{
157
		$item = $this->manager->find( $code, $this->domains, 'product', null, true );
158
		return \Aimeos\MShop::create( $this->getContext(), 'rule' )->apply( $item, 'catalog' );
159
	}
160
161
162
	/**
163
	 * Creates a search function string for the given name and parameters
164
	 *
165
	 * @param string $name Name of the search function without parenthesis, e.g. "product:has"
166
	 * @param array $params List of parameters for the search function with numeric keys starting at 0
167
	 * @return string Search function string that can be used in compare()
168
	 */
169
	public function function( string $name, array $params ) : string
170
	{
171
		return $this->filter->make( $name, $params );
172
	}
173
174
175
	/**
176
	 * Returns the product for the given product ID
177
	 *
178
	 * @param string $id Unique product ID
179
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
180
	 * @since 2019.04
181
	 */
182
	public function get( string $id ) : \Aimeos\MShop\Product\Item\Iface
183
	{
184
		$item = $this->manager->get( $id, $this->domains, true );
185
		return \Aimeos\MShop::create( $this->getContext(), 'rule' )->apply( $item, 'catalog' );
186
	}
187
188
189
	/**
190
	 * Adds a filter to return only items containing a reference to the given ID
191
	 *
192
	 * @param string $domain Domain name of the referenced item, e.g. "attribute"
193
	 * @param string|null $type Type code of the reference, e.g. "variant" or null for all types
194
	 * @param string|null $refId ID of the referenced item of the given domain or null for all references
195
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
196
	 * @since 2019.04
197
	 */
198
	public function has( string $domain, string $type = null, string $refId = null ) : Iface
199
	{
200
		$params = [$domain];
201
		!$type ?: $params[] = $type;
202
		!$refId ?: $params[] = $refId;
203
204
		$func = $this->filter->make( 'product:has', $params );
205
		$this->addExpression( $this->filter->compare( '!=', $func, null ) );
206
		return $this;
207
	}
208
209
210
	/**
211
	 * Adds attribute IDs for filtering where products must reference at least one ID
212
	 *
213
	 * If an array of ID lists is given, each ID list is added separately as condition.
214
	 *
215
	 * @param array|string $attrIds Attribute ID, list of IDs or array of lists with IDs
216
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
217
	 * @since 2019.04
218
	 */
219
	public function oneOf( $attrIds ) : Iface
220
	{
221
		$attrIds = (array) $attrIds;
222
223
		foreach( $attrIds as $key => $entry )
224
		{
225
			if( is_array( $entry ) && ( $ids = $this->validateIds( $entry ) ) !== [] )
226
			{
227
				$func = $this->filter->make( 'index.attribute:oneof', [$ids] );
228
				$this->addExpression( $this->filter->compare( '!=', $func, null ) );
229
				unset( $attrIds[$key] );
230
			}
231
		}
232
233
		if( ( $ids = $this->validateIds( $attrIds ) ) !== [] )
234
		{
235
			$func = $this->filter->make( 'index.attribute:oneof', [$ids] );
236
			$this->addExpression( $this->filter->compare( '!=', $func, null ) );
237
		}
238
239
		return $this;
240
	}
241
242
243
	/**
244
	 * Parses the given array and adds the conditions to the list of conditions
245
	 *
246
	 * @param array $conditions List of conditions, e.g. [['>' => ['product.status' => 0]], ['==' => ['product.type' => 'default']]]
247
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
248
	 * @since 2019.04
249
	 */
250
	public function parse( array $conditions ) : Iface
251
	{
252
		if( ( $cond = $this->filter->parse( $conditions ) ) !== null ) {
253
			$this->addExpression( $cond );
254
		}
255
256
		return $this;
257
	}
258
259
260
	/**
261
	 * Adds price restrictions for filtering
262
	 *
263
	 * @param array|string $value Upper price limit, list of lower and upper price or NULL for no restrictions
264
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
265
	 * @since 2020.10
266
	 */
267
	public function price( $value = null ) : Iface
268
	{
269
		if( $value )
270
		{
271
			$value = (array) $value;
272
			$func = $this->filter->make( 'index.price:value', [$this->getContext()->getLocale()->getCurrencyId()] );
273
274
			$this->addExpression( $this->filter->compare( '<=', $func, sprintf( '%013.2F', end( $value ) ) ) );
275
276
			if( count( $value ) > 1 ) {
277
				$this->addExpression( $this->filter->compare( '>=', $func, sprintf( '%013.2F', reset( $value ) ) ) );
278
			}
279
		}
280
281
		return $this;
282
	}
283
284
285
	/**
286
	 * Adds product IDs for filtering
287
	 *
288
	 * @param array|string $prodIds Product ID or list of IDs
289
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
290
	 * @since 2019.04
291
	 */
292
	public function product( $prodIds ) : Iface
293
	{
294
		if( !empty( $prodIds ) && ( $ids = array_unique( $this->validateIds( (array) $prodIds ) ) ) !== [] ) {
295
			$this->addExpression( $this->filter->compare( '==', 'product.id', $ids ) );
296
		}
297
298
		return $this;
299
	}
300
301
302
	/**
303
	 * Adds a filter to return only items containing the property
304
	 *
305
	 * @param string $type Type code of the property, e.g. "isbn"
306
	 * @param string|null $value Exact value of the property
307
	 * @param string|null $langid ISO country code (en or en_US) or null if not language specific
308
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
309
	 * @since 2019.04
310
	 */
311
	public function property( string $type, string $value = null, string $langid = null ) : Iface
312
	{
313
		$func = $this->filter->make( 'product:prop', [$type, $langid, $value] );
314
		$this->addExpression( $this->filter->compare( '!=', $func, null ) );
315
		return $this;
316
	}
317
318
319
	/**
320
	 * Returns the product for the given product URL name
321
	 *
322
	 * @param string $name Product URL name
323
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
324
	 * @since 2019.04
325
	 */
326
	public function resolve( string $name ) : \Aimeos\MShop\Product\Item\Iface
327
	{
328
		$search = $this->manager->filter( true )->slice( 0, 1 )->add( ['index.text:url()' => $name] );
329
330
		if( ( $item = $this->manager->search( $search, $this->domains )->first() ) === null )
331
		{
332
			$msg = $this->getContext()->getI18n()->dt( 'controller/frontend', 'Unable to find product "%1$s"' );
333
			throw new \Aimeos\Controller\Frontend\Product\Exception( sprintf( $msg, $name ) );
334
		}
335
336
		return \Aimeos\MShop::create( $this->getContext(), 'rule' )->apply( $item, 'catalog' );
337
	}
338
339
340
	/**
341
	 * Returns the products filtered by the previously assigned conditions
342
	 *
343
	 * @param int &$total Parameter where the total number of found products will be stored in
344
	 * @return \Aimeos\Map Ordered list of product items implementing \Aimeos\MShop\Product\Item\Iface
345
	 * @since 2019.04
346
	 */
347
	public function search( int &$total = null ) : \Aimeos\Map
348
	{
349
		$this->filter->setSortations( $this->getSortations() );
350
		$this->filter->setConditions( $this->filter->and( $this->getConditions() ) );
351
352
		$items = $this->manager->search( $this->filter, $this->domains, $total );
353
		return \Aimeos\MShop::create( $this->getContext(), 'rule' )->apply( $items, 'catalog' );
354
	}
355
356
357
	/**
358
	 * Sets the start value and the number of returned products for slicing the list of found products
359
	 *
360
	 * @param int $start Start value of the first product in the list
361
	 * @param int $limit Number of returned products
362
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
363
	 * @since 2019.04
364
	 */
365
	public function slice( int $start, int $limit ) : Iface
366
	{
367
		$this->filter->slice( $start, $limit );
368
		return $this;
369
	}
370
371
372
	/**
373
	 * Sets the sorting of the result list
374
	 *
375
	 * @param string|null $key Sorting of the result list like "name", "-name", "price", "-price", "code", "-code",
376
	 * 	"ctime, "-ctime", "relevance" or comma separated combinations and null for no sorting
377
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
378
	 * @since 2019.04
379
	 */
380
	public function sort( string $key = null ) : Iface
381
	{
382
		$list = $this->splitKeys( $key );
383
384
		foreach( $list as $sortkey )
385
		{
386
			$direction = ( $sortkey[0] === '-' ? '-' : '+' );
387
			$sortkey = ltrim( $sortkey, '+-' );
388
389
			switch( $sortkey )
390
			{
391
				case 'relevance':
392
					break;
393
394
				case 'code':
395
					$this->addExpression( $this->filter->sort( $direction, 'product.code' ) );
396
					break;
397
398
				case 'ctime':
399
					$this->addExpression( $this->filter->sort( $direction, 'product.ctime' ) );
400
					break;
401
402
				case 'name':
403
					$langid = $this->getContext()->getLocale()->getLanguageId();
404
405
					$cmpfunc = $this->filter->make( 'index.text:name', [$langid] );
406
					$this->addExpression( $this->filter->compare( '!=', $cmpfunc, null ) );
407
408
					$sortfunc = $this->filter->make( 'sort:index.text:name', [$langid] );
409
					$this->addExpression( $this->filter->sort( $direction, $sortfunc ) );
410
					break;
411
412
				case 'price':
413
					$currencyid = $this->getContext()->getLocale()->getCurrencyId();
414
					$sortfunc = $this->filter->make( 'sort:index.price:value', [$currencyid] );
415
416
					$cmpfunc = $this->filter->make( 'index.price:value', [$currencyid] );
417
					$this->addExpression( $this->filter->compare( '!=', $cmpfunc, null ) );
418
419
					$this->addExpression( $this->filter->sort( $direction, $sortfunc ) );
420
					break;
421
422
				default:
423
					$this->addExpression( $this->filter->sort( $direction, $sortkey ) );
424
			}
425
		}
426
427
		return $this;
428
	}
429
430
431
	/**
432
	 * Adds supplier IDs for filtering
433
	 *
434
	 * @param array|string $supIds Supplier ID or list of IDs
435
	 * @param string $listtype List type of the products referenced by the suppliers
436
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
437
	 * @since 2019.04
438
	 */
439
	public function supplier( $supIds, string $listtype = 'default' ) : Iface
440
	{
441
		if( !empty( $supIds ) && ( $ids = array_unique( $this->validateIds( (array) $supIds ) ) ) !== [] )
442
		{
443
			$func = $this->filter->make( 'index.supplier:position', [$listtype, $ids] );
444
445
			$this->addExpression( $this->filter->compare( '==', 'index.supplier.id', $ids ) );
446
			$this->addExpression( $this->filter->compare( '>=', $func, 0 ) );
447
448
			$func = $this->filter->make( 'sort:index.supplier:position', [$listtype, $ids] );
449
			$this->addExpression( $this->filter->sort( '+', $func ) );
450
		}
451
452
		return $this;
453
	}
454
455
456
	/**
457
	 * Adds input string for full text search
458
	 *
459
	 * @param string|null $text User input for full text search
460
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
461
	 * @since 2019.04
462
	 */
463
	public function text( string $text = null ) : Iface
464
	{
465
		if( !empty( $text ) )
466
		{
467
			$langid = $this->getContext()->getLocale()->getLanguageId();
468
			$func = $this->filter->make( 'index.text:relevance', [$langid, $text] );
469
			$sortfunc = $this->filter->make( 'sort:index.text:relevance', [$langid, $text] );
470
471
			$this->addExpression( $this->filter->compare( '>', $func, 0 ) );
472
			$this->addExpression( $this->filter->sort( '-', $sortfunc ) );
473
		}
474
475
		return $this;
476
	}
477
478
479
	/**
480
	 * Sets the referenced domains that will be fetched too when retrieving items
481
	 *
482
	 * @param array $domains Domain names of the referenced items that should be fetched too
483
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
484
	 * @since 2019.04
485
	 */
486
	public function uses( array $domains ) : Iface
487
	{
488
		$this->domains = $domains;
489
		return $this;
490
	}
491
492
493
	/**
494
	 * Returns the list of catalog IDs for the given catalog tree
495
	 *
496
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Catalog item with children
497
	 * @return array List of catalog IDs
498
	 */
499
	protected function getCatalogIdsFromTree( \Aimeos\MShop\Catalog\Item\Iface $item ) : array
500
	{
501
		if( $item->getStatus() < 1 ) {
502
			return [];
503
		}
504
505
		$list = [$item->getId()];
506
507
		foreach( $item->getChildren() as $child ) {
508
			$list = array_merge( $list, $this->getCatalogIdsFromTree( $child ) );
509
		}
510
511
		return $list;
512
	}
513
514
515
	/**
516
	 * Validates the given IDs as integers
517
	 *
518
	 * @param array $ids List of IDs to validate
519
	 * @return array List of validated IDs
520
	 */
521
	protected function validateIds( array $ids ) : array
522
	{
523
		$list = [];
524
525
		foreach( $ids as $id )
526
		{
527
			if( is_array( $id ) ) {
528
				$list[] = $this->validateIds( $id );
529
			} elseif( $id != '' && preg_match( '/^[A-Za-z0-9\-\_]+$/', $id ) === 1 ) {
530
				$list[] = (string) $id;
531
			}
532
		}
533
534
		return $list;
535
	}
536
}
537