Completed
Push — master ( e01579...0b39e0 )
by Aimeos
08:57
created

Base::__call()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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