Completed
Push — master ( 7d4729...4f5586 )
by Aimeos
07:42
created

Standard   C

Complexity

Total Complexity 39

Size/Duplication

Total Lines 986
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 16

Importance

Changes 0
Metric Value
wmc 39
lcom 2
cbo 16
dl 0
loc 986
rs 6.9384
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A createItem() 0 4 1
B saveItem() 0 79 5
B deleteItems() 0 94 3
A findItem() 0 4 1
A getItem() 0 4 1
A getResourceType() 0 6 1
A getSearchAttributes() 0 23 1
B getSubManager() 0 113 1
C searchItems() 0 122 7
A createSearch() 0 17 2
A getPath() 0 5 1
B getTree() 0 29 5
B insertItem() 0 106 2
A moveItem() 0 4 1
A registerItemFilter() 0 3 1
A getSearchResults() 0 9 1
A createItemBase() 0 4 1
A getSearchConfig() 0 4 1
A getTotal() 0 67 2
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 Locale
9
 */
10
11
12
namespace Aimeos\MShop\Locale\Manager\Site;
13
14
15
/**
16
 * Default implementation for managing sites.
17
 *
18
 * @package MShop
19
 * @subpackage Locale
20
 */
21
class Standard
22
	extends \Aimeos\MShop\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\Locale\Manager\Site\Iface
