Passed
Push — master ( 3311dc...3c99d6 )
by Aimeos
05:26
created

mshoplib/src/MShop/Catalog/Manager/Standard.php (1 issue)

Labels
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 :key LIMIT 1
138
			)',
139
			'label' => 'Catalog has list item, parameter(<domain>[,<list type>[,<reference ID>)]]',
140
			'type' => 'null',
141
			'internaltype' => 'null',
142
			'public' => false,
143
		),
144
	);
145
146
147
	/**
148
	 * Initializes the object.
149
	 *
150
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object
151
	 */
152
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
153
	{
154
		parent::__construct( $context, $this->searchConfig );
155
		$this->setResourceName( 'db-catalog' );
156
157
		$self = $this;
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
174
		$this->searchConfig['catalog:has']['function'] = function( &$source, array $params ) use ( $self, $siteIds ) {
175
176
			foreach( $params as $key => $param ) {
177
				$params[$key] = trim( $param, '\'' );
178
			}
179
180
			$source = str_replace( ':site', $self->toExpression( 'mcatli_has."siteid"', $siteIds ), $source );
181
			$str = $self->toExpression( 'mcatli_has."key"', join( '|', $params ), isset( $params[2] ) ? '==' : '=~' );
182
			$source = str_replace( ':key', $str, $source );
183
184
			return $params;
185
		};
186
	}
187
188
189
	/**
190
	 * Removes old entries from the storage.
191
	 *
192
	 * @param string[] $siteids List of IDs for sites whose entries should be deleted
193
	 * @return \Aimeos\MShop\Catalog\Manager\Iface Manager object for chaining method calls
194
	 */
195
	public function cleanup( array $siteids )
