Passed
Push — master ( 0dde8b...fa860a )
by Aimeos
04:21
created

Standard   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 1027
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 331
dl 0
loc 1027
rs 4.5599
c 0
b 0
f 0
wmc 58

21 Methods

Rating   Name   Duplication   Size   Complexity  
B searchItems() 0 197 7
A saveChildren() 0 26 5
A insertItem() 0 24 2
A deleteItems() 0 7 2
A updateUsage() 0 133 4
A getSubManager() 0 3 1
A __construct() 0 29 5
A saveItem() 0 18 2
A getItem() 0 3 1
A moveItem() 0 23 2
A lock() 0 15 2
A getPath() 0 25 5
A createItem() 0 4 1
A findItem() 0 3 1
B getTree() 0 48 7
A unlock() 0 15 2
A getSearchAttributes() 0 22 1
A getResourceType() 0 4 1
A createSearch() 0 7 2
A cleanup() 0 68 3
A deleteItem() 0 20 2

How to fix   Complexity   

Complex Class

Complex classes like Standard often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Standard, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2018
7
 * @package MShop
8
 * @subpackage Catalog
9
 */
10
11
12
namespace Aimeos\MShop\Catalog\Manager;
13
14
15
/**
16
 * Catalog manager with methods for managing categories products, text, media.
17
 *
18
 * @package MShop
19
 * @subpackage Catalog
20
 */
21
class Standard extends Base
22
	implements \Aimeos\MShop\Catalog\Manager\Iface, \Aimeos\MShop\Common\Manager\Factory\Iface
23
{
24
	private $searchConfig = array(
25
		'id' => array(
26
			'code' => 'catalog.id',
27
			'internalcode' => 'mcat."id"',
28
			'label' => 'ID',
29
			'type' => 'integer',
30
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
31
			'public' => false,
32
		),
33
		'catalog.siteid' => array(
34
			'code' => 'catalog.siteid',
35
			'internalcode' => 'mcat."siteid"',
36
			'label' => 'Site ID',
37
			'type' => 'integer',
38
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
39
			'public' => false,
40
		),
41
		'parentid' => array(
42
			'code' => 'catalog.parentid',
43
			'internalcode' => 'mcat."parentid"',
44
			'label' => 'Parent ID',
45
			'type' => 'integer',
46
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
47
			'public' => false,
48
		),
49
		'level' => array(
50
			'code' => 'catalog.level',
51
			'internalcode' => 'mcat."level"',
52
			'label' => 'Tree level',
53
			'type' => 'integer',
54
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
55
			'public' => false,
56
		),
57
		'left' => array(
58
			'code' => 'catalog.left',
59
			'internalcode' => 'mcat."nleft"',
60
			'label' => 'Left value',
61
			'type' => 'integer',
62
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
63
			'public' => false,
64
		),
65
		'right' => array(
66
			'code' => 'catalog.right',
67
			'internalcode' => 'mcat."nright"',
68
			'label' => 'Right value',
69
			'type' => 'integer',
70
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
71
			'public' => false,
72
		),
73
		'label' => array(
74
			'code' => 'catalog.label',
75
			'internalcode' => 'mcat."label"',
76
			'label' => 'Label',
77
			'type' => 'string',
78
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
79
		),
80
		'code' => array(
81
			'code' => 'catalog.code',
82
			'internalcode' => 'mcat."code"',
83
			'label' => 'Code',
84
			'type' => 'string',
85
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
86
		),
87
		'catalog.target' => array(
88
			'code' => 'catalog.target',
89
			'internalcode' => 'mcat."target"',
90
			'label' => 'URL target',
91
			'type' => 'string',
92
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
93
		),
94
		'status' => array(
95
			'code' => 'catalog.status',
96
			'internalcode' => 'mcat."status"',
97
			'label' => 'Status',
98
			'type' => 'integer',
99
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
100
		),
101
		'config' => array(
102
			'code' => 'catalog.config',
103
			'internalcode' => 'mcat."config"',
104
			'label' => 'Config',
105
			'type' => 'string',
106
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
107
			'public' => false,
108
		),
109
		'catalog.ctime' => array(
110
			'label' => 'Create date/time',
111
			'code' => 'catalog.ctime',
112
			'internalcode' => 'mcat."ctime"',
113
			'type' => 'datetime',
114
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
115
			'public' => false,
116
		),
117
		'catalog.mtime' => array(
118
			'label' => 'Modify date/time',
119
			'code' => 'catalog.mtime',
120
			'internalcode' => 'mcat."mtime"',
121
			'type' => 'datetime',
122
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
123
			'public' => false,
124
		),
125
		'catalog.editor' => array(
126
			'code' => 'catalog.editor',
127
			'internalcode' => 'mcat."editor"',
128
			'label' => 'Editor',
129
			'type' => 'string',
130
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
131
			'public' => false,
132
		),
133
		'catalog:has' => array(
134
			'code' => 'catalog:has()',
135
			'internalcode' => '(
136
				SELECT mcatli_has."id" FROM mshop_catalog_list AS mcatli_has
137
				WHERE mcat."id" = mcatli_has."parentid" AND :site AND mcatli_has."domain" = $1 :type :refid
138
				LIMIT 1
139
			)',
140
			'label' => 'Catalog has list item, parameter(<domain>[,<list type>[,<reference ID>)]]',
141
			'type' => 'null',
142
			'internaltype' => 'null',
143
			'public' => false,
144
		),
145
	);
