Passed
Push — master ( 29d5f6...586595 )
by Aimeos
04:48
created

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

Labels
Severity
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 Product
9
 */
10
11
12
namespace Aimeos\MShop\Product\Manager;
13
14
15
/**
16
 * Default product manager.
17
 *
18
 * @package MShop
19
 * @subpackage Product
20
 */
21
class Standard
22
	extends \Aimeos\MShop\Common\Manager\ListRef\Base
23
	implements \Aimeos\MShop\Product\Manager\Iface, \Aimeos\MShop\Common\Manager\Factory\Iface
24
{
25
	use \Aimeos\MShop\Common\Manager\PropertyRef\Traits;
26
27
28
	private $searchConfig = array(
29
		'product.siteid' => array(
30
			'code' => 'product.siteid',
31
			'internalcode' => 'mpro."siteid"',
32
			'label' => 'Site ID',
33
			'type' => 'integer',
34
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
35
			'public' => false,
36
		),
37
		'product.typeid' => array(
38
			'code' => 'product.typeid',
39
			'internalcode' => 'mpro."typeid"',
40
			'label' => 'Type ID',
41
			'type' => 'integer',
42
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
43
			'public' => false,
44
		),
45
		'product.label' => array(
46
			'code' => 'product.label',
47
			'internalcode' => 'mpro."label"',
48
			'label' => 'Label',
49
			'type' => 'string',
50
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
51
		),
52
		'product.code' => array(
53
			'code' => 'product.code',
54
			'internalcode' => 'mpro."code"',
55
			'label' => 'SKU',
56
			'type' => 'string',
57
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
58
		),
59
		'product.id' => array(
60
			'code' => 'product.id',
61
			'internalcode' => 'mpro."id"',
62
			'label' => 'ID',
63
			'type' => 'integer',
64
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
65
		),
66
		'product.datestart' => array(
67
			'code' => 'product.datestart',
68
			'internalcode' => 'mpro."start"',
69
			'label' => 'Start date/time',
70
			'type' => 'datetime',
71
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
72
		),
73
		'product.dateend' => array(
74
			'code' => 'product.dateend',
75
			'internalcode' => 'mpro."end"',
76
			'label' => 'End date/time',
77
			'type' => 'datetime',
78
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
79
		),
80
		'product.status' => array(
81
			'code' => 'product.status',
82
			'internalcode' => 'mpro."status"',
83
			'label' => 'Status',
84
			'type' => 'integer',
85
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
86
		),
87
		'product.config' => array(
88
			'code' => 'product.config',
89
			'internalcode' => 'mpro."config"',
90
			'label' => 'Config',
91
			'type' => 'string',
92
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
93
			'public' => false,
94
		),
95
		'product.target' => array(
96
			'code' => 'product.target',
97
			'internalcode' => 'mpro."target"',
98
			'label' => 'URL target',
99
			'type' => 'string',
100
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
101
			'public' => false,
102
		),
103
		'product.ctime' => array(
104
			'code' => 'product.ctime',
105
			'internalcode' => 'mpro."ctime"',
106
			'label' => 'Create date/time',
107
			'type' => 'datetime',
108
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
109
			'public' => false,
110
		),
111
		'product.mtime' => array(
112
			'code' => 'product.mtime',
113
			'internalcode' => 'mpro."mtime"',
114
			'label' => 'Modify date/time',
115
			'type' => 'datetime',
116
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
117
			'public' => false,
118
		),
119
		'product.editor' => array(
120
			'code' => 'product.editor',
121
			'internalcode' => 'mpro."editor"',
122
			'label' => 'Editor',
123
			'type' => 'string',
124
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
125
			'public' => false,
126
		),
127
		'product:has' => array(
128
			'code' => 'product:has()',
129
			'internalcode' => '( SELECT mpro_has."id" FROM mshop_product AS mpro_has
130
				WHERE mpro."id" = mpro_has."id" AND (
131
					SELECT COUNT(DISTINCT mproli_has."parentid")
132
					FROM "mshop_product_list" AS mproli_has
133
					JOIN "mshop_product_list_type" AS mprolity_has ON mproli_has."typeid" = mprolity_has."id"
134
					WHERE mpro."id" = mproli_has."parentid" AND :site
135
						AND mproli_has."domain" = $1 AND mprolity_has."code" = $2 AND mproli_has."refid" = $3
136
				) = 1
137
			)',
138
			'label' => 'Product list item, parameter(<domain>,<list type>,<reference ID>)',
139
			'type' => 'null',
140
			'internaltype' => 'null',
141
			'public' => false,
142
		),
143
		'product:prop' => array(
144
			'code' => 'product:prop()',
145
			'internalcode' => '( SELECT mpro_has."id" FROM mshop_product AS mpro_has
146
				WHERE mpro."id" = mpro_has."id" AND (
147
					SELECT COUNT(DISTINCT mpropr_prop."parentid")
148
					FROM "mshop_product_property" AS mpropr_prop
149
					JOIN "mshop_product_property_type" AS mproprty_prop ON mpropr_prop."typeid" = mproprty_prop."id"
150
					WHERE mpro."id" = mpropr_prop."parentid" AND :site
151
						AND mproprty_prop."code" = $1 AND mpropr_prop."value" = $3
152
						AND (
153
							NOT ( mpropr_prop."langid" <> $2 OR mpropr_prop."langid" IS NULL OR $2 IS NULL )
154
							OR ( mpropr_prop."langid" IS NULL AND $2 IS NULL )
155
						)
156
				) = 1
157
			)',
158
			'label' => 'Property has property item, parameter(<property type>,<language code>,<property value>)',
159
			'type' => 'null',
160
			'internaltype' => 'null',
161
			'public' => false,
162
		),