196
	{
197
		$context = $this->getContext();
198
		$config = $context->getConfig();
199
		$search = $this->getObject()->createSearch();
200
201
		foreach( $config->get( 'mshop/catalog/manager/submanagers', ['lists'] ) as $domain ) {
202
			$this->getObject()->getSubManager( $domain )->cleanup( $siteids );
203
		}
204
205
		$dbm = $context->getDatabaseManager();
206
		$dbname = $this->getResourceName();
207
		$conn = $dbm->acquire( $dbname );
208
209
		try
210
		{
211
			/** mshop/catalog/manager/standard/cleanup/mysql
212
			 * Deletes the categories for the given site from the database
213
			 *
214
			 * @see mshop/catalog/manager/standard/cleanup/ansi
215
			 */
216
217
			/** mshop/catalog/manager/standard/cleanup/ansi
218
			 * Deletes the categories for the given site from the database
219
			 *
220
			 * Removes the records matched by the given site ID from the catalog
221
			 * database.
222
			 *
223
			 * The ":siteid" placeholder is replaced by the name and value of the
224
			 * site ID column and the given ID or list of IDs.
225
			 *
226
			 * The SQL statement should conform to the ANSI standard to be
227
			 * compatible with most relational database systems. This also
228
			 * includes using double quotes for table and column names.
229
			 *
230
			 * @param string SQL statement for removing the records
231
			 * @since 2014.03
232
			 * @category Developer
233
			 * @see mshop/catalog/manager/standard/delete/ansi
234
			 * @see mshop/catalog/manager/standard/insert/ansi
235
			 * @see mshop/catalog/manager/standard/update/ansi
236
			 * @see mshop/catalog/manager/standard/newid/ansi
237
			 * @see mshop/catalog/manager/standard/search/ansi
238
			 * @see mshop/catalog/manager/standard/count/ansi
239
			 */
240
			$path = 'mshop/catalog/manager/standard/cleanup';
241
			$sql = $this->getSqlConfig( $path );
242
243
			$types = array( 'siteid' => \Aimeos\MW\DB\Statement\Base::PARAM_STR );
244
			$translations = array( 'siteid' => '"siteid"' );
245
246
			$search->setConditions( $search->compare( '==', 'siteid', $siteids ) );
247
			$sql = str_replace( ':siteid', $search->getConditionSource( $types, $translations ), $sql );
248
249
			$stmt = $conn->create( $sql );
250
			$stmt->bind( 1, 0, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
251
			$stmt->bind( 2, 0x7FFFFFFF, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
252
			$stmt->execute()->finish();
253
254
			$dbm->release( $conn, $dbname );
255
		}
256
		catch( \Exception $e )
257
		{
258
			$dbm->release( $conn, $dbname );
259
			throw $e;
260
		}
261
262
		return $this;
263
	}
264
265
266
	/**
267
	 * Creates a new empty item instance
268
	 *
269
	 * @param array $values Values the item should be initialized with
270
	 * @return \Aimeos\MShop\Catalog\Item\Iface New catalog item object
271
	 */
272
	public function createItem( array $values = [] )
273
	{
274
		$values['siteid'] = $this->getContext()->getLocale()->getSiteId();
275
		return $this->createItemBase( $values );
276
	}
277
278
279
	/**
280
	 * Creates a search object.
281
	 *
282
	 * @param boolean $default Add default criteria
283
	 * @return \Aimeos\MW\Criteria\Iface Returns the search object
284
	 */
285
	public function createSearch( $default = false )
286
	{
287
		if( $default === true ) {
288
			return $this->createSearchBase( 'catalog' );
289
		}
290
291
		return parent::createSearch();
292
	}
293
294
295
	/**
296
	 * Deletes the item specified by its ID.
297
	 *
298
	 * @param string $id ID of the item object
299
	 * @return \Aimeos\MShop\Catalog\Manager\Iface Manager object for chaining method calls
300
	 */
301
	public function deleteItem( $id )
302
	{
303
		$this->begin();
304
		$this->lock();
305
306
		try
307
		{
308
			$siteid = $this->getContext()->getLocale()->getSiteId();
309
			$this->createTreeManager( $siteid )->deleteNode( $id );
310
			$this->unlock();
311
			$this->commit();
312
		}
313
		catch( \Exception $e )
314
		{
315
			$this->unlock();
316
			$this->rollback();
317
			throw $e;
318
		}
319
320
		return $this;
321
	}
322
323
324
	/**
325
	 * Removes multiple items specified by ids in the array.
326
	 *
327
	 * @param string[] $ids List of IDs
328
	 * @return \Aimeos\MShop\Catalog\Manager\Iface Manager object for chaining method calls
329
	 */
330
	public function deleteItems( array $ids )
331
	{
332
		foreach( $ids as $id ) {
333
			$this->getObject()->deleteItem( $id );
334
		}
335
336
		return $this;
337
	}
338
339
340
	/**
341
	 * Returns the item specified by its code and domain/type if necessary
342
	 *
343
	 * @param string $code Code of the item
344
	 * @param string[] $ref List of domains to fetch list items and referenced items for
345
	 * @param string|null $domain Domain of the item if necessary to identify the item uniquely
346
	 * @param string|null $type Type code of the item if necessary to identify the item uniquely
347
	 * @param boolean $default True to add default criteria
348
	 * @return \Aimeos\MShop\Catalog\Item\Iface Catalog item object
349
	 */
350
	public function findItem( $code, array $ref = [], $domain = null, $type = null, $default = false )
351
	{
352
		return $this->findItemBase( array( 'catalog.code' => $code ), $ref, $default );
353
	}
354
355
356
	/**
357
	 * Returns the item specified by its ID.
358
	 *
359
	 * @param string $id Unique ID of the catalog item
360
	 * @param string[] $ref List of domains to fetch list items and referenced items for
361
	 * @param boolean $default Add default criteria
362
	 * @return \Aimeos\MShop\Catalog\Item\Iface Catalog item of the given ID
363
	 * @throws \Aimeos\MShop\Exception If item couldn't be found
364
	 */
365
	public function getItem( $id, array $ref = [], $default = false )
366
	{
367
		return $this->getItemBase( 'catalog.id', $id, $ref, $default );
368
	}
369
370
371
	/**
372
	 * Returns the available manager types
373
	 *
374
	 * @param boolean $withsub Return also the resource type of sub-managers if true
375
	 * @return string[] Type of the manager and submanagers, subtypes are separated by slashes
376
	 */
377
	public function getResourceType( $withsub = true )
378
	{
379
		$path = 'mshop/catalog/manager/submanagers';
380
		return $this->getResourceTypeBase( 'catalog', $path, array( 'lists' ), $withsub );
381
	}
382
383
384
	/**
385
	 * Returns the attributes that can be used for searching.
386
	 *
387
	 * @param boolean $withsub Return also attributes of sub-managers if true
388
	 * @return \Aimeos\MW\Criteria\Attribute\Iface[] List of search attribute items
389
	 */
390
	public function getSearchAttributes( $withsub = true )
391
	{
392
		/** mshop/catalog/manager/submanagers
393
		 * List of manager names that can be instantiated by the catalog manager
394
		 *
395
		 * Managers provide a generic interface to the underlying storage.
396
		 * Each manager has or can have sub-managers caring about particular
397
		 * aspects. Each of these sub-managers can be instantiated by its
398
		 * parent manager using the getSubManager() method.
399
		 *
400
		 * The search keys from sub-managers can be normally used in the
401
		 * manager as well. It allows you to search for items of the manager
402
		 * using the search keys of the sub-managers to further limit the
403
		 * retrieved list of items.
404
		 *
405
		 * @param array List of sub-manager names
406
		 * @since 2014.03
407
		 * @category Developer
408
		 */
409
		$path = 'mshop/catalog/manager/submanagers';
410
411
		return $this->getSearchAttributesBase( $this->searchConfig, $path, [], $withsub );
412
	}
413
414
415
	/**
416
	 * Adds a new item object.
417
	 *
418
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Item which should be inserted
419
	 * @param string|null $parentId ID of the parent item where the item should be inserted into
420
	 * @param string|null $refId ID of the item where the item should be inserted before (null to append)
421
	 * @return \Aimeos\MShop\Catalog\Item\Iface $item Updated item including the generated ID
422
	 */
423
	public function insertItem( \Aimeos\MShop\Catalog\Item\Iface $item, $parentId = null, $refId = null )
424
	{
425
		$this->begin();
426
		$this->lock();
427
428
		try
429
		{
430
			$node = $item->getNode();
431
			$siteid = $this->getContext()->getLocale()->getSiteId();
432
433
			$this->createTreeManager( $siteid )->insertNode( $node, $parentId, $refId );
434
			$this->updateUsage( $node->getId(), $item, true );
435
			$this->unlock();
436
			$this->commit();
437
		}
438
		catch( \Exception $e )
439
		{
440
			$this->unlock();
441
			$this->rollback();
442
			throw $e;
443
		}
444
445
		$item = $this->saveListItems( $item, 'catalog' );
446
		return $this->saveChildren( $item );
447
	}
448
449
450
	/**
451
	 * Moves an existing item to the new parent in the storage.
452
	 *
453
	 * @param string $id ID of the item that should be moved
454
	 * @param string $oldParentId ID of the old parent item which currently contains the item that should be removed
455
	 * @param string $newParentId ID of the new parent item where the item should be moved to
456
	 * @param string|null $refId ID of the item where the item should be inserted before (null to append)
457
	 * @return \Aimeos\MShop\Catalog\Manager\Iface Manager object for chaining method calls
458
	 */
459
	public function moveItem( $id, $oldParentId, $newParentId, $refId = null )
460
	{
461
		$this->begin();
462
		$this->lock();
463
464
		try
465
		{
466
			$item = $this->getObject()->getItem( $id );
467
			$siteid = $this->getContext()->getLocale()->getSiteId();
468
469
			$this->createTreeManager( $siteid )->moveNode( $id, $oldParentId, $newParentId, $refId );
470
			$this->updateUsage( $id, $item );
471
			$this->unlock();
472
			$this->commit();
473
		}
474
		catch( \Exception $e )
475
		{
476
			$this->unlock();
477
			$this->rollback();
478
			throw $e;
479
		}
480
481
		return $this;
482
	}
483
484
485
	/**
486
	 * Updates an item object.
487
	 *
488
	 * @param \Aimeos\MShop\Catalog\Item\Iface $item Item object whose data should be saved
489
	 * @param boolean $fetch True if the new ID should be returned in the item
490
	 * @return \Aimeos\MShop\Catalog\Item\Iface $item Updated item including the generated ID
491
	 */
492
	public function saveItem( \Aimeos\MShop\Common\Item\Iface $item, $fetch = true )
493
	{
494
		self::checkClass( \Aimeos\MShop\Catalog\Item\Iface::class, $item );
495
496
		if( !$item->isModified() )
497
		{
498
			$item = $this->saveListItems( $item, 'catalog', $fetch );
499
			return $this->saveChildren( $item );
500
		}
501
502
		$node = $item->getNode();
503
		$siteid = $this->getContext()->getLocale()->getSiteId();
504
505
		$this->createTreeManager( $siteid )->saveNode( $node );
506
		$this->updateUsage( $node->getId(), $item );
507
508
		$item = $this->saveListItems( $item, 'catalog', $fetch );
509
		return $this->saveChildren( $item );
510
	}
511
512
513
	/**
514
	 * Searches for all items matching the given critera.
515
	 *
516
	 * @param \Aimeos\MW\Criteria\Iface $search Search criteria object
517
	 * @param string[] $ref List of domains to fetch list items and referenced items for
518
	 * @param integer|null &$total Number of items that are available in total
519
	 * @return \Aimeos\MShop\Catalog\Item\Iface[] List of catalog items
520
	 */
521
	public function searchItems( \Aimeos\MW\Criteria\Iface $search, array $ref = [], &$total = null )
522
	{
523
		$nodeMap = $siteMap = [];
524
		$context = $this->getContext();
525
526
		$dbname = $this->getResourceName();
527
		$dbm = $context->getDatabaseManager();
528
		$conn = $dbm->acquire( $dbname );
529
530
		try
531
		{
532
			$required = array( 'catalog' );
533
534
			/** mshop/catalog/manager/sitemode
535
			 * Mode how items from levels below or above in the site tree are handled
536
			 *
537
			 * By default, only items from the current site are fetched from the
538
			 * storage. If the ai-sites extension is installed, you can create a
539
			 * tree of sites. Then, this setting allows you to define for the
540
			 * whole catalog domain if items from parent sites are inherited,
541
			 * sites from child sites are aggregated or both.
542
			 *
543
			 * Available constants for the site mode are:
544
			 * * 0 = only items from the current site
545
			 * * 1 = inherit items from parent sites
546
			 * * 2 = aggregate items from child sites
547
			 * * 3 = inherit and aggregate items at the same time
548
			 *
549
			 * You also need to set the mode in the locale manager
550
			 * (mshop/locale/manager/standard/sitelevel) to one of the constants.
551
			 * If you set it to the same value, it will work as described but you
552
			 * can also use different modes. For example, if inheritance and
553
			 * aggregation is configured the locale manager but only inheritance
554
			 * in the domain manager because aggregating items makes no sense in
555
			 * this domain, then items wil be only inherited. Thus, you have full
556
			 * control over inheritance and aggregation in each domain.
557
			 *
558
			 * @param integer Constant from Aimeos\MShop\Locale\Manager\Base class
559
			 * @category Developer
560
			 * @since 2018.01
561
			 * @see mshop/locale/manager/standard/sitelevel
562
			 */
563
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_PATH;
564
			$level = $context->getConfig()->get( 'mshop/catalog/manager/sitemode', $level );
565
566
			/** mshop/catalog/manager/standard/search-item/mysql
567
			 * Retrieves the records matched by the given criteria in the database
568
			 *
569
			 * @see mshop/catalog/manager/standard/search-item/ansi
570
			 */
571
572
			/** mshop/catalog/manager/standard/search-item/ansi
573
			 * Retrieves the records matched by the given criteria in the database
574
			 *
575
			 * Fetches the records matched by the given criteria from the catalog
576
			 * database. The records must be from one of the sites that are
577
			 * configured via the context item. If the current site is part of
578
			 * a tree of sites, the SELECT statement can retrieve all records
579
			 * from the current site and the complete sub-tree of sites.
580
			 *
581
			 * As the records can normally be limited by criteria from sub-managers,
582
			 * their tables must be joined in the SQL context. This is done by
583
			 * using the "internaldeps" property from the definition of the ID
584
			 * column of the sub-managers. These internal dependencies specify
585
			 * the JOIN between the tables and the used columns for joining. The
586
			 * ":joins" placeholder is then replaced by the JOIN strings from
587
			 * the sub-managers.
588
			 *
589
			 * To limit the records matched, conditions can be added to the given
590
			 * criteria object. It can contain comparisons like column names that
591
			 * must match specific values which can be combined by AND, OR or NOT
592
			 * operators. The resulting string of SQL conditions replaces the
593
			 * ":cond" placeholder before the statement is sent to the database
594
			 * server.
595
			 *
596
			 * If the records that are retrieved should be ordered by one or more
597
			 * columns, the generated string of column / sort direction pairs
598
			 * replaces the ":order" placeholder. In case no ordering is required,
599
			 * the complete ORDER BY part including the "\/*-orderby*\/...\/*orderby-*\/"
600
			 * markers is removed to speed up retrieving the records. Columns of
601
			 * sub-managers can also be used for ordering the result set but then
602
			 * no index can be used.
603
			 *
604
			 * The number of returned records can be limited and can start at any
605
			 * number between the begining and the end of the result set. For that
606
			 * the ":size" and ":start" placeholders are replaced by the
607
			 * corresponding values from the criteria object. The default values
608
			 * are 0 for the start and 100 for the size value.
609
			 *
610
			 * The SQL statement should conform to the ANSI standard to be
611
			 * compatible with most relational database systems. This also
612
			 * includes using double quotes for table and column names.
613
			 *
614
			 * @param string SQL statement for searching items
615
			 * @since 2014.03
616
			 * @category Developer
617
			 * @see mshop/catalog/manager/standard/delete/ansi
618
			 * @see mshop/catalog/manager/standard/get/ansi
619
			 * @see mshop/catalog/manager/standard/insert/ansi
620
			 * @see mshop/catalog/manager/standard/update/ansi
621
			 * @see mshop/catalog/manager/standard/newid/ansi
622
			 * @see mshop/catalog/manager/standard/search/ansi
623
			 * @see mshop/catalog/manager/standard/count/ansi
624
			 * @see mshop/catalog/manager/standard/move-left/ansi
625
			 * @see mshop/catalog/manager/standard/move-right/ansi
626
			 * @see mshop/catalog/manager/standard/update-parentid/ansi
627
			 */
628
			$cfgPathSearch = 'mshop/catalog/manager/standard/search-item';
629
630
			/** mshop/catalog/manager/standard/count/mysql
631
			 * Counts the number of records matched by the given criteria in the database
632
			 *
633
			 * @see mshop/catalog/manager/standard/count/ansi
634
			 */
635
636
			/** mshop/catalog/manager/standard/count/ansi
637
			 * Counts the number of records matched by the given criteria in the database
638
			 *
639
			 * Counts all records matched by the given criteria from the catalog
640
			 * database. The records must be from one of the sites that are
641
			 * configured via the context item. If the current site is part of
642
			 * a tree of sites, the statement can count all records from the
643
			 * current site and the complete sub-tree of sites.
644
			 *
645
			 * As the records can normally be limited by criteria from sub-managers,
646
			 * their tables must be joined in the SQL context. This is done by
647
			 * using the "internaldeps" property from the definition of the ID
648
			 * column of the sub-managers. These internal dependencies specify
649
			 * the JOIN between the tables and the used columns for joining. The
650
			 * ":joins" placeholder is then replaced by the JOIN strings from
651
			 * the sub-managers.
652
			 *
653
			 * To limit the records matched, conditions can be added to the given
654
			 * criteria object. It can contain comparisons like column names that
655
			 * must match specific values which can be combined by AND, OR or NOT
656
			 * operators. The resulting string of SQL conditions replaces the
657
			 * ":cond" placeholder before the statement is sent to the database
658
			 * server.
659
			 *
660
			 * Both, the strings for ":joins" and for ":cond" are the same as for
661
			 * the "search" SQL statement.
662
			 *
663
			 * Contrary to the "search" statement, it doesn't return any records
664
			 * but instead the number of records that have been found. As counting
665
			 * thousands of records can be a long running task, the maximum number
666
			 * of counted records is limited for performance reasons.
667
			 *
668
			 * The SQL statement should conform to the ANSI standard to be
669
			 * compatible with most relational database systems. This also
670
			 * includes using double quotes for table and column names.
671
			 *
672
			 * @param string SQL statement for counting items
673
			 * @since 2014.03
674
			 * @category Developer
675
			 * @see mshop/catalog/manager/standard/delete/ansi
676
			 * @see mshop/catalog/manager/standard/get/ansi
677
			 * @see mshop/catalog/manager/standard/insert/ansi
678
			 * @see mshop/catalog/manager/standard/update/ansi
679
			 * @see mshop/catalog/manager/standard/newid/ansi
680
			 * @see mshop/catalog/manager/standard/search/ansi
681
			 * @see mshop/catalog/manager/standard/search-item/ansi
682
			 * @see mshop/catalog/manager/standard/move-left/ansi
683
			 * @see mshop/catalog/manager/standard/move-right/ansi
684
			 * @see mshop/catalog/manager/standard/update-parentid/ansi
685
			 */
686
			$cfgPathCount = 'mshop/catalog/manager/standard/count';
687
688
			if( $search->getSortations() === [] ) {
689
				$search->setSortations( [$search->sort( '+', 'catalog.left')] );
690
			}
691
692
			$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount, $required, $total, $level );
693
694
			while( ( $row = $results->fetch() ) !== false ) {
695
				$siteMap[$row['siteid']][$row['id']] = new \Aimeos\MW\Tree\Node\Standard( $row );
696
			}
697
698
			$sitePath = array_reverse( $this->getContext()->getLocale()->getSitePath() );
699
700
			foreach( $sitePath as $siteId )
701
			{
702
				if( isset( $siteMap[$siteId] ) && !empty( $siteMap[$siteId] ) )
703
				{
704
					$nodeMap = $siteMap[$siteId];
705
					break;
706
				}
707
			}
708
709
			$dbm->release( $conn, $dbname );
710
		}
711
		catch( \Exception $e )
712
		{
713
			$dbm->release( $conn, $dbname );
714
			throw $e;
715
		}
716
717
		return $this->buildItems( $nodeMap, $ref, 'catalog' );
718
	}
719
720
721
	/**
722
	 * Returns a list of items starting with the given category that are in the path to the root node
723
	 *
724
	 * @param string $id ID of item to get the path for
725
	 * @param string[] $ref List of domains to fetch list items and referenced items for
726
	 * @return \Aimeos\MShop\Catalog\Item\Iface[] Associative list of catalog items with IDs as keys
727
	 */
728
	public function getPath( $id, array $ref = [] )
729
	{
730
		$sitePath = array_reverse( $this->getContext()->getLocale()->getSitePath() );
731
732
		foreach( $sitePath as $siteId )
733
		{
734
			try {
735
				$path = $this->createTreeManager( $siteId )->getPath( $id );
736
			} catch( \Exception $e ) {
737
				continue;
738
			}
739
740
			if( !empty( $path ) )
741
			{
742
				$itemMap = [];
743
744
				foreach( $path as $node ) {
745
					$itemMap[$node->getId()] = $node;
746
				}
747
748
				return $this->buildItems( $itemMap, $ref, 'catalog' );
749
			}
750
		}
751
752
		throw new \Aimeos\MShop\Catalog\Exception( sprintf( 'Catalog path for ID "%1$s" not found', $id ) );
753
	}
754
755
756
	/**
757
	 * Returns a node and its descendants depending on the given resource.
758
	 *
759
	 * @param string|null $id Retrieve nodes starting from the given ID
760
	 * @param string[] List of domains (e.g. text, media, etc.) whose referenced items should be attached to the objects
761
	 * @param integer $level One of the level constants from \Aimeos\MW\Tree\Manager\Base
762
	 * @param \Aimeos\MW\Criteria\Iface|null $criteria Optional criteria object with conditions
763
	 * @return \Aimeos\MShop\Catalog\Item\Iface Catalog item, maybe with subnodes
764
	 */
765
	public function getTree( $id = null, array $ref = [], $level = \Aimeos\MW\Tree\Manager\Base::LEVEL_TREE, \Aimeos\MW\Criteria\Iface $criteria = null )
766
	{
767
		$sitePath = array_reverse( $this->getContext()->getLocale()->getSitePath() );
768
769
		foreach( $sitePath as $siteId )
770
		{
771
			try {
772
				$node = $this->createTreeManager( $siteId )->getNode( $id, $level, $criteria );
773
			} catch( \Exception $e ) {
774
				continue;
775
			}
776
777
			$listItems = $listItemMap = $refIdMap = [];
778
			$nodeMap = $this->getNodeMap( $node );
779
780
			if( count( $ref ) > 0 ) {
781
				$listItems = $this->getListItems( array_keys( $nodeMap ), $ref, 'catalog' );
782
			}
783
784
			foreach( $listItems as $listItem )
785
			{
786
				$domain = $listItem->getDomain();
787
				$parentid = $listItem->getParentId();
788
789
				$listItemMap[$parentid][$domain][$listItem->getId()] = $listItem;
790
				$refIdMap[$domain][$listItem->getRefId()][] = $parentid;
791
			}
792
793
			$refItemMap = $this->getRefItems( $refIdMap );
794
			$nodeid = $node->getId();
795
796
			$listItems = [];
797
			if( array_key_exists( $nodeid, $listItemMap ) ) {
798
				$listItems = $listItemMap[$nodeid];
799
			}
800
801
			$refItems = [];
802
			if( array_key_exists( $nodeid, $refItemMap ) ) {
803
				$refItems = $refItemMap[$nodeid];
804
			}
805
806
			$item = $this->createItemBase( [], $listItems, $refItems, [], $node );
807
			$this->createTree( $node, $item, $listItemMap, $refItemMap );
0 ignored issues
show
$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

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