Completed
Push — master ( e87582...bc3c0c )
by Aimeos
02:27
created

Standard::validateIds()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 4
nc 3
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 $filter;
26
	private $manager;
27
	private $sort;
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->createSearch( true );
41
		$this->conditions[] = $this->filter->compare( '!=', 'index.catalog.id', null );
42
43
		/** controller/frontend/product/ignore-dates
44
		 * Ignore start and end dates of products
45
		 *
46
		 * Usually, products are only shown in the product list if their start/end
47
		 * dates are not set or if the current date is withing the start/end date
48
		 * range of the product. This settings will list all products that wouldn't
49
		 * be shown due to their start/end dates but they still can't be bought.
50
		 *
51
		 * @param boolean True to show products whose start/end date range doesn't match the current date, false to hide them
52
		 * @since 2017.08
53
		 * @category Developer
54
		 */
55
		if( $context->getConfig()->get( 'controller/frontend/product/ignore-dates', false ) ) {
56
			$this->conditions[] = $this->filter->compare( '>', 'product.status', 0 );
57
		} else {
58
			$this->conditions[] = $this->filter->getConditions();
59
		}
60
	}
61
62
63
	/**
64
	 * Clones objects in controller and resets values
65
	 */
66
	public function __clone()
67
	{
68
		$this->filter = clone $this->filter;
69
	}
70
71
72
	/**
73
	 * Returns the aggregated count of products for the given key.
74
	 *
75
	 * @param string $key Search key to aggregate for, e.g. "index.attribute.id"
76
	 * @return array Associative list of key values as key and the product count for this key as value
77
	 * @since 2019.04
78
	 */
79
	public function aggregate( $key )
80
	{
81
		$this->filter->setConditions( $this->filter->combine( '&&', $this->conditions ) );
82
		return $this->manager->aggregate( $this->filter, $key );
83
	}
84
85
86
	/**
87
	 * Adds attribute IDs for filtering where products must reference all IDs
88
	 *
89
	 * @param array|string $attrIds Attribute ID or list of IDs
90
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
91
	 * @since 2019.04
92
	 */
93
	public function allOf( $attrIds )
94
	{
95
		if( ( $ids = array_unique( $this->validateIds( (array) $attrIds ) ) ) !== [] )
96
		{
97
			$func = $this->filter->createFunction( 'index.attribute:all', [$ids] );
98
			$this->conditions[] = $this->filter->compare( '!=', $func, null );
99
		}
100
101
		return $this;
102
	}
103
104
105
	/**
106
	 * Adds catalog IDs for filtering
107
	 *
108
	 * @param array|string $catIds Catalog ID or list of IDs
109
	 * @param string $listtype List type of the products referenced by the categories
110
	 * @param integer $level Constant from \Aimeos\MW\Tree\Manager\Base if products in subcategories are matched too
111
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
112
	 * @since 2019.04
113
	 */
114
	public function category( $catIds, $listtype = 'default', $level = \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE )
115
	{
116
		if( ( $ids = $this->validateIds( (array) $catIds ) ) !== [] )
117
		{
118
			if( $level != \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE )
119
			{
120
				$list = [];
121
				$cntl = \Aimeos\Controller\Frontend::create( $this->getContext(), 'catalog' );
122
123
				foreach( $ids as $catId )
124
				{
125
					$tree = $cntl->getTree( $catId, [], $level );
126
					$list = array_merge( $list, $this->getCatalogIdsFromTree( $tree ) );
127
				}
128
129
				$ids = array_unique( $list );
130
			}
131
132
			$func = $this->filter->createFunction( 'index.catalog:position', [$listtype, $ids] );
133
134
			$this->conditions[] = $this->filter->compare( '==', 'index.catalog.id', $ids );
135
			$this->conditions[] = $this->filter->compare( '>=', $func, 0 );
136
137
			$func = $this->filter->createFunction( 'sort:index.catalog:position', [$listtype, $ids] );
138
			$this->sort = $this->filter->sort( '+', $func );
139
		}
140
141
		return $this;
142
	}
143
144
145
	/**
146
	 * Adds generic condition for filtering products
147
	 *
148
	 * @param string $operator Comparison operator, e.g. "==", "!=", "<", "<=", ">=", ">", "=~", "~="
149
	 * @param string $key Search key defined by the product manager, e.g. "product.status"
150
	 * @param array|string $value Value or list of values to compare to
151
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
152
	 * @since 2019.04
153
	 */
154
	public function compare( $operator, $key, $value )
