Passed
Push — master ( 9489a2...16b7ac )
by Aimeos
05:12
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-2021
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
	private $searchConfig = array(
26
		'locale.id' => array(
27
			'code' => 'locale.id',
28
			'internalcode' => 'mloc."id"',
29
			'label' => 'ID',
30
			'type' => 'integer',
31
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
32
			'public' => false,
33
		),
34
		'locale.siteid' => array(
35
			'code' => 'locale.siteid',
36
			'internalcode' => 'mloc."siteid"',
37
			'label' => 'Site ID',
38
			'type' => 'string',
39
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
40
			'public' => false,
41
		),
42
		'locale.languageid' => array(
43
			'code' => 'locale.languageid',
44
			'internalcode' => 'mloc."langid"',
45
			'label' => 'Language ID',
46
			'type' => 'string',
47
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
48
		),
49
		'locale.currencyid' => array(
50
			'code' => 'locale.currencyid',
51
			'internalcode' => 'mloc."currencyid"',
52
			'label' => 'Currency ID',
53
			'type' => 'string',
54
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
55
		),
56
		'locale.status' => array(
57
			'code' => 'locale.status',
58
			'internalcode' => 'mloc."status"',
59
			'label' => 'Status',
60
			'type' => 'integer',
61
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
62
		),
63
		'locale.position' => array(
64
			'code' => 'locale.position',
65
			'internalcode' => 'mloc."pos"',
66
			'label' => 'Position',
67
			'type' => 'integer',
68
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
69
		),
70
		'locale.ctime' => array(
71
			'code' => 'locale.ctime',
72
			'internalcode' => 'mloc."ctime"',
73
			'label' => 'Create date/time',
74
			'type' => 'datetime',
75
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
76
			'public' => false,
77
		),
78
		'locale.mtime' => array(
79
			'code' => 'locale.mtime',
80
			'internalcode' => 'mloc."mtime"',
81
			'label' => 'Modify date/time',
82
			'type' => 'datetime',
83
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
84
			'public' => false,
85
		),
86
		'locale.editor' => array(
87
			'code' => 'locale.editor',
88
			'internalcode' => 'mloc."editor"',
89
			'label' => 'Editor',
90
			'type' => 'string',
91
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
92
			'public' => false,
93
		),
94
	);
95
96
97
	/**
98
	 * Initializes the object.
99
	 *
100
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object
101
	 */
102
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
103
	{
104
		parent::__construct( $context );
105
		$this->setResourceName( 'db-locale' );
106
	}
107
108
109
	/**
110
	 * Returns the locale item for the given site code, language code and currency code.
111
	 *
112
	 * @param string $site Site code
113
	 * @param string $lang Language code (optional)
114
	 * @param string $currency Currency code (optional)
115
	 * @param bool $active Flag to get only active items (optional)
116
	 * @param int|null $level Constant from abstract class which site ID levels should be available (optional),
117
	 * 	based on config or value for SITE_PATH if null
118
	 * @param bool $bare Allow locale items with sites only
119
	 * @return \Aimeos\MShop\Locale\Item\Iface Locale item for the given parameters
120
	 * @throws \Aimeos\MShop\Locale\Exception If no locale item is found
121
	 */
122
	public function bootstrap( string $site, string $lang = '', string $currency = '', bool $active = true, int $level = null,
123
		bool $bare = false ) : \Aimeos\MShop\Locale\Item\Iface
124
	{
125
		$siteItem = $this->getObject()->getSubManager( 'site' )->find( $site );
126
127
		if( $active && $siteItem->getStatus() < 1 ) {
128
			throw new \Aimeos\MShop\Locale\Exception( 'Site not found' );
129
		}
130
131
		$siteId = $siteItem->getSiteId();
132
		$sites = [Base::SITE_ONE => $siteId];
133
134
		return $this->bootstrapBase( $site, $lang, $currency, $active, $siteItem, $siteId, $sites, $bare );
135
	}
136
137
138
	/**
139
	 * Removes old entries from the storage.
140
	 *
141
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
142
	 * @return \Aimeos\MShop\Locale\Manager\Iface Manager object for chaining method calls
143
	 */
144
	public function clear( iterable $siteids ) : \Aimeos\MShop\Common\Manager\Iface
145
	{
146
		return $this->clearBase( $siteids, 'mshop/locale/manager/delete' );
147
	}
148
149
150
	/**
151
	 * Creates a new empty item instance
152
	 *
153
	 * @param array $values Values the item should be initialized with
154
	 * @return \Aimeos\MShop\Locale\Item\Iface New locale item object
155
	 */
156
	public function create( array $values = [] ) : \Aimeos\MShop\Common\Item\Iface
157
	{
158
		try
159
		{
160
			$values['locale.siteid'] = $this->getContext()->getLocale()->getSiteId();
161
			return $this->createItemBase( $values );
162
		}
163
		catch( \Exception $e )
164
		{
165
			return $this->createItemBase( $values );
166
		}
167
	}
168
169
170
	/**
171
	 * Creates a filter object.
172
	 *
173
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
174
	 * @param bool $site TRUE for adding site criteria to limit items by the site of related items
175
	 * @return \Aimeos\MW\Criteria\Iface Returns the filter object
176
	 */
177
	public function filter( ?bool $default = false, bool $site = false ) : \Aimeos\MW\Criteria\Iface
178
	{
179
		return $this->filterBase( 'locale', $default );
180
	}
181
182
183
	/**
184
	 * Returns the item specified by its ID.
185
	 *
186
	 * @param string $id Unique ID of the locale item
187
	 * @param string[] $ref List of domains to fetch list items and referenced items for
188
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
189
	 * @return \Aimeos\MShop\Locale\Item\Iface Returns the locale item of the given id
190
	 * @throws \Aimeos\MShop\Exception If item couldn't be found
191
	 */
