Passed
Push — master ( dbd3b4...b6e939 )
by Aimeos
04:19
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' => 'integer',
142
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
143
			'public' => false,
144
		),
145
		'locale.siteid' => array(
146
			'code' => 'locale.siteid',
147
			'internalcode' => 'mloc."siteid"',
148
			'label' => 'Site ID',
149
			'type' => 'string',
150
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
151
			'public' => false,
152
		),
153
		'locale.languageid' => array(
154
			'code' => 'locale.languageid',
155
			'internalcode' => 'mloc."langid"',
156
			'label' => 'Language ID',
157
			'type' => 'string',
158
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
159
		),
160
		'locale.currencyid' => array(
161
			'code' => 'locale.currencyid',
162
			'internalcode' => 'mloc."currencyid"',
163
			'label' => 'Currency ID',
164
			'type' => 'string',
165
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
166
		),
167
		'locale.status' => array(
168
			'code' => 'locale.status',
169
			'internalcode' => 'mloc."status"',
170
			'label' => 'Status',
171
			'type' => 'integer',
172
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
173
		),
174
		'locale.position' => array(
175
			'code' => 'locale.position',
176
			'internalcode' => 'mloc."pos"',
177
			'label' => 'Position',
178
			'type' => 'integer',
179
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
180
		),
181
		'locale.ctime' => array(
182
			'code' => 'locale.ctime',
183
			'internalcode' => 'mloc."ctime"',
184
			'label' => 'Create date/time',
185
			'type' => 'datetime',
186
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
187
			'public' => false,
188
		),
189
		'locale.mtime' => array(
190
			'code' => 'locale.mtime',
191
			'internalcode' => 'mloc."mtime"',
192
			'label' => 'Modify date/time',
193
			'type' => 'datetime',
194
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
195
			'public' => false,
196
		),
197
		'locale.editor' => array(
198
			'code' => 'locale.editor',
199
			'internalcode' => 'mloc."editor"',
200
			'label' => 'Editor',
201
			'type' => 'string',
202
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
203
			'public' => false,
204
		),
205
	);
206
207
208
	/**
209
	 * Initializes the object.
210
	 *
211
	 * @param \Aimeos\MShop\ContextIface $context Context object
212
	 */
213
	public function __construct( \Aimeos\MShop\ContextIface $context )
214
	{
215
		parent::__construct( $context );
216
217
		/** mshop/locale/manager/resource
218
		 * Name of the database connection resource to use
219
		 *
220
		 * You can configure a different database connection for each data domain
221
		 * and if no such connection name exists, the "db" connection will be used.
222
		 * It's also possible to use the same database connection for different
223
		 * data domains by configuring the same connection name using this setting.
224
		 *
225
		 * @param string Database connection name
226
		 * @since 2023.04
227
		 */
228
		$this->setResourceName( $context->config()->get( 'mshop/locale/manager/resource', 'db-locale' ) );
229
	}
230
231
232
	/**
233
	 * Returns the locale item for the given site code, language code and currency code.
234
	 *
235
	 * @param string $site Site code
236
	 * @param string $lang Language code (optional)
237
	 * @param string $currency Currency code (optional)
238
	 * @param bool $active Flag to get only active items (optional)
239
	 * @param int|null $level Constant from abstract class which site ID levels should be available (optional),
240
	 * 	based on config or value for SITE_PATH if null
241
	 * @param bool $bare Allow locale items with sites only
242
	 * @return \Aimeos\MShop\Locale\Item\Iface Locale item for the given parameters
243
	 * @throws \Aimeos\MShop\Locale\Exception If no locale item is found
244
	 */
245
	public function bootstrap( string $site, string $lang = '', string $currency = '', bool $active = true, int $level = null,
246
		bool $bare = false ) : \Aimeos\MShop\Locale\Item\Iface
