Passed
Push — master ( 44c1d8...6b9f69 )
by Aimeos
16:18
created

Standard::bootstrapBase()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 8
dl 0
loc 17
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2023
7
 * @package MShop
8
 * @subpackage Locale
9
 */
10
11
12
namespace Aimeos\MShop\Locale\Manager;
13
14
15
/**
16
 * Default locale manager implementation.
17
 *
18
 * @package MShop
19
 * @subpackage Locale
20
 */
21
class Standard
22
	extends \Aimeos\MShop\Locale\Manager\Base
23
	implements \Aimeos\MShop\Locale\Manager\Iface, \Aimeos\MShop\Common\Manager\Factory\Iface
24
{
25
	/** mshop/locale/manager/name
26
	 * Class name of the used locale manager implementation
27
	 *
28
	 * Each default manager can be replace by an alternative imlementation.
29
	 * To use this implementation, you have to set the last part of the class
30
	 * name as configuration value so the manager factory knows which class it
31
	 * has to instantiate.
32
	 *
33
	 * For example, if the name of the default class is
34
	 *
35
	 *  \Aimeos\MShop\Locale\Manager\Standard
36
	 *
37
	 * and you want to replace it with your own version named
38
	 *
39
	 *  \Aimeos\MShop\Locale\Manager\Mymanager
40
	 *
41
	 * then you have to set the this configuration option:
42
	 *
43
	 *  mshop/locale/manager/name = Mymanager
44
	 *
45
	 * The value is the last part of your own class name and it's case sensitive,
46
	 * so take care that the configuration value is exactly named like the last
47
	 * part of the class name.
48
	 *
49
	 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
50
	 * characters are possible! You should always start the last part of the class
51
	 * name with an upper case character and continue only with lower case characters
52
	 * or numbers. Avoid chamel case names like "MyManager"!
53
	 *
54
	 * @param string Last part of the class name
55
	 * @since 2014.03
56
	 * @category Developer
57
	 */
58
59
	/** mshop/locale/manager/decorators/excludes
60
	 * Excludes decorators added by the "common" option from the locale manager
61
	 *
62
	 * Decorators extend the functionality of a class by adding new aspects
63
	 * (e.g. log what is currently done), executing the methods of the underlying
64
	 * class only in certain conditions (e.g. only for logged in users) or
65
	 * modify what is returned to the caller.
66
	 *
67
	 * This option allows you to remove a decorator added via
68
	 * "mshop/common/manager/decorators/default" before they are wrapped
69
	 * around the locale manager.
70
	 *
71
	 *  mshop/locale/manager/decorators/excludes = array( 'decorator1' )
72
	 *
73
	 * This would remove the decorator named "decorator1" from the list of
74
	 * common decorators ("\Aimeos\MShop\Common\Manager\Decorator\*") added via
75
	 * "mshop/common/manager/decorators/default" for the locale manager.
76
	 *
77
	 * @param array List of decorator names
78
	 * @since 2014.03
79
	 * @category Developer
80
	 * @see mshop/common/manager/decorators/default
81
	 * @see mshop/locale/manager/decorators/global
82
	 * @see mshop/locale/manager/decorators/local
83
	 */
84
85
	/** mshop/locale/manager/decorators/global
86
	 * Adds a list of globally available decorators only to the locale manager
87
	 *
88
	 * Decorators extend the functionality of a class by adding new aspects
89
	 * (e.g. log what is currently done), executing the methods of the underlying
90
	 * class only in certain conditions (e.g. only for logged in users) or
91
	 * modify what is returned to the caller.
92
	 *
93
	 * This option allows you to wrap global decorators
94
	 * ("\Aimeos\MShop\Common\Manager\Decorator\*") around the locale manager.
95
	 *
96
	 *  mshop/locale/manager/decorators/global = array( 'decorator1' )
97
	 *
98
	 * This would add the decorator named "decorator1" defined by
99
	 * "\Aimeos\MShop\Common\Manager\Decorator\Decorator1" only to the locale
100
	 * manager.
101
	 *
102
	 * @param array List of decorator names
103
	 * @since 2014.03
104
	 * @category Developer
105
	 * @see mshop/common/manager/decorators/default
106
	 * @see mshop/locale/manager/decorators/excludes
107
	 * @see mshop/locale/manager/decorators/local
108
	 */
109
110
	/** mshop/locale/manager/decorators/local
111
	 * Adds a list of local decorators only to the locale manager
112
	 *
113
	 * Decorators extend the functionality of a class by adding new aspects
114
	 * (e.g. log what is currently done), executing the methods of the underlying
115
	 * class only in certain conditions (e.g. only for logged in users) or
116
	 * modify what is returned to the caller.
117
	 *
118
	 * This option allows you to wrap local decorators
119
	 * ("\Aimeos\MShop\Locale\Manager\Decorator\*") around the locale manager.
120
	 *
121
	 *  mshop/locale/manager/decorators/local = array( 'decorator2' )
122
	 *
123
	 * This would add the decorator named "decorator2" defined by
124
	 * "\Aimeos\MShop\Locale\Manager\Decorator\Decorator2" only to the locale
125
	 * manager.
126
	 *
127
	 * @param array List of decorator names
128
	 * @since 2014.03
129
	 * @category Developer
130
	 * @see mshop/common/manager/decorators/default
131
	 * @see mshop/locale/manager/decorators/excludes
132
	 * @see mshop/locale/manager/decorators/global
133
	 */
134
135
136
	private array $searchConfig = array(
137
		'locale.id' => array(
138
			'code' => 'locale.id',
139
			'internalcode' => 'mloc."id"',
140
			'label' => 'ID',
141
			'type' => 'int',
142
			'public' => false,
143
		),
144
		'locale.siteid' => array(
145
			'code' => 'locale.siteid',
146
			'internalcode' => 'mloc."siteid"',
147
			'label' => 'Site ID',
148
			'public' => false,
149
		),
150
		'locale.languageid' => array(
151
			'code' => 'locale.languageid',
152
			'internalcode' => 'mloc."langid"',
153
			'label' => 'Language ID',
154
		),
155
		'locale.currencyid' => array(
156
			'code' => 'locale.currencyid',
157
			'internalcode' => 'mloc."currencyid"',
158
			'label' => 'Currency ID',
159
		),
160
		'locale.status' => array(
161
			'code' => 'locale.status',
162
			'internalcode' => 'mloc."status"',
163
			'label' => 'Status',
164
			'type' => 'int',
165
		),
166
		'locale.position' => array(
167
			'code' => 'locale.position',
168
			'internalcode' => 'mloc."pos"',
169
			'label' => 'Position',
170
			'type' => 'int',
171
		),
172
		'locale.ctime' => array(
173
			'code' => 'locale.ctime',
174
			'internalcode' => 'mloc."ctime"',
175
			'label' => 'Create date/time',
176
			'type' => 'datetime',
177
			'public' => false,
178
		),
179
		'locale.mtime' => array(
180
			'code' => 'locale.mtime',
181
			'internalcode' => 'mloc."mtime"',
182
			'label' => 'Modify date/time',
183
			'type' => 'datetime',
184
			'public' => false,
185
		),
186
		'locale.editor' => array(
187
			'code' => 'locale.editor',
188
			'internalcode' => 'mloc."editor"',
189
			'label' => 'Editor',
190
			'public' => false,
191
		),
192
	);
193
194
195
	/**
196
	 * Initializes the object.
197
	 *
198
	 * @param \Aimeos\MShop\ContextIface $context Context object
199
	 */
200
	public function __construct( \Aimeos\MShop\ContextIface $context )
201
	{
202
		parent::__construct( $context );
203
204
		/** mshop/locale/manager/resource
205
		 * Name of the database connection resource to use
206
		 *
207
		 * You can configure a different database connection for each data domain
208
		 * and if no such connection name exists, the "db" connection will be used.
209
		 * It's also possible to use the same database connection for different
210
		 * data domains by configuring the same connection name using this setting.
211
		 *
212
		 * @param string Database connection name
213
		 * @since 2023.04
214
		 */
215
		$this->setResourceName( $context->config()->get( 'mshop/locale/manager/resource', 'db-locale' ) );
216
	}
217
218
219
	/**
220
	 * Returns the locale item for the given site code, language code and currency code.
221
	 *
222
	 * @param string $site Site code
223
	 * @param string $lang Language code (optional)
224
	 * @param string $currency Currency code (optional)
225
	 * @param bool $active Flag to get only active items (optional)
226
	 * @param int|null $level Constant from abstract class which site ID levels should be available (optional),
227
	 * 	based on config or value for SITE_PATH if null
228
	 * @param bool $bare Allow locale items with sites only
229
	 * @return \Aimeos\MShop\Locale\Item\Iface Locale item for the given parameters
230
	 * @throws \Aimeos\MShop\Locale\Exception If no locale item is found
231
	 */
232
	public function bootstrap( string $site, string $lang = '', string $currency = '', bool $active = true, int $level = null,
233
		bool $bare = false ) : \Aimeos\MShop\Locale\Item\Iface
234
	{
235
		$siteItem = $this->object()->getSubManager( 'site' )->find( $site );
236
237
		// allow enabled sites and sites under review
238
		if( $active && $siteItem->getStatus() < 1 && $siteItem->getStatus() !== -1 ) {
239
			throw new \Aimeos\MShop\Locale\Exception( 'Site not found' );
240
		}
241
242
		$siteId = $siteItem->getSiteId();
243
		$sites = [Base::SITE_ONE => $siteId];
244
245
		return $this->bootstrapBase( $site, $lang, $currency, $active, $siteItem, $siteId, $sites, $bare );
246
	}
247
248
249
	/**
250
	 * Removes old entries from the storage.
251
	 *
252
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
253
	 * @return \Aimeos\MShop\Locale\Manager\Iface Manager object for chaining method calls
254
	 */
255
	public function clear( iterable $siteids ) : \Aimeos\MShop\Common\Manager\Iface
256
	{
257
		return $this->clearBase( $siteids, 'mshop/locale/manager/delete' );
258
	}
259
260
261
	/**
262
	 * Creates a new empty item instance
263
	 *
264
	 * @param array $values Values the item should be initialized with
265
	 * @return \Aimeos\MShop\Locale\Item\Iface New locale item object
266
	 */
267
	public function create( array $values = [] ) : \Aimeos\MShop\Common\Item\Iface
268
	{
269
		try {
270
			$values['locale.siteid'] = $values['locale.siteid'] ?? $this->context()->locale()->getSiteId();
271
		} catch( \Exception $e ) {} // if no locale item is available
272
273
		return $this->createItemBase( $values );
274
	}
275
276
277
	/**
278
	 * Creates a filter object.
279
	 *
280
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
281
	 * @param bool $site TRUE for adding site criteria to limit items by the site of related items
282
	 * @return \Aimeos\Base\Criteria\Iface Returns the filter object
283
	 */
284
	public function filter( ?bool $default = false, bool $site = false ) : \Aimeos\Base\Criteria\Iface
285
	{
286
		return $this->filterBase( 'locale', $default );
287
	}
288
289
290
	/**
291
	 * Returns the item specified by its ID.
292
	 *
293
	 * @param string $id Unique ID of the locale item
294
	 * @param string[] $ref List of domains to fetch list items and referenced items for
295
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
296
	 * @return \Aimeos\MShop\Locale\Item\Iface Returns the locale item of the given id
297
	 * @throws \Aimeos\MShop\Exception If item couldn't be found
298
	 */
299
	public function get( string $id, array $ref = [], ?bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
300
	{
301
		return $this->getItemBase( 'locale.id', $id, $ref, $default );
302
	}
303
304
305
	/**
306
	 * Searches for all items matching the given critera.
307
	 *
308
	 * @param \Aimeos\Base\Criteria\Iface $search Criteria object with conditions, sortations, etc.
309
	 * @param string[] $ref List of domains to fetch list items and referenced items for
310
	 * @param int &$total Number of items that are available in total
311
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Locale\Item\Iface with ids as keys
312
	 */
313
	public function search( \Aimeos\Base\Criteria\Iface $search, array $ref = [], int &$total = null ) : \Aimeos\Map
314
	{
315
		$items = [];
316
//		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_PATH;
317
//		$search = (clone $search)->add( $this->siteCondition( 'locale.siteid', $level ) );
318
319
		foreach( $this->searchEntries( $search, $ref, $total ) as $row )
320
		{
321
			if( $item = $this->applyFilter( $this->createItemBase( $row ) ) ) {
322
				$items[$row['locale.id']] = $item;
323
			}
324
		}
325
326
		return map( $items );
327
	}
328
329
330
	/**
331
	 * Removes multiple items.
332
	 *
333
	 * @param \Aimeos\MShop\Common\Item\Iface[]|string[] $itemIds List of item objects or IDs of the items
334
	 * @return \Aimeos\MShop\Locale\Manager\Iface Manager object for chaining method calls
335
	 */
336
	public function delete( $itemIds ) : \Aimeos\MShop\Common\Manager\Iface
337
	{
338
		/** mshop/locale/manager/delete/mysql
339
		 * Deletes the items matched by the given IDs from the database
340
		 *
341
		 * @see mshop/locale/manager/delete/ansi
342
		 */
343
344
		/** mshop/locale/manager/delete/ansi
345
		 * Deletes the items matched by the given IDs from the database
346
		 *
347
		 * Removes the records specified by the given IDs from the locale database.
348
		 * The records must be from the site that is configured via the
349
		 * context item.
350
		 *
351
		 * The ":cond" placeholder is replaced by the name of the ID column and
352
		 * the given ID or list of IDs while the site ID is bound to the question
353
		 * mark.
354
		 *
355
		 * The SQL statement should conform to the ANSI standard to be
356
		 * compatible with most relational database systems. This also
357
		 * includes using double quotes for table and column names.
358
		 *
359
		 * @param string SQL statement for deleting items
360
		 * @since 2014.03
361
		 * @category Developer
362
		 * @see mshop/locale/manager/insert/ansi
363
		 * @see mshop/locale/manager/update/ansi
364
		 * @see mshop/locale/manager/newid/ansi
365
		 * @see mshop/locale/manager/search/ansi
366
		 * @see mshop/locale/manager/count/ansi
367
		 */
368
		$path = 'mshop/locale/manager/delete';
369
370
		return $this->deleteItemsBase( $itemIds, $path );
371
	}
372
373
374
	/**
375
	 * Adds or updates an item object.
376
	 *
377
	 * @param \Aimeos\MShop\Locale\Item\Iface $item Item object whose data should be saved
378
	 * @param bool $fetch True if the new ID should be returned in the item
379
	 * @return \Aimeos\MShop\Locale\Item\Iface $item Updated item including the generated ID
380
	 */
381
	protected function saveItem( \Aimeos\MShop\Locale\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Locale\Item\Iface
382
	{
383
		if( !$item->isModified() ) {
384
			return $item;
385
		}
386
387
		$context = $this->context();
388
		$conn = $context->db( $this->getResourceName() );
389
390
		$id = $item->getId();
391
		$date = date( 'Y-m-d H:i:s' );
392
		$columns = $this->object()->getSaveAttributes();
393
394
		if( $id === null )
395
		{
396
			/** mshop/locale/manager/insert/mysql
397
			 * Inserts a new locale record into the database table
398
			 *
399
			 * @see mshop/locale/manager/insert/ansi
400
			 */
401
402
			/** mshop/locale/manager/insert/ansi
403
			 * Inserts a new locale record into the database table
404
			 *
405
			 * Items with no ID yet (i.e. the ID is NULL) will be created in
406
			 * the database and the newly created ID retrieved afterwards
407
			 * using the "newid" SQL statement.
408
			 *
409
			 * The SQL statement must be a string suitable for being used as
410
			 * prepared statement. It must include question marks for binding
411
			 * the values from the locale item to the statement before they are
412
			 * sent to the database server. The number of question marks must
413
			 * be the same as the number of columns listed in the INSERT
414
			 * statement. The order of the columns must correspond to the
415
			 * order in the save() method, so the correct values are
416
			 * bound to the columns.
417
			 *
418
			 * The SQL statement should conform to the ANSI standard to be
419
			 * compatible with most relational database systems. This also
420
			 * includes using double quotes for table and column names.
421
			 *
422
			 * @param string SQL statement for inserting records
423
			 * @since 2014.03
424
			 * @category Developer
425
			 * @see mshop/locale/manager/update/ansi
426
			 * @see mshop/locale/manager/newid/ansi
427
			 * @see mshop/locale/manager/delete/ansi
428
			 * @see mshop/locale/manager/search/ansi
429
			 * @see mshop/locale/manager/count/ansi
430
			 */
431
			$path = 'mshop/locale/manager/insert';
432
			$sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ) );
0 ignored issues
show
Bug introduced by
It seems like $this->getSqlConfig($path) can also be of type array; however, parameter $sql of Aimeos\MShop\Common\Manager\Base::addSqlColumns() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

432
			$sql = $this->addSqlColumns( array_keys( $columns ), /** @scrutinizer ignore-type */ $this->getSqlConfig( $path ) );
Loading history...
433
		}
434
		else
435
		{
436
			/** mshop/locale/manager/update/mysql
437
			 * Updates an existing locale record in the database
438
			 *
439
			 * @see mshop/locale/manager/update/ansi
440
			 */
441
442
			/** mshop/locale/manager/update/ansi
443
			 * Updates an existing locale record in the database
444
			 *
445
			 * Items which already have an ID (i.e. the ID is not NULL) will
446
			 * be updated in the database.
447
			 *
448
			 * The SQL statement must be a string suitable for being used as
449
			 * prepared statement. It must include question marks for binding
450
			 * the values from the locale item to the statement before they are
451
			 * sent to the database server. The order of the columns must
452
			 * correspond to the order in the save() method, so the
453
			 * correct values are bound to the columns.
454
			 *
455
			 * The SQL statement should conform to the ANSI standard to be
456
			 * compatible with most relational database systems. This also
457
			 * includes using double quotes for table and column names.
458
			 *
459
			 * @param string SQL statement for updating records
460
			 * @since 2014.03
461
			 * @category Developer
462
			 * @see mshop/locale/manager/insert/ansi
463
			 * @see mshop/locale/manager/newid/ansi
464
			 * @see mshop/locale/manager/delete/ansi
465
			 * @see mshop/locale/manager/search/ansi
466
			 * @see mshop/locale/manager/count/ansi
467
			 */
468
			$path = 'mshop/locale/manager/update';
469
			$sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ), false );
470
		}