155
	{
156
		$this->conditions[] = $this->filter->compare( $operator, $key, $value );
157
		return $this;
158
	}
159
160
161
	/**
162
	 * Returns the product for the given product ID
163
	 *
164
	 * @param string $id Unique product ID
165
	 * @param string[] $domains Domain names of items that are associated with the products and that should be fetched too
166
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
167
	 * @since 2019.04
168
	 */
169
	public function get( $id, $domains = ['media', 'price', 'text'] )
170
	{
171
		return $this->manager->getItem( $id, $domains );
172
	}
173
174
175
	/**
176
	 * Returns the product for the given product code
177
	 *
178
	 * @param string $code Unique product code
179
	 * @param string[] $domains Domain names of items that are associated with the products and that should be fetched too
180
	 * @return \Aimeos\MShop\Product\Item\Iface Product item including the referenced domains items
181
	 * @since 2019.04
182
	 */
183
	public function find( $code, $domains = ['media', 'price', 'text'] )
184
	{
185
		return $this->manager->findItem( $code, $domains );
186
	}
187
188
189
	/**
190
	 * Adds attribute IDs for filtering where products must reference at least one ID
191
	 *
192
	 * If an array of ID lists is given, each ID list is added separately as condition.
193
	 *
194
	 * @param array|string $attrIds Attribute ID, list of IDs or array of lists with IDs
195
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
196
	 * @since 2019.04
197
	 */
198
	public function oneOf( $attrIds )
199
	{
200
		foreach( (array) $attrIds as $key => $entry )
201
		{
202
			if( is_array( $entry ) && ( $ids = array_unique( $this->validateIds( $entry ) ) ) !== [] ) {
203
				$this->conditions[] = $this->filter->compare( '==', 'index.attribute.id', $ids );
204
				unset( $attrIds[$key] );
205
			}
206
		}
207
208
		if( ( $ids = array_unique( $this->validateIds( (array) $attrIds ) ) ) !== [] ) {
209
			$this->conditions[] = $this->filter->compare( '==', 'index.attribute.id', $ids );
210
		}
211
212
		return $this;
213
	}
214
215
216
	/**
217
	 * Parses the given array and adds the conditions to the list of conditions
218
	 *
219
	 * @param array $conditions List of conditions, e.g. [['>' => ['product.status' => 0]], ['==' => ['product.type' => 'default']]]
220
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
221
	 * @since 2019.04
222
	 */
223
	public function parse( array $conditions )
224
	{
225
		$this->conditions[] = $this->filter->toConditions( $conditions );
226
		return $this;
227
	}
228
229
230
	/**
231
	 * Adds product IDs for filtering
232
	 *
233
	 * @param array|string $prodIds Product ID or list of IDs
234
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
235
	 * @since 2019.04
236
	 */
237
	public function product( $prodIds )
238
	{
239
		if( ( $ids = array_unique( $this->validateIds( (array) $prodIds ) ) ) !== [] ) {
240
			$this->conditions[] = $this->filter->compare( '==', 'product.id', $ids );
241
		}
242
243
		return $this;
244
	}
245
246
247
	/**
248
	 * Returns the products filtered by the previously assigned conditions
249
	 *
250
	 * @param string[] $domains Domain names of items that are associated with the products and that should be fetched too
251
	 * @param integer &$total Parameter where the total number of found products will be stored in
252
	 * @return array Ordered list of product items implementing \Aimeos\MShop\Product\Item\Iface
253
	 * @since 2019.04
254
	 */
255
	public function search( $domains = ['media', 'price', 'text'], &$total = null )
256
	{
257
		$this->filter->setConditions( $this->filter->combine( '&&', $this->conditions ) );
258
		return $this->manager->searchItems( $this->filter, $domains, $total );
259
	}
260
261
262
	/**
263
	 * Sets the start value and the number of returned products for slicing the list of found products
264
	 *
265
	 * @param integer $start Start value of the first product in the list
266
	 * @param integer $limit Number of returned products
267
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
268
	 * @since 2019.04
269
	 */
270
	public function slice( $start, $limit )
271
	{
272
		$this->filter->setSlice( $start, $limit );
273
		return $this;
274
	}
275
276
277
	/**
278
	 * Sets the sorting of the product list
279
	 *
280
	 * @param string|null $key Sortation of the product list like "name", "-name", "price", "-price", "code", "-code", "ctime, "-ctime" and "relevance", null for no sortation
281
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
282
	 * @since 2019.04
283
	 */
284
	public function sort( $key = null )