24
{
25
	private $cache = [];
26
27
	private $searchConfig = array(
28
		'locale.site.id' => array(
29
			'code' => 'locale.site.id',
30
			'internalcode' => 'mlocsi."id"',
31
			'internaldeps' => array( 'LEFT JOIN "mshop_locale_site" AS mlocsi ON (mloc."siteid" = mlocsi."id")' ),
32
			'label' => 'Site ID',
33
			'type' => 'string',
34
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
35
			'public' => false,
36
		),
37
		'locale.site.siteid' => array(
38
			'code' => 'locale.site.siteid',
39
			'internalcode' => 'mlocsi."id"',
40
			'label' => 'Site ID',
41
			'type' => 'string',
42
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
43
			'public' => false,
44
		),
45
		'locale.site.label' => array(
46
			'code' => 'locale.site.label',
47
			'internalcode' => 'mlocsi."label"',
48
			'label' => 'Site label',
49
			'type' => 'string',
50
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
51
		),
52
		'locale.site.code' => array(
53
			'code' => 'locale.site.code',
54
			'internalcode' => 'mlocsi."code"',
55
			'label' => 'Site code',
56
			'type' => 'string',
57
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
58
		),
59
		'locale.site.status' => array(
60
			'code' => 'locale.site.status',
61
			'internalcode' => 'mlocsi."status"',
62
			'label' => 'Site status',
63
			'type' => 'integer',
64
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
65
		),
66
		'locale.site.config' => array(
67
			'code' => 'locale.site.config',
68
			'internalcode' => 'mlocsi."config"',
69
			'label' => 'Site config',
70
			'type' => 'string',
71
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
72
			'public' => false,
73
		),
74
		'locale.site.ctime' => array(
75
			'code' => 'locale.site.ctime',
76
			'internalcode' => 'mlocsi."ctime"',
77
			'label' => 'Site create date/time',
78
			'type' => 'datetime',
79
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
80
			'public' => false,
81
		),
82
		'locale.site.mtime' => array(
83
			'code' => 'locale.site.mtime',
84
			'internalcode' => 'mlocsi."mtime"',
85
			'label' => 'Site modify date/time',
86
			'type' => 'datetime',
87
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
88
			'public' => false,
89
		),
90
		'locale.site.editor' => array(
91
			'code' => 'locale.site.editor',
92
			'internalcode' => 'mlocsi."editor"',
93
			'label' => 'Site editor',
94
			'type' => 'string',
95
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
96
			'public' => false,
97
		),
98
		'level' => array(
99
			'code' => 'locale.site.level',
100
			'internalcode' => 'mlocsi."level"',
101
			'label' => 'Site tree level',
102
			'type' => 'integer',
103
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
104
			'public' => false,
105
		),
106
		'left' => array(
107
			'code' => 'locale.site.left',
108
			'internalcode' => 'mlocsi."nleft"',
109
			'label' => 'Site left value',
110
			'type' => 'integer',
111
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
112
			'public' => false,
113
		),
114
		'right' => array(
115
			'code' => 'locale.site.right',
116
			'internalcode' => 'mlocsi."nright"',
117
			'label' => 'Site right value',
118
			'type' => 'integer',
119
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
120
			'public' => false,
121
		),
122
	);
123
124
125
	/**
126
	 * Initializes the object.
127
	 *
128
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object
129
	 */
130
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
131
	{
132
		parent::__construct( $context );
133
		$this->setResourceName( 'db-locale' );
134
	}
135
136
137
	/**
138
	 * Creates a new site object.
139
	 *
140
	 * @return \Aimeos\MShop\Locale\Item\Site\Iface
141
	 * @throws \Aimeos\MShop\Locale\Exception
142
	 */
143
	public function createItem()
144
	{
145
		return $this->createItemBase();
146
	}
147
148
149
	/**
150
	 * Adds a new site to the storage or updates an existing one.
151
	 *
152
	 * @param \Aimeos\MShop\Common\Item\Iface $item New site item for saving to the storage
153
	 * @param boolean $fetch True if the new ID should be returned in the item
154
	 * @return \Aimeos\MShop\Common\Item\Iface $item Updated item including the generated ID
155
	 * @throws \Aimeos\MShop\Locale\Exception
156
	 */
157
	public function saveItem( \Aimeos\MShop\Common\Item\Iface $item, $fetch = true )
158
	{
159
		$iface = '\\Aimeos\\MShop\\Locale\\Item\\Site\\Iface';
160
		if( !( $item instanceof $iface ) ) {
161
			throw new \Aimeos\MShop\Locale\Exception( sprintf( 'Object is not of required type "%1$s"', $iface ) );
162
		}
163
164
		if( $item->getId() === null ) {
165
			throw new \Aimeos\MShop\Locale\Exception( sprintf( 'Newly created site can not be saved using method "saveItem()". Try using method "insertItem()" instead.' ) );
166
		}
167
168
		if( !$item->isModified() ) {
169
			return $item;
170
		}
171
172
		$context = $this->getContext();
173
174
		$dbm = $context->getDatabaseManager();
175
		$dbname = $this->getResourceName();
176
		$conn = $dbm->acquire( $dbname );
177
178
		try
179
		{
180
			$id = $item->getId();
181
182
			/** mshop/locale/manager/site/standard/update/mysql
183
			 * Updates an existing site record in the database
184
			 *
185
			 * @see mshop/locale/manager/site/standard/update/ansi
186
			 */
187
188
			/** mshop/locale/manager/site/standard/update/ansi
189
			 * Updates an existing site record in the database
190
			 *
191
			 * The SQL statement must be a string suitable for being used as
192
			 * prepared statement. It must include question marks for binding
193
			 * the values from the site item to the statement before they are
194
			 * sent to the database server. The order of the columns must
195
			 * correspond to the order in the saveItems() method, so the
196
			 * correct values are bound to the columns.
197
			 *
198
			 * The SQL statement should conform to the ANSI standard to be
199
			 * compatible with most relational database systems. This also
200
			 * includes using double quotes for table and column names.
201
			 *
202
			 * @param string SQL statement for updating records
203
			 * @since 2014.03
204
			 * @category Developer
205
			 * @see mshop/locale/manager/site/standard/insert/ansi
206
			 * @see mshop/locale/manager/site/standard/delete/ansi
207
			 * @see mshop/locale/manager/site/standard/search/ansi
208
			 * @see mshop/locale/manager/site/standard/count/ansi
209
			 * @see mshop/locale/manager/site/standard/newid/ansi
210
			 */
211
			$path = 'mshop/locale/manager/site/standard/update';
212
213
			$stmt = $this->getCachedStatement( $conn, $path );
214
215
			$stmt->bind( 1, $item->getCode() );
216
			$stmt->bind( 2, $item->getLabel() );
217
			$stmt->bind( 3, json_encode( $item->getConfig() ) );
218
			$stmt->bind( 4, $item->getStatus(), \Aimeos\MW\DB\Statement\Base::PARAM_INT );
219
			$stmt->bind( 5, $context->getEditor() );
220
			$stmt->bind( 6, date( 'Y-m-d H:i:s' ) ); // mtime
221
			$stmt->bind( 7, $id, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
222
223
			$stmt->execute()->finish();
224
			$item->setId( $id ); // set Modified false
225
226
			$dbm->release( $conn, $dbname );
227
		}
228
		catch( \Exception $e )
229
		{
230
			$dbm->release( $conn, $dbname );
231
			throw $e;
232
		}
233
234
		return $item;
235
	}
236
237
238
	/**
239
	 * Removes multiple items specified by ids in the array.
240
	 *
241
	 * @param array $ids List of IDs
242
	 */
243
	public function deleteItems( array $ids )
244
	{
245
		$context = $this->getContext();
246
		$config = $context->getConfig();
247
248
		/** mshop/locale/manager/site/standard/delete/mysql
249
		 * Deletes the items matched by the given IDs from the database
250
		 *
251
		 * @see mshop/locale/manager/site/standard/delete/ansi
252
		 */
253
254
		/** mshop/locale/manager/site/standard/delete/ansi
255
		 * Deletes the items matched by the given IDs from the database
256
		 *
257
		 * Removes the site records specified by the given IDs from the
258
		 * locale database. The records must be from the site that is configured
259
		 * via the context item.
260
		 *
261
		 * The ":cond" placeholder is replaced by the name of the ID column and
262
		 * the given ID or list of IDs while the site ID is bound to the question
263
		 * mark.
264
		 *
265
		 * The SQL statement should conform to the ANSI standard to be
266
		 * compatible with most relational database systems. This also
267
		 * includes using double quotes for table and column names.
268
		 *
269
		 * @param string SQL statement for deleting items
270
		 * @since 2014.03
271
		 * @category Developer
272
		 * @see mshop/locale/manager/site/standard/insert/ansi
273
		 * @see mshop/locale/manager/site/standard/update/ansi
274
		 * @see mshop/locale/manager/site/standard/search/ansi
275
		 * @see mshop/locale/manager/site/standard/count/ansi
276
		 * @see mshop/locale/manager/site/standard/newid/ansi
277
		 */
278
		$path = 'mshop/locale/manager/site/standard/delete';
279
		$this->deleteItemsBase( $ids, $path, false );
280
281
		/** mshop/locale/manager/site/cleanup/shop/domains
282
		 * List of madmin domains names whose items referring to the same site should be deleted as well
283
		 *
284
		 * As items for each domain can be stored in a separate database, the
285
		 * site manager needs a list of domain names used to connect to the
286
		 * correct database and to remove all items that belong the the deleted
287
		 * site.
288
		 *
289
		 * For each domain the cleanup will be done by the corresponding MShop
290
		 * manager. To keep records for old sites in the database even if the
291
		 * site was already deleted, you can configure a new list with the
292
		 * domains removed you would like to keep, e.g. the "order" domain to
293
		 * keep all orders ever placed.
294
		 *
295
		 * @param array List of domain names in lower case
296
		 * @since 2014.03
297
		 * @category Developer
298
		 * @see mshop/locale/manager/site/cleanup/admin/domains
299
		 */
300
		$path = 'mshop/locale/manager/site/cleanup/shop/domains';
301
		$default = array(
302
			'attribute', 'catalog', 'coupon', 'customer', 'index',
303
			'media', 'order', 'plugin', 'price', 'product', 'tag',
304
			'service', 'supplier', 'text'
305
		);
306
307
		foreach( $config->get( $path, $default ) as $domain ) {
308
			\Aimeos\MShop\Factory::createManager( $context, $domain )->cleanup( $ids );
309
		}
310
311
		/** mshop/locale/manager/site/cleanup/admin/domains
312
		 * List of mshop domains names whose items referring to the same site should be deleted as well
313
		 *
314
		 * As items for each domain can be stored in a separate database, the
315
		 * site manager needs a list of domain names used to connect to the
316
		 * correct database and to remove all items that belong the the deleted
317
		 * site.
318
		 *
319
		 * For each domain the cleanup will be done by the corresponding MAdmin
320
		 * manager. To keep records for old sites in the database even if the
321
		 * site was already deleted, you can configure a new list with the
322
		 * domains removed you would like to keep, e.g. the "log" domain to
323
		 * keep all log entries ever written.
324
		 *
325
		 * @param array List of domain names in lower case
326
		 * @since 2014.03
327
		 * @category Developer
328
		 * @see mshop/locale/manager/site/cleanup/shop/domains
329
		 */
330
		$path = 'mshop/locale/manager/site/cleanup/admin/domains';
331
		$default = array( 'job', 'log', 'cache' );
332
333
		foreach( $config->get( $path, $default ) as $domain ) {
334
			\Aimeos\MAdmin\Factory::createManager( $context, $domain )->cleanup( $ids );
335
		}
336
	}
337
338
339
	/**
340
	 * Returns the item specified by its code and domain/type if necessary
341
	 *
342
	 * @param string $code Code of the item
343
	 * @param string[] $ref List of domains to fetch list items and referenced items for
344
	 * @param string|null $domain Domain of the item if necessary to identify the item uniquely
345
	 * @param string|null $type Type code of the item if necessary to identify the item uniquely
346
	 * @return \Aimeos\MShop\Common\Item\Iface Item object
347
	 */
348
	public function findItem( $code, array $ref = [], $domain = null, $type = null )
349
	{
350
		return $this->findItemBase( array( 'locale.site.code' => $code ), $ref );
351
	}
352
353
354
	/**
355
	 * Returns the site item specified by its ID.
356
	 *
357
	 * @param string $id Unique ID of the site data in the storage
358
	 * @param string[] $ref List of domains to fetch list items and referenced items for
359
	 * @param boolean $default Add default criteria
360
	 * @return \Aimeos\MShop\Locale\Item\Site\Iface Returns the site item of the given id
361
	 * @throws \Aimeos\MShop\Exception If the item couldn't be found
362
	 */
363
	public function getItem( $id, array $ref = [], $default = false )
364
	{
365
		return $this->getItemBase( 'locale.site.id', $id, $ref, $default );
366
	}
367
368
369
	/**
370
	 * Returns the available manager types
371
	 *
372
	 * @param boolean $withsub Return also the resource type of sub-managers if true
373
	 * @return array Type of the manager and submanagers, subtypes are separated by slashes
374
	 */
375
	public function getResourceType( $withsub = true )
376
	{
377
		$path = 'mshop/locale/manager/site/submanagers';
378
379
		return $this->getResourceTypeBase( 'locale/site', $path, [], $withsub );
380
	}
381
382
383
	/**
384
	 * Returns the attributes that can be used for searching.
385
	 *
386
	 * @param boolean $withsub Return also attributes of sub-managers if true
387
	 * @return array List of attribute items implementing \Aimeos\MW\Criteria\Attribute\Iface
388
	 */
389
	public function getSearchAttributes( $withsub = true )
390
	{
391
		/** mshop/locale/manager/site/submanagers
392
		 * List of manager names that can be instantiated by the locale site manager
393
		 *
394
		 * Managers provide a generic interface to the underlying storage.
395
		 * Each manager has or can have sub-managers caring about particular
396
		 * aspects. Each of these sub-managers can be instantiated by its
397
		 * parent manager using the getSubManager() method.
398
		 *
399
		 * The search keys from sub-managers can be normally used in the
400
		 * manager as well. It allows you to search for items of the manager
401
		 * using the search keys of the sub-managers to further limit the
402
		 * retrieved list of items.
403
		 *
404
		 * @param array List of sub-manager names
405
		 * @since 2014.03
406
		 * @category Developer
407
		 */
408
		$path = 'mshop/locale/manager/site/submanagers';
409
410
		return $this->getSearchAttributesBase( $this->searchConfig, $path, [], $withsub );
411
	}
412
413
414
	/**
415
	 * Returns a new sub manager of the given type and name.
416
	 *
417
	 * @param string $manager Name of the sub manager type in lower case
418
	 * @param string|null $name Name of the implementation, will be from configuration (or Default) if null
419
	 * @return \Aimeos\MShop\Locale\Manager\Iface manager
420
	 */
421
	public function getSubManager( $manager, $name = null )
422
	{
423
		/** mshop/locale/manager/site/name
424
		 * Class name of the used locale site manager implementation
425
		 *
426
		 * Each default locale site manager can be replaced by an alternative imlementation.
427
		 * To use this implementation, you have to set the last part of the class
428
		 * name as configuration value so the manager factory knows which class it
429
		 * has to instantiate.
430
		 *
431
		 * For example, if the name of the default class is
432
		 *
433
		 *  \Aimeos\MShop\Locale\Manager\Site\Standard
434
		 *
435
		 * and you want to replace it with your own version named
436
		 *
437
		 *  \Aimeos\MShop\Locale\Manager\Site\Mysite
438
		 *
439
		 * then you have to set the this configuration option:
440
		 *
441
		 *  mshop/locale/manager/site/name = Mysite
442
		 *
443
		 * The value is the last part of your own class name and it's case sensitive,
444
		 * so take care that the configuration value is exactly named like the last
445
		 * part of the class name.
446
		 *
447
		 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
448
		 * characters are possible! You should always start the last part of the class
449
		 * name with an upper case character and continue only with lower case characters
450
		 * or numbers. Avoid chamel case names like "MySite"!
451
		 *
452
		 * @param string Last part of the class name
453
		 * @since 2014.03
454
		 * @category Developer
455
		 */
456
457
		/** mshop/locale/manager/site/decorators/excludes
458
		 * Excludes decorators added by the "common" option from the locale site manager
459
		 *
460
		 * Decorators extend the functionality of a class by adding new aspects
461
		 * (e.g. log what is currently done), executing the methods of the underlying
462
		 * class only in certain conditions (e.g. only for logged in users) or
463
		 * modify what is returned to the caller.
464
		 *
465
		 * This option allows you to remove a decorator added via
466
		 * "mshop/common/manager/decorators/default" before they are wrapped
467
		 * around the locale site manager.
468
		 *
469
		 *  mshop/locale/manager/site/decorators/excludes = array( 'decorator1' )
470
		 *
471
		 * This would remove the decorator named "decorator1" from the list of
472
		 * common decorators ("\Aimeos\MShop\Common\Manager\Decorator\*") added via
473
		 * "mshop/common/manager/decorators/default" for the locale site manager.
474
		 *
475
		 * @param array List of decorator names
476
		 * @since 2014.03
477
		 * @category Developer
478
		 * @see mshop/common/manager/decorators/default
479
		 * @see mshop/locale/manager/site/decorators/global
480
		 * @see mshop/locale/manager/site/decorators/local
481
		 */
482
483
		/** mshop/locale/manager/site/decorators/global
484
		 * Adds a list of globally available decorators only to the locale site manager
485
		 *
486
		 * Decorators extend the functionality of a class by adding new aspects
487
		 * (e.g. log what is currently done), executing the methods of the underlying
488
		 * class only in certain conditions (e.g. only for logged in users) or
489
		 * modify what is returned to the caller.
490
		 *
491
		 * This option allows you to wrap global decorators
492
		 * ("\Aimeos\MShop\Common\Manager\Decorator\*") around the locale site manager.
493
		 *
494
		 *  mshop/locale/manager/site/decorators/global = array( 'decorator1' )
495
		 *
496
		 * This would add the decorator named "decorator1" defined by
497
		 * "\Aimeos\MShop\Common\Manager\Decorator\Decorator1" only to the locale controller.
498
		 *
499
		 * @param array List of decorator names
500
		 * @since 2014.03
501
		 * @category Developer
502
		 * @see mshop/common/manager/decorators/default
503
		 * @see mshop/locale/manager/site/decorators/excludes
504
		 * @see mshop/locale/manager/site/decorators/local
505
		 */
506
507
		/** mshop/locale/manager/site/decorators/local
508
		 * Adds a list of local decorators only to the locale site manager
509
		 *
510
		 * Decorators extend the functionality of a class by adding new aspects
511
		 * (e.g. log what is currently done), executing the methods of the underlying
512
		 * class only in certain conditions (e.g. only for logged in users) or
513
		 * modify what is returned to the caller.
514
		 *
515
		 * This option allows you to wrap local decorators
516
		 * ("\Aimeos\MShop\Common\Manager\Decorator\*") around the locale site manager.
517
		 *
518
		 *  mshop/locale/manager/site/decorators/local = array( 'decorator2' )
519
		 *
520
		 * This would add the decorator named "decorator2" defined by
521
		 * "\Aimeos\MShop\Common\Manager\Decorator\Decorator2" only to the locale
522
		 * controller.
523
		 *
524
		 * @param array List of decorator names
525
		 * @since 2014.03
526
		 * @category Developer
527
		 * @see mshop/common/manager/decorators/default
528
		 * @see mshop/locale/manager/site/decorators/excludes
529
		 * @see mshop/locale/manager/site/decorators/global
530
		 */
531
532
		return $this->getSubManagerBase( 'locale', 'site/' . $manager, $name );
533
	}
534
535
536
	/**
537
	 * Searches for site items matching the given criteria.
538
	 *
539
	 * @param \Aimeos\MW\Criteria\Iface $search Search criteria object
540
	 * @param string[] $ref List of domains to fetch list items and referenced items for
541
	 * @param integer|null &$total Number of items that are available in total
542
	 * @return array List of site items implementing \Aimeos\MShop\Locale\Item\Site\Iface
543
	 */
544
	public function searchItems( \Aimeos\MW\Criteria\Iface $search, array $ref = [], &$total = null )
545
	{
546
		$items = [];
547
		$context = $this->getContext();
548
549
		$dbm = $context->getDatabaseManager();
550
		$dbname = $this->getResourceName();
551
		$conn = $dbm->acquire( $dbname );
552
553
		try
554
		{
555
			$attributes = $this->getObject()->getSearchAttributes();
556
			$types = $this->getSearchTypes( $attributes );
557
			$translations = $this->getSearchTranslations( $attributes );
558
			$columns = $search->getColumnString( $search->getSortations(), $translations );
559
560
			$find = array( ':cond', ':order', ':columns', ':start', ':size' );
561
			$replace = array(
562
				$search->getConditionString( $types, $translations ),
563
				$search->getSortationString( $types, $translations ),
564
				( $columns ? ', ' . $columns : '' ),
565
				$search->getSliceStart(),
566
				$search->getSliceSize(),
567
			);
568
569
			/** mshop/locale/manager/site/standard/search/mysql
570
			 * Retrieves the records matched by the given criteria in the database
571
			 *
572
			 * @see mshop/locale/manager/site/standard/search/ansi
573
			 */
574
575
			/** mshop/locale/manager/site/standard/search/ansi
576
			 * Retrieves the records matched by the given criteria in the database
577
			 *
578
			 * Fetches the records matched by the given criteria from the attribute
579
			 * database. The records must be from one of the sites that are
580
			 * configured via the context item. If the current site is part of
581
			 * a tree of sites, the SELECT statement can retrieve all records
582
			 * from the current site and the complete sub-tree of sites.
583
			 *
584
			 * As the records can normally be limited by criteria from sub-managers,
585
			 * their tables must be joined in the SQL context. This is done by
586
			 * using the "internaldeps" property from the definition of the ID
587
			 * column of the sub-managers. These internal dependencies specify
588
			 * the JOIN between the tables and the used columns for joining. The
589
			 * ":joins" placeholder is then replaced by the JOIN strings from
590
			 * the sub-managers.
591
			 *
592
			 * To limit the records matched, conditions can be added to the given
593
			 * criteria object. It can contain comparisons like column names that
594
			 * must match specific values which can be combined by AND, OR or NOT
595
			 * operators. The resulting string of SQL conditions replaces the
596
			 * ":cond" placeholder before the statement is sent to the database
597
			 * server.
598
			 *
599
			 * If the records that are retrieved should be ordered by one or more
600
			 * columns, the generated string of column / sort direction pairs
601
			 * replaces the ":order" placeholder. In case no ordering is required,
602
			 * the complete ORDER BY part including the "\/*-orderby*\/...\/*orderby-*\/"
603
			 * markers is removed to speed up retrieving the records. Columns of
604
			 * sub-managers can also be used for ordering the result set but then
605
			 * no index can be used.
606
			 *
607
			 * The number of returned records can be limited and can start at any
608
			 * number between the begining and the end of the result set. For that
609
			 * the ":size" and ":start" placeholders are replaced by the
610
			 * corresponding values from the criteria object. The default values
611
			 * are 0 for the start and 100 for the size value.
612
			 *
613
			 * The SQL statement should conform to the ANSI standard to be
614
			 * compatible with most relational database systems. This also
615
			 * includes using double quotes for table and column names.
616
			 *
617
			 * @param string SQL statement for searching items
618
			 * @since 2014.03
619
			 * @category Developer
620
			 * @see mshop/locale/manager/site/standard/insert/ansi
621
			 * @see mshop/locale/manager/site/standard/update/ansi
622
			 * @see mshop/locale/manager/site/standard/delete/ansi
623
			 * @see mshop/locale/manager/site/standard/count/ansi
624
			 * @see mshop/locale/manager/site/standard/newid/ansi
625
			 */
626
			$path = 'mshop/locale/manager/site/standard/search';
627
628
			$sql = $this->getSqlConfig( $path );
629
			$results = $this->getSearchResults( $conn, str_replace( $find, $replace, $sql ) );
630
631
			try
632
			{
633
				while( ( $row = $results->fetch() ) !== false )
634
				{
635
					$config = $row['locale.site.config'];
636
637
					if( ( $row['locale.site.config'] = json_decode( $row['locale.site.config'], true ) ) === null )
638
					{
639
						$msg = sprintf( 'Invalid JSON as result of search for ID "%2$s" in "%1$s": %3$s', 'mshop_locale.config', $row['locale.site.id'], $config );
640
						$this->getContext()->getLogger()->log( $msg, \Aimeos\MW\Logger\Base::WARN );
641
					}
642
643
					$items[$row['locale.site.id']] = $this->createItemBase( $row );
644
				}
645
			}
646
			catch( \Exception $e )
647
			{
648
				$results->finish();
649
				throw $e;
650
			}
651
652
			if( $total !== null ) {
653
				$total = $this->getTotal( $conn, $find, $replace );
654
			}
655
656
			$dbm->release( $conn, $dbname );
657
		}
658
		catch( \Exception $e )
659
		{
660
			$dbm->release( $conn, $dbname );
661
			throw $e;
662
		}
663
664
		return $items;
665
	}
666
667
668
	/**
669
	 * Creates a search object and sets base criteria.
670
	 *
671
	 * @param boolean $default
672
	 * @return \Aimeos\MW\Criteria\Iface
673
	 */
674
	public function createSearch( $default = false )
675
	{
676
		if( $default === true ) {
677
			$search = $this->createSearchBase( 'locale.site' );
678
		} else {
679
			$search = parent::createSearch( $default );
680
		}
681
682
		$expr = array(
683
			$search->compare( '==', 'locale.site.level', 0 ),
684
			$search->getConditions(),
685
		);
686
687
		$search->setConditions( $search->combine( '&&', $expr ) );
688
689
		return $search;
690
	}
691
692
693
	/**
694
	 * Returns a list of item IDs, that are in the path of given item ID.
695
	 *
696
	 * @param integer $id ID of item to get the path for
697
	 * @param array $ref List of domains to fetch list items and referenced items for
698
	 * @return \Aimeos\MShop\Locale\Item\Site\Iface[] Associative list of items implementing \Aimeos\MShop\Locale\Item\Site\Iface with IDs as keys
699
	 */
700
	public function getPath( $id, array $ref = [] )
701
	{
702
		$item = $this->getTree( $id, $ref, \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE );
703
		return array( $item->getId() => $item );
704
	}
705
706
707
	/**
708
	 * Returns a node and its descendants depending on the given resource.
709
	 *
710
	 * @param integer|null $id Retrieve nodes starting from the given ID
711
	 * @param array List of domains (e.g. text, media, etc.) whose referenced items should be attached to the objects
712
	 * @param integer $level One of the level constants from \Aimeos\MW\Tree\Manager\Base
713
	 * @return \Aimeos\MShop\Locale\Item\Site\Iface Site item
714
	 */
715
	public function getTree( $id = null, array $ref = [], $level = \Aimeos\MW\Tree\Manager\Base::LEVEL_TREE )
716
	{
717
		if( $id !== null )
718
		{
719
			if( count( $ref ) > 0 ) {
720
				return $this->getObject()->getItem( $id, $ref );
721
			}
722
723
			if( !isset( $this->cache[$id] ) ) {
724
				$this->cache[$id] = $this->getObject()->getItem( $id, $ref );
725
			}
726
727
			return $this->cache[$id];
728
		}
729
730
		$criteria = $this->getObject()->createSearch();
731
		$criteria->setConditions( $criteria->compare( '==', 'locale.site.code', 'default' ) );
732
		$criteria->setSlice( 0, 1 );
733
734
		$items = $this->getObject()->searchItems( $criteria, $ref );
735
736
		if( ( $item = reset( $items ) ) === false ) {
737
			throw new \Aimeos\MShop\Locale\Exception( sprintf( 'Tree root with code "%1$s" in "%2$s" not found', 'default', 'locale.site.code' ) );
738
		}
739
740
		$this->cache[$item->getId()] = $item;
741
742
		return $item;
743
	}
744
745
746
	/**
747
	 * Adds a new item object.
748
	 *
749
	 * @param \Aimeos\MShop\Locale\Item\Site\Iface $item Item which should be inserted
750
	 * @param integer|null $parentId ID of the parent item where the item should be inserted into
751
	 * @param integer|null $refId ID of the item where the item should be inserted before (null to append)
752
	 * @return \Aimeos\MShop\Common\Item\Iface $item Updated item including the generated ID
753
	 */
754
	public function insertItem( \Aimeos\MShop\Locale\Item\Site\Iface $item, $parentId = null, $refId = null )
755
	{
756
		$context = $this->getContext();
757
758
		$dbm = $context->getDatabaseManager();
759
		$dbname = $this->getResourceName();
760
		$conn = $dbm->acquire( $dbname );
761
762
		try
763
		{
764
			$date = date( 'Y-m-d H:i:s' );
765
766
			/** mshop/locale/manager/site/standard/insert/mysql
767
			 * Inserts a new currency record into the database table
768
			 *
769
			 * @see mshop/locale/manager/site/standard/insert/ansi
770
			 */
771
772
			/** mshop/locale/manager/site/standard/insert/ansi
773
			 * Inserts a new currency record into the database table
774
			 *
775
			 * The SQL statement must be a string suitable for being used as
776
			 * prepared statement. It must include question marks for binding
777
			 * the values from the site item to the statement before they are
778
			 * sent to the database server. The number of question marks must
779
			 * be the same as the number of columns listed in the INSERT
780
			 * statement. The order of the columns must correspond to the
781
			 * order in the saveItems() method, so the correct values are
782
			 * bound to the columns.
783
			 *
784
			 * The SQL statement should conform to the ANSI standard to be
785
			 * compatible with most relational database systems. This also
786
			 * includes using double quotes for table and column names.
787
			 *
788
			 * @param string SQL statement for inserting records
789
			 * @since 2014.03
790
			 * @category Developer
791
			 * @see mshop/locale/manager/site/standard/update/ansi
792
			 * @see mshop/locale/manager/site/standard/delete/ansi
793
			 * @see mshop/locale/manager/site/standard/search/ansi
794
			 * @see mshop/locale/manager/site/standard/count/ansi
795
			 * @see mshop/locale/manager/site/standard/newid/ansi
796
			 */
797
			$path = 'mshop/locale/manager/site/standard/insert';
798
799
			$stmt = $this->getCachedStatement( $conn, $path );
800
801
			$stmt->bind( 1, $item->getCode() );
802
			$stmt->bind( 2, $item->getLabel() );
803
			$stmt->bind( 3, json_encode( $item->getConfig() ) );
804
			$stmt->bind( 4, $item->getStatus(), \Aimeos\MW\DB\Statement\Base::PARAM_INT );
805
			$stmt->bind( 5, $context->getEditor() );
806
			$stmt->bind( 6, $date ); // mtime
807
			$stmt->bind( 7, $date ); // ctime
808
809
			$stmt->execute()->finish();
810
811
			/** mshop/locale/manager/site/standard/newid/mysql
812
			 * Retrieves the ID generated by the database when inserting a new record
813
			 *
814
			 * @see mshop/locale/manager/site/standard/newid/ansi
815
			 */
816
817
			/** mshop/locale/manager/site/standard/newid/ansi
818
			 * Retrieves the ID generated by the database when inserting a new record
819
			 *
820
			 * As soon as a new record is inserted into the database table,
821
			 * the database server generates a new and unique identifier for
822
			 * that record. This ID can be used for retrieving, updating and
823
			 * deleting that specific record from the table again.
824
			 *
825
			 * For MySQL:
826
			 *  SELECT LAST_INSERT_ID()
827
			 * For PostgreSQL:
828
			 *  SELECT currval('seq_matt_id')
829
			 * For SQL Server:
830
			 *  SELECT SCOPE_IDENTITY()
831
			 * For Oracle:
832
			 *  SELECT "seq_matt_id".CURRVAL FROM DUAL
833
			 *
834
			 * There's no way to retrive the new ID by a SQL statements that
835
			 * fits for most database servers as they implement their own
836
			 * specific way.
837
			 *
838
			 * @param string SQL statement for retrieving the last inserted record ID
839
			 * @since 2014.03
840
			 * @category Developer
841
			 * @see mshop/locale/manager/site/standard/insert/ansi
842
			 * @see mshop/locale/manager/site/standard/update/ansi
843
			 * @see mshop/locale/manager/site/standard/delete/ansi
844
			 * @see mshop/locale/manager/site/standard/search/ansi
845
			 * @see mshop/locale/manager/site/standard/count/ansi
846
			 */
847
			$path = 'mshop/locale/manager/standard/newid';
848
			$item->setId( $this->newId( $conn, $this->getSqlConfig( $path ) ) );
849
850
			$dbm->release( $conn, $dbname );
851
		}
852
		catch( \Exception $e )
853
		{
854
			$dbm->release( $conn, $dbname );
855
			throw $e;
856
		}
857
858
		return $item;
859
	}
860
861
862
	/**
863
	 * Moves an existing item to the new parent in the storage.
864
	 *
865
	 * @param integer $id ID of the item that should be moved
866
	 * @param integer $oldParentId ID of the old parent item which currently contains the item that should be removed
867
	 * @param integer $newParentId ID of the new parent item where the item should be moved to
868
	 * @param integer|null $refId ID of the item where the item should be inserted before (null to append)
869
	 */
870
	public function moveItem( $id, $oldParentId, $newParentId, $refId = null )
871
	{
872
		throw new \Aimeos\MShop\Locale\Exception( sprintf( 'Method "%1$s" for locale site manager not available', 'moveItem()' ) );
873
	}
874
875
876
	/**
877
	 * Registers a new item filter for the given name
878
	 *
879
	 * Not used for site items but required for trees
880
	 *
881
	 * @param string $name Filter name
882
	 * @param \Closure $fcn Callback function
883
	 */
884
	public function registerItemFilter( $name, \Closure $fcn )
885
	{
886
	}
887
888
889
	/**
890
	 * Returns the search results for the given SQL statement.
891
	 *
892
	 * @param \Aimeos\MW\DB\Connection\Iface $conn Database connection
893
	 * @param $sql SQL statement
894
	 * @return \Aimeos\MW\DB\Result\Iface Search result object
895
	 */
896
	protected function getSearchResults( \Aimeos\MW\DB\Connection\Iface $conn, $sql )
897
	{
898
		$statement = $conn->create( $sql );
899
		$this->getContext()->getLogger()->log( __METHOD__ . ': SQL statement: ' . $statement, \Aimeos\MW\Logger\Base::DEBUG );
900
901
		$results = $statement->execute();
902
903
		return $results;
904
	}
905
906
907
	/**
908
	 * Create new item object initialized with given parameters.
909
	 *
910
	 * @param array $data Associative list of item key/value pairs
911
	 * @return \Aimeos\MShop\Locale\Item\Site\Iface Site item object
912
	 */
913
	protected function createItemBase( array $data = [] )
914
	{
915
		return new \Aimeos\MShop\Locale\Item\Site\Standard( $data );
916
	}
917
918
919
	/**
920
	 * Returns the raw search config array.
921
	 *
922
	 * @return array List of search config arrays
923
	 */
924
	protected function getSearchConfig()
925
	{
926
		return $this->searchConfig;
927
	}
928
929
930
	/**
931
	 * Returns the total number of items found for the conditions
932
	 *
933
	 * @param \Aimeos\MW\DB\Connection\Iface $conn Database connection
934
	 * @param array $find List of markers that should be replaced in the SQL statement
935
	 * @param array $replace List of replacements for the markers in the SQL statement
936
	 * @throws \Aimeos\MShop\Locale\Exception If no total value was found
937
	 * @return integer Total number of found items
938
	 */
939
	protected function getTotal( \Aimeos\MW\DB\Connection\Iface $conn, array $find, array $replace )
940
	{
941
		/** mshop/locale/manager/site/standard/count/mysql
942
		 * Counts the number of records matched by the given criteria in the database
943
		 *
944
		 * @see mshop/locale/manager/site/standard/count/ansi
945
		 */
946
947
		/** mshop/locale/manager/site/standard/count/ansi
948
		 * Counts the number of records matched by the given criteria in the database
949
		 *
950
		 * Counts all records matched by the given criteria from the attribute
951
		 * database. The records must be from one of the sites that are
952
		 * configured via the context item. If the current site is part of
953
		 * a tree of sites, the statement can count all records from the
954
		 * current site and the complete sub-tree of sites.
955
		 *
956
		 * As the records can normally be limited by criteria from sub-managers,
957
		 * their tables must be joined in the SQL context. This is done by
958
		 * using the "internaldeps" property from the definition of the ID
959
		 * column of the sub-managers. These internal dependencies specify
960
		 * the JOIN between the tables and the used columns for joining. The
961
		 * ":joins" placeholder is then replaced by the JOIN strings from
962
		 * the sub-managers.
963
		 *
964
		 * To limit the records matched, conditions can be added to the given
965
		 * criteria object. It can contain comparisons like column names that
966
		 * must match specific values which can be combined by AND, OR or NOT
967
		 * operators. The resulting string of SQL conditions replaces the
968
		 * ":cond" placeholder before the statement is sent to the database
969
		 * server.
970
		 *
971
		 * Both, the strings for ":joins" and for ":cond" are the same as for
972
		 * the "search" SQL statement.
973
		 *
974
		 * Contrary to the "search" statement, it doesn't return any records
975
		 * but instead the number of records that have been found. As counting
976
		 * thousands of records can be a long running task, the maximum number
977
		 * of counted records is limited for performance reasons.
978
		 *
979
		 * The SQL statement should conform to the ANSI standard to be
980
		 * compatible with most relational database systems. This also
981
		 * includes using double quotes for table and column names.
982
		 *
983
		 * @param string SQL statement for counting items
984
		 * @since 2014.03
985
		 * @category Developer
986
		 * @see mshop/locale/manager/site/standard/insert/ansi
987
		 * @see mshop/locale/manager/site/standard/update/ansi
988
		 * @see mshop/locale/manager/site/standard/delete/ansi
989
		 * @see mshop/locale/manager/site/standard/search/ansi
990
		 * @see mshop/locale/manager/site/standard/newid/ansi
991
		 */
992
		$path = 'mshop/locale/manager/site/standard/count';
993
994
		$sql = $this->getSqlConfig( $path );
995
		$results = $this->getSearchResults( $conn, str_replace( $find, $replace, $sql ) );
996
997
		$row = $results->fetch();
998
		$results->finish();
999
1000
		if( $row === false ) {
1001
			throw new \Aimeos\MShop\Locale\Exception( 'No total results value found' );
1002
		}
1003
1004
		return $row['count'];
1005
	}
1006
}
1007