Passed
Push — master ( 04e202...9b7189 )
by Aimeos
05:11
created

Base::searchItemsBase()   B

Complexity

Conditions 6
Paths 18

Size

Total Lines 48
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 26
nc 18
nop 8
dl 0
loc 48
rs 8.8817
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2023
6
 * @package MShop
7
 * @subpackage Common
8
 */
9
10
11
namespace Aimeos\MShop\Common\Manager;
12
13
14
/**
15
 * Provides common methods required by most of the manager classes.
16
 *
17
 * @package MShop
18
 * @subpackage Common
19
 */
20
abstract class Base implements \Aimeos\Macro\Iface
21
{
22
	use \Aimeos\Macro\Macroable;
23
	use Sub\Traits;
24
	use Methods;
25
	use Site;
26
27
28
	private $context;
29
	private $stmts = [];
30
	private $search;
31
32
33
	/**
34
	 * Initialization of class.
35
	 *
36
	 * @param \Aimeos\MShop\ContextIface $context Context object
37
	 */
38
	public function __construct( \Aimeos\MShop\ContextIface $context )
39
	{
40
		$this->context = $context;
41
	}
42
43
44
	/**
45
	 * Adds additional column names to SQL statement
46
	 *
47
	 * @param string[] $columns List of column names
48
	 * @param string $sql Insert or update SQL statement
49
	 * @param bool $mode True for insert, false for update statement
50
	 * @return string Modified insert or update SQL statement
51
	 */
52
	protected function addSqlColumns( array $columns, string $sql, bool $mode = true ) : string
53
	{
54
		$names = $values = '';
55
56
		if( $mode )
57
		{
58
			foreach( $columns as $name ) {
59
				$names .= '"' . $name . '", '; $values .= '?, ';
60
			}
61
		}
62
		else
63
		{
64
			foreach( $columns as $name ) {
65
				$names .= '"' . $name . '" = ?, ';
66
			}
67
		}
68
69
		return str_replace( [':names', ':values'], [$names, $values], $sql );
70
	}
71
72
73
	/**
74
	 * Counts the number products that are available for the values of the given key.
75
	 *
76
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
77
	 * @param array|string $keys Search key or list of keys for aggregation
78
	 * @param string $cfgPath Configuration key for the SQL statement
79
	 * @param string[] $required List of domain/sub-domain names like "catalog.index" that must be additionally joined
80
	 * @param string|null $value Search key for aggregating the value column
81
	 * @param string|null $type Type of aggregation, e.g.  "sum", "min", "max" or NULL for "count"
82
	 * @return \Aimeos\Map List of ID values as key and the number of counted products as value
83
	 * @todo 2018.01 Reorder Parameter list
84
	 */
85
	protected function aggregateBase( \Aimeos\Base\Criteria\Iface $search, $keys, string $cfgPath,
86
		array $required = [], string $value = null, string $type = null ) : \Aimeos\Map
87
	{
88
		/** mshop/common/manager/aggregate/limit
89
		 * Limits the number of records that are used when aggregating items
90
		 *
91
		 * As counting huge amount of records (several 10 000 records) takes a long time,
92
		 * the limit can cut down response times so the counts are available more quickly
93
		 * in the front-end and the server load is reduced.
94
		 *
95
		 * Using a low limit can lead to incorrect numbers if the amount of found items
96
		 * is very high. Approximate item counts are normally not a problem but it can
97
		 * lead to the situation that visitors see that no items are available despite
98
		 * the fact that there would be at least one.
99
		 *
100
		 * @param integer Number of records
101
		 * @since 2021.04
102
		 */
103
		$limit = $this->context->config()->get( 'mshop/common/manager/aggregate/limit', 10000 );
104
		$keys = (array) $keys;
105
106
		if( !count( $keys ) )
107
		{
108
			$msg = $this->context()->translate( 'mshop', 'At least one key is required for aggregation' );
109
			throw new \Aimeos\MShop\Exception( $msg );
110
		}
111
112
		$conn = $this->context->db( $this->getResourceName() );
113
114
		$total = null;
115
		$cols = $map = [];
116
		$search = clone $search;
117
		$search->slice( $search->getOffset(), min( $search->getLimit(), $limit ) );
118
119
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
120
		$attrList = $this->object()->getSearchAttributes();
121
122
		if( $value === null && ( $value = key( $attrList ) ) === null )
123
		{
124
			$msg = $this->context()->translate( 'mshop', 'No search keys available' );
125
			throw new \Aimeos\MShop\Exception( $msg );
126
		}
127
128
		if( ( $pos = strpos( $valkey = $value, '(' ) ) !== false ) {
129
			$value = substr( $value, 0, $pos );
130
		}
131
132
		if( !isset( $attrList[$value] ) )
133
		{
134
			$msg = $this->context()->translate( 'mshop', 'Unknown search key "%1$s"' );
135
			throw new \Aimeos\MShop\Exception( sprintf( $msg, $value ) );
136
		}
137
138
		foreach( $keys as $string )
139
		{
140
			if( !isset( $attrList[$string] ) )
141
			{
142
				$msg = $this->context()->translate( 'mshop', 'Unknown search key "%1$s"' );
143
				throw new \Aimeos\MShop\Exception( sprintf( $msg, $string ) );
144
			}
145
146
			$cols[] = $attrList[$string]->getInternalCode();
147
			$acols[] = $attrList[$string]->getInternalCode() . ' AS "' . $string . '"';
0 ignored issues
show
Bug introduced by
Are you sure $attrList[$string]->getInternalCode() of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

147
			$acols[] = /** @scrutinizer ignore-type */ $attrList[$string]->getInternalCode() . ' AS "' . $string . '"';
Loading history...
148
149
			/** @todo Required to get the joins, but there should be a better way */
150
			$search->add( [$string => null], '!=' );
151
		}
152
		$search->add( [$valkey => null], '!=' );
153
154
		$sql = $this->getSqlConfig( $cfgPath );
155
		$sql = str_replace( ':cols', join( ', ', $cols ), $sql );
156
		$sql = str_replace( ':acols', join( ', ', $acols ), $sql );
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $acols seems to be defined by a foreach iteration on line 138. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
157
		$sql = str_replace( ':keys', '"' . join( '", "', $keys ) . '"', $sql );
158
		$sql = str_replace( ':val', $attrList[$value]->getInternalCode(), $sql );
159
		$sql = str_replace( ':type', in_array( $type, ['avg', 'count', 'max', 'min', 'sum'] ) ? $type : 'count', $sql );
160
161
		$results = $this->searchItemsBase( $conn, $search, $sql, '', $required, $total, $level );
162
163
		while( ( $row = $results->fetch() ) !== null )
164
		{
165
			$row = $this->transform( $row );
166
167
			$temp = &$map;
168
			$last = array_pop( $row );
169
170
			foreach( $row as $val ) {
171
				$temp[$val] = $temp[$val] ?? [];
172
				$temp = &$temp[$val];
173
			}
174
			$temp = $last;
175
		}
176
177
		return map( $map );
178
	}
179
180
181
	/**
182
	 * Removes old entries from the storage.
183
	 *
184
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
185
	 * @param string $cfgpath Configuration key to the cleanup statement
186
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
187
	 */
188
	protected function clearBase( iterable $siteids, string $cfgpath ) : \Aimeos\MShop\Common\Manager\Iface
189
	{
190
		if( empty( $siteids ) ) {
191
			return $this;
192
		}
193
194
		$conn = $this->context->db( $this->getResourceName() );
195
196
		$sql = $this->getSqlConfig( $cfgpath );
197
		$sql = str_replace( ':cond', '1=1', $sql );
198
199
		$stmt = $conn->create( $sql );
200
201
		foreach( $siteids as $siteid )
202
		{
203
			$stmt->bind( 1, $siteid );
204
			$stmt->execute()->finish();
205
		}
206
207
		return $this;
208
	}
209
210
211
	/**
212
	 * Returns the context object.
213
	 *
214
	 * @return \Aimeos\MShop\ContextIface Context object
215
	 */
216
	protected function context() : \Aimeos\MShop\ContextIface
217
	{
218
		return $this->context;
219
	}
220
221
222
	/**
223
	 * Creates the criteria attribute items from the list of entries
224
	 *
225
	 * @param array $list Associative array of code as key and array with properties as values
226
	 * @return \Aimeos\Base\Criteria\Attribute\Standard[] List of criteria attribute items
227
	 */
228
	protected function createAttributes( array $list ) : array
229
	{
230
		$attr = [];
231
232
		foreach( $list as $key => $fields ) {
233
			$attr[$key] = new \Aimeos\Base\Criteria\Attribute\Standard( $fields );
234
		}
235
236
		return $attr;
237
	}
238
239
240
	/**
241
	 * Deletes items.
242
	 *
243
	 * @param \Aimeos\MShop\Common\Item\Iface|\Aimeos\Map|array|string $items List of item objects or IDs of the items
244
	 * @param string $cfgpath Configuration path to the SQL statement
245
	 * @param bool $siteid If siteid should be used in the statement
246
	 * @param string $name Name of the ID column
247
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
248
	 */
249
	protected function deleteItemsBase( $items, string $cfgpath, bool $siteid = true,
250
		string $name = 'id' ) : \Aimeos\MShop\Common\Manager\Iface
251
	{
252
		if( map( $items )->isEmpty() ) {
253
			return $this;
254
		}
255
256
		$search = $this->object()->filter();
257
		$search->setConditions( $search->compare( '==', $name, $items ) );
258
259
		$types = array( $name => \Aimeos\Base\DB\Statement\Base::PARAM_STR );
260
		$translations = array( $name => '"' . $name . '"' );
261
262
		$cond = $search->getConditionSource( $types, $translations );
263
		$sql = str_replace( ':cond', $cond, $this->getSqlConfig( $cfgpath ) );
264
265
		$context = $this->context();
266
		$conn = $context->db( $this->getResourceName() );
267
268
		$stmt = $conn->create( $sql );
269
270
		if( $siteid ) {
271
			$stmt->bind( 1, $context->locale()->getSiteId() . '%' );
272
		}
273
274
		$stmt->execute()->finish();
275
276
		return $this;
277
	}
278
279
280
	/**
281
	 * Returns a sorted list of required criteria keys.
282
	 *
283
	 * @param \Aimeos\Base\Criteria\Iface $criteria Search criteria object
284
	 * @param string[] $required List of prefixes of required search conditions
285
	 * @return string[] Sorted list of criteria keys
286
	 */
287
	protected function getCriteriaKeyList( \Aimeos\Base\Criteria\Iface $criteria, array $required ) : array
288
	{
289
		$keys = array_merge( $required, $this->getCriteriaKeys( $required, $criteria->getConditions() ) );
290
291
		foreach( $criteria->getSortations() as $sortation ) {
292
			$keys = array_merge( $keys, $this->getCriteriaKeys( $required, $sortation ) );
293
		}
294
295
		$keys = array_unique( array_merge( $required, $keys ) );
296
		sort( $keys );
297
298
		return $keys;
299
	}
300
301
302
	/**
303
	 * Returns the search attribute objects used for searching.
304
	 *
305
	 * @param array $list Associative list of search keys and the lists of search definitions
306
	 * @param string $path Configuration path to the sub-domains for fetching the search definitions
307
	 * @param string[] $default List of sub-domains if no others are configured
308
	 * @param bool $withsub True to include search definitions of sub-domains, false if not
309
	 * @return \Aimeos\Base\Criteria\Attribute\Iface[] Associative list of search keys and criteria attribute items as values
310
	 * @since 2014.09
311
	 */
312
	protected function getSearchAttributesBase( array $list, string $path, array $default, bool $withsub ) : array
313
	{
314
		$attr = $this->createAttributes( $list );
315
316
		if( $withsub === true )
317
		{
318
			$domains = $this->context->config()->get( $path, $default );
319
320
			foreach( $domains as $domain ) {
321
				$attr += $this->object()->getSubManager( $domain )->getSearchAttributes( true );
322
			}
323
		}
324
325
		return $attr;
326
	}
327
328
329
	/**
330
	 * Returns the attribute helper functions for searching defined by the manager.
331
	 *
332
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of search attribute items
333
	 * @return array Associative array of attribute code and helper function
334
	 */
335
	protected function getSearchFunctions( array $attributes ) : array
336
	{
337
		$list = [];
338
		$iface = \Aimeos\Base\Criteria\Attribute\Iface::class;
339
340
		foreach( $attributes as $key => $item )
341
		{
342
			if( $item instanceof $iface ) {
343
				$list[$item->getCode()] = $item->getFunction();
344
			} else if( isset( $item['code'] ) ) {
345
				$list[$item['code']] = $item['function'] ?? null;
346
			} else {
347
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid attribute at position "%1$d"', $key ) );
348
			}
349
		}
350
351
		return $list;
352
	}
353
354
355
	/**
356
	 * Returns the search results for the given SQL statement.
357
	 *
358
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
359
	 * @param string $sql SQL statement
360
	 * @return \Aimeos\Base\DB\Result\Iface Search result object
361
	 */
362
	protected function getSearchResults( \Aimeos\Base\DB\Connection\Iface $conn, string $sql ) : \Aimeos\Base\DB\Result\Iface
363
	{
364
		$time = microtime( true );
365
366
		$stmt = $conn->create( $sql );
367
		$result = $stmt->execute();
368
369
		$level = \Aimeos\Base\Logger\Iface::DEBUG;
370
		$time = ( microtime( true ) - $time ) * 1000;
371
		$msg = 'Time: ' . $time . "ms\n"
372
			. 'Class: ' . get_class( $this ) . "\n"
373
			. str_replace( ["\t", "\n\n"], ['', "\n"], trim( (string) $stmt ) );
374
375
		if( $time > 1000.0 )
376
		{
377
			$level = \Aimeos\Base\Logger\Iface::NOTICE;
378
			$msg .= "\n" . ( new \Exception() )->getTraceAsString();
379
		}
380
381
		$this->context->logger()->log( $msg, $level, 'core/sql' );
382
383
		return $result;
384
	}
385
386
387
	/**
388
	 * Returns the attribute translations for searching defined by the manager.
389
	 *
390
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of search attribute items
391
	 * @return array Associative array of attribute code and internal attribute code
392
	 */
393
	protected function getSearchTranslations( array $attributes ) : array
394
	{
395
		$translations = [];
396
		$iface = \Aimeos\Base\Criteria\Attribute\Iface::class;
397
398
		foreach( $attributes as $key => $item )
399
		{
400
			if( $item instanceof $iface ) {
401
				$translations[$item->getCode()] = $item->getInternalCode();
402
			} else if( isset( $item['code'] ) ) {
403
				$translations[$item['code']] = $item['internalcode'];
404
			} else {
405
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid attribute at position "%1$d"', $key ) );
406
			}
407
		}
408
409
		return $translations;
410
	}
411
412
413
	/**
414
	 * Returns the attribute types for searching defined by the manager.
415
	 *
416
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of search attribute items
417
	 * @return array Associative array of attribute code and internal attribute type
418
	 */
419
	protected function getSearchTypes( array $attributes ) : array
420
	{
421
		$types = [];
422
		$iface = \Aimeos\Base\Criteria\Attribute\Iface::class;
423
424
		foreach( $attributes as $key => $item )
425
		{
426
			if( $item instanceof $iface ) {
427
				$types[$item->getCode()] = $item->getInternalType();
428
			} else if( isset( $item['code'] ) ) {
429
				$types[$item['code']] = $item['internaltype'];
430
			} else {
431
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid attribute at position "%1$d"', $key ) );
432
			}
433
		}
434
435
		return $types;
436
	}
437
438
439
	/**
440
	 * Returns the SQL statement for the given config path
441
	 *
442
	 * If available, the database specific SQL statement is returned, otherwise
443
	 * the ANSI SQL statement. The database type is determined via the resource
444
	 * adapter.
445
	 *
446
	 * @param string $path Configuration path to the SQL statement
447
	 * @return array|string ANSI or database specific SQL statement
448
	 */
449
	protected function getSqlConfig( string $path )
450
	{
451
		$config = $this->context()->config();
452
		$adapter = $config->get( 'resource/' . $this->getResourceName() . '/adapter' );
453
454
		return $config->get( $path . '/' . $adapter, $config->get( $path . '/ansi', $path ) );
455
	}
456
457
458
	/**
459
	 * Sets the base criteria "status".
460
	 * (setConditions overwrites the base criteria)
461
	 *
462
	 * @param string $domain Name of the domain/sub-domain like "product" or "product.list"
463
	 * @param bool|null $default TRUE for status=1, NULL for status>0, FALSE for no restriction
464
	 * @return \Aimeos\Base\Criteria\Iface Search critery object
465
	 */
466
	protected function filterBase( string $domain, ?bool $default = false ) : \Aimeos\Base\Criteria\Iface
467
	{
468
		$filter = self::filter();
0 ignored issues
show
Bug Best Practice introduced by
The method Aimeos\MShop\Common\Manager\Base::filter() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

468
		/** @scrutinizer ignore-call */ 
469
  $filter = self::filter();
Loading history...
469
470
		if( $default !== false ) {
471
			$filter->add( $domain . '.status', $default ? '==' : '>=', 1 );
472
		}
473
474
		return $filter;
475
	}
476
477
478
	/**
479
	 * Returns the item for the given search key/value pairs.
480
	 *
481
	 * @param array $pairs Search key/value pairs for the item
482
	 * @param string[] $ref List of domains whose items should be fetched too
483
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
484
	 * @return \Aimeos\MShop\Common\Item\Iface Requested item
485
	 * @throws \Aimeos\MShop\Exception if no item with the given ID found
486
	 */
487
	protected function findBase( array $pairs, array $ref, ?bool $default ) : \Aimeos\MShop\Common\Item\Iface
488
	{
489
		$expr = [];
490
		$criteria = $this->object()->filter( $default )->slice( 0, 1 );
491
492
		foreach( $pairs as $key => $value )
493
		{
494
			if( $value === null )
495
			{
496
				$msg = $this->context()->translate( 'mshop', 'Required value for "%1$s" is missing' );
497
				throw new \Aimeos\MShop\Exception( sprintf( $msg, $key ) );
498
			}
499
			$expr[] = $criteria->compare( '==', $key, $value );
500
		}
501
502
		$criteria->setConditions( $criteria->and( $expr ) );
503
504
		if( ( $item = $this->object()->search( $criteria, $ref )->first() ) ) {
505
			return $item;
506
		}
507
508
		$msg = $this->context()->translate( 'mshop', 'No item found for conditions: %1$s' );
509
		throw new \Aimeos\MShop\Exception( sprintf( $msg, print_r( $pairs, true ) ), 404 );
0 ignored issues
show
Bug introduced by
It seems like print_r($pairs, true) can also be of type true; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

509
		throw new \Aimeos\MShop\Exception( sprintf( $msg, /** @scrutinizer ignore-type */ print_r( $pairs, true ) ), 404 );
Loading history...
510
	}
511
512
513
	/**
514
	 * Returns the cached statement for the given key or creates a new prepared statement.
515
	 * If no SQL string is given, the key is used to retrieve the SQL string from the configuration.
516
	 *
517
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
518
	 * @param string $cfgkey Unique key for the SQL
519
	 * @param string|null $sql SQL string if it shouldn't be retrieved from the configuration
520
	 * @return \Aimeos\Base\DB\Statement\Iface Database statement object
521
	 */
522
	protected function getCachedStatement( \Aimeos\Base\DB\Connection\Iface $conn, string $cfgkey,
523
		string $sql = null ) : \Aimeos\Base\DB\Statement\Iface
524
	{
525
		if( !isset( $this->stmts['stmt'][$cfgkey] )
526
			|| !isset( $this->stmts['conn'][$cfgkey] )
527
			|| $conn !== $this->stmts['conn'][$cfgkey]
528
		) {
529
			if( $sql === null ) {
530
				$sql = $this->getSqlConfig( $cfgkey );
531
			}
532
533
			$this->stmts['stmt'][$cfgkey] = $conn->create( $sql );
0 ignored issues
show
Bug introduced by
It seems like $sql can also be of type array; however, parameter $sql of Aimeos\Base\DB\Connection\Iface::create() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

533
			$this->stmts['stmt'][$cfgkey] = $conn->create( /** @scrutinizer ignore-type */ $sql );
Loading history...
534
			$this->stmts['conn'][$cfgkey] = $conn;
535
		}
536
537
		return $this->stmts['stmt'][$cfgkey];
538
	}
539
540
541
	/**
542
	 * Returns the item for the given search key and ID.
543
	 *
544
	 * @param string $key Search key for the requested ID
545
	 * @param string $id Unique ID to search for
546
	 * @param string[] $ref List of domains whose items should be fetched too
547
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
548
	 * @return \Aimeos\MShop\Common\Item\Iface Requested item
549
	 * @throws \Aimeos\MShop\Exception if no item with the given ID found
550
	 */
551
	protected function getItemBase( string $key, string $id, array $ref, ?bool $default ) : \Aimeos\MShop\Common\Item\Iface
552
	{
553
		$criteria = $this->object()->filter( $default )->add( [$key => $id] )->slice( 0, 1 );
554
555
		if( ( $item = $this->object()->search( $criteria, $ref )->first() ) ) {
556
			return $item;
557
		}
558
559
		$msg = $this->context()->translate( 'mshop', 'Item with ID "%2$s" in "%1$s" not found' );
560
		throw new \Aimeos\MShop\Exception( sprintf( $msg, $key, $id ), 404 );
561
	}
562
563
564
	/**
565
	 * Returns the SQL strings for joining dependent tables.
566
	 *
567
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of criteria attribute items
568
	 * @param string $prefix Search key prefix
569
	 * @return array List of JOIN SQL strings
570
	 */
571
	private function getJoins( array $attributes, string $prefix ) : array
572
	{
573
		$iface = \Aimeos\Base\Criteria\Attribute\Iface::class;
574
		$name = $prefix . '.id';
575
576
		if( isset( $attributes[$prefix] ) && $attributes[$prefix] instanceof $iface ) {
577
			return $attributes[$prefix]->getInternalDeps();
578
		}
579
		elseif( isset( $attributes[$name] ) && $attributes[$name] instanceof $iface ) {
580
			return $attributes[$name]->getInternalDeps();
581
		}
582
		else if( isset( $attributes['id'] ) && $attributes['id'] instanceof $iface ) {
583
			return $attributes['id']->getInternalDeps();
584
		}
585
586
		return [];
587
	}
588
589
590
	/**
591
	 * Returns the available manager types
592
	 *
593
	 * @param string $type Main manager type
594
	 * @param string $path Configuration path to the sub-domains
595
	 * @param string[] $default List of sub-domains if no others are configured
596
	 * @param bool $withsub Return also the resource type of sub-managers if true
597
	 * @return string[] Type of the manager and submanagers, subtypes are separated by slashes
598
	 */
599
	protected function getResourceTypeBase( string $type, string $path, array $default, bool $withsub ) : array
600
	{
601
		$list = [$type];
602
603
		if( $withsub )
604
		{
605
			foreach( $this->context->config()->get( $path, $default ) as $domain ) {
606
				$list = array_merge( $list, $this->object()->getSubManager( $domain )->getResourceType( $withsub ) );
607
			}
608
		}
609
610
		return $list;
611
	}
612
613
614
	/**
615
	 * Returns a search object singleton
616
	 *
617
	 * @return \Aimeos\Base\Criteria\Iface Search object
618
	 */
619
	protected function getSearch() : \Aimeos\Base\Criteria\Iface
620
	{
621
		if( !isset( $this->search ) ) {
622
			$this->search = $this->filter();
623
		}
624
625
		return $this->search;
626
	}
627
628
629
	/**
630
	 * Returns the site coditions for the search request
631
	 *
632
	 * @param string[] $keys Sorted list of criteria keys
633
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values
634
	 * @param int $sitelevel Site level constant from \Aimeos\MShop\Locale\Manager\Base
635
	 * @return \Aimeos\Base\Criteria\Expression\Iface[] List of search conditions
636
	 * @since 2015.01
637
	 */
638
	protected function getSiteConditions( array $keys, array $attributes, int $sitelevel ) : array
639
	{
640
		$list = [];
641
642
		foreach( $keys as $key )
643
		{
644
			$name = $key . '.siteid';
645
646
			if( isset( $attributes[$name] ) ) {
647
				$list[] = $this->siteCondition( $name, $sitelevel );
648
			}
649
		}
650
651
		return $list;
652
	}
653
654
655
	/**
656
	 * Returns the string replacements for the SQL statements
657
	 *
658
	 * @param \Aimeos\Base\Criteria\Iface $search Search critera object
659
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values
660
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of search keys and criteria plugin items as values
661
	 * @param string[] $joins Associative list of SQL joins
662
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $columns Additional columns to retrieve values from
663
	 * @return array Array of keys, find and replace arrays
664
	 */
665
	protected function getSQLReplacements( \Aimeos\Base\Criteria\Iface $search, array $attributes, array $plugins,
666
		array $joins, array $columns = [] ) : array
667
	{
668
		$types = $this->getSearchTypes( $attributes );
669
		$funcs = $this->getSearchFunctions( $attributes );
670
		$translations = $this->getSearchTranslations( $attributes );
671
672
		$colstring = '';
673
		foreach( $columns as $name => $entry ) {
674
			$colstring .= $entry->getInternalCode() . ', ';
675
		}
676
677
		$find = array( ':columns', ':joins', ':cond', ':start', ':size' );
678
		$replace = array(
679
			$colstring,
680
			implode( "\n", array_unique( $joins ) ),
681
			$search->getConditionSource( $types, $translations, $plugins, $funcs ),
682
			$search->getOffset(),
683
			$search->getLimit(),
684
		);
685
686
		if( empty( $search->getSortations() ) && ( $attribute = reset( $attributes ) ) !== false ) {
687
			$search = ( clone $search )->setSortations( [$search->sort( '+', $attribute->getCode() )] );
688
		}
689
690
		$find[] = ':order';
691
		$replace[] = $search->getSortationSource( $types, $translations, $funcs );
692
693
		$find[] = ':group';
694
		$replace[] = implode( ', ', $search->translate( $search->getSortations(), $translations, $funcs ) ) . ', ';
695
696
		return [$find, $replace];
697
	}
698
699
700
	/**
701
	 * Returns the newly created ID for the last record which was inserted.
702
	 *
703
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection used to insert the new record
704
	 * @param string $cfgpath Configuration path to the SQL statement for retrieving the new ID of the last inserted record
705
	 * @return string ID of the last record that was inserted by using the given connection
706
	 * @throws \Aimeos\MShop\Exception if there's no ID of the last record available
707
	 */
708
	protected function newId( \Aimeos\Base\DB\Connection\Iface $conn, string $cfgpath ) : string
709
	{
710
		$result = $conn->create( $this->getSqlConfig( $cfgpath ) )->execute();
0 ignored issues
show
Bug introduced by
It seems like $this->getSqlConfig($cfgpath) can also be of type array; however, parameter $sql of Aimeos\Base\DB\Connection\Iface::create() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

710
		$result = $conn->create( /** @scrutinizer ignore-type */ $this->getSqlConfig( $cfgpath ) )->execute();
Loading history...
711
712
		if( ( $row = $result->fetch( \Aimeos\Base\DB\Result\Base::FETCH_NUM ) ) === false )
713
		{
714
			$msg = $this->context()->translate( 'mshop', 'ID of last inserted database record not available' );
715
			throw new \Aimeos\MShop\Exception( $msg );
716
		}
717
		$result->finish();
718
719
		return $row[0];
720
	}
721
722
723
	/**
724
	 * Returns the search result of the statement combined with the given criteria.
725
	 *
726
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
727
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria object
728
	 * @param string $cfgPathSearch Path to SQL statement in configuration for searching
729
	 * @param string $cfgPathCount Path to SQL statement in configuration for counting
730
	 * @param string[] $required Additional search keys to add conditions for even if no conditions are available
731
	 * @param int|null $total Contains the number of all records matching the criteria if not null
732
	 * @param int $sitelevel Constant from \Aimeos\MShop\Locale\Manager\Base for defining which site IDs should be used for searching
733
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of search keys and criteria plugin items as values
734
	 * @return \Aimeos\Base\DB\Result\Iface SQL result object for accessing the found records
735
	 * @throws \Aimeos\MShop\Exception if no number of all matching records is available
736
	 */
737
	protected function searchItemsBase( \Aimeos\Base\DB\Connection\Iface $conn, \Aimeos\Base\Criteria\Iface $search,
738
		string $cfgPathSearch, string $cfgPathCount, array $required, int &$total = null,
739
		int $sitelevel = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL, array $plugins = [] ) : \Aimeos\Base\DB\Result\Iface
740
	{
741
		$joins = [];
742
		$conditions = $search->getConditions();
743
		$columns = $this->object()->getSaveAttributes();
744
		$attributes = $this->object()->getSearchAttributes();
745
		$keys = $this->getCriteriaKeyList( $search, $required );
746
747
		$basekey = array_shift( $required );
748
749
		foreach( $keys as $key )
750
		{
751
			if( $key !== $basekey ) {
752
				$joins = array_merge( $joins, $this->getJoins( $attributes, $key ) );
753
			}
754
		}
755
756
		$joins = array_unique( $joins );
757
		$cond = $this->getSiteConditions( $keys, $attributes, $sitelevel );
758
759
		if( $conditions !== null ) {
760
			$cond[] = $conditions;
761
		}
762
763
		$search = clone $search;
764
		$search->setConditions( $search->and( $cond ) );
765
766
		list( $find, $replace ) = $this->getSQLReplacements( $search, $attributes, $plugins, $joins, $columns );
767
768
		if( $total !== null )
769
		{
770
			$sql = str_replace( $find, $replace, $this->getSqlConfig( $cfgPathCount ) );
771
			$result = $this->getSearchResults( $conn, $sql );
772
			$row = $result->fetch();
773
			$result->finish();
774
775
			if( $row === null )
776
			{
777
				$msg = $this->context()->translate( 'mshop', 'Total results value not found' );
778
				throw new \Aimeos\MShop\Exception( $msg );
779
			}
780
781
			$total = (int) $row['count'];
782
		}
783
784
		return $this->getSearchResults( $conn, str_replace( $find, $replace, $this->getSqlConfig( $cfgPathSearch ) ) );
785
	}
786
787
788
	/**
789
	 * Replaces the given marker with an expression
790
	 *
791
	 * @param string $column Name (including alias) of the column
792
	 * @param mixed $value Value used in the expression
793
	 * @param string $op Operator used in the expression
794
	 * @param int $type Type constant from \Aimeos\Base\DB\Statement\Base class
795
	 * @return string Created expression
796
	 */
797
	protected function toExpression( string $column, $value, string $op = '==',
798
		int $type = \Aimeos\Base\DB\Statement\Base::PARAM_STR ) : string
799
	{
800
		$types = ['marker' => $type];
801
		$translations = ['marker' => $column];
802
		$value = ( is_array( $value ) ? array_unique( $value ) : $value );
803
804
		return $this->getSearch()->compare( $op, 'marker', $value )->toSource( $types, $translations );
805
	}
806
807
808
	/**
809
	 * Transforms the application specific values to Aimeos standard values.
810
	 *
811
	 * @param array $values Associative list of key/value pairs from the storage
812
	 * @return array Associative list of key/value pairs with standard Aimeos values
813
	 */
814
	protected function transform( array $values ) : array
815
	{
816
		return $values;
817
	}
818
819
820
	/**
821
	 * Cuts the last part separated by a dot repeatedly and returns the list of resulting string.
822
	 *
823
	 * @param string[] $prefix Required base prefixes of the search keys
824
	 * @param string $string String containing parts separated by dots
825
	 * @return array List of resulting strings
826
	 */
827
	private function cutNameTail( array $prefix, string $string ) : array
828
	{
829
		$result = [];
830
		$noprefix = true;
831
		$strlen = strlen( $string );
832
833
		foreach( $prefix as $key )
834
		{
835
			$len = strlen( $key );
836
837
			if( strncmp( $string, $key, $len ) === 0 )
838
			{
839
				if( $strlen > $len && ( $pos = strrpos( $string, '.' ) ) !== false )
840
				{
841
					$result[] = $string = substr( $string, 0, $pos );
842
					$result = array_merge( $result, $this->cutNameTail( $prefix, $string ) );
843
					$noprefix = false;
844
				}
845
846
				break;
847
			}
848
		}
849
850
		if( $noprefix )
851
		{
852
			if( ( $pos = strrpos( $string, ':' ) ) !== false ) {
853
				$result[] = substr( $string, 0, $pos );
854
				$result[] = $string;
855
			} elseif( ( $pos = strrpos( $string, '.' ) ) !== false ) {
856
				$result[] = substr( $string, 0, $pos );
857
			} else {
858
				$result[] = $string;
859
			}
860
		}
861
862
		return $result;
863
	}
864
865
866
	/**
867
	 * Returns a list of unique criteria names shortend by the last element after the ''
868
	 *
869
	 * @param string[] $prefix Required base prefixes of the search keys
870
	 * @param \Aimeos\Base\Criteria\Expression\Iface|null Criteria object
0 ignored issues
show
Bug introduced by
The type Aimeos\MShop\Common\Manager\Criteria 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...
871
	 * @return array List of shortend criteria names
872
	 */
873
	private function getCriteriaKeys( array $prefix, \Aimeos\Base\Criteria\Expression\Iface $expr = null ) : array
874
	{
875
		if( $expr === null ) { return []; }
876
877
		$result = [];
878
879
		foreach( $this->getCriteriaNames( $expr ) as $item )
880
		{
881
			if( strncmp( $item, 'sort:', 5 ) === 0 ) {
882
				$item = substr( $item, 5 );
883
			}
884
885
			if( ( $pos = strpos( $item, '(' ) ) !== false ) {
886
				$item = substr( $item, 0, $pos );
887
			}
888
889
			$result = array_merge( $result, $this->cutNameTail( $prefix, $item ) );
890
		}
891
892
		return $result;
893
	}
894
895
896
	/**
897
	 * Returns a list of criteria names from a expression and its sub-expressions.
898
	 *
899
	 * @param \Aimeos\Base\Criteria\Expression\Iface Criteria object
900
	 * @return array List of criteria names
901
	 */
902
	private function getCriteriaNames( \Aimeos\Base\Criteria\Expression\Iface $expr ) : array
903
	{
904
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Compare\Iface ) {
905
			return array( $expr->getName() );
906
		}
907
908
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Combine\Iface )
909
		{
910
			$list = [];
911
			foreach( $expr->getExpressions() as $item ) {
912
				$list = array_merge( $list, $this->getCriteriaNames( $item ) );
913
			}
914
			return $list;
915
		}
916
917
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Sort\Iface ) {
918
			return array( $expr->getName() );
919
		}
920
921
		return [];
922
	}
923
}
924