285
	{
286
		$direction = '+';
287
288
		if( $key != null && $key[0] === '-' )
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $key of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
289
		{
290
			$key = substr( $key, 1 );
291
			$direction = '-';
292
		}
293
294
		switch( $key )
295
		{
296
			case 'code':
297
				$this->sort = $this->filter->sort( $direction, 'product.code' );
298
				break;
299
300
			case 'ctime':
301
				$this->sort = $this->filter->sort( $direction, 'product.ctime' );
302
				break;
303
304
			case 'name':
305
				$langid = $this->getContext()->getLocale()->getLanguageId();
306
307
				$cmpfunc = $this->filter->createFunction( 'index.text:name', [$langid] );
308
				$this->conditions[] = $this->filter->compare( '!=', $cmpfunc, null );
309
310
				$sortfunc = $this->filter->createFunction( 'sort:index.text:name', [$langid] );
311
				$this->sort = $this->filter->sort( $direction, $sortfunc );
312
				break;
313
314
			case 'price':
315
				$currencyid = $this->getContext()->getLocale()->getCurrencyId();
316
317
				$cmpfunc = $this->filter->createFunction( 'index.price:value', [$currencyid] );
318
				$this->conditions[] = $this->filter->compare( '!=', $cmpfunc, null );
319
320
				$sortfunc = $this->filter->createFunction( 'sort:index.price:value', [$currencyid] );
321
				$this->sort = $this->filter->sort( $direction, $sortfunc );
322
				break;
323
324
			case null:
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $key of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
325
				$this->sort = null;
326
				break;
327
		}
328
329
		if( $this->sort ) {
330
			$this->filter->setSortations( [$this->sort] );
331
		}
332
333
		return $this;
334
	}
335
336
337
	/**
338
	 * Adds supplier IDs for filtering
339
	 *
340
	 * @param array|string $supIds Supplier ID or list of IDs
341
	 * @param string $listtype List type of the products referenced by the suppliers
342
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
343
	 * @since 2019.04
344
	 */
345
	public function supplier( $supIds, $listtype = 'default' )
346
	{
347
		if( ( $ids = array_unique( $this->validateIds( (array) $supIds ) ) ) !== [] )
348
		{
349
			$func = $this->filter->createFunction( 'index.supplier:position', [$listtype, $ids] );
350
351
			$this->conditions[] = $this->filter->compare( '==', 'index.supplier.id', $ids );
352
			$this->conditions[] = $this->filter->compare( '>=', $func, 0 );
353
354
			$func = $this->filter->createFunction( 'sort:index.supplier:position', [$listtype, $ids] );
355
			$this->sort = $this->filter->sort( '+', $func );
356
		}
357
358
		return $this;
359
	}
360
361
362
	/**
363
	 * Adds input string for full text search
364
	 *
365
	 * @param string|null $text User input for full text search
366
	 * @return \Aimeos\Controller\Frontend\Product\Iface Product controller for fluent interface
367
	 * @since 2019.04
368
	 */
369
	public function text( $text )
370
	{
371
		if( $text )
0 ignored issues
show
Bug Best Practice introduced by
The expression $text of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
372
		{
373
			$langid = $this->getContext()->getLocale()->getLanguageId();
374
			$func = $this->filter->createFunction( 'index.text:relevance', [$langid, $text] );
375
376
			$this->conditions[] = $this->filter->compare( '>', $func, 0 );
377
		}
378
379
		return $this;
380
	}
381
382
383
	/**
384
	 * Returns the list of catalog IDs for the given catalog tree
385
	 *
386
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Catalog item with children
387
	 * @return array List of catalog IDs
388
	 */
389
	protected function getCatalogIdsFromTree( \Aimeos\MShop\Catalog\Item\Iface $item )
390
	{
391
		if( $item->getStatus() < 1 ) {
392
			return [];
393
		}
394
395
		$list = [ $item->getId() ];
396
397
		foreach( $item->getChildren() as $child ) {
398
			$list = array_merge( $list, $this->getCatalogIdsFromTree( $child ) );
399
		}
400
401
		return $list;
402
	}
403
404
405
	/**
406
	 * Validates the given IDs as integers
407
	 *
408
	 * @param array $ids List of IDs to validate
409
	 * @return array List of validated IDs
410
	 */
411
	protected function validateIds( array $ids )
412
	{
413
		$list = [];
414
415
		foreach( $ids as $id )
416
		{
417
			if( $id != '' && preg_match( '/^[A-Za-z0-9\-\_]+$/', $id ) === 1 ) {
418
				$list[] = (string) $id;
419
			}
420
		}
421
422
		return $list;
423
	}
424
}
425