471
472
		$idx = 1;
473
		$stmt = $this->getCachedStatement( $conn, $path, $sql );
474
		$siteIds = explode( '.', trim( $item->getSiteId(), '.' ) );
475
476
		foreach( $columns as $name => $entry ) {
477
			$stmt->bind( $idx++, $item->get( $name ), \Aimeos\Base\Criteria\SQL::type( $entry->getType() ) );
478
		}
479
480
		$stmt->bind( $idx++, $item->getLanguageId() );
481
		$stmt->bind( $idx++, $item->getCurrencyId() );
482
		$stmt->bind( $idx++, $item->getPosition(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
483
		$stmt->bind( $idx++, $item->getStatus(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
484
		$stmt->bind( $idx++, $date ); // mtime
485
		$stmt->bind( $idx++, $context->editor() );
486
		$stmt->bind( $idx++, end( $siteIds ), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
487
		$stmt->bind( $idx++, $item->getSiteId() );
488
489
		if( $id !== null ) {
490
			$stmt->bind( $idx++, $id, \Aimeos\Base\DB\Statement\Base::PARAM_INT );
491
		} else {
492
			$stmt->bind( $idx++, $date ); // ctime
493
		}
494
495
		$stmt->execute()->finish();
496
497
		if( $id === null && $fetch === true )
498
		{
499
			/** mshop/locale/manager/newid/mysql
500
			 * Retrieves the ID generated by the database when inserting a new record
501
			 *
502
			 * @see mshop/locale/manager/newid/ansi
503
			 */
504
505
			/** mshop/locale/manager/newid/ansi
506
			 * Retrieves the ID generated by the database when inserting a new record
507
			 *
508
			 * As soon as a new record is inserted into the database table,
509
			 * the database server generates a new and unique identifier for
510
			 * that record. This ID can be used for retrieving, updating and
511
			 * deleting that specific record from the table again.
512
			 *
513
			 * For MySQL:
514
			 *  SELECT LAST_INSERT_ID()
515
			 * For PostgreSQL:
516
			 *  SELECT currval('seq_mloc_id')
517
			 * For SQL Server:
518
			 *  SELECT SCOPE_IDENTITY()
519
			 * For Oracle:
520
			 *  SELECT "seq_mloc_id".CURRVAL FROM DUAL
521
			 *
522
			 * There's no way to retrive the new ID by a SQL statements that
523
			 * fits for most database servers as they implement their own
524
			 * specific way.
525
			 *
526
			 * @param string SQL statement for retrieving the last inserted record ID
527
			 * @since 2014.03
528
			 * @category Developer
529
			 * @see mshop/locale/manager/insert/ansi
530
			 * @see mshop/locale/manager/update/ansi
531
			 * @see mshop/locale/manager/delete/ansi
532
			 * @see mshop/locale/manager/search/ansi
533
			 * @see mshop/locale/manager/count/ansi
534
			 */
535
			$path = 'mshop/locale/manager/newid';
536
			$id = $this->newId( $conn, $path );
537
		}
538
539
		$item->setId( $id );
540
541
		return $item;
542
	}
543
544
545
	/**
546
	 * Returns a new manager for locale extensions
547
	 *
548
	 * @param string $manager Name of the sub manager type in lower case
549
	 * @param string|null $name Name of the implementation, will be from configuration (or Default) if null
550
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager for different extensions, e.g site, language, currency.
551
	 */
552
	public function getSubManager( string $manager, string $name = null ) : \Aimeos\MShop\Common\Manager\Iface
553
	{
554
		return $this->getSubManagerBase( 'locale', $manager, $name );
555
	}
556
557
558
	/**
559
	 * Returns the available manager types
560
	 *
561
	 * @param bool $withsub Return also the resource type of sub-managers if true
562
	 * @return string[] Type of the manager and submanagers, subtypes are separated by slashes
563
	 */
564
	public function getResourceType( bool $withsub = true ) : array
565
	{
566
		$path = 'mshop/locale/manager/submanagers';
567
		return $this->getResourceTypeBase( 'locale', $path, array( 'currency', 'language', 'site' ), $withsub );
568
	}
569
570
571
	/**
572
	 * Returns the attributes that can be used for searching.
573
	 *
574
	 * @param bool $withsub Return also attributes of sub-managers if true
575
	 * @return \Aimeos\Base\Criteria\Attribute\Iface[] List of search attribute items
576
	 */
577
	public function getSearchAttributes( bool $withsub = true ) : array
578
	{
579
		/** mshop/locale/manager/submanagers
580
		 * List of manager names that can be instantiated by the locale manager
581
		 *
582
		 * Managers provide a generic interface to the underlying storage.
583
		 * Each manager has or can have sub-managers caring about particular
584
		 * aspects. Each of these sub-managers can be instantiated by its
585
		 * parent manager using the getSubManager() method.
586
		 *
587
		 * The search keys from sub-managers can be normally used in the
588
		 * manager as well. It allows you to search for items of the manager
589
		 * using the search keys of the sub-managers to further limit the
590
		 * retrieved list of items.
591
		 *
592
		 * @param array List of sub-manager names
593
		 * @since 2014.03
594
		 * @category Developer
595
		 */
596
		$path = 'mshop/locale/manager/submanagers';
597
		$default = array( 'language', 'currency', 'site' );
598
599
		return $this->getSearchAttributesBase( $this->searchConfig, $path, $default, $withsub );
600
	}
601
602
603
	/**
604
	 * Returns the locale item for the given site code, language code and currency code.
605
	 *
606
	 * If the locale item is inherited from a parent site, the site ID of this locale item
607
	 * is changed to the site ID of the actual site. This ensures that items assigned to
608
	 * the same site as the site item are still used.
609
	 *
610
	 * @param string $site Site code
611
	 * @param string $lang Language code
612
	 * @param string $currency Currency code
613
	 * @param bool $active Flag to get only active items
614
	 * @param \Aimeos\MShop\Locale\Item\Site\Iface $siteItem Site item
615
	 * @param string $siteId Site ID
616
	 * @param array $sites Associative list of site constant as key and sites as values
617
	 * @param bool $bare Allow locale items with sites only
618
	 * @return \Aimeos\MShop\Locale\Item\Iface Locale item for the given parameters
619
	 * @throws \Aimeos\MShop\Locale\Exception If no locale item is found
620
	 */
621
	protected function bootstrapBase( string $site, string $lang, string $currency, bool $active,
622
		\Aimeos\MShop\Locale\Item\Site\Iface $siteItem, string $siteId, array $sites, bool $bare ) : \Aimeos\MShop\Locale\Item\Iface
623
	{
624
		if( $result = $this->bootstrapMatch( $siteId, $lang, $currency, $active, $siteItem, $sites ) ) {
625
			return $result;
626
		}
627
628
		if( $result = $this->bootstrapClosest( $siteId, $lang, $active, $siteItem, $sites ) ) {
629
			return $result;
630
		}
631
632
		if( $bare === true ) {
633
			return $this->createItemBase( ['locale.siteid' => $siteId], $siteItem, $sites );
634
		}
635
636
		$msg = $this->context()->translate( 'mshop', 'Locale item for site "%1$s" not found' );
637
		throw new \Aimeos\MShop\Locale\Exception( sprintf( $msg, $site ) );
638
	}
639
640
641
	/**
642
	 * Returns the matching locale item for the given site code, language code and currency code.
643
	 *
644
	 * If the locale item is inherited from a parent site, the site ID of this locale item
645
	 * is changed to the site ID of the actual site. This ensures that items assigned to
646
	 * the same site as the site item are still used.
647
	 *
648
	 * @param string $siteId Site ID
649
	 * @param string $lang Language code
650
	 * @param string $currency Currency code
651
	 * @param bool $active Flag to get only active items
652
	 * @param \Aimeos\MShop\Locale\Item\Site\Iface $siteItem Site item
653
	 * @param array $sites Associative list of site constant as key and sites as values
654
	 * @return \Aimeos\MShop\Locale\Item\Iface|null Locale item for the given parameters or null if no item was found
655
	 */
656
	private function bootstrapMatch( string $siteId, string $lang, string $currency, bool $active,
657
		\Aimeos\MShop\Locale\Item\Site\Iface $siteItem, array $sites ) : ?\Aimeos\MShop\Locale\Item\Iface
658
	{
659
		// Try to find exact match
660
		$search = $this->object()->filter( $active );
661
662
		$expr = array( $search->compare( '==', 'locale.siteid', $sites[Base::SITE_PATH] ?? $sites[Base::SITE_ONE] ) );
663
664
		if( !empty( $lang ) )
665
		{
666
			$langIds = strlen( $lang ) > 2 ? [$lang, substr( $lang, 0, 2 )] : [$lang];
667
			$expr[] = $search->compare( '==', 'locale.languageid', $langIds );
668
		}
669
670
		if( !empty( $currency ) ) {
671
			$expr[] = $search->compare( '==', 'locale.currencyid', $currency );
672
		}
673
674
		$expr[] = $search->getConditions();
675
676
677
		if( $active === true )
678
		{
679
			$expr[] = $search->compare( '>', 'locale.currency.status', 0 );
680
			$expr[] = $search->compare( '>', 'locale.language.status', 0 );
681
			$expr[] = $search->compare( '>', 'locale.site.status', 0 );
682
		}
683
684
		$search->setConditions( $search->and( $expr ) );
685
		$search->setSortations( array( $search->sort( '+', 'locale.position' ) ) );
686
		$result = $this->searchEntries( $search );
687
688
		// Try to find first item where site matches
689
		foreach( $result as $row )
690
		{
691
			if( $row['locale.siteid'] === $siteId ) {
692
				return $this->createItemBase( $row, $siteItem, $sites );
693
			}
694
		}
695
696
		if( ( $row = reset( $result ) ) !== false )
697
		{
698
			$row['locale.siteid'] = $siteId;
699
			return $this->createItemBase( $row, $siteItem, $sites );
700
		}
701
702
		return null;
703
	}
704
705
706
	/**
707
	 * Returns the locale item for the given site code, language code and currency code.
708
	 *
709
	 * If the locale item is inherited from a parent site, the site ID of this locale item
710
	 * is changed to the site ID of the actual site. This ensures that items assigned to
711
	 * the same site as the site item are still used.
712
	 *
713
	 * @param string $siteId Site ID
714
	 * @param string $lang Language code
715
	 * @param bool $active Flag to get only active items
716
	 * @param \Aimeos\MShop\Locale\Item\Site\Iface $siteItem Site item
717
	 * @param array $sites Associative list of site constant as key and sites as values
718
	 * @return \Aimeos\MShop\Locale\Item\Iface|null Locale item for the given parameters or null if no item was found
719
	 */
720
	private function bootstrapClosest( string $siteId, string $lang, bool $active,
721
		\Aimeos\MShop\Locale\Item\Site\Iface $siteItem, array $sites ) : ?\Aimeos\MShop\Locale\Item\Iface
722
	{
723
		// Try to find the best matching locale
724
		$search = $this->object()->filter( $active );
725
726
		$expr = array(
727
			$search->compare( '==', 'locale.siteid', $sites[Base::SITE_PATH] ?? $sites[Base::SITE_ONE] ),
728
			$search->getConditions()
729
		);
730
731
		if( $active === true )
732
		{
733
			$expr[] = $search->compare( '>', 'locale.currency.status', 0 );
734
			$expr[] = $search->compare( '>', 'locale.language.status', 0 );
735
			$expr[] = $search->compare( '>', 'locale.site.status', 0 );
736
		}
737
738
		$search->setConditions( $search->and( $expr ) );
739
		$search->setSortations( array( $search->sort( '+', 'locale.position' ) ) );
740
		$result = $this->searchEntries( $search );
741
742
		// Try to find first item where site and language matches
743
		foreach( $result as $row )
744
		{
745
			if( $row['locale.siteid'] === $siteId && $row['locale.languageid'] === $lang ) {
746
				return $this->createItemBase( $row, $siteItem, $sites );
747
			}
748
		}
749
750
		$short = strlen( $lang ) > 2 ? substr( $lang, 0, 2 ) : null;
751
752
		// Try to find first item where site and language without country matches
753
		if( $short )
754
		{
755
			foreach( $result as $row )
756
			{
757
				if( $row['locale.siteid'] === $siteId && $row['locale.languageid'] === $short ) {
758
					return $this->createItemBase( $row, $siteItem, $sites );
759
				}
760
			}
761
		}
762
763
		// Try to find first item where language matches
764
		foreach( $result as $row )
765
		{
766
			if( $row['locale.languageid'] === $lang )
767
			{
768
				$row['locale.siteid'] = $siteId;
769
				return $this->createItemBase( $row, $siteItem, $sites );
770
			}
771
		}
772
773
		// Try to find first item where language without country matches
774
		if( $short )
775
		{
776
			foreach( $result as $row )
777
			{
778
				if( $row['locale.siteid'] === $siteId && $row['locale.languageid'] === $short ) {
779
					return $this->createItemBase( $row, $siteItem, $sites );
780
				}
781
			}
782
		}
783
784
		// Try to find first item where site matches
785
		foreach( $result as $row )
786
		{
787
			if( $row['locale.siteid'] === $siteId ) {
788
				return $this->createItemBase( $row, $siteItem, $sites );
789
			}
790
		}
791
792
		// Return first item (no other match found)
793
		if( ( $row = reset( $result ) ) !== false )
794
		{
795
			$row['locale.siteid'] = $siteId;
796
			return $this->createItemBase( $row, $siteItem, $sites );
797
		}
798
799
		return null;
800
	}
801
802
803
	/**
804
	 * Instances a new locale item object.
805
	 *
806
	 * @param array $values Parameter to initialise the item
807
	 * @param \Aimeos\MShop\Locale\Item\Site\Iface|null $site Site item
808
	 * @param array $sites Associative list of site constant as key and sites as values
809
	 * @return \Aimeos\MShop\Locale\Item\Iface Locale item
810
	 */
811
	protected function createItemBase( array $values = [], \Aimeos\MShop\Locale\Item\Site\Iface $site = null,
812
		array $sites = [] ) : \Aimeos\MShop\Locale\Item\Iface
813
	{
814
		return new \Aimeos\MShop\Locale\Item\Standard( $values, $site, $sites );
815
	}
816
817
818
	/**
819
	 * Searches for all items matching the given critera.
820
	 *
821
	 * @param \Aimeos\Base\Criteria\Iface $search Criteria object with conditions, sortations, etc.
822
	 * @param string[] $ref List of domains to fetch list items and referenced items for
823
	 * @param int &$total Number of items that are available in total
824
	 * @return array Associative list of key/value pairs
825
	 */
826
	protected function searchEntries( \Aimeos\Base\Criteria\Iface $search, array $ref = [], int &$total = null ) : array
827
	{
828
		$map = [];
829
		$context = $this->context();
830
		$conn = $context->db( $this->getResourceName() );
831
832
		$required = ['locale'];
833
834
		/** mshop/locale/manager/search/mysql
835
		 * Retrieves the records matched by the given criteria in the database
836
		 *
837
		 * @see mshop/locale/manager/search/ansi
838
		 */
839
840
		/** mshop/locale/manager/search/ansi
841
		 * Retrieves the records matched by the given criteria in the database
842
		 *
843
		 * Fetches the records matched by the given criteria from the locale
844
		 * database. The records must be from one of the sites that are
845
		 * configured via the context item. If the current site is part of
846
		 * a tree of sites, the SELECT statement can retrieve all records
847
		 * from the current site and the complete sub-tree of sites.
848
		 *
849
		 * To limit the records matched, conditions can be added to the given
850
		 * criteria object. It can contain comparisons like column names that
851
		 * must match specific values which can be combined by AND, OR or NOT
852
		 * operators. The resulting string of SQL conditions replaces the
853
		 * ":cond" placeholder before the statement is sent to the database
854
		 * server.
855
		 *
856
		 * If the records that are retrieved should be ordered by one or more
857
		 * columns, the generated string of column / sort direction pairs
858
		 * replaces the ":order" placeholder. In case no ordering is required,
859
		 * the complete ORDER BY part including the "\/*-orderby*\/...\/*orderby-*\/"
860
		 * markers is removed to speed up retrieving the records. Columns of
861
		 * sub-managers can also be used for ordering the result set but then
862
		 * no index can be used.
863
		 *
864
		 * The number of returned records can be limited and can start at any
865
		 * number between the begining and the end of the result set. For that
866
		 * the ":size" and ":start" placeholders are replaced by the
867
		 * corresponding values from the criteria object. The default values
868
		 * are 0 for the start and 100 for the size value.
869
		 *
870
		 * The SQL statement should conform to the ANSI standard to be
871
		 * compatible with most relational database systems. This also
872
		 * includes using double quotes for table and column names.
873
		 *
874
		 * @param string SQL statement for searching items
875
		 * @since 2014.03
876
		 * @category Developer
877
		 * @see mshop/locale/manager/insert/ansi
878
		 * @see mshop/locale/manager/update/ansi
879
		 * @see mshop/locale/manager/newid/ansi
880
		 * @see mshop/locale/manager/delete/ansi
881
		 * @see mshop/locale/manager/count/ansi
882
		 */
883
		$cfgPathSearch = 'mshop/locale/manager/search';
884
885
		/** mshop/locale/manager/count/mysql
886
		 * Counts the number of records matched by the given criteria in the database
887
		 *
888
		 * @see mshop/locale/manager/count/ansi
889
		 */
890
891
		/** mshop/locale/manager/count/ansi
892
		 * Counts the number of records matched by the given criteria in the database
893
		 *
894
		 * Counts all records matched by the given criteria from the locale
895
		 * database. The records must be from one of the sites that are
896
		 * configured via the context item. If the current site is part of
897
		 * a tree of sites, the statement can count all records from the
898
		 * current site and the complete sub-tree of sites.
899
		 *
900
		 * To limit the records matched, conditions can be added to the given
901
		 * criteria object. It can contain comparisons like column names that
902
		 * must match specific values which can be combined by AND, OR or NOT
903
		 * operators. The resulting string of SQL conditions replaces the
904
		 * ":cond" placeholder before the statement is sent to the database
905
		 * server.
906
		 *
907
		 * Both, the strings for ":joins" and for ":cond" are the same as for
908
		 * the "search" SQL statement.
909
		 *
910
		 * Contrary to the "search" statement, it doesn't return any records
911
		 * but instead the number of records that have been found. As counting
912
		 * thousands of records can be a long running task, the maximum number
913
		 * of counted records is limited for performance reasons.
914
		 *
915
		 * The SQL statement should conform to the ANSI standard to be
916
		 * compatible with most relational database systems. This also
917
		 * includes using double quotes for table and column names.
918
		 *
919
		 * @param string SQL statement for counting items
920
		 * @since 2014.03
921
		 * @category Developer
922
		 * @see mshop/locale/manager/insert/ansi
923
		 * @see mshop/locale/manager/update/ansi
924
		 * @see mshop/locale/manager/newid/ansi
925
		 * @see mshop/locale/manager/delete/ansi
926
		 * @see mshop/locale/manager/search/ansi
927
		 */
928
		$cfgPathCount = 'mshop/locale/manager/count';
929
930
		$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount, $required, $total );
931
932
		while( $row = $results->fetch() ) {
933
			$map[$row['locale.id']] = $row;
934
		}
935
936
		return $map ;
937
	}
938
939
940
	/**
941
	 * Returns the site coditions for the search request
942
	 *
943
	 * @param string[] $keys Sorted list of criteria keys
944
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values
945
	 * @param int $sitelevel Site level constant from \Aimeos\MShop\Locale\Manager\Base
946
	 * @return \Aimeos\Base\Criteria\Expression\Iface[] List of search conditions
947
	 */
948
	protected function getSiteConditions( array $keys, array $attributes, int $sitelevel ) : array
949
	{
950
		return [];
951
	}
952
}
953