247
	{
248
		$siteItem = $this->object()->getSubManager( 'site' )->find( $site );
249
250
		// allow enabled sites and sites under review
251
		if( $active && $siteItem->getStatus() < 1 && $siteItem->getStatus() !== -1 ) {
252
			throw new \Aimeos\MShop\Locale\Exception( 'Site not found' );
253
		}
254
255
		$siteId = $siteItem->getSiteId();
256
		$sites = [Base::SITE_ONE => $siteId];
257
258
		return $this->bootstrapBase( $site, $lang, $currency, $active, $siteItem, $siteId, $sites, $bare );
259
	}
260
261
262
	/**
263
	 * Removes old entries from the storage.
264
	 *
265
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
266
	 * @return \Aimeos\MShop\Locale\Manager\Iface Manager object for chaining method calls
267
	 */
268
	public function clear( iterable $siteids ) : \Aimeos\MShop\Common\Manager\Iface
269
	{
270
		return $this->clearBase( $siteids, 'mshop/locale/manager/delete' );
271
	}
272
273
274
	/**
275
	 * Creates a new empty item instance
276
	 *
277
	 * @param array $values Values the item should be initialized with
278
	 * @return \Aimeos\MShop\Locale\Item\Iface New locale item object
279
	 */
280
	public function create( array $values = [] ) : \Aimeos\MShop\Common\Item\Iface
281
	{
282
		try {
283
			$values['locale.siteid'] = $values['locale.siteid'] ?? $this->context()->locale()->getSiteId();
284
		} catch( \Exception $e ) {} // if no locale item is available
285
286
		return $this->createItemBase( $values );
287
	}
288
289
290
	/**
291
	 * Creates a filter object.
292
	 *
293
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
294
	 * @param bool $site TRUE for adding site criteria to limit items by the site of related items
295
	 * @return \Aimeos\Base\Criteria\Iface Returns the filter object
296
	 */
297
	public function filter( ?bool $default = false, bool $site = false ) : \Aimeos\Base\Criteria\Iface
298
	{
299
		return $this->filterBase( 'locale', $default );
300
	}
301
302
303
	/**
304
	 * Returns the item specified by its ID.
305
	 *
306
	 * @param string $id Unique ID of the locale item
307
	 * @param string[] $ref List of domains to fetch list items and referenced items for
308
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
309
	 * @return \Aimeos\MShop\Locale\Item\Iface Returns the locale item of the given id
310
	 * @throws \Aimeos\MShop\Exception If item couldn't be found
311
	 */