163
	);
164
165
	private $date;
166
167
168
	/**
169
	 * Creates the product manager that will use the given context object.
170
	 *
171
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object with required objects
172
	 */
173
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
174
	{
175
		parent::__construct( $context );
176
		$this->setResourceName( 'db-product' );
177
178
		$locale = $context->getLocale();
179
		$this->date = $context->getDateTime();
180
181
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
182
		$level = $context->getConfig()->get( 'mshop/product/manager/sitemode', $level );
183
184
		$siteIds = [$locale->getSiteId()];
185
186
		if( $level & \Aimeos\MShop\Locale\Manager\Base::SITE_PATH ) {
187
			$siteIds = array_merge( $siteIds, $locale->getSitePath() );
188
		}
189
190
		if( $level & \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE ) {
191
			$siteIds = array_merge( $siteIds, $locale->getSiteSubTree() );
192
		}
193
194
		$this->replaceSiteMarker( $this->searchConfig['product:has'], 'mproli_has."siteid"', $siteIds, ':site' );
195
		$this->replaceSiteMarker( $this->searchConfig['product:prop'], 'mpropr_prop."siteid"', $siteIds, ':site' );
196
	}
197
198
199
	/**
200
	 * Removes old entries from the storage.
201
	 *
202
	 * @param integer[] $siteids List of IDs for sites whose entries should be deleted
203
	 */
204
	public function cleanup( array $siteids )
205
	{
206
		$path = 'mshop/product/manager/submanagers';
207
		$default = ['lists', 'property', 'type'];
208
209
		foreach( $this->getContext()->getConfig()->get( $path, $default ) as $domain ) {
210
			$this->getObject()->getSubManager( $domain )->cleanup( $siteids );
211
		}
212
213
		$this->cleanupBase( $siteids, 'mshop/product/manager/standard/delete' );
214
	}
215
216
217
	/**
218
	 * Creates a new empty item instance
219
	 *
220
	 * @param string|null Type the item should be created with
221
	 * @param string|null Domain of the type the item should be created with
222
	 * @param array $values Values the item should be initialized with
223
	 * @return \Aimeos\MShop\Product\Item\Iface New product item object
224
	 */
225
	public function createItem( $type = null, $domain = null, array $values = [] )
226
	{
227
		$values['product.siteid'] = $this->getContext()->getLocale()->getSiteId();
228
229
		if( $type !== null )
230
		{
231
			$values['product.typeid'] = $this->getTypeId( $type, 'product' );
232
			$values['product.type'] = $type;
233
		}
234
235
		return $this->createItemBase( $values );
236
	}
237
238
239
	/**
240
	 * Adds a new product to the storage.
241
	 *
242
	 * @param \Aimeos\MShop\Common\Item\Iface $item Product item that should be saved to the storage
243
	 * @param boolean $fetch True if the new ID should be returned in the item
244
	 * @return \Aimeos\MShop\Common\Item\Iface $item Updated item including the generated ID
245
	 */
246
	public function saveItem( \Aimeos\MShop\Common\Item\Iface $item, $fetch = true )