146
147
148
	/**
149
	 * Initializes the object.
150
	 *
151
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object
152
	 */
153
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
154
	{
155
		parent::__construct( $context, $this->searchConfig );
156
		$this->setResourceName( 'db-catalog' );
157
158
		$locale = $context->getLocale();
159
160
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
161
		$level = $context->getConfig()->get( 'mshop/catalog/manager/sitemode', $level );
162
163
		$siteIds = [$locale->getSiteId()];
164
165
		if( $level & \Aimeos\MShop\Locale\Manager\Base::SITE_PATH ) {
166
			$siteIds = array_merge( $siteIds, $locale->getSitePath() );
167
		}
168
169
		if( $level & \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE ) {
170
			$siteIds = array_merge( $siteIds, $locale->getSiteSubTree() );
171
		}
172
173
		$this->replaceSiteMarker( $this->searchConfig['catalog:has'], 'mcatli_has."siteid"', $siteIds, ':site' );
174
175
176
		$this->searchConfig['catalog:has']['function'] = function( &$source, array $params ) {
177
178
			$source = str_replace( ':type', isset( $params[1] ) ? 'AND mcatli_has."type" = $2' : '', $source );
179
			$source = str_replace( ':refid', isset( $params[2] ) ? 'AND mcatli_has."refid" = $3' : '', $source );
180
181
			return $params;
182
		};
183
	}
184
185
186
	/**
187
	 * Removes old entries from the storage.
188
	 *
189
	 * @param string[] $siteids List of IDs for sites whose entries should be deleted
190
	 * @return \Aimeos\MShop\Catalog\Manager\Iface Manager object for chaining method calls
191
	 */
192
	public function cleanup( array $siteids )
