Completed
Push — master ( 9dfb2a...67d3ca )
by Aimeos
09:16
created

Base::setObject()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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