Completed
Push — master ( 3b0799...0bfde0 )
by Aimeos
09:08
created

Standard::__construct()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 14
nc 4
nop 1
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
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-2017
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
0 ignored issues
show
Coding Style introduced by
Expected 0 spaces between "Base" and comma; 1 found
Loading history...
23
	implements \Aimeos\MShop\Product\Manager\Iface
24
{
25
	private $searchConfig = array(
26
		'product.siteid' => array(
27
			'code' => 'product.siteid',
28
			'internalcode' => 'mpro."siteid"',
29
			'label' => 'Site ID',
30
			'type' => 'integer',
31
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
32
			'public' => false,
33
		),
34
		'product.typeid' => array(
35
			'code' => 'product.typeid',
36
			'internalcode' => 'mpro."typeid"',
37
			'label' => 'Type ID',
38
			'type' => 'integer',
39
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
40
			'public' => false,
41
		),
42
		'product.label' => array(
43
			'code' => 'product.label',
44
			'internalcode' => 'mpro."label"',
45
			'label' => 'Label',
46
			'type' => 'string',
47
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
48
		),
49
		'product.code' => array(
50
			'code' => 'product.code',
51
			'internalcode' => 'mpro."code"',
52
			'label' => 'SKU',
53
			'type' => 'string',
54
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
55
		),
56
		'product.id' => array(
57
			'code' => 'product.id',
58
			'internalcode' => 'mpro."id"',
59
			'label' => 'ID',
60
			'type' => 'integer',
61
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
62
		),
63
		'product.datestart' => array(
64
			'code' => 'product.datestart',
65
			'internalcode' => 'mpro."start"',
66
			'label' => 'Start date/time',
67
			'type' => 'datetime',
68
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
69
		),
70
		'product.dateend' => array(
71
			'code' => 'product.dateend',
72
			'internalcode' => 'mpro."end"',
73
			'label' => 'End date/time',
74
			'type' => 'datetime',
75
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
76
		),
77
		'product.status' => array(
78
			'code' => 'product.status',
79
			'internalcode' => 'mpro."status"',
80
			'label' => 'Status',
81
			'type' => 'integer',
82
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
83
		),
84
		'product.config' => array(
85
			'code' => 'product.config',
86
			'internalcode' => 'mpro."config"',
87
			'label' => 'Config',
88
			'type' => 'string',
89
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
90
			'public' => false,
91
		),
92
		'product.target' => array(
93
			'code' => 'product.target',
94
			'internalcode' => 'mpro."target"',
95
			'label' => 'URL target',
96
			'type' => 'string',
97
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
98
			'public' => false,
99
		),
100
		'product.ctime' => array(
101
			'code' => 'product.ctime',
102
			'internalcode' => 'mpro."ctime"',
103
			'label' => 'Create date/time',
104
			'type' => 'datetime',
105
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
106
			'public' => false,
107
		),
108
		'product.mtime' => array(
109
			'code' => 'product.mtime',
110
			'internalcode' => 'mpro."mtime"',
111
			'label' => 'Modify date/time',
112
			'type' => 'datetime',
113
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
114
			'public' => false,
115
		),
116
		'product.editor' => array(
117
			'code' => 'product.editor',
118
			'internalcode' => 'mpro."editor"',
119
			'label' => 'Editor',
120
			'type' => 'string',
121
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
122
			'public' => false,
123
		),
124
		'product.contains' => array(
125
			'code' => 'product.contains()',
126
			'internalcode' => '( SELECT COUNT(mproli_cs."parentid")
127
				FROM "mshop_product_list" AS mproli_cs
128
				WHERE mpro."id" = mproli_cs."parentid" AND :site
129
					AND mproli_cs."domain" = $1 AND mproli_cs."refid" IN ( $3 ) AND mproli_cs."typeid" = $2
130
					AND ( mproli_cs."start" IS NULL OR mproli_cs."start" <= \':date\' )
131
					AND ( mproli_cs."end" IS NULL OR mproli_cs."end" >= \':date\' ) )',
132
			'label' => 'Number of product list items, parameter(<domain>,<list type ID>,<reference IDs>)',
133
			'type' => 'integer',
134
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_INT,
135
			'public' => false,
136
		),
137
	);
138
139
140
	/**
141
	 * Creates the product manager that will use the given context object.
142
	 *
143
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object with required objects
144
	 */
145
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
146
	{
147
		parent::__construct( $context );
148
		$this->setResourceName( 'db-product' );
149
150
		$date = date( 'Y-m-d H:i:00' );
151
		$locale = $context->getLocale();
152
153
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
154
		$level = $context->getConfig()->get( 'mshop/product/manager/sitemode', $level );
155
156
		$siteIds = [$locale->getSiteId()];
157
158
		if( $level & \Aimeos\MShop\Locale\Manager\Base::SITE_PATH ) {
159
			$siteIds = array_merge( $siteIds, $locale->getSitePath() );
160
		}
161
162
		if( $level & \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE ) {
163
			$siteIds = array_merge( $siteIds, $locale->getSiteSubTree() );
164
		}
165
166
		$this->replaceSiteMarker( $this->searchConfig['product.contains'], 'mproli_cs."siteid"', $siteIds, ':site' );
167
		$this->searchConfig['product.contains'] = str_replace( ':date', $date, $this->searchConfig['product.contains'] );
168
	}
169
170
171
	/**
172
	 * Removes old entries from the storage.
173
	 *
174
	 * @param integer[] $siteids List of IDs for sites whose entries should be deleted
175
	 */
176
	public function cleanup( array $siteids )
177
	{
178
		$path = 'mshop/product/manager/submanagers';
179
		$default = array( 'type', 'property', 'lists' );
180
181
		foreach( $this->getContext()->getConfig()->get( $path, $default ) as $domain ) {
182
			$this->getObject()->getSubManager( $domain )->cleanup( $siteids );
183
		}
184
185
		$this->cleanupBase( $siteids, 'mshop/product/manager/standard/delete' );
186
	}
187
188
189
	/**
190
	 * Create new product item object.
191
	 *
192
	 * @return \Aimeos\MShop\Product\Item\Iface
193
	 */
194
	public function createItem()
195
	{
196
		$values = array( 'product.siteid' => $this->getContext()->getLocale()->getSiteId() );
197
		return $this->createItemBase( $values );
198
	}
199
200
201
	/**
202
	 * Adds a new product to the storage.
203
	 *
204
	 * @param \Aimeos\MShop\Common\Item\Iface $item Product item that should be saved to the storage
205
	 * @param boolean $fetch True if the new ID should be returned in the item
206
	 * @return \Aimeos\MShop\Common\Item\Iface $item Updated item including the generated ID
207
	 */
208
	public function saveItem( \Aimeos\MShop\Common\Item\Iface $item, $fetch = true )
209
	{
210
		$iface = '\\Aimeos\\MShop\\Product\\Item\\Iface';
211
		if( !( $item instanceof $iface ) ) {
212
			throw new \Aimeos\MShop\Product\Exception( sprintf( 'Object is not of required type "%1$s"', $iface ) );
213
		}
214
215
		if( !$item->isModified() ) {
216
			return $item;
217
		}
218
219
		$context = $this->getContext();
220
221
		$dbm = $context->getDatabaseManager();
222
		$dbname = $this->getResourceName();
223
		$conn = $dbm->acquire( $dbname );
224
225
		try
226
		{
227
			$id = $item->getId();
228
			$date = date( 'Y-m-d H:i:s' );
229
230
			if( $id === null )
231
			{
232
				/** mshop/product/manager/standard/insert/mysql
233
				 * Inserts a new product record into the database table
234
				 *
235
				 * @see mshop/product/manager/standard/insert/ansi
236
				 */
237
238
				/** mshop/product/manager/standard/insert/ansi
239
				 * Inserts a new product record into the database table
240
				 *
241
				 * Items with no ID yet (i.e. the ID is NULL) will be created in
242
				 * the database and the newly created ID retrieved afterwards
243
				 * using the "newid" SQL statement.
244
				 *
245
				 * The SQL statement must be a string suitable for being used as
246
				 * prepared statement. It must include question marks for binding
247
				 * the values from the product item to the statement before they are
248
				 * sent to the database server. The number of question marks must
249
				 * be the same as the number of columns listed in the INSERT
250
				 * statement. The order of the columns must correspond to the
251
				 * order in the saveItems() method, so the correct values are
252
				 * bound to the columns.
253
				 *
254
				 * The SQL statement should conform to the ANSI standard to be
255
				 * compatible with most relational database systems. This also
256
				 * includes using double quotes for table and column names.
257
				 *
258
				 * @param string SQL statement for inserting records
259
				 * @since 2014.03
260
				 * @category Developer
261
				 * @see mshop/product/manager/standard/update/ansi
262
				 * @see mshop/product/manager/standard/newid/ansi
263
				 * @see mshop/product/manager/standard/delete/ansi
264
				 * @see mshop/product/manager/standard/search/ansi
265
				 * @see mshop/product/manager/standard/count/ansi
266
				 */
267
				$path = 'mshop/product/manager/standard/insert';
268
			}
269
			else
270
			{
271
				/** mshop/product/manager/standard/update/mysql
272
				 * Updates an existing product record in the database
273
				 *
274
				 * @see mshop/product/manager/standard/update/ansi
275
				 */
276
277
				/** mshop/product/manager/standard/update/ansi
278
				 * Updates an existing product record in the database
279
				 *
280
				 * Items which already have an ID (i.e. the ID is not NULL) will
281
				 * be updated in the database.
282
				 *
283
				 * The SQL statement must be a string suitable for being used as
284
				 * prepared statement. It must include question marks for binding
285
				 * the values from the product item to the statement before they are
286
				 * sent to the database server. The order of the columns must
287
				 * correspond to the order in the saveItems() method, so the
288
				 * correct values are 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 updating records
295
				 * @since 2014.03
296
				 * @category Developer
297
				 * @see mshop/product/manager/standard/insert/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/update';
304
			}
305
306
			$stmt = $this->getCachedStatement( $conn, $path );
307
308
			$stmt->bind( 1, $item->getTypeId(), \Aimeos\MW\DB\Statement\Base::PARAM_INT );
309
			$stmt->bind( 2, $item->getCode() );
310
			$stmt->bind( 3, $item->getLabel() );
311
			$stmt->bind( 4, $item->getStatus(), \Aimeos\MW\DB\Statement\Base::PARAM_INT );
312
			$stmt->bind( 5, $item->getDateStart() );
313
			$stmt->bind( 6, $item->getDateEnd() );
314
			$stmt->bind( 7, json_encode( $item->getConfig() ) );
315
			$stmt->bind( 8, $date ); // mtime
316
			$stmt->bind( 9, $context->getEditor() );
317
			$stmt->bind( 10, $item->getTarget() );
318
			$stmt->bind( 11, $context->getLocale()->getSiteId(), \Aimeos\MW\DB\Statement\Base::PARAM_INT );
319
320
			if( $id !== null ) {
321
				$stmt->bind( 12, $id, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
322
				$item->setId( $id ); //so item is no longer modified
323
			} else {
324
				$stmt->bind( 12, $date ); // ctime
325
			}
326
327
			$stmt->execute()->finish();
328
329
			if( $id === null && $fetch === true )
330
			{
331
				/** mshop/product/manager/standard/newid/mysql
332
				 * Retrieves the ID generated by the database when inserting a new record
333
				 *
334
				 * @see mshop/product/manager/standard/newid/ansi
335
				 */
336
337
				/** mshop/product/manager/standard/newid/ansi
338
				 * Retrieves the ID generated by the database when inserting a new record
339
				 *
340
				 * As soon as a new record is inserted into the database table,
341
				 * the database server generates a new and unique identifier for
342
				 * that record. This ID can be used for retrieving, updating and
343
				 * deleting that specific record from the table again.
344
				 *
345
				 * For MySQL:
346
				 *  SELECT LAST_INSERT_ID()
347
				 * For PostgreSQL:
348
				 *  SELECT currval('seq_mpro_id')
349
				 * For SQL Server:
350
				 *  SELECT SCOPE_IDENTITY()
351
				 * For Oracle:
352
				 *  SELECT "seq_mpro_id".CURRVAL FROM DUAL
353
				 *
354
				 * There's no way to retrive the new ID by a SQL statements that
355
				 * fits for most database servers as they implement their own
356
				 * specific way.
357
				 *
358
				 * @param string SQL statement for retrieving the last inserted record ID
359
				 * @since 2014.03
360
				 * @category Developer
361
				 * @see mshop/product/manager/standard/insert/ansi
362
				 * @see mshop/product/manager/standard/update/ansi
363
				 * @see mshop/product/manager/standard/delete/ansi
364
				 * @see mshop/product/manager/standard/search/ansi
365
				 * @see mshop/product/manager/standard/count/ansi
366
				 */
367
				$path = 'mshop/product/manager/standard/newid';
368
				$item->setId( $this->newId( $conn, $path ) );
369
			}
370
371
			$dbm->release( $conn, $dbname );
372
		}
373
		catch( \Exception $e )
374
		{
375
			$dbm->release( $conn, $dbname );
376
			throw $e;
377
		}
378
379
		return $item;
380
	}
381
382
383
	/**
384
	 * Removes multiple items specified by ids in the array.
385
	 *
386
	 * @param array $ids List of IDs
387
	 */
388
	public function deleteItems( array $ids )
389
	{
390
		/** mshop/product/manager/standard/delete/mysql
391
		 * Deletes the items matched by the given IDs from the database
392
		 *
393
		 * @see mshop/product/manager/standard/delete/ansi
394
		 */
395
396
		/** mshop/product/manager/standard/delete/ansi
397
		 * Deletes the items matched by the given IDs from the database
398
		 *
399
		 * Removes the records specified by the given IDs from the product database.
400
		 * The records must be from the site that is configured via the
401
		 * context item.
402
		 *
403
		 * The ":cond" placeholder is replaced by the name of the ID column and
404
		 * the given ID or list of IDs while the site ID is bound to the question
405
		 * mark.
406
		 *
407
		 * The SQL statement should conform to the ANSI standard to be
408
		 * compatible with most relational database systems. This also
409
		 * includes using double quotes for table and column names.
410
		 *
411
		 * @param string SQL statement for deleting items
412
		 * @since 2014.03
413
		 * @category Developer
414
		 * @see mshop/product/manager/standard/insert/ansi
415
		 * @see mshop/product/manager/standard/update/ansi
416
		 * @see mshop/product/manager/standard/newid/ansi
417
		 * @see mshop/product/manager/standard/search/ansi
418
		 * @see mshop/product/manager/standard/count/ansi
419
		 */
420
		$path = 'mshop/product/manager/standard/delete';
421
		$this->deleteItemsBase( $ids, $path );
422
	}
423
424
425
	/**
426
	 * Returns the item specified by its code and domain/type if necessary
427
	 *
428
	 * @param string $code Code of the item
429
	 * @param string[] $ref List of domains to fetch list items and referenced items for
430
	 * @param string|null $domain Domain of the item if necessary to identify the item uniquely
431
	 * @param string|null $type Type code of the item if necessary to identify the item uniquely
432
	 * @return \Aimeos\MShop\Common\Item\Iface Item object
433
	 */
434
	public function findItem( $code, array $ref = [], $domain = null, $type = null )
435
	{
436
		return $this->findItemBase( array( 'product.code' => $code ), $ref );
437
	}
438
439
440
	/**
441
	 * Returns the product item for the given product ID.
442
	 *
443
	 * @param integer $id Unique ID of the product item
444
	 * @param string[] $ref List of domains to fetch list items and referenced items for
445
	 * @param boolean $default Add default criteria
446
	 * @return \Aimeos\MShop\Product\Item\Iface Returns the product item of the given id
447
	 * @throws \Aimeos\MShop\Exception If item couldn't be found
448
	 */
449
	public function getItem( $id, array $ref = [], $default = false )
450
	{
451
		return $this->getItemBase( 'product.id', $id, $ref, $default );
452
	}
453
454
455
	/**
456
	 * Search for products based on the given criteria.
457
	 *
458
	 * @param \Aimeos\MW\Criteria\Iface $search Search criteria object
459
	 * @param string[] $ref List of domains to fetch list items and referenced items for
460
	 * @param integer|null &$total Number of items that are available in total
461
	 * @return array List of products implementing \Aimeos\MShop\Product\Item\Iface
462
	 */
463
	public function searchItems( \Aimeos\MW\Criteria\Iface $search, array $ref = [], &$total = null )
464
	{
465
		$map = $typeIds = [];
466
		$context = $this->getContext();
467
468
		$dbm = $context->getDatabaseManager();
469
		$dbname = $this->getResourceName();
470
		$conn = $dbm->acquire( $dbname );
471
472
		try
473
		{
474
			$required = array( 'product' );
475
476
			/** mshop/product/manager/sitemode
477
			 * Mode how items from levels below or above in the site tree are handled
478
			 *
479
			 * By default, only items from the current site are fetched from the
480
			 * storage. If the ai-sites extension is installed, you can create a
481
			 * tree of sites. Then, this setting allows you to define for the
482
			 * whole product domain if items from parent sites are inherited,
483
			 * sites from child sites are aggregated or both.
484
			 *
485
			 * Available constants for the site mode are:
486
			 * * 0 = only items from the current site
487
			 * * 1 = inherit items from parent sites
488
			 * * 2 = aggregate items from child sites
489
			 * * 3 = inherit and aggregate items at the same time
490
			 *
491
			 * You also need to set the mode in the locale manager
492
			 * (mshop/locale/manager/standard/sitelevel) to one of the constants.
493
			 * If you set it to the same value, it will work as described but you
494
			 * can also use different modes. For example, if inheritance and
495
			 * aggregation is configured the locale manager but only inheritance
496
			 * in the domain manager because aggregating items makes no sense in
497
			 * this domain, then items wil be only inherited. Thus, you have full
498
			 * control over inheritance and aggregation in each domain.
499
			 *
500
			 * @param integer Constant from Aimeos\MShop\Locale\Manager\Base class
501
			 * @category Developer
502
			 * @since 2018.01
503
			 * @see mshop/locale/manager/standard/sitelevel
504
			 */
505
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
506
			$level = $context->getConfig()->get( 'mshop/product/manager/sitemode', $level );
507
508
			/** mshop/product/manager/standard/search/mysql
509
			 * Retrieves the records matched by the given criteria in the database
510
			 *
511
			 * @see mshop/product/manager/standard/search/ansi
512
			 */
513
514
			/** mshop/product/manager/standard/search/ansi
515
			 * Retrieves the records matched by the given criteria in the database
516
			 *
517
			 * Fetches the records matched by the given criteria from the product
518
			 * database. The records must be from one of the sites that are
519
			 * configured via the context item. If the current site is part of
520
			 * a tree of sites, the SELECT statement can retrieve all records
521
			 * from the current site and the complete sub-tree of sites.
522
			 *
523
			 * As the records can normally be limited by criteria from sub-managers,
524
			 * their tables must be joined in the SQL context. This is done by
525
			 * using the "internaldeps" property from the definition of the ID
526
			 * column of the sub-managers. These internal dependencies specify
527
			 * the JOIN between the tables and the used columns for joining. The
528
			 * ":joins" placeholder is then replaced by the JOIN strings from
529
			 * the sub-managers.
530
			 *
531
			 * To limit the records matched, conditions can be added to the given
532
			 * criteria object. It can contain comparisons like column names that
533
			 * must match specific values which can be combined by AND, OR or NOT
534
			 * operators. The resulting string of SQL conditions replaces the
535
			 * ":cond" placeholder before the statement is sent to the database
536
			 * server.
537
			 *
538
			 * If the records that are retrieved should be ordered by one or more
539
			 * columns, the generated string of column / sort direction pairs
540
			 * replaces the ":order" placeholder. In case no ordering is required,
541
			 * the complete ORDER BY part including the "\/*-orderby*\/...\/*orderby-*\/"
542
			 * markers is removed to speed up retrieving the records. Columns of
543
			 * sub-managers can also be used for ordering the result set but then
544
			 * no index can be used.
545
			 *
546
			 * The number of returned records can be limited and can start at any
547
			 * number between the begining and the end of the result set. For that
548
			 * the ":size" and ":start" placeholders are replaced by the
549
			 * corresponding values from the criteria object. The default values
550
			 * are 0 for the start and 100 for the size value.
551
			 *
552
			 * The SQL statement should conform to the ANSI standard to be
553
			 * compatible with most relational database systems. This also
554
			 * includes using double quotes for table and column names.
555
			 *
556
			 * @param string SQL statement for searching items
557
			 * @since 2014.03
558
			 * @category Developer
559
			 * @see mshop/product/manager/standard/insert/ansi
560
			 * @see mshop/product/manager/standard/update/ansi
561
			 * @see mshop/product/manager/standard/newid/ansi
562
			 * @see mshop/product/manager/standard/delete/ansi
563
			 * @see mshop/product/manager/standard/count/ansi
564
			 */
565
			$cfgPathSearch = 'mshop/product/manager/standard/search';
566
567
			/** mshop/product/manager/standard/count/mysql
568
			 * Counts the number of records matched by the given criteria in the database
569
			 *
570
			 * @see mshop/product/manager/standard/count/ansi
571
			 */
572
573
			/** mshop/product/manager/standard/count/ansi
574
			 * Counts the number of records matched by the given criteria in the database
575
			 *
576
			 * Counts all records matched by the given criteria from the product
577
			 * database. The records must be from one of the sites that are
578
			 * configured via the context item. If the current site is part of
579
			 * a tree of sites, the statement can count all records from the
580
			 * current site and the complete sub-tree of sites.
581
			 *
582
			 * As the records can normally be limited by criteria from sub-managers,
583
			 * their tables must be joined in the SQL context. This is done by
584
			 * using the "internaldeps" property from the definition of the ID
585
			 * column of the sub-managers. These internal dependencies specify
586
			 * the JOIN between the tables and the used columns for joining. The
587
			 * ":joins" placeholder is then replaced by the JOIN strings from
588
			 * the sub-managers.
589
			 *
590
			 * To limit the records matched, conditions can be added to the given
591
			 * criteria object. It can contain comparisons like column names that
592
			 * must match specific values which can be combined by AND, OR or NOT
593
			 * operators. The resulting string of SQL conditions replaces the
594
			 * ":cond" placeholder before the statement is sent to the database
595
			 * server.
596
			 *
597
			 * Both, the strings for ":joins" and for ":cond" are the same as for
598
			 * the "search" SQL statement.
599
			 *
600
			 * Contrary to the "search" statement, it doesn't return any records
601
			 * but instead the number of records that have been found. As counting
602
			 * thousands of records can be a long running task, the maximum number
603
			 * of counted records is limited for performance reasons.
604
			 *
605
			 * The SQL statement should conform to the ANSI standard to be
606
			 * compatible with most relational database systems. This also
607
			 * includes using double quotes for table and column names.
608
			 *
609
			 * @param string SQL statement for counting items
610
			 * @since 2014.03
611
			 * @category Developer
612
			 * @see mshop/product/manager/standard/insert/ansi
613
			 * @see mshop/product/manager/standard/update/ansi
614
			 * @see mshop/product/manager/standard/newid/ansi
615
			 * @see mshop/product/manager/standard/delete/ansi
616
			 * @see mshop/product/manager/standard/search/ansi
617
			 */
618
			$cfgPathCount = 'mshop/product/manager/standard/count';
619
620
			$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount, $required, $total, $level );
621
622
			while( ( $row = $results->fetch() ) !== false )
623
			{
624
				$config = $row['product.config'];
625
626
				if( $config && ( $row['product.config'] = json_decode( $config, true ) ) === null )
627
				{
628
					$msg = sprintf( 'Invalid JSON as result of search for ID "%2$s" in "%1$s": %3$s', 'mshop_product.config', $row['product.id'], $config );
629
					$this->getContext()->getLogger()->log( $msg, \Aimeos\MW\Logger\Base::WARN );
630
				}
631
632
				$map[$row['product.id']] = $row;
633
				$typeIds[$row['product.typeid']] = null;
634
			}
635
636
			$dbm->release( $conn, $dbname );
637
		}
638
		catch( \Exception $e )
639
		{
640
			$dbm->release( $conn, $dbname );
641
			throw $e;
642
		}
643
644
		if( !empty( $typeIds ) )
645
		{
646
			$typeManager = $this->getObject()->getSubManager( 'type' );
647
			$typeSearch = $typeManager->createSearch();
648
			$typeSearch->setConditions( $typeSearch->compare( '==', 'product.type.id', array_keys( $typeIds ) ) );
649
			$typeSearch->setSlice( 0, $search->getSliceSize() );
650
			$typeItems = $typeManager->searchItems( $typeSearch );
651
652
			foreach( $map as $id => $row )
653
			{
654
				if( isset( $typeItems[$row['product.typeid']] ) )
655
				{
656
					$map[$id]['product.type'] = $typeItems[$row['product.typeid']]->getCode();
657
					$map[$id]['product.typename'] = $typeItems[$row['product.typeid']]->getName();
658
				}
659
			}
660
		}
661
662
		$propItems = [];
663
		if( in_array( 'product/property', $ref, true ) ) {
664
			$propItems = $this->getPropertyItems( array_keys( $map ) );
665
		}
666
667
668
		return $this->buildItems( $map, $ref, 'product', $propItems );
669
	}
670
671
672
	/**
673
	 * Returns the available manager types
674
	 *
675
	 * @param boolean $withsub Return also the resource type of sub-managers if true
676
	 * @return array Type of the manager and submanagers, subtypes are separated by slashes
677
	 */
678
	public function getResourceType( $withsub = true )
679
	{
680
		$path = 'mshop/product/manager/submanagers';
681
682
		return $this->getResourceTypeBase( 'product', $path, array( 'type', 'lists', 'property' ), $withsub );
683
	}
684
685
686
	/**
687
	 * Returns the attributes that can be used for searching.
688
	 *
689
	 * @param boolean $withsub Return also attributes of sub-managers if true
690
	 * @return array Returns a list of attribtes implementing \Aimeos\MW\Criteria\Attribute\Iface
691
	 */
692
	public function getSearchAttributes( $withsub = true )
693
	{
694
		/** mshop/product/manager/submanagers
695
		 * List of manager names that can be instantiated by the product manager
696
		 *
697
		 * Managers provide a generic interface to the underlying storage.
698
		 * Each manager has or can have sub-managers caring about particular
699
		 * aspects. Each of these sub-managers can be instantiated by its
700
		 * parent manager using the getSubManager() method.
701
		 *
702
		 * The search keys from sub-managers can be normally used in the
703
		 * manager as well. It allows you to search for items of the manager
704
		 * using the search keys of the sub-managers to further limit the
705
		 * retrieved list of items.
706
		 *
707
		 * @param array List of sub-manager names
708
		 * @since 2014.03
709
		 * @category Developer
710
		 */
711
		$path = 'mshop/product/manager/submanagers';
712
		$default = array( 'type', 'property', 'lists' );
713
714
		return $this->getSearchAttributesBase( $this->searchConfig, $path, $default, $withsub );
715
	}
716
717
718
	/**
719
	 * Returns a new manager for product extensions.
720
	 *
721
	 * @param string $manager Name of the sub manager type in lower case
722
	 * @param string|null $name Name of the implementation, will be from configuration (or Default) if null
723
	 * @return \Aimeos\MShop\Common\Manager\Iface Submanager, e.g. type, property, etc.
724
	 */
725
	public function getSubManager( $manager, $name = null )
726
	{
727
		return $this->getSubManagerBase( 'product', $manager, $name );
728
	}
729
730
731
	/**
732
	 * Creates a search object and optionally sets base criteria.
733
	 *
734
	 * @param boolean $default Add default criteria
735
	 * @return \Aimeos\MW\Criteria\Iface Criteria object
736
	 */
737
	public function createSearch( $default = false )
738
	{
739
		if( $default === true )
740
		{
741
			$curDate = date( 'Y-m-d H:i:00', time() );
742
			$object = $this->createSearchBase( 'product' );
743
744
			$expr = array( $object->getConditions() );
745
746
			$temp = array(
747
				$object->compare( '==', 'product.datestart', null ),
748
				$object->compare( '<=', 'product.datestart', $curDate ),
749
			);
750
			$expr[] = $object->combine( '||', $temp );
751
752
			$temp = array(
753
				$object->compare( '==', 'product.dateend', null ),
754
				$object->compare( '>=', 'product.dateend', $curDate ),
755
			);
756
			$expr[] = $object->combine( '||', $temp );
757
758
			$object->setConditions( $object->combine( '&&', $expr ) );
759
760
			return $object;
761
		}
762
763
		return parent::createSearch();
764
	}
765
766
767
	/**
768
	 * Create new product item object initialized with given parameters.
769
	 *
770
	 * @param array $values Associative list of key/value pairs
771
	 * @param array $listItems List of items implementing \Aimeos\MShop\Common\Item\Lists\Iface
772
	 * @param array $refItems List of items implementing \Aimeos\MShop\Common\Item\Iface
773
	 * @param array $propertyItems List of items implementing \Aimeos\MShop\Common\Item\Property\Iface
774
	 * @return \Aimeos\MShop\Product\Item\Iface New product item
775
	 */
776
	protected function createItemBase( array $values = [], array $listItems = [],
777
		array $refItems = [], array $propertyItems = [] )
778
	{
779
		return new \Aimeos\MShop\Product\Item\Standard( $values, $listItems, $refItems, $propertyItems );
780
	}
781
782
783
	/**
784
	 * Returns the property items for the given product IDs
785
	 *
786
	 * @param array $prodIds List of product IDs
787
	 * @return array Associative list of product IDs / property IDs as keys and items implementing
788
	 * 	\Aimeos\MShop\Product\Item\Property\Iface as values
789
	 */
790
	protected function getPropertyItems( array $prodIds )
791
	{
792
		$list = [];
793
		$propManager = $this->getObject()->getSubManager( 'property' );
794
795
		$propSearch = $propManager->createSearch();
796
		$propSearch->setConditions( $propSearch->compare( '==', 'product.property.parentid', $prodIds ) );
797
		$propSearch->setSlice( 0, 0x7fffffff );
798
799
		foreach( $propManager->searchItems( $propSearch ) as $id => $propItem ) {
800
			$list[$propItem->getParentId()][$id] = $propItem;
801
		}
802
803
		return $list;
804
	}
805
}
806