193
	{
194
		$context = $this->getContext();
195
		$config = $context->getConfig();
196
		$search = $this->getObject()->createSearch();
197
198
		foreach( $config->get( 'mshop/catalog/manager/submanagers', ['lists'] ) as $domain ) {
199
			$this->getObject()->getSubManager( $domain )->cleanup( $siteids );
200
		}
201
202
		$dbm = $context->getDatabaseManager();
203
		$dbname = $this->getResourceName();
204
		$conn = $dbm->acquire( $dbname );
205
206
		try
207
		{
208
			/** mshop/catalog/manager/standard/cleanup/mysql
209
			 * Deletes the categories for the given site from the database
210
			 *
211
			 * @see mshop/catalog/manager/standard/cleanup/ansi
212
			 */
213
214
			/** mshop/catalog/manager/standard/cleanup/ansi
215
			 * Deletes the categories for the given site from the database
216
			 *
217
			 * Removes the records matched by the given site ID from the catalog
218
			 * database.
219
			 *
220
			 * The ":siteid" placeholder is replaced by the name and value of the
221
			 * site ID column and the given ID or list of IDs.
222
			 *
223
			 * The SQL statement should conform to the ANSI standard to be
224
			 * compatible with most relational database systems. This also
225
			 * includes using double quotes for table and column names.
226
			 *
227
			 * @param string SQL statement for removing the records
228
			 * @since 2014.03
229
			 * @category Developer
230
			 * @see mshop/catalog/manager/standard/delete/ansi
231
			 * @see mshop/catalog/manager/standard/insert/ansi
232
			 * @see mshop/catalog/manager/standard/update/ansi
233
			 * @see mshop/catalog/manager/standard/newid/ansi
234
			 * @see mshop/catalog/manager/standard/search/ansi
235
			 * @see mshop/catalog/manager/standard/count/ansi
236
			 */
237
			$path = 'mshop/catalog/manager/standard/cleanup';
238
			$sql = $this->getSqlConfig( $path );
239
240
			$types = array( 'siteid' => \Aimeos\MW\DB\Statement\Base::PARAM_STR );
241
			$translations = array( 'siteid' => '"siteid"' );
242
243
			$search->setConditions( $search->compare( '==', 'siteid', $siteids ) );
244
			$sql = str_replace( ':siteid', $search->getConditionSource( $types, $translations ), $sql );
245
246
			$stmt = $conn->create( $sql );
247
			$stmt->bind( 1, 0, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
248
			$stmt->bind( 2, 0x7FFFFFFF, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
249
			$stmt->execute()->finish();
250
251
			$dbm->release( $conn, $dbname );
252
		}
253
		catch( \Exception $e )
254
		{
255
			$dbm->release( $conn, $dbname );
256
			throw $e;
257
		}
258
259
		return $this;
260
	}
261
262
263
	/**
264
	 * Creates a new empty item instance
265
	 *
266
	 * @param array $values Values the item should be initialized with
267
	 * @return \Aimeos\MShop\Catalog\Item\Iface New catalog item object
268
	 */
269
	public function createItem( array $values = [] )
270
	{
271
		$values['siteid'] = $this->getContext()->getLocale()->getSiteId();
272
		return $this->createItemBase( $values );
273
	}
274
275
276
	/**
277
	 * Creates a search object.
278
	 *
279
	 * @param boolean $default Add default criteria
280
	 * @return \Aimeos\MW\Criteria\Iface Returns the search object
281
	 */
282
	public function createSearch( $default = false )
283
	{
284
		if( $default === true ) {
285
			return $this->createSearchBase( 'catalog' );
286
		}
287
288
		return parent::createSearch();
289
	}
290
291
292
	/**
293
	 * Deletes the item specified by its ID.
294
	 *
295
	 * @param string $id ID of the item object
296
	 * @return \Aimeos\MShop\Catalog\Manager\Iface Manager object for chaining method calls
297
	 */
298
	public function deleteItem( $id )
299
	{
300
		$this->begin();
301
		$this->lock();
302
303
		try
304
		{
305
			$siteid = $this->getContext()->getLocale()->getSiteId();
306
			$this->createTreeManager( $siteid )->deleteNode( $id );
307
			$this->unlock();
308
			$this->commit();
309
		}
310
		catch( \Exception $e )
311
		{
312
			$this->unlock();
313
			$this->rollback();
314
			throw $e;
315
		}
316
317
		return $this;
318
	}
319
320
321
	/**
322
	 * Removes multiple items specified by ids in the array.
323
	 *
324
	 * @param string[] $ids List of IDs
325
	 * @return \Aimeos\MShop\Catalog\Manager\Iface Manager object for chaining method calls
326
	 */
327
	public function deleteItems( array $ids )
328
	{
329
		foreach( $ids as $id ) {
330
			$this->getObject()->deleteItem( $id );
331
		}
332
333
		return $this;
334
	}
335
336
337
	/**
338
	 * Returns the item specified by its code and domain/type if necessary
339
	 *
340
	 * @param string $code Code of the item
341
	 * @param string[] $ref List of domains to fetch list items and referenced items for
342
	 * @param string|null $domain Domain of the item if necessary to identify the item uniquely
343
	 * @param string|null $type Type code of the item if necessary to identify the item uniquely
344
	 * @param boolean $default True to add default criteria
345
	 * @return \Aimeos\MShop\Catalog\Item\Iface Catalog item object
346
	 */
347
	public function findItem( $code, array $ref = [], $domain = null, $type = null, $default = false )
348
	{
349
		return $this->findItemBase( array( 'catalog.code' => $code ), $ref, $default );
350
	}
351
352
353
	/**
354
	 * Returns the item specified by its ID.
355
	 *
356
	 * @param string $id Unique ID of the catalog item
357
	 * @param string[] $ref List of domains to fetch list items and referenced items for
358
	 * @param boolean $default Add default criteria
359
	 * @return \Aimeos\MShop\Catalog\Item\Iface Catalog item of the given ID
360
	 * @throws \Aimeos\MShop\Exception If item couldn't be found
361
	 */
362
	public function getItem( $id, array $ref = [], $default = false )
363
	{
364
		return $this->getItemBase( 'catalog.id', $id, $ref, $default );
365
	}
366
367
368
	/**
369
	 * Returns the available manager types
370
	 *
371
	 * @param boolean $withsub Return also the resource type of sub-managers if true
372
	 * @return string[] Type of the manager and submanagers, subtypes are separated by slashes
373
	 */
374
	public function getResourceType( $withsub = true )
375
	{
376
		$path = 'mshop/catalog/manager/submanagers';
377
		return $this->getResourceTypeBase( 'catalog', $path, array( 'lists' ), $withsub );
378
	}
379
380
381
	/**
382
	 * Returns the attributes that can be used for searching.
383
	 *
384
	 * @param boolean $withsub Return also attributes of sub-managers if true
385
	 * @return \Aimeos\MW\Criteria\Attribute\Iface[] List of search attribute items
386
	 */
387
	public function getSearchAttributes( $withsub = true )
388
	{
389
		/** mshop/catalog/manager/submanagers
390
		 * List of manager names that can be instantiated by the catalog manager
391
		 *
392
		 * Managers provide a generic interface to the underlying storage.
393
		 * Each manager has or can have sub-managers caring about particular
394
		 * aspects. Each of these sub-managers can be instantiated by its
395
		 * parent manager using the getSubManager() method.
396
		 *
397
		 * The search keys from sub-managers can be normally used in the
398
		 * manager as well. It allows you to search for items of the manager
399
		 * using the search keys of the sub-managers to further limit the
400
		 * retrieved list of items.
401
		 *
402
		 * @param array List of sub-manager names
403
		 * @since 2014.03
404
		 * @category Developer
405
		 */
406
		$path = 'mshop/catalog/manager/submanagers';
407
408
		return $this->getSearchAttributesBase( $this->searchConfig, $path, [], $withsub );
409
	}
410
411
412
	/**
413
	 * Adds a new item object.
414
	 *
415
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Item which should be inserted
416
	 * @param string|null $parentId ID of the parent item where the item should be inserted into
417
	 * @param string|null $refId ID of the item where the item should be inserted before (null to append)
418
	 * @return \Aimeos\MShop\Catalog\Item\Iface $item Updated item including the generated ID
419
	 */
420
	public function insertItem( \Aimeos\MShop\Catalog\Item\Iface $item, $parentId = null, $refId = null )
421
	{
422
		$this->begin();
423
		$this->lock();
424
425
		try
426
		{
427
			$node = $item->getNode();
0 ignored issues
show
Bug introduced by
The method getNode() does not exist on Aimeos\MShop\Catalog\Item\Iface. Did you maybe mean getCode()? ( Ignorable by Annotation )

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

427
			/** @scrutinizer ignore-call */ 
428
   $node = $item->getNode();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
428
			$siteid = $this->getContext()->getLocale()->getSiteId();
429
430
			$this->createTreeManager( $siteid )->insertNode( $node, $parentId, $refId );
431
			$this->updateUsage( $node->getId(), $item, true );
432
			$this->unlock();
433
			$this->commit();
434
		}
435
		catch( \Exception $e )
436
		{
437
			$this->unlock();
438
			$this->rollback();
439
			throw $e;
440
		}
441
442
		$item = $this->saveListItems( $item, 'catalog' );
443
		return $this->saveChildren( $item );
444
	}
445
446
447
	/**
448
	 * Moves an existing item to the new parent in the storage.
449
	 *
450
	 * @param string $id ID of the item that should be moved
451
	 * @param string $oldParentId ID of the old parent item which currently contains the item that should be removed
452
	 * @param string $newParentId ID of the new parent item where the item should be moved to
453
	 * @param string|null $refId ID of the item where the item should be inserted before (null to append)
454
	 * @return \Aimeos\MShop\Catalog\Manager\Iface Manager object for chaining method calls
455
	 */
456
	public function moveItem( $id, $oldParentId, $newParentId, $refId = null )
457
	{
458
		$this->begin();
459
		$this->lock();
460
461
		try
462
		{
463
			$item = $this->getObject()->getItem( $id );
464
			$siteid = $this->getContext()->getLocale()->getSiteId();
465
466
			$this->createTreeManager( $siteid )->moveNode( $id, $oldParentId, $newParentId, $refId );
467
			$this->updateUsage( $id, $item );
468
			$this->unlock();
469
			$this->commit();
470
		}
471
		catch( \Exception $e )
472
		{
473
			$this->unlock();
474
			$this->rollback();
475
			throw $e;
476
		}
477
478
		return $this;
479
	}
480
481
482
	/**
483
	 * Updates an item object.
484
	 *
485
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Item object whose data should be saved
486
	 * @param boolean $fetch True if the new ID should be returned in the item
487
	 * @return \Aimeos\MShop\Catalog\Item\Iface $item Updated item including the generated ID
488
	 */
489
	public function saveItem( \Aimeos\MShop\Common\Item\Iface $item, $fetch = true )
490
	{
491
		self::checkClass( \Aimeos\MShop\Catalog\Item\Iface::class, $item );
492
493
		if( !$item->isModified() )
494
		{
495
			$item = $this->saveListItems( $item, 'catalog', $fetch );
496
			return $this->saveChildren( $item );
497
		}
498
499
		$node = $item->getNode();
500
		$siteid = $this->getContext()->getLocale()->getSiteId();
501
502
		$this->createTreeManager( $siteid )->saveNode( $node );
503
		$this->updateUsage( $node->getId(), $item );
504
505
		$item = $this->saveListItems( $item, 'catalog', $fetch );
506
		return $this->saveChildren( $item );
507
	}
508
509
510
	/**
511
	 * Searches for all items matching the given critera.
512
	 *
513
	 * @param \Aimeos\MW\Criteria\Iface $search Search criteria object
514
	 * @param string[] $ref List of domains to fetch list items and referenced items for
515
	 * @param integer|null &$total Number of items that are available in total
516
	 * @return \Aimeos\MShop\Catalog\Item\Iface[] List of catalog items
517
	 */
518
	public function searchItems( \Aimeos\MW\Criteria\Iface $search, array $ref = [], &$total = null )
519
	{
520
		$nodeMap = $siteMap = [];
521
		$context = $this->getContext();
522
523
		$dbname = $this->getResourceName();
524
		$dbm = $context->getDatabaseManager();
525
		$conn = $dbm->acquire( $dbname );
526
527
		try
528
		{
529
			$required = array( 'catalog' );
530
531
			/** mshop/catalog/manager/sitemode
532
			 * Mode how items from levels below or above in the site tree are handled
533
			 *
534
			 * By default, only items from the current site are fetched from the
535
			 * storage. If the ai-sites extension is installed, you can create a
536
			 * tree of sites. Then, this setting allows you to define for the
537
			 * whole catalog domain if items from parent sites are inherited,
538
			 * sites from child sites are aggregated or both.
539
			 *
540
			 * Available constants for the site mode are:
541
			 * * 0 = only items from the current site
542
			 * * 1 = inherit items from parent sites
543
			 * * 2 = aggregate items from child sites
544
			 * * 3 = inherit and aggregate items at the same time
545
			 *
546
			 * You also need to set the mode in the locale manager
547
			 * (mshop/locale/manager/standard/sitelevel) to one of the constants.
548
			 * If you set it to the same value, it will work as described but you
549
			 * can also use different modes. For example, if inheritance and
550
			 * aggregation is configured the locale manager but only inheritance
551
			 * in the domain manager because aggregating items makes no sense in
552
			 * this domain, then items wil be only inherited. Thus, you have full
553
			 * control over inheritance and aggregation in each domain.
554
			 *
555
			 * @param integer Constant from Aimeos\MShop\Locale\Manager\Base class
556
			 * @category Developer
557
			 * @since 2018.01
558
			 * @see mshop/locale/manager/standard/sitelevel
559
			 */
560
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_PATH;
561
			$level = $context->getConfig()->get( 'mshop/catalog/manager/sitemode', $level );
562
563
			/** mshop/catalog/manager/standard/search-item/mysql
564
			 * Retrieves the records matched by the given criteria in the database
565
			 *
566
			 * @see mshop/catalog/manager/standard/search-item/ansi
567
			 */
568
569
			/** mshop/catalog/manager/standard/search-item/ansi
570
			 * Retrieves the records matched by the given criteria in the database
571
			 *
572
			 * Fetches the records matched by the given criteria from the catalog
573
			 * database. The records must be from one of the sites that are
574
			 * configured via the context item. If the current site is part of
575
			 * a tree of sites, the SELECT statement can retrieve all records
576
			 * from the current site and the complete sub-tree of sites.
577
			 *
578
			 * As the records can normally be limited by criteria from sub-managers,
579
			 * their tables must be joined in the SQL context. This is done by
580
			 * using the "internaldeps" property from the definition of the ID
581
			 * column of the sub-managers. These internal dependencies specify
582
			 * the JOIN between the tables and the used columns for joining. The
583
			 * ":joins" placeholder is then replaced by the JOIN strings from
584
			 * the sub-managers.
585
			 *
586
			 * To limit the records matched, conditions can be added to the given
587
			 * criteria object. It can contain comparisons like column names that
588
			 * must match specific values which can be combined by AND, OR or NOT
589
			 * operators. The resulting string of SQL conditions replaces the
590
			 * ":cond" placeholder before the statement is sent to the database
591
			 * server.
592
			 *
593
			 * If the records that are retrieved should be ordered by one or more
594
			 * columns, the generated string of column / sort direction pairs
595
			 * replaces the ":order" placeholder. In case no ordering is required,
596
			 * the complete ORDER BY part including the "\/*-orderby*\/...\/*orderby-*\/"
597
			 * markers is removed to speed up retrieving the records. Columns of
598
			 * sub-managers can also be used for ordering the result set but then
599
			 * no index can be used.
600
			 *
601
			 * The number of returned records can be limited and can start at any
602
			 * number between the begining and the end of the result set. For that
603
			 * the ":size" and ":start" placeholders are replaced by the
604
			 * corresponding values from the criteria object. The default values
605
			 * are 0 for the start and 100 for the size value.
606
			 *
607
			 * The SQL statement should conform to the ANSI standard to be
608
			 * compatible with most relational database systems. This also
609
			 * includes using double quotes for table and column names.
610
			 *
611
			 * @param string SQL statement for searching items
612
			 * @since 2014.03
613
			 * @category Developer
614
			 * @see mshop/catalog/manager/standard/delete/ansi
615
			 * @see mshop/catalog/manager/standard/get/ansi
616
			 * @see mshop/catalog/manager/standard/insert/ansi
617
			 * @see mshop/catalog/manager/standard/update/ansi
618
			 * @see mshop/catalog/manager/standard/newid/ansi
619
			 * @see mshop/catalog/manager/standard/search/ansi
620
			 * @see mshop/catalog/manager/standard/count/ansi
621
			 * @see mshop/catalog/manager/standard/move-left/ansi
622
			 * @see mshop/catalog/manager/standard/move-right/ansi
623
			 * @see mshop/catalog/manager/standard/update-parentid/ansi
624
			 */
625
			$cfgPathSearch = 'mshop/catalog/manager/standard/search-item';
626
627
			/** mshop/catalog/manager/standard/count/mysql
628
			 * Counts the number of records matched by the given criteria in the database
629
			 *
630
			 * @see mshop/catalog/manager/standard/count/ansi
631
			 */
632
633
			/** mshop/catalog/manager/standard/count/ansi
634
			 * Counts the number of records matched by the given criteria in the database
635
			 *
636
			 * Counts all records matched by the given criteria from the catalog
637
			 * database. The records must be from one of the sites that are
638
			 * configured via the context item. If the current site is part of
639
			 * a tree of sites, the statement can count all records from the
640
			 * current site and the complete sub-tree of sites.
641
			 *
642
			 * As the records can normally be limited by criteria from sub-managers,
643
			 * their tables must be joined in the SQL context. This is done by
644
			 * using the "internaldeps" property from the definition of the ID
645
			 * column of the sub-managers. These internal dependencies specify
646
			 * the JOIN between the tables and the used columns for joining. The
647
			 * ":joins" placeholder is then replaced by the JOIN strings from
648
			 * the sub-managers.
649
			 *
650
			 * To limit the records matched, conditions can be added to the given
651
			 * criteria object. It can contain comparisons like column names that
652
			 * must match specific values which can be combined by AND, OR or NOT
653
			 * operators. The resulting string of SQL conditions replaces the
654
			 * ":cond" placeholder before the statement is sent to the database
655
			 * server.
656
			 *
657
			 * Both, the strings for ":joins" and for ":cond" are the same as for
658
			 * the "search" SQL statement.
659
			 *
660
			 * Contrary to the "search" statement, it doesn't return any records
661
			 * but instead the number of records that have been found. As counting
662
			 * thousands of records can be a long running task, the maximum number
663
			 * of counted records is limited for performance reasons.
664
			 *
665
			 * The SQL statement should conform to the ANSI standard to be
666
			 * compatible with most relational database systems. This also
667
			 * includes using double quotes for table and column names.
668
			 *
669
			 * @param string SQL statement for counting items
670
			 * @since 2014.03
671
			 * @category Developer
672
			 * @see mshop/catalog/manager/standard/delete/ansi
673
			 * @see mshop/catalog/manager/standard/get/ansi
674
			 * @see mshop/catalog/manager/standard/insert/ansi
675
			 * @see mshop/catalog/manager/standard/update/ansi
676
			 * @see mshop/catalog/manager/standard/newid/ansi
677
			 * @see mshop/catalog/manager/standard/search/ansi
678
			 * @see mshop/catalog/manager/standard/search-item/ansi
679
			 * @see mshop/catalog/manager/standard/move-left/ansi
680
			 * @see mshop/catalog/manager/standard/move-right/ansi
681
			 * @see mshop/catalog/manager/standard/update-parentid/ansi
682
			 */
683
			$cfgPathCount = 'mshop/catalog/manager/standard/count';
684
685
			if( $search->getSortations() === [] ) {
686
				$search->setSortations( [$search->sort( '+', 'catalog.left')] );
687
			}
688
689
			$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount, $required, $total, $level );
690
691
			while( ( $row = $results->fetch() ) !== false ) {
692
				$siteMap[$row['siteid']][$row['id']] = new \Aimeos\MW\Tree\Node\Standard( $row );
693
			}
694
695
			$sitePath = array_reverse( $this->getContext()->getLocale()->getSitePath() );
696
697
			foreach( $sitePath as $siteId )
698
			{
699
				if( isset( $siteMap[$siteId] ) && !empty( $siteMap[$siteId] ) )
700
				{
701
					$nodeMap = $siteMap[$siteId];
702
					break;
703
				}
704
			}
705
706
			$dbm->release( $conn, $dbname );
707
		}
708
		catch( \Exception $e )
709
		{
710
			$dbm->release( $conn, $dbname );
711
			throw $e;
712
		}
713
714
		return $this->buildItems( $nodeMap, $ref, 'catalog' );
715
	}
716
717
718
	/**
719
	 * Returns a list of items starting with the given category that are in the path to the root node
720
	 *
721
	 * @param string $id ID of item to get the path for
722
	 * @param string[] $ref List of domains to fetch list items and referenced items for
723
	 * @return \Aimeos\MShop\Catalog\Item\Iface[] Associative list of catalog items with IDs as keys
724
	 */
725
	public function getPath( $id, array $ref = [] )
726
	{
727
		$sitePath = array_reverse( $this->getContext()->getLocale()->getSitePath() );
728
729
		foreach( $sitePath as $siteId )
730
		{
731
			try {
732
				$path = $this->createTreeManager( $siteId )->getPath( $id );
733
			} catch( \Exception $e ) {
734
				continue;
735
			}
736
737
			if( !empty( $path ) )
738
			{
739
				$itemMap = [];
740
741
				foreach( $path as $node ) {
742
					$itemMap[$node->getId()] = $node;
743
				}
744
745
				return $this->buildItems( $itemMap, $ref, 'catalog' );
746
			}
747
		}
748
749
		throw new \Aimeos\MShop\Catalog\Exception( sprintf( 'Catalog path for ID "%1$s" not found', $id ) );
750
	}
751
752
753
	/**
754
	 * Returns a node and its descendants depending on the given resource.
755
	 *
756
	 * @param string|null $id Retrieve nodes starting from the given ID
757
	 * @param string[] List of domains (e.g. text, media, etc.) whose referenced items should be attached to the objects
758
	 * @param integer $level One of the level constants from \Aimeos\MW\Tree\Manager\Base
759
	 * @param \Aimeos\MW\Criteria\Iface|null $criteria Optional criteria object with conditions
760
	 * @return \Aimeos\MShop\Catalog\Item\Iface Catalog item, maybe with subnodes
761
	 */
762
	public function getTree( $id = null, array $ref = [], $level = \Aimeos\MW\Tree\Manager\Base::LEVEL_TREE, \Aimeos\MW\Criteria\Iface $criteria = null )
763
	{
764
		$sitePath = array_reverse( $this->getContext()->getLocale()->getSitePath() );
765
766
		foreach( $sitePath as $siteId )
767
		{
768
			try {
769
				$node = $this->createTreeManager( $siteId )->getNode( $id, $level, $criteria );
770
			} catch( \Exception $e ) {
771
				continue;
772
			}
773
774
			$listItems = $listItemMap = $refIdMap = [];
775
			$nodeMap = $this->getNodeMap( $node );
776
777
			if( count( $ref ) > 0 ) {
778
				$listItems = $this->getListItems( array_keys( $nodeMap ), $ref, 'catalog' );
779
			}
780
781
			foreach( $listItems as $listItem )
782
			{
783
				$domain = $listItem->getDomain();
784
				$parentid = $listItem->getParentId();
785
786
				$listItemMap[$parentid][$domain][$listItem->getId()] = $listItem;
787
				$refIdMap[$domain][$listItem->getRefId()][] = $parentid;
788
			}
789
790
			$refItemMap = $this->getRefItems( $refIdMap );
791
			$nodeid = $node->getId();
792
793
			$listItems = [];
794
			if( array_key_exists( $nodeid, $listItemMap ) ) {
795
				$listItems = $listItemMap[$nodeid];
796
			}
797
798
			$refItems = [];
799
			if( array_key_exists( $nodeid, $refItemMap ) ) {
800
				$refItems = $refItemMap[$nodeid];
801
			}
802
803
			$item = $this->createItemBase( [], $listItems, $refItems, [], $node );
804
			$this->createTree( $node, $item, $listItemMap, $refItemMap );
0 ignored issues
show
Bug introduced by
$node of type void is incompatible with the type Aimeos\MW\Tree\Node\Iface expected by parameter $node of Aimeos\MShop\Catalog\Manager\Base::createTree(). ( Ignorable by Annotation )

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

804
			$this->createTree( /** @scrutinizer ignore-type */ $node, $item, $listItemMap, $refItemMap );
Loading history...
805
806
			return $item;
807
		}
808
809
		throw new \Aimeos\MShop\Catalog\Exception( sprintf( 'No catalog node for ID "%1$s"', $id ) );
810
	}
811
812
813
	/**
814
	 * Creates a new extension manager in the domain.
815
	 *
816
	 * @param string $manager Name of the sub manager type
817
	 * @param string|null $name Name of the implementation, will be from configuration (or Default)
818
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager extending the domain functionality
819
	 */
820
	public function getSubManager( $manager, $name = null )
821
	{
822
		return $this->getSubManagerBase( 'catalog', $manager, $name );
823
	}
824
825
826
	/**
827
	 * Saves the children of the given node
828
	 *
829
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Catalog item object incl. child items
830
	 * @return \Aimeos\MShop\Catalog\Item\Iface Catalog item with saved child items
831
	 */
832
	protected function saveChildren( \Aimeos\MShop\Catalog\Item\Iface $item )
833
	{
834
		$rmIds = [];
835
		foreach( $item->getChildrenDeleted() as $child ) {
836
			$rmIds[] = $child->getId();
837
		}
838
839
		$this->deleteItems( $rmIds );
840
841
		foreach( $item->getChildren() as $child )
842
		{
843
			if( $child->getId() !== null )
844
			{
845
				$this->saveItem( $child );
846
847
				if( $child->getParentId() !== $item->getParentId() ) {
848
					$this->moveItem( $child->getId(), $item->getParentId(), $child->getParentId() );
849
				}
850
			}
851
			else
852
			{
853
				$this->insertItem( $child, $item->getId() );
854
			}
855
		}
856
857
		return $item;
858
	}
859
860
861
	/**
862
	 * Locks the catalog table against modifications from other connections
863
	 *
864
	 * @return \Aimeos\MShop\Catalog\Manager\Iface Manager object for chaining method calls
865
	 */
866
	protected function lock()
867
	{
868
		$path = 'mshop/catalog/manager/standard/lock';
869
870
		if( ( $sql = $this->getSqlConfig( $path ) ) !== $path )
871
		{
872
			$dbname = $this->getResourceName();
873
			$dbm = $this->getContext()->getDatabaseManager();
874
875
			$conn = $dbm->acquire( $dbname );
876
			$conn->create( $sql )->execute()->finish();
877
			$dbm->release( $conn, $dbname );
878
		}
879
880
		return $this;
881
	}
882
883
884
	/**
885
	 * Unlocks the catalog table for modifications from other connections
886
	 *
887
	 * @return \Aimeos\MShop\Catalog\Manager\Iface Manager object for chaining method calls
888
	 */
889
	protected function unlock()
890
	{
891
		$path = 'mshop/catalog/manager/standard/unlock';
892
893
		if( ( $sql = $this->getSqlConfig( $path ) ) !== $path )
894
		{
895
			$dbname = $this->getResourceName();
896
			$dbm = $this->getContext()->getDatabaseManager();
897
898
			$conn = $dbm->acquire( $dbname );
899
			$conn->create( $sql )->execute()->finish();
900
			$dbm->release( $conn, $dbname );
901
		}
902
903
		return $this;
904
	}
905
906
907
	/**
908
	 * Updates the usage information of a node.
909
	 *
910
	 * @param string $id Id of the record
911
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Catalog item
912
	 * @param boolean $case True if the record shoud be added or false for an update
913
	 * @return \Aimeos\MShop\Catalog\Manager\Iface Manager object for chaining method calls
914
	 */
915
	private function updateUsage( $id, \Aimeos\MShop\Catalog\Item\Iface $item, $case = false )
916
	{
917
		$date = date( 'Y-m-d H:i:s' );
918
		$context = $this->getContext();
919
920
		$dbm = $context->getDatabaseManager();
921
		$dbname = $this->getResourceName();
922
		$conn = $dbm->acquire( $dbname );
923
924
		try
925
		{
926
			$siteid = $context->getLocale()->getSiteId();
927
928
			if( $case !== true )
929
			{
930
				/** mshop/catalog/manager/standard/update-usage/mysql
931
				 * Updates the config, editor and mtime value of an updated record
932
				 *
933
				 * @see mshop/catalog/manager/standard/update-usage/ansi
934
				 */
935
936
				/** mshop/catalog/manager/standard/update-usage/ansi
937
				 * Updates the config, editor and mtime value of an updated record
938
				 *
939
				 * Each record contains some usage information like when it was
940
				 * created, last modified and by whom. These information are part
941
				 * of the catalog items and the generic tree manager doesn't care
942
				 * about this information. Thus, they are updated after the tree
943
				 * manager saved the basic record information.
944
				 *
945
				 * The SQL statement must be a string suitable for being used as
946
				 * prepared statement. It must include question marks for binding
947
				 * the values from the catalog item to the statement before they are
948
				 * sent to the database server. The order of the columns must
949
				 * correspond to the order in the method using this statement,
950
				 * so the correct values are bound to the columns.
951
				 *
952
				 * The SQL statement should conform to the ANSI standard to be
953
				 * compatible with most relational database systems. This also
954
				 * includes using double quotes for table and column names.
955
				 *
956
				 * @param string SQL statement for updating records
957
				 * @since 2014.03
958
				 * @category Developer
959
				 * @see mshop/catalog/manager/standard/delete/ansi
960
				 * @see mshop/catalog/manager/standard/get/ansi
961
				 * @see mshop/catalog/manager/standard/insert/ansi
962
				 * @see mshop/catalog/manager/standard/newid/ansi
963
				 * @see mshop/catalog/manager/standard/search/ansi
964
				 * @see mshop/catalog/manager/standard/search-item/ansi
965
				 * @see mshop/catalog/manager/standard/count/ansi
966
				 * @see mshop/catalog/manager/standard/move-left/ansi
967
				 * @see mshop/catalog/manager/standard/move-right/ansi
968
				 * @see mshop/catalog/manager/standard/update-parentid/ansi
969
				 * @see mshop/catalog/manager/standard/insert-usage/ansi
970
				 */
971
				$path = 'mshop/catalog/manager/standard/update-usage';
972
			}
973
			else
974
			{
975
				/** mshop/catalog/manager/standard/insert-usage/mysql
976
				 * Updates the config, editor, ctime and mtime value of an inserted record
977
				 *
978
				 * @see mshop/catalog/manager/standard/insert-usage/ansi
979
				 */
980
981
				/** mshop/catalog/manager/standard/insert-usage/ansi
982
				 * Updates the config, editor, ctime and mtime value of an inserted record
983
				 *
984
				 * Each record contains some usage information like when it was
985
				 * created, last modified and by whom. These information are part
986
				 * of the catalog items and the generic tree manager doesn't care
987
				 * about this information. Thus, they are updated after the tree
988
				 * manager inserted the basic record information.
989
				 *
990
				 * The SQL statement must be a string suitable for being used as
991
				 * prepared statement. It must include question marks for binding
992
				 * the values from the catalog item to the statement before they are
993
				 * sent to the database server. The order of the columns must
994
				 * correspond to the order in the method using this statement,
995
				 * so the correct values are bound to the columns.
996
				 *
997
				 * The SQL statement should conform to the ANSI standard to be
998
				 * compatible with most relational database systems. This also
999
				 * includes using double quotes for table and column names.
1000
				 *
1001
				 * @param string SQL statement for updating records
1002
				 * @since 2014.03
1003
				 * @category Developer
1004
				 * @see mshop/catalog/manager/standard/delete/ansi
1005
				 * @see mshop/catalog/manager/standard/get/ansi
1006
				 * @see mshop/catalog/manager/standard/insert/ansi
1007
				 * @see mshop/catalog/manager/standard/newid/ansi
1008
				 * @see mshop/catalog/manager/standard/search/ansi
1009
				 * @see mshop/catalog/manager/standard/search-item/ansi
1010
				 * @see mshop/catalog/manager/standard/count/ansi
1011
				 * @see mshop/catalog/manager/standard/move-left/ansi
1012
				 * @see mshop/catalog/manager/standard/move-right/ansi
1013
				 * @see mshop/catalog/manager/standard/update-parentid/ansi
1014
				 * @see mshop/catalog/manager/standard/update-usage/ansi
1015
				 */
1016
				$path = 'mshop/catalog/manager/standard/insert-usage';
1017
			}
1018
1019
			$stmt = $conn->create( $this->getSqlConfig( $path ) );
1020
			$stmt->bind( 1, json_encode( $item->getConfig() ) );
1021
			$stmt->bind( 2, $date ); // mtime
1022
			$stmt->bind( 3, $context->getEditor() );
1023
			$stmt->bind( 4, $item->getTarget() );
1024
1025
			if( $case !== true )
1026
			{
1027
				$stmt->bind( 5, $siteid, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
1028
				$stmt->bind( 6, $id, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
1029
			}
1030
			else
1031
			{
1032
				$stmt->bind( 5, $date ); // ctime
1033
				$stmt->bind( 6, $siteid, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
1034
				$stmt->bind( 7, $id, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
1035
			}
1036
1037
			$stmt->execute()->finish();
1038
1039
			$dbm->release( $conn, $dbname );
1040
		}
1041
		catch( \Exception $e )
1042
		{
1043
			$dbm->release( $conn, $dbname );
1044
			throw $e;
1045
		}
1046
1047
		return $this;
1048
	}
1049
}
1050