Passed
Push — master ( a4ce90...ff9225 )
by Aimeos
02:04
created

Standard   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 466
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 53
eloc 130
dl 0
loc 466
rs 6.96
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A find() 0 3 1
A allOf() 0 9 3
A compare() 0 4 1
A __construct() 0 8 1
A oneOf() 0 21 5
A slice() 0 4 1
A aggregate() 0 4 1
A parse() 0 4 1
A property() 0 5 1
A has() 0 9 3
A get() 0 3 1
A search() 0 4 1
A __clone() 0 3 1
A product() 0 7 3
A category() 0 26 5
A validateIds() 0 12 4
A getCatalogIdsFromTree() 0 13 3
B sort() 0 76 11
A uses() 0 4 1
A text() 0 11 2
A supplier() 0 14 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-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 products filtered by the previously assigned conditions
273
	 *
274
	 * @param integer &$total Parameter where the total number of found products will be stored in
275
	 * @return \Aimeos\MShop\Product\Item\Iface[] Ordered list of product items
276
	 * @since 2019.04
277
	 */
278
	public function search( &$total = null )
279
	{
280
		$this->filter->setConditions( $this->filter->combine( '&&', $this->conditions ) );
281
		return $this->manager->searchItems( $this->filter, $this->domains, $total );
282
	}
283
284
285
	/**
286
	 * Sets the start value and the number of returned products for slicing the list of found products
287
	 *
288
	 * @param integer $start Start value of the first product in the list
289
	 * @param integer $limit Number of returned products
290
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
291
	 * @since 2019.04
292
	 */
293
	public function slice( $start, $limit )
294
	{
295
		$this->filter->setSlice( $start, $limit );
296
		return $this;
297
	}
298
299
300
	/**
301
	 * Sets the sorting of the result list
302
	 *
303
	 * @param string|null $key Sorting of the result list like "name", "-name", "price", "-price", "code", "-code", "ctime, "-ctime" and "relevance", null for no sorting
304
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
305
	 * @since 2019.04
306
	 */
307
	public function sort( $key = null )
308
	{
309
		$direction = '+';
310
311
		if( $key != null && $key[0] === '-' )
312
		{
313
			$key = substr( $key, 1 );
314
			$direction = '-';
315
		}
316
317
		switch( $key )
318
		{
319
			case null:
320
				$this->sort = null;
321
				break;
322
323
			case 'relevance':
324
				break;
325
326
			case 'code':
327
				$this->sort = $this->filter->sort( $direction, 'product.code' );
328
				break;
329
330
			case 'ctime':
331
				$this->sort = $this->filter->sort( $direction, 'product.ctime' );
332
				break;
333
334
			case 'name':
335
				$langid = $this->getContext()->getLocale()->getLanguageId();
336
337
				$cmpfunc = $this->filter->createFunction( 'index.text:name', [$langid] );
338
				$this->conditions[] = $this->filter->compare( '!=', $cmpfunc, null );
339
340
				$sortfunc = $this->filter->createFunction( 'sort:index.text:name', [$langid] );
341
				$this->sort = $this->filter->sort( $direction, $sortfunc );
342
				break;
343
344
			case 'price':
345
				$expr = [];
346
				$context = $this->getContext();
347
348
				/** controller/frontend/product/price-types
349
				 * Use different product prices types for sorting by price
350
				 *
351
				 * In some cases, prices are stored with different types, eg. price per kg.
352
				 * This configuration option defines which types are incorporated when sorting
353
				 * the product list by price.
354
				 *
355
				 * @param array List of price types codes
356
				 * @since 2018.10
357
				 * @category Developer
358
				 */
359
				$types = $context->getConfig()->get( 'controller/frontend/product/price-types', ['default'] );
360
				$currencyid = $context->getLocale()->getCurrencyId();
361
362
				foreach( $types as $type )
363
				{
364
					$cmpfunc = $this->filter->createFunction( 'index.price:value', [$currencyid] );
365
					$expr[] = $this->filter->compare( '!=', $cmpfunc, null );
366
				}
367
368
				$this->conditions[] = $this->filter->combine( '||', $expr );
369
370
				$sortfunc = $this->filter->createFunction( 'sort:index.price:value', [$currencyid] );
371
				$this->sort = $this->filter->sort( $direction, $sortfunc );
372
				break;
373
374
			default:
375
				$this->sort = $this->filter->sort( $direction, $key );
376
		}
377
378
		if( $this->sort ) {
379
			$this->filter->setSortations( [$this->sort] );
380
		}
381
382
		return $this;
383
	}
384
385
386
	/**
387
	 * Adds supplier IDs for filtering
388
	 *
389
	 * @param array|string $supIds Supplier ID or list of IDs
390
	 * @param string $listtype List type of the products referenced by the suppliers
391
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
392
	 * @since 2019.04
393
	 */
394
	public function supplier( $supIds, $listtype = 'default' )
395
	{
396
		if( !empty( $supIds ) && ( $ids = array_unique( $this->validateIds( (array) $supIds ) ) ) !== [] )
397
		{
398
			$func = $this->filter->createFunction( 'index.supplier:position', [$listtype, $ids] );
399
400
			$this->conditions[] = $this->filter->compare( '==', 'index.supplier.id', $ids );
401
			$this->conditions[] = $this->filter->compare( '>=', $func, 0 );
402
403
			$func = $this->filter->createFunction( 'sort:index.supplier:position', [$listtype, $ids] );
404
			$this->sort = $this->filter->sort( '+', $func );
405
		}
406
407
		return $this;
408
	}
409
410
411
	/**
412
	 * Adds input string for full text search
413
	 *
414
	 * @param string|null $text User input for full text search
415
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
416
	 * @since 2019.04
417
	 */
418
	public function text( $text )
419
	{
420
		if( !empty( $text ) )
421
		{
422
			$langid = $this->getContext()->getLocale()->getLanguageId();
423
			$func = $this->filter->createFunction( 'index.text:relevance', [$langid, $text] );
424
425
			$this->conditions[] = $this->filter->compare( '>', $func, 0 );
426
		}
427
428
		return $this;
429
	}
430
431
432
	/**
433
	 * Sets the referenced domains that will be fetched too when retrieving items
434
	 *
435
	 * @param array $domains Domain names of the referenced items that should be fetched too
436
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
437
	 * @since 2019.04
438
	 */
439
	public function uses( array $domains )
440
	{
441
		$this->domains = $domains;
442
		return $this;
443
	}
444
445
446
	/**
447
	 * Returns the list of catalog IDs for the given catalog tree
448
	 *
449
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Catalog item with children
450
	 * @return array List of catalog IDs
451
	 */
452
	protected function getCatalogIdsFromTree( \Aimeos\MShop\Catalog\Item\Iface $item )
453
	{
454
		if( $item->getStatus() < 1 ) {
455
			return [];
456
		}
457
458
		$list = [ $item->getId() ];
459
460
		foreach( $item->getChildren() as $child ) {
461
			$list = array_merge( $list, $this->getCatalogIdsFromTree( $child ) );
462
		}
463
464
		return $list;
465
	}
466
467
468
	/**
469
	 * Validates the given IDs as integers
470
	 *
471
	 * @param array $ids List of IDs to validate
472
	 * @return array List of validated IDs
473
	 */
474
	protected function validateIds( array $ids )
475
	{
476
		$list = [];
477
478
		foreach( $ids as $id )
479
		{
480
			if( $id != '' && preg_match( '/^[A-Za-z0-9\-\_]+$/', $id ) === 1 ) {
481
				$list[] = (string) $id;
482
			}
483
		}
484
485
		return $list;
486
	}
487
}
488