Passed
Push — master ( c9dfc7...a4ce90 )
by Aimeos
01:38
created

Standard::search()   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 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
				$currencyid = $this->getContext()->getLocale()->getCurrencyId();
346
347
				$cmpfunc = $this->filter->createFunction( 'index.price:value', [$currencyid] );
348
				$this->conditions[] = $this->filter->compare( '!=', $cmpfunc, null );
349
350
				$sortfunc = $this->filter->createFunction( 'sort:index.price:value', [$currencyid] );
351
				$this->sort = $this->filter->sort( $direction, $sortfunc );
352
				break;
353
354
			default:
355
				$this->sort = $this->filter->sort( $direction, $key );
356
		}
357
358
		if( $this->sort ) {
359
			$this->filter->setSortations( [$this->sort] );
360
		}
361
362
		return $this;
363
	}
364
365
366
	/**
367
	 * Adds supplier IDs for filtering
368
	 *
369
	 * @param array|string $supIds Supplier ID or list of IDs
370
	 * @param string $listtype List type of the products referenced by the suppliers
371
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
372
	 * @since 2019.04
373
	 */
374
	public function supplier( $supIds, $listtype = 'default' )
375
	{
376
		if( !empty( $supIds ) && ( $ids = array_unique( $this->validateIds( (array) $supIds ) ) ) !== [] )
377
		{
378
			$func = $this->filter->createFunction( 'index.supplier:position', [$listtype, $ids] );
379
380
			$this->conditions[] = $this->filter->compare( '==', 'index.supplier.id', $ids );
381
			$this->conditions[] = $this->filter->compare( '>=', $func, 0 );
382
383
			$func = $this->filter->createFunction( 'sort:index.supplier:position', [$listtype, $ids] );
384
			$this->sort = $this->filter->sort( '+', $func );
385
		}
386
387
		return $this;
388
	}
389
390
391
	/**
392
	 * Adds input string for full text search
393
	 *
394
	 * @param string|null $text User input for full text search
395
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
396
	 * @since 2019.04
397
	 */
398
	public function text( $text )
399
	{
400
		if( !empty( $text ) )
401
		{
402
			$langid = $this->getContext()->getLocale()->getLanguageId();
403
			$func = $this->filter->createFunction( 'index.text:relevance', [$langid, $text] );
404
405
			$this->conditions[] = $this->filter->compare( '>', $func, 0 );
406
		}
407
408
		return $this;
409
	}
410
411
412
	/**
413
	 * Sets the referenced domains that will be fetched too when retrieving items
414
	 *
415
	 * @param array $domains Domain names of the referenced items that should be fetched too
416
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
417
	 * @since 2019.04
418
	 */
419
	public function uses( array $domains )
420
	{
421
		$this->domains = $domains;
422
		return $this;
423
	}
424
425
426
	/**
427
	 * Returns the list of catalog IDs for the given catalog tree
428
	 *
429
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Catalog item with children
430
	 * @return array List of catalog IDs
431
	 */
432
	protected function getCatalogIdsFromTree( \Aimeos\MShop\Catalog\Item\Iface $item )
433
	{
434
		if( $item->getStatus() < 1 ) {
435
			return [];
436
		}
437
438
		$list = [ $item->getId() ];
439
440
		foreach( $item->getChildren() as $child ) {
441
			$list = array_merge( $list, $this->getCatalogIdsFromTree( $child ) );
442
		}
443
444
		return $list;
445
	}
446
447
448
	/**
449
	 * Validates the given IDs as integers
450
	 *
451
	 * @param array $ids List of IDs to validate
452
	 * @return array List of validated IDs
453
	 */
454
	protected function validateIds( array $ids )
455
	{
456
		$list = [];
457
458
		foreach( $ids as $id )
459
		{
460
			if( $id != '' && preg_match( '/^[A-Za-z0-9\-\_]+$/', $id ) === 1 ) {
461
				$list[] = (string) $id;
462
			}
463
		}
464
465
		return $list;
466
	}
467
}
468