Completed
Push — master ( 52c730...9eba1c )
by Aimeos
08:52
created

lib/mshoplib/src/MShop/Common/Manager/Base.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2017
7
 * @package MShop
8
 * @subpackage Common
9
 */
10
11
12
namespace Aimeos\MShop\Common\Manager;
13
14
15
/**
16
 * Provides common methods required by most of the manager classes.
17
 *
18
 * @package MShop
19
 * @subpackage Common
20
 */
21
abstract class Base
22
	extends \Aimeos\MW\Common\Manager\Base
23
	implements \Aimeos\MShop\Common\Manager\Iface
24
{
25
	private $context;
26
	private $object;
27
	private $resourceName;
28
	private $stmts = [];
29
	private $subManagers = [];
30
31
32
	/**
33
	 * Initialization of class.
34
	 *
35
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object
36
	 */
37
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
38
	{
39
		$this->context = $context;
40
	}
41
42
43
	/**
44
	 * Catch unknown methods
45
	 *
46
	 * @param string $name Name of the method
47
	 * @param array $param List of method parameter
48
	 * @throws \Aimeos\MShop\Common\Manager\Exception If method call failed
49
	 */
50
	public function __call( $name, array $param )
51
	{
52
		throw new \Aimeos\MShop\Exception( sprintf( 'Unable to call method "%1$s"', $name ) );
53
	}
54
55
56
	/**
57
	 * Removes old entries from the storage.
58
	 *
59
	 * @param array $siteids List of IDs for sites whose entries should be deleted
60
	 */
61
	public function cleanup( array $siteids )
62
	{
63
	}
64
65
66
	/**
67
	 * Creates a search object.
68
	 *
69
	 * @param boolean $default Add default criteria; Optional
70
	 * @return \Aimeos\MW\Criteria\Iface
71
	 */
72
	public function createSearch( $default = false )
73
	{
74
		$dbm = $this->context->getDatabaseManager();
75
		$db = $this->getResourceName();
76
77
		$conn = $dbm->acquire( $db );
78
		$search = new \Aimeos\MW\Criteria\SQL( $conn );
79
		$dbm->release( $conn, $db );
80
81
		return $search;
82
	}
83
84
85
	/**
86
	 * Deletes an item from storage.
87
	 *
88
	 * @param integer $itemId Unique ID of the item in the storage
89
	 */
90
	public function deleteItem( $itemId )
91
	{
92
		$this->getObject()->deleteItems( array( $itemId ) );
93
	}
94
95
96
	/**
97
	 * Starts a database transaction on the connection identified by the given name.
98
	 */
99
	public function begin()
100
	{
101
		$this->beginTransation( $this->getResourceName() );
102
	}
103
104
105
	/**
106
	 * Commits the running database transaction on the connection identified by the given name.
107
	 */
108
	public function commit()
109
	{
110
		$this->commitTransaction( $this->getResourceName() );
111
	}
112
113
114
	/**
115
	 * Rolls back the running database transaction on the connection identified by the given name.
116
	 */
117
	public function rollback()
118
	{
119
		$this->rollbackTransaction( $this->getResourceName() );
120
	}
121
122
123
	/**
124
	 * Injects the reference of the outmost object
125
	 *
126
	 * @param \Aimeos\MShop\Common\Manager\Iface $object Reference to the outmost manager or decorator
127
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
128
	 */
129
	public function setObject( \Aimeos\MShop\Common\Manager\Iface $object )
130
	{
131
		$this->object = $object;
132
		return $this;
133
	}
134
135
136
	/**
137
	 * Counts the number products that are available for the values of the given key.
138
	 *
139
	 * @param \Aimeos\MW\Criteria\Iface $search Search criteria
140
	 * @param string $key Search key for aggregating the key column
141
	 * @param string $cfgPath Configuration key for the SQL statement
142
	 * @param string[] $required List of domain/sub-domain names like "catalog.index" that must be additionally joined
143
	 * @param string $value Search key for aggregating the value column
0 ignored issues
show
Should the type for parameter $value not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
144
	 * @return array List of ID values as key and the number of counted products as value
145
	 * @todo 2018.01 Reorder Parameter list
146
	 */
147
	protected function aggregateBase( \Aimeos\MW\Criteria\Iface $search, $key, $cfgPath, $required = [], $value = null )
148
	{
149
		$list = [];
150
		$context = $this->getContext();
151
152
		$dbname = $this->getResourceName();
153
		$dbm = $context->getDatabaseManager();
154
		$conn = $dbm->acquire( $dbname );
155
156
		try
157
		{
158
			$search = clone $search;
159
			$attrList = $this->getObject()->getSearchAttributes();
160
161
			if( $value === null && ( $value = key( $attrList ) ) === null ) {
162
				throw new \Aimeos\MShop\Exception( sprintf( 'No search keys available' ) );
163
			}
164
165
			if( !isset( $attrList[$key] ) ) {
166
				throw new \Aimeos\MShop\Exception( sprintf( 'Unknown search key "%1$s"', $key ) );
167
			}
168
169
			if( $value !== null && !isset( $attrList[$value] ) ) {
170
				throw new \Aimeos\MShop\Exception( sprintf( 'Unknown search key "%1$s"', $value ) );
171
			}
172
173
			/** @todo Required to get the joins, but there should be a better way */
174
			$expr = array(
175
				$search->getConditions(),
176
				$search->compare( '!=', $key, null ),
177
				$search->compare( '!=', $value, null ),
178
			);
179
			$search->setConditions( $search->combine( '&&', $expr ) );
180
181
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
182
			$total = null;
183
184
			$sql = $this->getSqlConfig( $cfgPath );
185
			$sql = str_replace( ':key', $attrList[$key]->getInternalCode(), $sql );
186
			$sql = str_replace( ':val', $attrList[$value]->getInternalCode(), $sql );
187
188
			$results = $this->searchItemsBase( $conn, $search, $sql, '', $required, $total, $level );
189
190
			while( ( $row = $results->fetch() ) !== false ) {
191
				$list[$row['key']] = $row['count'];
192
			}
193
194
			$dbm->release( $conn, $dbname );
195
		}
196
		catch( \Exception $e )
197
		{
198
			$dbm->release( $conn, $dbname );
199
			throw $e;
200
		}
201
202
		return $list;
203
	}
204
205
206
	/**
207
	 * Returns the newly created ID for the last record which was inserted.
208
	 *
209
	 * @param \Aimeos\MW\DB\Connection\Iface $conn Database connection used to insert the new record
210
	 * @param string $cfgpath Configuration path to the SQL statement for retrieving the new ID of the last inserted record
211
	 * @return string ID of the last record that was inserted by using the given connection
212
	 * @throws \Aimeos\MShop\Common\Exception if there's no ID of the last record available
213
	 */
214
	protected function newId( \Aimeos\MW\DB\Connection\Iface $conn, $cfgpath )
215
	{
216
		$result = $conn->create( $this->getSqlConfig( $cfgpath ) )->execute();
217
218
		if( ( $row = $result->fetch( \Aimeos\MW\DB\Result\Base::FETCH_NUM ) ) === false ) {
219
			throw new \Aimeos\MShop\Exception( sprintf( 'ID of last inserted database record not available' ) );
220
		}
221
		$result->finish();
222
223
		return $row[0];
224
	}
225
226
227
	/**
228
	 * Removes old entries from the storage.
229
	 *
230
	 * @param array $siteids List of IDs for sites whose entries should be deleted
231
	 * @param string $cfgpath Configuration key to the cleanup statement
232
	 */
233
	protected function cleanupBase( array $siteids, $cfgpath )
234
	{
235
		$dbm = $this->context->getDatabaseManager();
236
		$dbname = $this->getResourceName();
237
		$conn = $dbm->acquire( $dbname );
238
239
		try
240
		{
241
			$sql = $this->getSqlConfig( $cfgpath );
242
			$sql = str_replace( ':cond', '1=1', $sql );
243
244
			$stmt = $conn->create( $sql );
245
246
			foreach( $siteids as $siteid )
247
			{
248
				$stmt->bind( 1, $siteid, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
249
				$stmt->execute()->finish();
250
			}
251
252
			$dbm->release( $conn, $dbname );
253
		}
254
		catch( \Exception $e )
255
		{
256
			$dbm->release( $conn, $dbname );
257
			throw $e;
258
		}
259
	}
260
261
262
	/**
263
	 * Sets the base criteria "status".
264
	 * (setConditions overwrites the base criteria)
265
	 *
266
	 * @param string $domain Name of the domain/sub-domain like "product" or "product.list"
267
	 * @return \Aimeos\MW\Criteria\Iface Search critery object
268
	 */
269
	protected function createSearchBase( $domain )
270
	{
271
		$dbm = $this->context->getDatabaseManager();
272
		$dbname = $this->getResourceName();
273
		$conn = $dbm->acquire( $dbname );
274
275
		$object = new \Aimeos\MW\Criteria\SQL( $conn );
276
		$object->setConditions( $object->compare( '==', $domain . '.status', 1 ) );
277
278
		$dbm->release( $conn, $dbname );
279
280
		return $object;
281
	}
282
283
284
	/**
285
	 * Returns the context object.
286
	 *
287
	 * @return \Aimeos\MShop\Context\Item\Iface Context object
288
	 */
289
	protected function getContext()
290
	{
291
		return $this->context;
292
	}
293
294
295
	/**
296
	 * Returns the outmost decorator of the decorator stack
297
	 *
298
	 * @return \Aimeos\MShop\Common\Manager\Iface Outmost decorator object
299
	 */
300
	protected function getObject()
301
	{
302
		if( $this->object !== null ) {
303
			return $this->object;
304
		}
305
306
		return $this;
307
	}
308
309
310
	/**
311
	 * Returns the search attribute objects used for searching.
312
	 *
313
	 * @param array $list Associative list of search keys and the lists of search definitions
314
	 * @param string $path Configuration path to the sub-domains for fetching the search definitions
315
	 * @param array $default List of sub-domains if no others are configured
316
	 * @param boolean $withsub True to include search definitions of sub-domains, false if not
317
	 * @return array Associative list of search keys and objects implementing the \Aimeos\MW\Criteria\Attribute\Iface
318
	 * @since 2014.09
319
	 */
320
	protected function getSearchAttributesBase( array $list, $path, array $default, $withsub )
321
	{
322
		$attr = [];
323
324
		foreach( $list as $key => $fields ) {
325
			$attr[$key] = new \Aimeos\MW\Criteria\Attribute\Standard( $fields );
326
		}
327
328
		if( $withsub === true )
329
		{
330
			$domains = $this->context->getConfig()->get( $path, $default );
331
332
			foreach( $domains as $domain ) {
333
				$attr += $this->getObject()->getSubManager( $domain )->getSearchAttributes( true );
334
			}
335
		}
336
337
		return $attr;
338
	}
339
340
341
	/**
342
	 * Returns the site IDs for the given site level constant.
343
	 *
344
	 * @param integer $sitelevel Site level constant from \Aimeos\MShop\Locale\Manager\Base
345
	 * @return string[] List of site IDs
346
	 */
347
	private function getSiteIds( $sitelevel )
348
	{
349
		$locale = $this->context->getLocale();
350
		$siteIds = array( $locale->getSiteId() );
351
352
		if( $sitelevel & \Aimeos\MShop\Locale\Manager\Base::SITE_PATH ) {
353
			$siteIds = array_merge( $siteIds, $locale->getSitePath() );
354
		}
355
356
		if( $sitelevel & \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE ) {
357
			$siteIds = array_merge( $siteIds, $locale->getSiteSubTree() );
358
		}
359
360
		$siteIds = array_unique( $siteIds );
361
362
		return $siteIds;
363
	}
364
365
366
	/**
367
	 * Returns the SQL statement for the given config path
368
	 *
369
	 * If available, the database specific SQL statement is returned, otherwise
370
	 * the ANSI SQL statement. The database type is determined via the resource
371
	 * adapter.
372
	 *
373
	 * @param string $path Configuration path to the SQL statement
374
	 * @return string ANSI or database specific SQL statement
375
	 */
376
	protected function getSqlConfig( $path )
377
	{
378
		$config = $this->getContext()->getConfig();
379
		$adapter = $config->get( 'resource/' . $this->getResourceName() . '/adapter' );
380
381
		return $config->get( $path . '/' . $adapter, $config->get( $path . '/ansi', $path ) );
382
	}
383
384
385
	/**
386
	 * Returns a new manager the given extension name.
387
	 *
388
	 * @param string $domain Name of the domain (product, text, media, etc.)
389
	 * @param string $manager Name of the sub manager type in lower case (can contain a path like base/product)
390
	 * @param string|null $name Name of the implementation, will be from configuration (or Standard) if null
391
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager for different extensions
392
	 */
393
	protected function getSubManagerBase( $domain, $manager, $name )
394
	{
395
		$domain = strtolower( $domain );
396
		$manager = strtolower( $manager );
397
		$key = $domain . $manager . $name;
398
399
		if( !isset( $this->subManagers[$key] ) )
400
		{
401
			if( empty( $domain ) || ctype_alnum( $domain ) === false ) {
402
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid characters in domain name "%1$s"', $domain ) );
403
			}
404
405
			if( preg_match( '/^[a-z0-9\/]+$/', $manager ) !== 1 ) {
406
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid characters in manager name "%1$s"', $manager ) );
407
			}
408
409
			if( $name === null ) {
410
				$path = 'mshop/' . $domain . '/manager/' . $manager . '/name';
411
				$name = $this->context->getConfig()->get( $path, 'Standard' );
412
			}
413
414
			if( empty( $name ) || ctype_alnum( $name ) === false ) {
415
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid characters in manager name "%1$s"', $name ) );
416
			}
417
418
			$domainname = ucfirst( $domain );
419
			$subnames = $this->createSubNames( $manager );
420
421
			$classname = '\\Aimeos\\MShop\\' . $domainname . '\\Manager\\' . $subnames . '\\' . $name;
422
			$interface = '\\Aimeos\\MShop\\' . $domainname . '\\Manager\\' . $subnames . '\\Iface';
423
424
			if( class_exists( $classname ) === false ) {
425
				throw new \Aimeos\MShop\Exception( sprintf( 'Class "%1$s" not available', $classname ) );
426
			}
427
428
			$subManager = new $classname( $this->context );
429
430
			if( ( $subManager instanceof $interface ) === false ) {
431
				throw new \Aimeos\MShop\Exception( sprintf( 'Class "%1$s" does not implement interface "%2$s"', $classname, $interface ) );
432
			}
433
434
			$this->subManagers[$key] = $this->addManagerDecorators( $subManager, $manager, $domain );
435
		}
436
437
		return $this->subManagers[$key];
438
	}
439
440
441
	/**
442
	 * Adds the decorators to the manager object.
443
	 *
444
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context instance with necessary objects
445
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object
446
	 * @param array $decorators List of decorator names that should be wrapped around the manager object
447
	 * @param string $classprefix Decorator class prefix, e.g. "\Aimeos\MShop\Product\Manager\Decorator\"
448
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object
449
	 */
450
	protected function addDecorators( \Aimeos\MShop\Context\Item\Iface $context,
451
		\Aimeos\MShop\Common\Manager\Iface $manager, array $decorators, $classprefix )
452
	{
453
		$iface = '\\Aimeos\\MShop\\Common\\Manager\\Decorator\\Iface';
454
455
		foreach( $decorators as $name )
456
		{
457
			if( ctype_alnum( $name ) === false ) {
458
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid characters in class name "%1$s"', $name ) );
459
			}
460
461
			$classname = $classprefix . $name;
462
463
			if( class_exists( $classname ) === false ) {
464
				throw new \Aimeos\MShop\Exception( sprintf( 'Class "%1$s" not available', $classname ) );
465
			}
466
467
			$manager = new $classname( $manager, $context );
468
469
			if( !( $manager instanceof $iface ) ) {
470
				throw new \Aimeos\MShop\Exception( sprintf( 'Class "%1$s" does not implement interface "%2$s"', $classname, $iface ) );
471
			}
472
		}
473
474
		return $manager;
475
	}
476
477
478
	/**
479
	 * Adds the configured decorators to the given manager object.
480
	 *
481
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object
482
	 * @param string $managerpath Manager sub-names separated by slashes, e.g. "list/type"
483
	 * @param string $domain Domain name in lower case, e.g. "product"
484
	 */
485
	protected function addManagerDecorators( \Aimeos\MShop\Common\Manager\Iface $manager, $managerpath, $domain )
486
	{
487
		$config = $this->context->getConfig();
488
489
		$decorators = $config->get( 'mshop/common/manager/decorators/default', [] );
490
		$excludes = $config->get( 'mshop/' . $domain . '/manager/' . $managerpath . '/decorators/excludes', [] );
491
492
		foreach( $decorators as $key => $name )
493
		{
494
			if( in_array( $name, $excludes ) ) {
495
				unset( $decorators[$key] );
496
			}
497
		}
498
499
		$classprefix = '\\Aimeos\\MShop\\Common\\Manager\\Decorator\\';
500
		$manager = $this->addDecorators( $this->context, $manager, $decorators, $classprefix );
501
502
		$classprefix = '\\Aimeos\\MShop\\Common\\Manager\\Decorator\\';
503
		$decorators = $config->get( 'mshop/' . $domain . '/manager/' . $managerpath . '/decorators/global', [] );
504
		$manager = $this->addDecorators( $this->context, $manager, $decorators, $classprefix );
505
506
		$subpath = $this->createSubNames( $managerpath );
507
		$classprefix = 'MShop_' . ucfirst( $domain ) . '_Manager_' . $subpath . '_Decorator_';
508
		$decorators = $config->get( 'mshop/' . $domain . '/manager/' . $managerpath . '/decorators/local', [] );
509
510
		return $this->addDecorators( $this->context, $manager, $decorators, $classprefix );
511
	}
512
513
514
	/**
515
	 * Transforms the manager path to the appropriate class names.
516
	 *
517
	 * @param string $manager Path of manager names, e.g. "list/type"
518
	 * @return string Class names, e.g. "List_Type"
519
	 */
520
	protected function createSubNames( $manager )
521
	{
522
		$names = explode( '/', $manager );
523
524
		foreach( $names as $key => $subname )
525
		{
526
			if( empty( $subname ) || ctype_alnum( $subname ) === false ) {
527
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid characters in manager name "%1$s"', $manager ) );
528
			}
529
530
			$names[$key] = ucfirst( $subname );
531
		}
532
533
		return implode( '\\', $names );
534
	}
535
536
537
	/**
538
	 * Returns the item for the given search key/value pairs.
539
	 *
540
	 * @param array $pairs Search key/value pairs for the item
541
	 * @param string[] $ref List of domains whose items should be fetched too
542
	 * @return \Aimeos\MShop\Common\Item\Iface Requested item
543
	 * @throws \Aimeos\MShop\Exception if no item with the given ID found
544
	 */
545
	protected function findItemBase( array $pairs, array $ref = [] )
546
	{
547
		$expr = [];
548
		$criteria = $this->getObject()->createSearch();
549
550
		foreach( $pairs as $key => $value )
551
		{
552
			if( $value === null ) {
553
				throw new \Aimeos\MShop\Exception( sprintf( 'Required value for "%1$s" is missing', $key ) );
554
			}
555
			$expr[] = $criteria->compare( '==', $key, $value );
556
		}
557
558
		$criteria->setConditions( $criteria->combine( '&&', $expr ) );
559
		$items = $this->getObject()->searchItems( $criteria, $ref );
560
561
		if( ( $item = reset( $items ) ) === false ) {
562
			throw new \Aimeos\MShop\Exception( sprintf( 'No item found for conditions: %1$s', print_r( $pairs, true ) ) );
563
		}
564
565
		return $item;
566
	}
567
568
569
	/**
570
	 * Returns the cached statement for the given key or creates a new prepared statement.
571
	 * If no SQL string is given, the key is used to retrieve the SQL string from the configuration.
572
	 *
573
	 * @param \Aimeos\MW\DB\Connection\Iface $conn Database connection
574
	 * @param string $cfgkey Unique key for the SQL
575
	 * @param string|null $sql SQL string if it shouldn't be retrieved from the configuration
576
	 * @return \Aimeos\MW\DB\Statement\Iface Database statement object
577
	 */
578
	protected function getCachedStatement( \Aimeos\MW\DB\Connection\Iface $conn, $cfgkey, $sql = null )
579
	{
580
		if( !isset( $this->stmts['stmt'][$cfgkey] ) || !isset( $this->stmts['conn'][$cfgkey] )
581
				|| $conn !== $this->stmts['conn'][$cfgkey]
582
		) {
583
			if( $sql === null ) {
584
				$sql = $this->getSqlConfig( $cfgkey );
585
			}
586
587
			$this->stmts['stmt'][$cfgkey] = $conn->create( $sql );
588
			$this->stmts['conn'][$cfgkey] = $conn;
589
		}
590
591
		return $this->stmts['stmt'][$cfgkey];
592
	}
593
594
595
	/**
596
	 * Returns the item for the given search key and ID.
597
	 *
598
	 * @param string $key Search key for the requested ID
599
	 * @param integer $id Unique ID to search for
600
	 * @param string[] $ref List of domains whose items should be fetched too
601
	 * @param boolean $default Add default criteria
602
	 * @return \Aimeos\MShop\Common\Item\Iface Requested item
603
	 * @throws \Aimeos\MShop\Exception if no item with the given ID found
604
	 */
605
	protected function getItemBase( $key, $id, array $ref = [], $default = false )
606
	{
607
		$criteria = $this->getObject()->createSearch( $default );
608
		$expr = [
609
			$criteria->compare( '==', $key, $id ),
610
			$criteria->getConditions()
611
		];
612
		$criteria->setConditions( $criteria->combine( '&&', $expr ) );
613
		$items = $this->getObject()->searchItems( $criteria, $ref );
614
615
		if( ( $item = reset( $items ) ) === false ) {
616
			throw new \Aimeos\MShop\Exception( sprintf( 'Item with ID "%2$s" in "%1$s" not found', $key, $id ) );
617
		}
618
619
		return $item;
620
	}
621
622
623
	/**
624
	 * Returns the SQL strings for joining dependent tables.
625
	 *
626
	 * @param array $attributes List of search attributes
627
	 * @param string $prefix Search key prefix
628
	 * @return array List of JOIN SQL strings
629
	 */
630
	private function getJoins( array $attributes, $prefix )
631
	{
632
		$iface = '\\Aimeos\\MW\\Criteria\\Attribute\\Iface';
633
		$sep = $this->getKeySeparator();
634
		$name = $prefix . $sep . 'id';
635
636
		if( isset( $attributes[$name] ) && $attributes[$name] instanceof $iface ) {
637
			return $attributes[$name]->getInternalDeps();
638
		}
639
		else if( isset( $attributes['id'] ) && $attributes['id'] instanceof $iface ) {
640
			return $attributes['id']->getInternalDeps();
641
		}
642
643
		return [];
644
	}
645
646
647
	/**
648
	 * Returns the available manager types
649
	 *
650
	 * @param string Main manager type
651
	 * @param string $path Configuration path to the sub-domains
652
	 * @param array $default List of sub-domains if no others are configured
653
	 * @param boolean $withsub Return also the resource type of sub-managers if true
654
	 * @return array Type of the manager and submanagers, subtypes are separated by slashes
655
	 */
656
	protected function getResourceTypeBase( $type, $path, array $default, $withsub )
657
	{
658
		$list = array( $type );
659
660
		foreach( $this->context->getConfig()->get( $path, $default ) as $domain ) {
661
			$list = array_merge( $list, $this->getObject()->getSubManager( $domain )->getResourceType( $withsub ) );
662
		}
663
664
		return $list;
665
	}
666
667
668
	/**
669
	 * Returns the name of the resource or of the default resource.
670
	 *
671
	 * @return string Name of the resource
672
	 */
673
	protected function getResourceName()
674
	{
675
		if( $this->resourceName === null ) {
676
			$this->resourceName = $this->context->getConfig()->get( 'resource/default', 'db' );
677
		}
678
679
		return $this->resourceName;
680
	}
681
682
683
	/**
684
	 * Sets the name of the database resource that should be used.
685
	 *
686
	 * @param string $name Name of the resource
687
	 */
688
	protected function setResourceName( $name )
689
	{
690
		$config = $this->context->getConfig();
691
692
		if( $config->get( 'resource/' . $name ) === null ) {
693
			$this->resourceName = $config->get( 'resource/default', 'db' );
694
		} else {
695
			$this->resourceName = $name;
696
		}
697
	}
698
699
700
	/**
701
	 * Replaces ":site" marker in a search config item array.
702
	 *
703
	 * @param array &$searchAttr Single search config definition including the "internalcode" key
704
	 * @param string $column Name (including alias) of the column containing the site ID in the storage
705
	 * @param integer|array $value Site ID or list of site IDs
706
	 * @param string $marker Marker to replace
707
	 */
708
	protected function replaceSiteMarker( &$searchAttr, $column, $value, $marker = ':site' )
709
	{
710
		$types = array( 'siteid' => \Aimeos\MW\DB\Statement\Base::PARAM_INT );
711
		$translations = array( 'siteid' => $column );
712
		$conn = new \Aimeos\MW\DB\Connection\None();
713
714
		$search = new \Aimeos\MW\Criteria\SQL( $conn );
715
716
		$expr = $search->compare( '==', 'siteid', $value );
717
		$string = $expr->toString( $types, $translations );
718
719
		$searchAttr['internalcode'] = str_replace( $marker, $string, $searchAttr['internalcode'] );
720
	}
721
722
723
	/**
724
	 * Returns the site coditions for the search request
725
	 *
726
	 * @param \Aimeos\MW\Criteria\Iface $search Search criteria object
727
	 * @param string[] $keys Sorted list of criteria keys
728
	 * @param array $attributes Associative list of search keys and objects implementing the \Aimeos\MW\Criteria\Attribute\Iface
729
	 * @param string[] $siteIds List of site IDs that should be used for searching
730
	 * @return array List of search conditions implementing \Aimeos\MW\Criteria\Expression\Iface
731
	 * @since 2015.01
732
	 */
733
	protected function getSearchSiteConditions( \Aimeos\MW\Criteria\Iface $search, array $keys, array $attributes, array $siteIds )
734
	{
735
		/** mshop/common/manager/sitecheck
736
		 * Enables or disables using the site IDs in search queries
737
		 *
738
		 * For market places, products of different shop owners managing their
739
		 * own sites should be shown in the frontend. By default, only the items
740
		 * from the current site are displayed. Setting this option to false
741
		 * disables the restriction to the current site and shows all products
742
		 * from all sites. This does also apply to all other records from
743
		 * different domains than "product".
744
		 *
745
		 * This option is most effective if it's only set for the shop frontend,
746
		 * so the shop owners will only see and manager their own products in
747
		 * the administration interface.
748
		 *
749
		 * @param boolean True to resrict items to the current site, false to show item form all sites
750
		 * @since 2016.10
751
		 * @category Developer
752
		 */
753
		if( $this->context->getConfig()->get( 'mshop/common/manager/sitecheck', true ) == false ) {
754
			return [];
755
		}
756
757
		$cond = [];
758
		$sep = $this->getKeySeparator();
759
760
		foreach( $keys as $key )
761
		{
762
			$name = $key . $sep . 'siteid';
763
764
			if( isset( $attributes[$name] ) ) {
765
				$cond[] = $search->compare( '==', $name, $siteIds );
766
			}
767
		}
768
769
		return $cond;
770
	}
771
772
773
	/**
774
	 * Returns the search result of the statement combined with the given criteria.
775
	 *
776
	 * @param \Aimeos\MW\DB\Connection\Iface $conn Database connection
777
	 * @param \Aimeos\MW\Criteria\Iface $search Search criteria object
778
	 * @param string $cfgPathSearch Path to SQL statement in configuration for searching
779
	 * @param string $cfgPathCount Path to SQL statement in configuration for counting
780
	 * @param string[] $required Additional search keys to add conditions for even if no conditions are available
781
	 * @param integer|null $total Contains the number of all records matching the criteria if not null
782
	 * @param integer $sitelevel Constant from \Aimeos\MShop\Locale\Manager\Base for defining which site IDs should be used for searching
783
	 * @param array $plugins Associative list of item keys and plugin objects implementing \Aimeos\MW\Criteria\Plugin\Iface
784
	 * @return \Aimeos\MW\DB\Result\Iface SQL result object for accessing the found records
785
	 * @throws \Aimeos\MShop\Exception if no number of all matching records is available
786
	 */
787
	protected function searchItemsBase( \Aimeos\MW\DB\Connection\Iface $conn, \Aimeos\MW\Criteria\Iface $search,
788
		$cfgPathSearch, $cfgPathCount, array $required, &$total = null,
789
		$sitelevel = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL, array $plugins = [] )
790
	{
791
		$joins = [];
792
		$conditions = $search->getConditions();
793
		$attributes = $this->getObject()->getSearchAttributes();
794
		$siteIds = $this->getSiteIds( $sitelevel );
795
		$keys = $this->getCriteriaKeyList( $search, $required );
796
797
		$basekey = array_shift( $required );
798
799
		foreach( $keys as $key )
800
		{
801
			if( $key !== $basekey ) {
802
				$joins = array_merge( $joins, $this->getJoins( $attributes, $key ) );
803
			}
804
		}
805
806
		$cond = $this->getSearchSiteConditions( $search, $keys, $attributes, $siteIds );
807
808
		if( $conditions !== null ) {
809
			$cond[] = $conditions;
810
		}
811
812
		$search = clone $search;
813
		$search->setConditions( $search->combine( '&&', $cond ) );
814
815
816
		$types = $this->getSearchTypes( $attributes );
817
		$translations = $this->getSearchTranslations( $attributes );
818
819
		$find = array( ':joins', ':cond', ':start', ':size' );
820
		$replace = array(
821
			implode( "\n", array_unique( $joins ) ),
822
			$search->getConditionString( $types, $translations, $plugins ),
823
			$search->getSliceStart(),
824
			$search->getSliceSize(),
825
		);
826
827
		if( count( $search->getSortations() ) > 0 )
828
		{
829
			$keys[] = 'orderby';
830
			$find[] = ':order';
831
			$replace[] = $search->getSortationString( $types, $translations );
832
833
			$keys[] = 'columns';
834
			$find[] = ':columns';
835
			$replace[] = $search->getColumnString( $search->getSortations(), $translations );
836
		}
837
838
839
		if( $total !== null )
840
		{
841
			$sql = new \Aimeos\MW\Template\SQL( $this->getSqlConfig( $cfgPathCount ) );
842
			$sql->replace( $find, $replace )->enable( $keys );
843
844
			$time = microtime( true );
845
			$stmt = $conn->create( $sql->str() );
846
			$results = $stmt->execute();
847
			$row = $results->fetch();
848
			$results->finish();
849
850
			$msg = get_class( $this ) . ' (' . ( ( microtime( true ) - $time ) * 1000 ) . 'ms): SQL = ' . $stmt;
851
			$this->context->getLogger()->log( $msg, \Aimeos\MW\Logger\Base::DEBUG, 'core/sql' );
852
853
			if( $row === false ) {
854
				throw new \Aimeos\MShop\Exception( sprintf( 'Total results value not found' ) );
855
			}
856
857
			$total = (int) $row['count'];
858
		}
859
860
861
		$sql = new \Aimeos\MW\Template\SQL( $this->getSqlConfig( $cfgPathSearch ) );
862
		$sql->replace( $find, $replace )->enable( $keys );
863
864
		$time = microtime( true );
865
		$stmt = $conn->create( $sql->str() );
866
		$results = $stmt->execute();
867
868
		$msg = get_class( $this ) . ' (' . ( ( microtime( true ) - $time ) * 1000 ) . 'ms): SQL = ' . $stmt;
869
		$this->context->getLogger()->log( $msg, \Aimeos\MW\Logger\Base::DEBUG, 'core/sql' );
870
871
		return $results;
872
	}
873
874
875
	/**
876
	 * Deletes items specified by its IDs.
877
	 *
878
	 * @param array $ids List of IDs
879
	 * @param string $cfgpath Configuration path to the SQL statement
880
	 * @param boolean $siteidcheck If siteid should be used in the statement
881
	 * @param string $name Name of the ID column
882
	 */
883
	protected function deleteItemsBase( array $ids, $cfgpath, $siteidcheck = true, $name = 'id' )
884
	{
885
		if( empty( $ids ) ) { return; }
886
887
		$context = $this->getContext();
888
		$dbname = $this->getResourceName();
889
890
		$search = $this->getObject()->createSearch();
891
		$search->setConditions( $search->compare( '==', $name, $ids ) );
892
893
		$types = array( $name => \Aimeos\MW\DB\Statement\Base::PARAM_STR );
894
		$translations = array( $name => '"' . $name . '"' );
895
896
		$cond = $search->getConditionString( $types, $translations );
897
		$sql = str_replace( ':cond', $cond, $this->getSqlConfig( $cfgpath ) );
898
899
		$dbm = $context->getDatabaseManager();
900
		$conn = $dbm->acquire( $dbname );
901
902
		try
903
		{
904
			$stmt = $conn->create( $sql );
905
906
			if( $siteidcheck ) {
907
				$stmt->bind( 1, $context->getLocale()->getSiteId(), \Aimeos\MW\DB\Statement\Base::PARAM_INT );
908
			}
909
910
			$stmt->execute()->finish();
911
912
			$dbm->release( $conn, $dbname );
913
		}
914
		catch( \Exception $e )
915
		{
916
			$dbm->release( $conn, $dbname );
917
			throw $e;
918
		}
919
	}
920
921
922
	/**
923
	 * Starts a database transaction on the connection identified by the given name.
924
	 *
925
	 * @param string $dbname Name of the database settings in the resource configuration
926
	 */
927
	protected function beginTransation( $dbname = 'db' )
928
	{
929
		$dbm = $this->context->getDatabaseManager();
930
931
		$conn = $dbm->acquire( $dbname );
932
		$conn->begin();
933
		$dbm->release( $conn, $dbname );
934
	}
935
936
937
	/**
938
	 * Commits the running database transaction on the connection identified by the given name.
939
	 *
940
	 * @param string $dbname Name of the database settings in the resource configuration
941
	 */
942
	protected function commitTransaction( $dbname = 'db' )
943
	{
944
		$dbm = $this->context->getDatabaseManager();
945
946
		$conn = $dbm->acquire( $dbname );
947
		$conn->commit();
948
		$dbm->release( $conn, $dbname );
949
	}
950
951
952
	/**
953
	 * Rolls back the running database transaction on the connection identified by the given name.
954
	 *
955
	 * @param string $dbname Name of the database settings in the resource configuration
956
	 */
957
	protected function rollbackTransaction( $dbname = 'db' )
958
	{
959
		$dbm = $this->context->getDatabaseManager();
960
961
		$conn = $dbm->acquire( $dbname );
962
		$conn->rollback();
963
		$dbm->release( $conn, $dbname );
964
	}
965
}
966