247
	{
248
		self::checkClass( '\\Aimeos\\MShop\\Product\\Item\\Iface', $item );
249
250
		if( !$item->isModified() )
251
		{
252
			$item = $this->savePropertyItems( $item, 'product', $fetch );
253
			return $this->saveListItems( $item, 'product', $fetch );
254
		}
255
256
		$context = $this->getContext();
257
258
		$dbm = $context->getDatabaseManager();
259
		$dbname = $this->getResourceName();
260
		$conn = $dbm->acquire( $dbname );
261
262
		try
263
		{
264
			$id = $item->getId();
265
266
			if( $id === null )
267
			{
268
				/** mshop/product/manager/standard/insert/mysql
269
				 * Inserts a new product record into the database table
270
				 *
271
				 * @see mshop/product/manager/standard/insert/ansi
272
				 */
273
274
				/** mshop/product/manager/standard/insert/ansi
275
				 * Inserts a new product record into the database table
276
				 *
277
				 * Items with no ID yet (i.e. the ID is NULL) will be created in
278
				 * the database and the newly created ID retrieved afterwards
279
				 * using the "newid" SQL statement.
280
				 *
281
				 * The SQL statement must be a string suitable for being used as
282
				 * prepared statement. It must include question marks for binding
283
				 * the values from the product item to the statement before they are
284
				 * sent to the database server. The number of question marks must
285
				 * be the same as the number of columns listed in the INSERT
286
				 * statement. The order of the columns must correspond to the
287
				 * order in the saveItems() method, so the correct values are
288
				 * bound to the columns.
289
				 *
290
				 * The SQL statement should conform to the ANSI standard to be
291
				 * compatible with most relational database systems. This also
292
				 * includes using double quotes for table and column names.
293
				 *
294
				 * @param string SQL statement for inserting records
295
				 * @since 2014.03
296
				 * @category Developer
297
				 * @see mshop/product/manager/standard/update/ansi
298
				 * @see mshop/product/manager/standard/newid/ansi
299
				 * @see mshop/product/manager/standard/delete/ansi
300
				 * @see mshop/product/manager/standard/search/ansi
301
				 * @see mshop/product/manager/standard/count/ansi
302
				 */
303
				$path = 'mshop/product/manager/standard/insert';
304
			}
305
			else
306
			{
307
				/** mshop/product/manager/standard/update/mysql
308
				 * Updates an existing product record in the database
309
				 *
310
				 * @see mshop/product/manager/standard/update/ansi
311
				 */
312
313
				/** mshop/product/manager/standard/update/ansi
314
				 * Updates an existing product record in the database
315
				 *
316
				 * Items which already have an ID (i.e. the ID is not NULL) will
317
				 * be updated in the database.
318
				 *
319
				 * The SQL statement must be a string suitable for being used as
320
				 * prepared statement. It must include question marks for binding
321
				 * the values from the product item to the statement before they are
322
				 * sent to the database server. The order of the columns must
323
				 * correspond to the order in the saveItems() method, so the
324
				 * correct values are bound to the columns.
325
				 *
326
				 * The SQL statement should conform to the ANSI standard to be
327
				 * compatible with most relational database systems. This also
328
				 * includes using double quotes for table and column names.
329
				 *
330
				 * @param string SQL statement for updating records
331
				 * @since 2014.03
332
				 * @category Developer
333
				 * @see mshop/product/manager/standard/insert/ansi
334
				 * @see mshop/product/manager/standard/newid/ansi
335
				 * @see mshop/product/manager/standard/delete/ansi
336
				 * @see mshop/product/manager/standard/search/ansi
337
				 * @see mshop/product/manager/standard/count/ansi
338
				 */
339
				$path = 'mshop/product/manager/standard/update';
340
			}
341
342
			$stmt = $this->getCachedStatement( $conn, $path );
343
344
			$stmt->bind( 1, $item->getTypeId(), \Aimeos\MW\DB\Statement\Base::PARAM_INT );
345
			$stmt->bind( 2, $item->getCode() );
346
			$stmt->bind( 3, $item->getLabel() );
1 ignored issue
show
The method getLabel() does not exist on Aimeos\MShop\Common\Item\Iface. It seems like you code against a sub-type of Aimeos\MShop\Common\Item\Iface such as Aimeos\MShop\Product\Item\Iface or Aimeos\MShop\Service\Item\Iface or Aimeos\MShop\Locale\Item\Site\Iface or Aimeos\MShop\Customer\Item\Iface or Aimeos\MShop\Text\Item\Iface or Aimeos\MShop\Customer\Item\Group\Iface or Aimeos\MShop\Media\Item\Iface or Aimeos\MShop\Coupon\Item\Iface or Aimeos\MAdmin\Job\Item\Iface or Aimeos\MShop\Tag\Item\Iface or Aimeos\MShop\Common\Item\Type\Iface or Aimeos\MShop\Attribute\Item\Iface or Aimeos\MShop\Locale\Item\Language\Iface or Aimeos\MShop\Catalog\Item\Iface or Aimeos\MShop\Plugin\Item\Iface or Aimeos\MShop\Supplier\Item\Iface or Aimeos\MShop\Locale\Item\Currency\Iface or Aimeos\MShop\Attribute\Item\Standard or Aimeos\MShop\Catalog\Item\Standard or Aimeos\MShop\Customer\Item\Base or Aimeos\MShop\Plugin\Item\Standard or Aimeos\MShop\Tag\Item\Standard or Aimeos\MShop\Customer\Item\Group\Standard or Aimeos\MShop\Media\Item\Standard or Aimeos\MAdmin\Job\Item\Standard or Aimeos\MShop\Common\Item\Type\Standard or Aimeos\MShop\Locale\Item\Site\Standard or Aimeos\MShop\Locale\Item\Currency\Standard or Aimeos\MShop\Text\Item\Standard or Aimeos\MShop\Locale\Item\Language\Standard or Aimeos\MShop\Service\Item\Standard or Aimeos\MShop\Common\Item\ListRef\Base or Aimeos\MShop\Price\Item\Base or Aimeos\MShop\Supplier\Item\Standard or Aimeos\MShop\Coupon\Item\Standard or Aimeos\MShop\Product\Item\Standard or Aimeos\MShop\Product\Item\Iface or Aimeos\MShop\Service\Item\Iface or Aimeos\MShop\Customer\Item\Iface or Aimeos\MShop\Text\Item\Iface or Aimeos\MShop\Media\Item\Iface or Aimeos\MShop\Coupon\Item\Iface or Aimeos\MAdmin\Job\Item\Iface or Aimeos\MShop\Common\Item\Type\Iface or Aimeos\MShop\Attribute\Item\Iface or Aimeos\MShop\Locale\Item\Language\Iface or Aimeos\MShop\Common\Item\Tree\Iface or Aimeos\MShop\Plugin\Item\Iface or Aimeos\MShop\Supplier\Item\Iface or Aimeos\MShop\Locale\Item\Currency\Iface or Aimeos\MShop\Price\Item\Base or Aimeos\MShop\Price\Item\Base. ( Ignorable by Annotation )

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

346
			$stmt->bind( 3, $item->/** @scrutinizer ignore-call */ getLabel() );
Loading history...
347
			$stmt->bind( 4, $item->getStatus(), \Aimeos\MW\DB\Statement\Base::PARAM_INT );
348
			$stmt->bind( 5, $item->getDateStart() );
349
			$stmt->bind( 6, $item->getDateEnd() );
350
			$stmt->bind( 7, json_encode( $item->getConfig() ) );
351
			$stmt->bind( 8, $item->getTarget() );
352
			$stmt->bind( 9, $context->getEditor() );
353
			$stmt->bind( 10, date( 'Y-m-d H:i:s' ) ); // mtime
354
			$stmt->bind( 11, $item->getTimeCreated() );
355
			$stmt->bind( 12, $context->getLocale()->getSiteId(), \Aimeos\MW\DB\Statement\Base::PARAM_INT );
356
357
			if( $id !== null )
358
			{
359
				$stmt->bind( 13, $id, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
360
				$item->setId( $id ); //so item is no longer modified
361
			}
362
363
			$stmt->execute()->finish();
364
365
			if( $id === null )
366
			{
367
				/** mshop/product/manager/standard/newid/mysql
368
				 * Retrieves the ID generated by the database when inserting a new record
369
				 *
370
				 * @see mshop/product/manager/standard/newid/ansi
371
				 */
372
373
				/** mshop/product/manager/standard/newid/ansi
374
				 * Retrieves the ID generated by the database when inserting a new record
375
				 *
376
				 * As soon as a new record is inserted into the database table,
377
				 * the database server generates a new and unique identifier for
378
				 * that record. This ID can be used for retrieving, updating and
379
				 * deleting that specific record from the table again.
380
				 *
381
				 * For MySQL:
382
				 *  SELECT LAST_INSERT_ID()
383
				 * For PostgreSQL:
384
				 *  SELECT currval('seq_mpro_id')
385
				 * For SQL Server:
386
				 *  SELECT SCOPE_IDENTITY()
387
				 * For Oracle:
388
				 *  SELECT "seq_mpro_id".CURRVAL FROM DUAL
389
				 *
390
				 * There's no way to retrive the new ID by a SQL statements that
391
				 * fits for most database servers as they implement their own
392
				 * specific way.
393
				 *
394
				 * @param string SQL statement for retrieving the last inserted record ID
395
				 * @since 2014.03
396
				 * @category Developer
397
				 * @see mshop/product/manager/standard/insert/ansi
398
				 * @see mshop/product/manager/standard/update/ansi
399
				 * @see mshop/product/manager/standard/delete/ansi
400
				 * @see mshop/product/manager/standard/search/ansi
401
				 * @see mshop/product/manager/standard/count/ansi
402
				 */
403
				$path = 'mshop/product/manager/standard/newid';
404
				$item->setId( $this->newId( $conn, $path ) );
405
			}
406
407
			$dbm->release( $conn, $dbname );
408
		}
409
		catch( \Exception $e )
410
		{
411
			$dbm->release( $conn, $dbname );
412
			throw $e;
413
		}
414
415
		$item = $this->savePropertyItems( $item, 'product', $fetch );
416
		return $this->saveListItems( $item, 'product', $fetch );
417
	}
418
419
420
	/**
421
	 * Removes multiple items specified by ids in the array.
422
	 *
423
	 * @param array $ids List of IDs
424
	 */
425
	public function deleteItems( array $ids )
426
	{
427
		/** mshop/product/manager/standard/delete/mysql
428
		 * Deletes the items matched by the given IDs from the database
429
		 *
430
		 * @see mshop/product/manager/standard/delete/ansi
431
		 */
432
433
		/** mshop/product/manager/standard/delete/ansi
434
		 * Deletes the items matched by the given IDs from the database
435
		 *
436
		 * Removes the records specified by the given IDs from the product database.
437
		 * The records must be from the site that is configured via the
438
		 * context item.
439
		 *
440
		 * The ":cond" placeholder is replaced by the name of the ID column and
441
		 * the given ID or list of IDs while the site ID is bound to the question
442
		 * mark.
443
		 *
444
		 * The SQL statement should conform to the ANSI standard to be
445
		 * compatible with most relational database systems. This also
446
		 * includes using double quotes for table and column names.
447
		 *
448
		 * @param string SQL statement for deleting items
449
		 * @since 2014.03
450
		 * @category Developer
451
		 * @see mshop/product/manager/standard/insert/ansi
452
		 * @see mshop/product/manager/standard/update/ansi
453
		 * @see mshop/product/manager/standard/newid/ansi
454
		 * @see mshop/product/manager/standard/search/ansi
455
		 * @see mshop/product/manager/standard/count/ansi
456
		 */
457
		$path = 'mshop/product/manager/standard/delete';
458
		$this->deleteItemsBase( $ids, $path );
459
	}
460
461
462
	/**
463
	 * Returns the item specified by its code and domain/type if necessary
464
	 *
465
	 * @param string $code Code of the item
466
	 * @param string[] $ref List of domains to fetch list items and referenced items for
467
	 * @param string|null $domain Domain of the item if necessary to identify the item uniquely
468
	 * @param string|null $type Type code of the item if necessary to identify the item uniquely
469
	 * @param boolean $default True to add default criteria
470
	 * @return \Aimeos\MShop\Common\Item\Iface Item object
471
	 */
472
	public function findItem( $code, array $ref = [], $domain = null, $type = null, $default = false )
473
	{
474
		return $this->findItemBase( array( 'product.code' => $code ), $ref, $default );
475
	}
476
477
478
	/**
479
	 * Returns the product item for the given product ID.
480
	 *
481
	 * @param integer $id Unique ID of the product item
482
	 * @param string[] $ref List of domains to fetch list items and referenced items for
483
	 * @param boolean $default Add default criteria
484
	 * @return \Aimeos\MShop\Product\Item\Iface Returns the product item of the given id
485
	 * @throws \Aimeos\MShop\Exception If item couldn't be found
486
	 */
487
	public function getItem( $id, array $ref = [], $default = false )
488
	{
489
		return $this->getItemBase( 'product.id', $id, $ref, $default );
490
	}
491
492
493
	/**
494
	 * Search for products based on the given criteria.
495
	 *
496
	 * @param \Aimeos\MW\Criteria\Iface $search Search criteria object
497
	 * @param string[] $ref List of domains to fetch list items and referenced items for
498
	 * @param integer|null &$total Number of items that are available in total
499
	 * @return array List of products implementing \Aimeos\MShop\Product\Item\Iface
500
	 */
501
	public function searchItems( \Aimeos\MW\Criteria\Iface $search, array $ref = [], &$total = null )
502
	{
503
		$map = $typeIds = [];
504
		$context = $this->getContext();
505
506
		$dbm = $context->getDatabaseManager();
507
		$dbname = $this->getResourceName();
508
		$conn = $dbm->acquire( $dbname );
509
510
		try
511
		{
512
			$required = array( 'product' );
513
514
			/** mshop/product/manager/sitemode
515
			 * Mode how items from levels below or above in the site tree are handled
516
			 *
517
			 * By default, only items from the current site are fetched from the
518
			 * storage. If the ai-sites extension is installed, you can create a
519
			 * tree of sites. Then, this setting allows you to define for the
520
			 * whole product domain if items from parent sites are inherited,
521
			 * sites from child sites are aggregated or both.
522
			 *
523
			 * Available constants for the site mode are:
524
			 * * 0 = only items from the current site
525
			 * * 1 = inherit items from parent sites
526
			 * * 2 = aggregate items from child sites
527
			 * * 3 = inherit and aggregate items at the same time
528
			 *
529
			 * You also need to set the mode in the locale manager
530
			 * (mshop/locale/manager/standard/sitelevel) to one of the constants.
531
			 * If you set it to the same value, it will work as described but you
532
			 * can also use different modes. For example, if inheritance and
533
			 * aggregation is configured the locale manager but only inheritance
534
			 * in the domain manager because aggregating items makes no sense in
535
			 * this domain, then items wil be only inherited. Thus, you have full
536
			 * control over inheritance and aggregation in each domain.
537
			 *
538
			 * @param integer Constant from Aimeos\MShop\Locale\Manager\Base class
539
			 * @category Developer
540
			 * @since 2018.01
541
			 * @see mshop/locale/manager/standard/sitelevel
542
			 */
543
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
544
			$level = $context->getConfig()->get( 'mshop/product/manager/sitemode', $level );
545
546
			/** mshop/product/manager/standard/search/mysql
547
			 * Retrieves the records matched by the given criteria in the database
548
			 *
549
			 * @see mshop/product/manager/standard/search/ansi
550
			 */
551
552
			/** mshop/product/manager/standard/search/ansi
553
			 * Retrieves the records matched by the given criteria in the database
554
			 *
555
			 * Fetches the records matched by the given criteria from the product
556
			 * database. The records must be from one of the sites that are
557
			 * configured via the context item. If the current site is part of
558
			 * a tree of sites, the SELECT statement can retrieve all records
559
			 * from the current site and the complete sub-tree of sites.
560
			 *
561
			 * As the records can normally be limited by criteria from sub-managers,
562
			 * their tables must be joined in the SQL context. This is done by
563
			 * using the "internaldeps" property from the definition of the ID
564
			 * column of the sub-managers. These internal dependencies specify
565
			 * the JOIN between the tables and the used columns for joining. The
566
			 * ":joins" placeholder is then replaced by the JOIN strings from
567
			 * the sub-managers.
568
			 *
569
			 * To limit the records matched, conditions can be added to the given
570
			 * criteria object. It can contain comparisons like column names that
571
			 * must match specific values which can be combined by AND, OR or NOT
572
			 * operators. The resulting string of SQL conditions replaces the
573
			 * ":cond" placeholder before the statement is sent to the database
574
			 * server.
575
			 *
576
			 * If the records that are retrieved should be ordered by one or more
577
			 * columns, the generated string of column / sort direction pairs
578
			 * replaces the ":order" placeholder. In case no ordering is required,
579
			 * the complete ORDER BY part including the "\/*-orderby*\/...\/*orderby-*\/"
580
			 * markers is removed to speed up retrieving the records. Columns of
581
			 * sub-managers can also be used for ordering the result set but then
582
			 * no index can be used.
583
			 *
584
			 * The number of returned records can be limited and can start at any
585
			 * number between the begining and the end of the result set. For that
586
			 * the ":size" and ":start" placeholders are replaced by the
587
			 * corresponding values from the criteria object. The default values
588
			 * are 0 for the start and 100 for the size value.
589
			 *
590
			 * The SQL statement should conform to the ANSI standard to be
591
			 * compatible with most relational database systems. This also
592
			 * includes using double quotes for table and column names.
593
			 *
594
			 * @param string SQL statement for searching items
595
			 * @since 2014.03
596
			 * @category Developer
597
			 * @see mshop/product/manager/standard/insert/ansi
598
			 * @see mshop/product/manager/standard/update/ansi
599
			 * @see mshop/product/manager/standard/newid/ansi
600
			 * @see mshop/product/manager/standard/delete/ansi
601
			 * @see mshop/product/manager/standard/count/ansi
602
			 */
603
			$cfgPathSearch = 'mshop/product/manager/standard/search';
604
605
			/** mshop/product/manager/standard/count/mysql
606
			 * Counts the number of records matched by the given criteria in the database
607
			 *
608
			 * @see mshop/product/manager/standard/count/ansi
609
			 */
610
611
			/** mshop/product/manager/standard/count/ansi
612
			 * Counts the number of records matched by the given criteria in the database
613
			 *
614
			 * Counts all records matched by the given criteria from the product
615
			 * database. The records must be from one of the sites that are
616
			 * configured via the context item. If the current site is part of
617
			 * a tree of sites, the statement can count all records from the
618
			 * current site and the complete sub-tree of sites.
619
			 *
620
			 * As the records can normally be limited by criteria from sub-managers,
621
			 * their tables must be joined in the SQL context. This is done by
622
			 * using the "internaldeps" property from the definition of the ID
623
			 * column of the sub-managers. These internal dependencies specify
624
			 * the JOIN between the tables and the used columns for joining. The
625
			 * ":joins" placeholder is then replaced by the JOIN strings from
626
			 * the sub-managers.
627
			 *
628
			 * To limit the records matched, conditions can be added to the given
629
			 * criteria object. It can contain comparisons like column names that
630
			 * must match specific values which can be combined by AND, OR or NOT
631
			 * operators. The resulting string of SQL conditions replaces the
632
			 * ":cond" placeholder before the statement is sent to the database
633
			 * server.
634
			 *
635
			 * Both, the strings for ":joins" and for ":cond" are the same as for
636
			 * the "search" SQL statement.
637
			 *
638
			 * Contrary to the "search" statement, it doesn't return any records
639
			 * but instead the number of records that have been found. As counting
640
			 * thousands of records can be a long running task, the maximum number
641
			 * of counted records is limited for performance reasons.
642
			 *
643
			 * The SQL statement should conform to the ANSI standard to be
644
			 * compatible with most relational database systems. This also
645
			 * includes using double quotes for table and column names.
646
			 *
647
			 * @param string SQL statement for counting items
648
			 * @since 2014.03
649
			 * @category Developer
650
			 * @see mshop/product/manager/standard/insert/ansi
651
			 * @see mshop/product/manager/standard/update/ansi
652
			 * @see mshop/product/manager/standard/newid/ansi
653
			 * @see mshop/product/manager/standard/delete/ansi
654
			 * @see mshop/product/manager/standard/search/ansi
655
			 */
656
			$cfgPathCount = 'mshop/product/manager/standard/count';
657
658
			$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount, $required, $total, $level );
659
660
			while( ( $row = $results->fetch() ) !== false )
661
			{
662
				$config = $row['product.config'];
663
664
				if( $config && ( $row['product.config'] = json_decode( $config, true ) ) === null )
665
				{
666
					$msg = sprintf( 'Invalid JSON as result of search for ID "%2$s" in "%1$s": %3$s', 'mshop_product.config', $row['product.id'], $config );
667
					$this->getContext()->getLogger()->log( $msg, \Aimeos\MW\Logger\Base::WARN );
668
				}
669
670
				$map[$row['product.id']] = $row;
671
				$typeIds[$row['product.typeid']] = null;
672
			}
673
674
			$dbm->release( $conn, $dbname );
675
		}
676
		catch( \Exception $e )
677
		{
678
			$dbm->release( $conn, $dbname );
679
			throw $e;
680
		}
681
682
		if( !empty( $typeIds ) )
683
		{
684
			$typeManager = $this->getObject()->getSubManager( 'type' );
685
			$typeSearch = $typeManager->createSearch();
686
			$typeSearch->setConditions( $typeSearch->compare( '==', 'product.type.id', array_keys( $typeIds ) ) );
687
			$typeSearch->setSlice( 0, $search->getSliceSize() );
688
			$typeItems = $typeManager->searchItems( $typeSearch );
689
690
			foreach( $map as $id => $row )
691
			{
692
				if( isset( $typeItems[$row['product.typeid']] ) )
693
				{
694
					$map[$id]['product.type'] = $typeItems[$row['product.typeid']]->getCode();
695
					$map[$id]['product.typename'] = $typeItems[$row['product.typeid']]->getName();
696
				}
697
			}
698
		}
699
700
		$propItems = [];
701
		if( in_array( 'product/property', $ref, true ) ) {
702
			$propItems = $this->getPropertyItems( array_keys( $map ), 'product' );
703
		}
704
705
		return $this->buildItems( $map, $ref, 'product', $propItems );
706
	}