192
	public function get( string $id, array $ref = [], ?bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
193
	{
194
		return $this->getItemBase( 'locale.id', $id, $ref, $default );
195
	}
196
197
198
	/**
199
	 * Searches for all items matching the given critera.
200
	 *
201
	 * @param \Aimeos\MW\Criteria\Iface $search Criteria object with conditions, sortations, etc.
202
	 * @param string[] $ref List of domains to fetch list items and referenced items for
203
	 * @param int &$total Number of items that are available in total
204
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Locale\Item\Iface with ids as keys
205
	 */
206
	public function search( \Aimeos\MW\Criteria\Iface $search, array $ref = [], int &$total = null ) : \Aimeos\Map
207
	{
208
		$items = [];
209
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_PATH;
210
211
		$search = clone $search;
212
		$expr = array(
213
			$this->getSiteCondition( $search, 'locale.siteid', $level ),
214
			$search->getConditions(),
215
		);
216
		$search->setConditions( $search->and( $expr ) );
217
218
		foreach( $this->searchEntries( $search, $ref, $total ) as $row )
219
		{
220
			if( $item = $this->applyFilter( $this->createItemBase( $row ) ) ) {
221
				$items[$row['locale.id']] = $item;
222
			}
223
		}
224
225
		return map( $items );
226
	}
227
228
229
	/**
230
	 * Removes multiple items.
231
	 *
232
	 * @param \Aimeos\MShop\Common\Item\Iface[]|string[] $itemIds List of item objects or IDs of the items
233
	 * @return \Aimeos\MShop\Locale\Manager\Iface Manager object for chaining method calls
234
	 */
235
	public function delete( $itemIds ) : \Aimeos\MShop\Common\Manager\Iface
236
	{
237
		/** mshop/locale/manager/delete/mysql
238
		 * Deletes the items matched by the given IDs from the database
239
		 *
240
		 * @see mshop/locale/manager/delete/ansi
241
		 */
242
243
		/** mshop/locale/manager/delete/ansi
244
		 * Deletes the items matched by the given IDs from the database
245
		 *
246
		 * Removes the records specified by the given IDs from the locale database.
247
		 * The records must be from the site that is configured via the
248
		 * context item.
249
		 *
250
		 * The ":cond" placeholder is replaced by the name of the ID column and
251
		 * the given ID or list of IDs while the site ID is bound to the question
252
		 * mark.
253
		 *
254
		 * The SQL statement should conform to the ANSI standard to be
255
		 * compatible with most relational database systems. This also
256
		 * includes using double quotes for table and column names.
257
		 *
258
		 * @param string SQL statement for deleting items
259
		 * @since 2014.03
260
		 * @category Developer
261
		 * @see mshop/locale/manager/insert/ansi
262
		 * @see mshop/locale/manager/update/ansi
263
		 * @see mshop/locale/manager/newid/ansi
264
		 * @see mshop/locale/manager/search/ansi
265
		 * @see mshop/locale/manager/count/ansi
266
		 */
267
		$path = 'mshop/locale/manager/delete';
268
269
		return $this->deleteItemsBase( $itemIds, $path );
270
	}
271
272
273
	/**
274
	 * Adds or updates an item object.
275
	 *
276
	 * @param \Aimeos\MShop\Locale\Item\Iface $item Item object whose data should be saved
277
	 * @param bool $fetch True if the new ID should be returned in the item
278
	 * @return \Aimeos\MShop\Locale\Item\Iface $item Updated item including the generated ID
279
	 */
280
	public function saveItem( \Aimeos\MShop\Locale\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Locale\Item\Iface
281
	{
282
		if( !$item->isModified() ) {
283
			return $item;
284
		}
285
286
		$context = $this->getContext();
287
288
		$dbm = $context->getDatabaseManager();
289
		$dbname = $this->getResourceName();
290
		$conn = $dbm->acquire( $dbname );
291
292
		try
293
		{
294
			$id = $item->getId();
295
			$date = date( 'Y-m-d H:i:s' );
296
			$columns = $this->getObject()->getSaveAttributes();
297
298
			if( $id === null )
299
			{
300
				/** mshop/locale/manager/insert/mysql
301
				 * Inserts a new locale record into the database table
302
				 *
303
				 * @see mshop/locale/manager/insert/ansi
304
				 */
305
306
				/** mshop/locale/manager/insert/ansi
307
				 * Inserts a new locale record into the database table
308
				 *
309
				 * Items with no ID yet (i.e. the ID is NULL) will be created in
310
				 * the database and the newly created ID retrieved afterwards
311
				 * using the "newid" SQL statement.
312
				 *
313
				 * The SQL statement must be a string suitable for being used as
314
				 * prepared statement. It must include question marks for binding
315
				 * the values from the locale item to the statement before they are
316
				 * sent to the database server. The number of question marks must
317
				 * be the same as the number of columns listed in the INSERT
318
				 * statement. The order of the columns must correspond to the
319
				 * order in the save() method, so the correct values are
320
				 * bound to the columns.
321
				 *
322
				 * The SQL statement should conform to the ANSI standard to be
323
				 * compatible with most relational database systems. This also
324
				 * includes using double quotes for table and column names.
325
				 *
326
				 * @param string SQL statement for inserting records
327
				 * @since 2014.03
328
				 * @category Developer
329
				 * @see mshop/locale/manager/update/ansi
330
				 * @see mshop/locale/manager/newid/ansi
331
				 * @see mshop/locale/manager/delete/ansi
332
				 * @see mshop/locale/manager/search/ansi
333
				 * @see mshop/locale/manager/count/ansi
334
				 */
335
				$path = 'mshop/locale/manager/insert';
336
				$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

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