312
	public function get( string $id, array $ref = [], ?bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
313
	{
314
		return $this->getItemBase( 'locale.id', $id, $ref, $default );
315
	}
316
317
318
	/**
319
	 * Searches for all items matching the given critera.
320
	 *
321
	 * @param \Aimeos\Base\Criteria\Iface $search Criteria object with conditions, sortations, etc.
322
	 * @param string[] $ref List of domains to fetch list items and referenced items for
323
	 * @param int &$total Number of items that are available in total
324
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Locale\Item\Iface with ids as keys
325
	 */
326
	public function search( \Aimeos\Base\Criteria\Iface $search, array $ref = [], int &$total = null ) : \Aimeos\Map
327
	{
328
		$items = [];
329
//		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_PATH;
330
//		$search = (clone $search)->add( $this->siteCondition( 'locale.siteid', $level ) );
331
332
		foreach( $this->searchEntries( $search, $ref, $total ) as $row )
333
		{
334
			if( $item = $this->applyFilter( $this->createItemBase( $row ) ) ) {
335
				$items[$row['locale.id']] = $item;
336
			}
337
		}
338
339
		return map( $items );
340
	}
341
342
343
	/**
344
	 * Removes multiple items.
345
	 *
346
	 * @param \Aimeos\MShop\Common\Item\Iface[]|string[] $itemIds List of item objects or IDs of the items
347
	 * @return \Aimeos\MShop\Locale\Manager\Iface Manager object for chaining method calls
348
	 */
349
	public function delete( $itemIds ) : \Aimeos\MShop\Common\Manager\Iface
350
	{
351
		/** mshop/locale/manager/delete/mysql
352
		 * Deletes the items matched by the given IDs from the database
353
		 *
354
		 * @see mshop/locale/manager/delete/ansi
355
		 */
356
357
		/** mshop/locale/manager/delete/ansi
358
		 * Deletes the items matched by the given IDs from the database
359
		 *
360
		 * Removes the records specified by the given IDs from the locale database.
361
		 * The records must be from the site that is configured via the
362
		 * context item.
363
		 *
364
		 * The ":cond" placeholder is replaced by the name of the ID column and
365
		 * the given ID or list of IDs while the site ID is bound to the question
366
		 * mark.
367
		 *
368
		 * The SQL statement should conform to the ANSI standard to be
369
		 * compatible with most relational database systems. This also
370
		 * includes using double quotes for table and column names.
371
		 *
372
		 * @param string SQL statement for deleting items
373
		 * @since 2014.03
374
		 * @category Developer
375
		 * @see mshop/locale/manager/insert/ansi
376
		 * @see mshop/locale/manager/update/ansi
377
		 * @see mshop/locale/manager/newid/ansi
378
		 * @see mshop/locale/manager/search/ansi
379
		 * @see mshop/locale/manager/count/ansi
380
		 */
381
		$path = 'mshop/locale/manager/delete';
382
383
		return $this->deleteItemsBase( $itemIds, $path );
384
	}
385
386
387
	/**
388
	 * Adds or updates an item object.
389
	 *
390
	 * @param \Aimeos\MShop\Locale\Item\Iface $item Item object whose data should be saved
391
	 * @param bool $fetch True if the new ID should be returned in the item
392
	 * @return \Aimeos\MShop\Locale\Item\Iface $item Updated item including the generated ID
393
	 */
394
	protected function saveItem( \Aimeos\MShop\Locale\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Locale\Item\Iface
395
	{
396
		if( !$item->isModified() ) {
397
			return $item;
398
		}
399
400
		$context = $this->context();
401
		$conn = $context->db( $this->getResourceName() );
402
403
		$id = $item->getId();
404
		$date = date( 'Y-m-d H:i:s' );
405
		$columns = $this->object()->getSaveAttributes();
406
407
		if( $id === null )
408
		{
409
			/** mshop/locale/manager/insert/mysql
410
			 * Inserts a new locale record into the database table
411
			 *
412
			 * @see mshop/locale/manager/insert/ansi
413
			 */
414
415
			/** mshop/locale/manager/insert/ansi
416
			 * Inserts a new locale record into the database table
417
			 *
418
			 * Items with no ID yet (i.e. the ID is NULL) will be created in
419
			 * the database and the newly created ID retrieved afterwards
420
			 * using the "newid" SQL statement.
421
			 *
422
			 * The SQL statement must be a string suitable for being used as
423
			 * prepared statement. It must include question marks for binding
424
			 * the values from the locale item to the statement before they are
425
			 * sent to the database server. The number of question marks must
426
			 * be the same as the number of columns listed in the INSERT
427
			 * statement. The order of the columns must correspond to the
428
			 * order in the save() method, so the correct values are
429
			 * bound to the columns.
430
			 *
431
			 * The SQL statement should conform to the ANSI standard to be
432
			 * compatible with most relational database systems. This also
433
			 * includes using double quotes for table and column names.
434
			 *
435
			 * @param string SQL statement for inserting records
436
			 * @since 2014.03
437
			 * @category Developer
438
			 * @see mshop/locale/manager/update/ansi
439
			 * @see mshop/locale/manager/newid/ansi
440
			 * @see mshop/locale/manager/delete/ansi
441
			 * @see mshop/locale/manager/search/ansi
442
			 * @see mshop/locale/manager/count/ansi
443
			 */
444
			$path = 'mshop/locale/manager/insert';
445
			$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

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