707
708
709
	/**
710
	 * Returns the available manager types
711
	 *
712
	 * @param boolean $withsub Return also the resource type of sub-managers if true
713
	 * @return array Type of the manager and submanagers, subtypes are separated by slashes
714
	 */
715
	public function getResourceType( $withsub = true )
716
	{
717
		$path = 'mshop/product/manager/submanagers';
718
		$default = ['lists', 'property', 'type'];
719
720
		return $this->getResourceTypeBase( 'product', $path, $default, $withsub );
721
	}
722
723
724
	/**
725
	 * Returns the attributes that can be used for searching.
726
	 *
727
	 * @param boolean $withsub Return also attributes of sub-managers if true
728
	 * @return array Returns a list of attribtes implementing \Aimeos\MW\Criteria\Attribute\Iface
729
	 */
730
	public function getSearchAttributes( $withsub = true )
731
	{
732
		/** mshop/product/manager/submanagers
733
		 * List of manager names that can be instantiated by the product manager
734
		 *
735
		 * Managers provide a generic interface to the underlying storage.
736
		 * Each manager has or can have sub-managers caring about particular
737
		 * aspects. Each of these sub-managers can be instantiated by its
738
		 * parent manager using the getSubManager() method.
739
		 *
740
		 * The search keys from sub-managers can be normally used in the
741
		 * manager as well. It allows you to search for items of the manager
742
		 * using the search keys of the sub-managers to further limit the
743
		 * retrieved list of items.
744
		 *
745
		 * @param array List of sub-manager names
746
		 * @since 2014.03
747
		 * @category Developer
748
		 */
749
		$path = 'mshop/product/manager/submanagers';
750
		$default = ['lists', 'property', 'type'];
751
752
		return $this->getSearchAttributesBase( $this->searchConfig, $path, $default, $withsub );
753
	}
754
755
756
	/**
757
	 * Returns a new manager for product extensions.
758
	 *
759
	 * @param string $manager Name of the sub manager type in lower case
760
	 * @param string|null $name Name of the implementation, will be from configuration (or Default) if null
761
	 * @return \Aimeos\MShop\Common\Manager\Iface Submanager, e.g. type, property, etc.
762
	 */
763
	public function getSubManager( $manager, $name = null )
764
	{
765
		return $this->getSubManagerBase( 'product', $manager, $name );
766
	}
767
768
769
	/**
770
	 * Creates a search object and optionally sets base criteria.
771
	 *
772
	 * @param boolean $default Add default criteria
773
	 * @return \Aimeos\MW\Criteria\Iface Criteria object
774
	 */
775
	public function createSearch( $default = false )
776
	{
777
		if( $default === true )
778
		{
779
			$object = $this->createSearchBase( 'product' );
780
781
			$expr = array( $object->getConditions() );
782
783
			$temp = array(
784
				$object->compare( '==', 'product.datestart', null ),
785
				$object->compare( '<=', 'product.datestart', $this->date ),
786
			);
787
			$expr[] = $object->combine( '||', $temp );
788
789
			$temp = array(
790
				$object->compare( '==', 'product.dateend', null ),
791
				$object->compare( '>=', 'product.dateend', $this->date ),
792
			);
793
			$expr[] = $object->combine( '||', $temp );
794
795
			$object->setConditions( $object->combine( '&&', $expr ) );
796
797
			return $object;
798
		}
799
800
		return parent::createSearch();
801
	}
802
803
804
	/**
805
	 * Create new product item object initialized with given parameters.
806
	 *
807
	 * @param array $values Associative list of key/value pairs
808
	 * @param array $listItems List of items implementing \Aimeos\MShop\Common\Item\Lists\Iface
809
	 * @param array $refItems List of items implementing \Aimeos\MShop\Common\Item\Iface
810
	 * @param array $propertyItems List of items implementing \Aimeos\MShop\Common\Item\Property\Iface
811
	 * @return \Aimeos\MShop\Product\Item\Iface New product item
812
	 */
813
	protected function createItemBase( array $values = [], array $listItems = [],
814
		array $refItems = [], array $propertyItems = [] )
815
	{
816
		$values['date'] = $this->date;
817
818
		return new \Aimeos\MShop\Product\Item\Standard( $values, $listItems, $refItems, $propertyItems );
819
	}
820
}
821