Passed
Push — master ( 7a5ddf...5d4f58 )
by Aimeos
10:45 queued 06:39
created

Standard::search()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 164
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 33
dl 0
loc 164
rs 8.7697
c 0
b 0
f 0
cc 6
nc 8
nop 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2022
7
 * @package MShop
8
 * @subpackage Order
9
 */
10
11
12
namespace Aimeos\MShop\Order\Manager\Base;
13
14
15
/**
16
 * Default implementation for order base manager.
17
 *
18
 * @package MShop
19
 * @subpackage Order
20
 */
21
class Standard extends Base
22
	implements \Aimeos\MShop\Order\Manager\Base\Iface, \Aimeos\MShop\Common\Manager\Factory\Iface
23
{
24
	private $searchConfig = array(
25
		'order.base.id' => array(
26
			'code' => 'order.base.id',
27
			'internalcode' => 'mordba."id"',
28
			'internaldeps' => array( 'LEFT JOIN "mshop_order_base" AS mordba ON ( mord."baseid" = mordba."id" )' ),
29
			'label' => 'Order ID',
30
			'type' => 'integer',
31
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
32
			'public' => false,
33
		),
34
		'order.base.siteid' => array(
35
			'code' => 'order.base.siteid',
36
			'internalcode' => 'mordba."siteid"',
37
			'label' => 'Order site ID',
38
			'type' => 'string',
39
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
40
			'public' => false,
41
		),
42
		'order.base.sitecode' => array(
43
			'code' => 'order.base.sitecode',
44
			'internalcode' => 'mordba."sitecode"',
45
			'label' => 'Order site code',
46
			'type' => 'string',
47
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
48
			'public' => false,
49
		),
50
		'order.base.customerid' => array(
51
			'code' => 'order.base.customerid',
52
			'internalcode' => 'mordba."customerid"',
53
			'label' => 'Order customer ID',
54
			'type' => 'string',
55
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
56
		),
57
		'order.base.customerref' => array(
58
			'code' => 'order.base.customerref',
59
			'internalcode' => 'mordba."customerref"',
60
			'label' => 'Order customer reference',
61
			'type' => 'string',
62
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
63
		),
64
		'order.base.languageid' => array(
65
			'code' => 'order.base.languageid',
66
			'internalcode' => 'mordba."langid"',
67
			'label' => 'Order language code',
68
			'type' => 'string',
69
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
70
		),
71
		'order.base.currencyid' => array(
72
			'code' => 'order.base.currencyid',
73
			'internalcode' => 'mordba."currencyid"',
74
			'label' => 'Order currencyid code',
75
			'type' => 'string',
76
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
77
		),
78
		'order.base.price' => array(
79
			'code' => 'order.base.price',
80
			'internalcode' => 'mordba."price"',
81
			'label' => 'Order price amount',
82
			'type' => 'string',
83
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
84
		),
85
		'order.base.costs' => array(
86
			'code' => 'order.base.costs',
87
			'internalcode' => 'mordba."costs"',
88
			'label' => 'Order shipping amount',
89
			'type' => 'string',
90
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
91
		),
92
		'order.base.rebate' => array(
93
			'code' => 'order.base.rebate',
94
			'internalcode' => 'mordba."rebate"',
95
			'label' => 'Order rebate amount',
96
			'type' => 'string',
97
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
98
		),
99
		'order.base.taxvalue' => array(
100
			'code' => 'order.base.taxvalue',
101
			'internalcode' => 'mordba."tax"',
102
			'label' => 'Order tax amount',
103
			'type' => 'string',
104
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
105
		),
106
		'order.base.taxflag' => array(
107
			'code' => 'order.base.taxflag',
108
			'internalcode' => 'mordba."taxflag"',
109
			'label' => 'Order tax flag (0=net, 1=gross)',
110
			'type' => 'string',
111
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_INT,
112
		),
113
		'order.base.comment' => array(
114
			'code' => 'order.base.comment',
115
			'internalcode' => 'mordba."comment"',
116
			'label' => 'Order comment',
117
			'type' => 'string',
118
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
119
		),
120
		'order.base.ctime' => array(
121
			'code' => 'order.base.ctime',
122
			'internalcode' => 'mordba."ctime"',
123
			'label' => 'Order create date/time',
124
			'type' => 'datetime',
125
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
126
			'public' => false,
127
		),
128
		'order.base.mtime' => array(
129
			'code' => 'order.base.mtime',
130
			'internalcode' => 'mordba."mtime"',
131
			'label' => 'Order modify date/time',
132
			'type' => 'datetime',
133
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
134
			'public' => false,
135
		),
136
		'order.base.editor' => array(
137
			'code' => 'order.base.editor',
138
			'internalcode' => 'mordba."editor"',
139
			'label' => 'Order editor',
140
			'type' => 'string',
141
			'internaltype' => \Aimeos\Base\DB\Statement\Base::PARAM_STR,
142
			'public' => false,
143
		),
144
	);
145
146
147
	/**
148
	 * Initializes the object.
149
	 *
150
	 * @param \Aimeos\MShop\ContextIface $context Context object
151
	 */
152
	public function __construct( \Aimeos\MShop\ContextIface $context )
153
	{
154
		parent::__construct( $context );
155
		$this->setResourceName( 'db-order' );
156
	}
157
158
159
	/**
160
	 * Counts the number items that are available for the values of the given key.
161
	 *
162
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
163
	 * @param array|string $key Search key or list of keys to aggregate items for
164
	 * @param string|null $value Search key for aggregating the value column
165
	 * @param string|null $type Type of the aggregation, empty string for count or "sum" or "avg" (average)
166
	 * @return \Aimeos\Map List of the search keys as key and the number of counted items as value
167
	 */
168
	public function aggregate( \Aimeos\Base\Criteria\Iface $search, $key, string $value = null, string $type = null ) : \Aimeos\Map
169
	{
170
		/** mshop/order/manager/base/aggregate/mysql
171
		 * Counts the number of records grouped by the values in the key column and matched by the given criteria
172
		 *
173
		 * @see mshop/order/manager/base/aggregate/ansi
174
		 */
175
176
		/** mshop/order/manager/base/aggregate/ansi
177
		 * Counts the number of records grouped by the values in the key column and matched by the given criteria
178
		 *
179
		 * Groups all records by the values in the key column and counts their
180
		 * occurence. The matched records can be limited by the given criteria
181
		 * from the order database. The records must be from one of the sites
182
		 * that are configured via the context item. If the current site is part
183
		 * of a tree of sites, the statement can count all records from the
184
		 * current site and the complete sub-tree of sites.
185
		 *
186
		 * As the records can normally be limited by criteria from sub-managers,
187
		 * their tables must be joined in the SQL context. This is done by
188
		 * using the "internaldeps" property from the definition of the ID
189
		 * column of the sub-managers. These internal dependencies specify
190
		 * the JOIN between the tables and the used columns for joining. The
191
		 * ":joins" placeholder is then replaced by the JOIN strings from
192
		 * the sub-managers.
193
		 *
194
		 * To limit the records matched, conditions can be added to the given
195
		 * criteria object. It can contain comparisons like column names that
196
		 * must match specific values which can be combined by AND, OR or NOT
197
		 * operators. The resulting string of SQL conditions replaces the
198
		 * ":cond" placeholder before the statement is sent to the database
199
		 * server.
200
		 *
201
		 * This statement doesn't return any records. Instead, it returns pairs
202
		 * of the different values found in the key column together with the
203
		 * number of records that have been found for that key values.
204
		 *
205
		 * The SQL statement should conform to the ANSI standard to be
206
		 * compatible with most relational database systems. This also
207
		 * includes using double quotes for table and column names.
208
		 *
209
		 * @param string SQL statement for aggregating order items
210
		 * @since 2014.09
211
		 * @category Developer
212
		 * @see mshop/order/manager/base/insert/ansi
213
		 * @see mshop/order/manager/base/update/ansi
214
		 * @see mshop/order/manager/base/newid/ansi
215
		 * @see mshop/order/manager/base/delete/ansi
216
		 * @see mshop/order/manager/base/search/ansi
217
		 * @see mshop/order/manager/base/count/ansi
218
		 */
219
220
		/** mshop/order/manager/base/aggregateavg/mysql
221
		 * Computes the average of all values grouped by the key column and matched by the given criteria
222
		 *
223
		 * @param string SQL statement for aggregating the order base items and computing the average value
224
		 * @since 2017.10
225
		 * @category Developer
226
		 * @see mshop/order/manager/base/aggregateavg/ansi
227
		 * @see mshop/order/manager/base/aggregate/mysql
228
		 */
229
230
		/** mshop/order/manager/base/aggregateavg/ansi
231
		 * Computes the average of all values grouped by the key column and matched by the given criteria
232
		 *
233
		 * @param string SQL statement for aggregating the order base items and computing the average value
234
		 * @since 2017.10
235
		 * @category Developer
236
		 * @see mshop/order/manager/base/aggregate/ansi
237
		 */
238
239
		/** mshop/order/manager/base/aggregatesum/mysql
240
		 * Computes the sum of all values grouped by the key column and matched by the given criteria
241
		 *
242
		 * @param string SQL statement for aggregating the order base items and computing the sum
243
		 * @since 2017.10
244
		 * @category Developer
245
		 * @see mshop/order/manager/base/aggregatesum/ansi
246
		 * @see mshop/order/manager/base/aggregate/mysql
247
		 */
248
249
		/** mshop/order/manager/base/aggregatesum/ansi
250
		 * Computes the sum of all values grouped by the key column and matched by the given criteria
251
		 *
252
		 * @param string SQL statement for aggregating the order base items and computing the sum
253
		 * @since 2017.10
254
		 * @category Developer
255
		 * @see mshop/order/manager/base/aggregate/ansi
256
		 */
257
258
		$cfgkey = 'mshop/order/manager/base/aggregate';
259
		return $this->aggregateBase( $search, $key, $cfgkey, ['order.base'], $value, $type );
260
	}
261
262
263
	/**
264
	 * Removes old entries from the storage.
265
	 *
266
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
267
	 * @return \Aimeos\MShop\Order\Manager\Base\Iface Manager object for chaining method calls
268
	 */
269
	public function clear( iterable $siteids ) : \Aimeos\MShop\Common\Manager\Iface
270
	{
271
		$path = 'mshop/order/manager/base/submanagers';
272
		$default = array( 'address', 'coupon', 'product', 'service' );
273
274
		foreach( $this->context()->config()->get( $path, $default ) as $domain ) {
275
			$this->object()->getSubManager( $domain )->clear( $siteids );
276
		}
277
278
		return $this->clearBase( $siteids, 'mshop/order/manager/base/delete' );
279
	}
280
281
282
	/**
283
	 * Creates a new empty item instance
284
	 *
285
	 * @param array $values Values the item should be initialized with
286
	 * @return \Aimeos\MShop\Order\Item\Base\Iface New order base item object
287
	 */
288
	public function create( array $values = [] ) : \Aimeos\MShop\Common\Item\Iface
289
	{
290
		$context = $this->context();
291
		$locale = $context->locale();
292
293
		$values['order.base.siteid'] = $values['order.base.siteid'] ?? $locale->getSiteId();
294
		$priceManager = \Aimeos\MShop::create( $context, 'price' );
295
296
		$base = $this->createItemBase( $priceManager->create(), clone $locale, $values );
297
298
		\Aimeos\MShop::create( $context, 'plugin' )->register( $base, 'order' );
299
300
		return $base;
301
	}
302
303
304
	/**
305
	 * Creates a search critera object
306
	 *
307
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
308
	 * @param bool $site TRUE to add site criteria to show orders with available products only
309
	 * @return \Aimeos\Base\Criteria\Iface New search criteria object
310
	 */
311
	public function filter( ?bool $default = false, bool $site = false ) : \Aimeos\Base\Criteria\Iface
312
	{
313
		$search = parent::filter( $default );
314
		$context = $this->context();
315
316
		if( $default !== false ) {
317
			$search->add( ['order.base.customerid' => $context->user()] );
318
		}
319
320
		if( $site === true )
321
		{
322
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE;
323
			$search->add( $this->siteCondition( 'order.base.product.siteid', $level ) );
324
		}
325
326
		return $search;
327
	}
328
329
330
	/**
331
	 * Removes multiple items.
332
	 *
333
	 * @param \Aimeos\MShop\Common\Item\Iface[]|string[] $itemIds List of item objects or IDs of the items
334
	 * @return \Aimeos\MShop\Order\Manager\Base\Iface Manager object for chaining method calls
335
	 */
336
	public function delete( $itemIds ) : \Aimeos\MShop\Common\Manager\Iface
337
	{
338
		/** mshop/order/manager/base/delete/mysql
339
		 * Deletes the items matched by the given IDs from the database
340
		 *
341
		 * @see mshop/order/manager/base/delete/ansi
342
		 */
343
344
		/** mshop/order/manager/base/delete/ansi
345
		 * Deletes the items matched by the given IDs from the database
346
		 *
347
		 * Removes the records specified by the given IDs from the order database.
348
		 * The records must be from the site that is configured via the
349
		 * context item.
350
		 *
351
		 * The ":cond" placeholder is replaced by the name of the ID column and
352
		 * the given ID or list of IDs while the site ID is bound to the question
353
		 * mark.
354
		 *
355
		 * The SQL statement should conform to the ANSI standard to be
356
		 * compatible with most relational database systems. This also
357
		 * includes using double quotes for table and column names.
358
		 *
359
		 * @param string SQL statement for deleting items
360
		 * @since 2014.03
361
		 * @category Developer
362
		 * @see mshop/order/manager/base/insert/ansi
363
		 * @see mshop/order/manager/base/update/ansi
364
		 * @see mshop/order/manager/base/newid/ansi
365
		 * @see mshop/order/manager/base/search/ansi
366
		 * @see mshop/order/manager/base/count/ansi
367
		 */
368
		$path = 'mshop/order/manager/base/delete';
369
370
		return $this->deleteItemsBase( $itemIds, $path );
371
	}
372
373
374
	/**
375
	 * Returns the order base item specified by the given ID.
376
	 *
377
	 * @param string $id Unique id of the order base
378
	 * @param string[] $ref List of domains to fetch list items and referenced items for
379
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
380
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Returns Order base item of the given id
381
	 * @throws \Aimeos\MShop\Exception If item couldn't be found
382
	 */
383
	public function get( string $id, array $ref = [], ?bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
384
	{
385
		return $this->getItemBase( 'order.base.id', $id, $ref, $default );
386
	}
387
388
389
	/**
390
	 * Returns the available manager types
391
	 *
392
	 * @param bool $withsub Return also the resource type of sub-managers if true
393
	 * @return string[] Type of the manager and submanagers, subtypes are separated by slashes
394
	 */
395
	public function getResourceType( bool $withsub = true ) : array
396
	{
397
		$path = 'mshop/order/manager/base/submanagers';
398
		return $this->getResourceTypeBase( 'order/base', $path, ['address', 'coupon', 'product', 'service'], $withsub );
399
	}
400
401
402
	/**
403
	 * Returns the attributes that can be used for searching.
404
	 *
405
	 * @param bool $withsub Return also attributes of sub-managers if true
406
	 * @return \Aimeos\Base\Criteria\Attribute\Iface[] List of search attribute items
407
	 */
408
	public function getSearchAttributes( bool $withsub = true ) : array
409
	{
410
		/** mshop/order/manager/base/submanagers
411
		 * List of manager names that can be instantiated by the order base manager
412
		 *
413
		 * Managers provide a generic interface to the underlying storage.
414
		 * Each manager has or can have sub-managers caring about particular
415
		 * aspects. Each of these sub-managers can be instantiated by its
416
		 * parent manager using the getSubManager() method.
417
		 *
418
		 * The search keys from sub-managers can be normally used in the
419
		 * manager as well. It allows you to search for items of the manager
420
		 * using the search keys of the sub-managers to further limit the
421
		 * retrieved list of items.
422
		 *
423
		 * @param array List of sub-manager names
424
		 * @since 2014.03
425
		 * @category Developer
426
		 */
427
		$path = 'mshop/order/manager/base/submanagers';
428
		$default = array( 'address', 'coupon', 'product', 'service' );
429
430
		return $this->getSearchAttributesBase( $this->searchConfig, $path, $default, $withsub );
431
	}
432
433
434
	/**
435
	 * Returns a new manager for order base extensions.
436
	 *
437
	 * @param string $manager Name of the sub manager type in lower case
438
	 * @param string|null $name Name of the implementation, will be from configuration (or Default) if null
439
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager for different extensions, e.g address, coupon, product, service, etc.
440
	 */
441
	public function getSubManager( string $manager, string $name = null ) : \Aimeos\MShop\Common\Manager\Iface
442
	{
443
		/** mshop/order/manager/base/name
444
		 * Class name of the used order base manager implementation
445
		 *
446
		 * Each default order base manager can be replaced by an alternative imlementation.
447
		 * To use this implementation, you have to set the last part of the class
448
		 * name as configuration value so the manager factory knows which class it
449
		 * has to instantiate.
450
		 *
451
		 * For example, if the name of the default class is
452
		 *
453
		 *  \Aimeos\MShop\Order\Manager\Base\Standard
454
		 *
455
		 * and you want to replace it with your own version named
456
		 *
457
		 *  \Aimeos\MShop\Order\Manager\Base\Mybase
458
		 *
459
		 * then you have to set the this configuration option:
460
		 *
461
		 *  mshop/order/manager/base/name = Mybase
462
		 *
463
		 * The value is the last part of your own class name and it's case sensitive,
464
		 * so take care that the configuration value is exactly named like the last
465
		 * part of the class name.
466
		 *
467
		 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
468
		 * characters are possible! You should always start the last part of the class
469
		 * name with an upper case character and continue only with lower case characters
470
		 * or numbers. Avoid chamel case names like "MyBase"!
471
		 *
472
		 * @param string Last part of the class name
473
		 * @since 2014.03
474
		 * @category Developer
475
		 */
476
477
		/** mshop/order/manager/base/decorators/excludes
478
		 * Excludes decorators added by the "common" option from the order base manager
479
		 *
480
		 * Decorators extend the functionality of a class by adding new aspects
481
		 * (e.g. log what is currently done), executing the methods of the underlying
482
		 * class only in certain conditions (e.g. only for logged in users) or
483
		 * modify what is returned to the caller.
484
		 *
485
		 * This option allows you to remove a decorator added via
486
		 * "mshop/common/manager/decorators/default" before they are wrapped
487
		 * around the order base manager.
488
		 *
489
		 *  mshop/order/manager/base/decorators/excludes = array( 'decorator1' )
490
		 *
491
		 * This would remove the decorator named "decorator1" from the list of
492
		 * common decorators ("\Aimeos\MShop\Common\Manager\Decorator\*") added via
493
		 * "mshop/common/manager/decorators/default" for the order base manager.
494
		 *
495
		 * @param array List of decorator names
496
		 * @since 2014.03
497
		 * @category Developer
498
		 * @see mshop/common/manager/decorators/default
499
		 * @see mshop/order/manager/base/decorators/global
500
		 * @see mshop/order/manager/base/decorators/local
501
		 */
502
503
		/** mshop/order/manager/base/decorators/global
504
		 * Adds a list of globally available decorators only to the order base manager
505
		 *
506
		 * Decorators extend the functionality of a class by adding new aspects
507
		 * (e.g. log what is currently done), executing the methods of the underlying
508
		 * class only in certain conditions (e.g. only for logged in users) or
509
		 * modify what is returned to the caller.
510
		 *
511
		 * This option allows you to wrap global decorators
512
		 * ("\Aimeos\MShop\Common\Manager\Decorator\*") around the order base
513
		 * manager.
514
		 *
515
		 *  mshop/order/manager/base/decorators/global = array( 'decorator1' )
516
		 *
517
		 * This would add the decorator named "decorator1" defined by
518
		 * "\Aimeos\MShop\Common\Manager\Decorator\Decorator1" only to the order
519
		 * base manager.
520
		 *
521
		 * @param array List of decorator names
522
		 * @since 2014.03
523
		 * @category Developer
524
		 * @see mshop/common/manager/decorators/default
525
		 * @see mshop/order/manager/base/decorators/excludes
526
		 * @see mshop/order/manager/base/decorators/local
527
		 */
528
529
		/** mshop/order/manager/base/decorators/local
530
		 * Adds a list of local decorators only to the order base manager
531
		 *
532
		 * Decorators extend the functionality of a class by adding new aspects
533
		 * (e.g. log what is currently done), executing the methods of the underlying
534
		 * class only in certain conditions (e.g. only for logged in users) or
535
		 * modify what is returned to the caller.
536
		 *
537
		 * This option allows you to wrap local decorators
538
		 * ("\Aimeos\MShop\Order\Manager\Base\Decorator\*") around the order base
539
		 * manager.
540
		 *
541
		 *  mshop/order/manager/base/decorators/local = array( 'decorator2' )
542
		 *
543
		 * This would add the decorator named "decorator2" defined by
544
		 * "\Aimeos\MShop\Order\Manager\Base\Decorator\Decorator2" only to the
545
		 * order base manager.
546
		 *
547
		 * @param array List of decorator names
548
		 * @since 2014.03
549
		 * @category Developer
550
		 * @see mshop/common/manager/decorators/default
551
		 * @see mshop/order/manager/base/decorators/excludes
552
		 * @see mshop/order/manager/base/decorators/global
553
		 */
554
555
		return $this->getSubManagerBase( 'order', 'base/' . $manager, $name );
556
	}
557
558
559
	/**
560
	 * Adds or updates an order base item in the storage.
561
	 *
562
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $item Order base object (sub-items are not saved)
563
	 * @param bool $fetch True if the new ID should be returned in the item
564
	 * @return \Aimeos\MShop\Order\Item\Base\Iface $item Updated item including the generated ID
565
	 */
566
	public function saveItem( \Aimeos\MShop\Order\Item\Base\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Order\Item\Base\Iface
567
	{
568
		if( !$item->isModified() && !$item->locale()->isModified() ) {
569
			return $this->saveBasket( $item );
570
		}
571
572
		$context = $this->context();
573
		$conn = $context->db( $this->getResourceName() );
574
575
		$id = $item->getId();
576
		$date = date( 'Y-m-d H:i:s' );
577
		$columns = $this->object()->getSaveAttributes();
578
579
		if( $id === null )
580
		{
581
			/** mshop/order/manager/base/insert/mysql
582
			 * Inserts a new order record into the database table
583
			 *
584
			 * @see mshop/order/manager/base/insert/ansi
585
			 */
586
587
			/** mshop/order/manager/base/insert/ansi
588
			 * Inserts a new order record into the database table
589
			 *
590
			 * Items with no ID yet (i.e. the ID is NULL) will be created in
591
			 * the database and the newly created ID retrieved afterwards
592
			 * using the "newid" SQL statement.
593
			 *
594
			 * The SQL statement must be a string suitable for being used as
595
			 * prepared statement. It must include question marks for binding
596
			 * the values from the order item to the statement before they are
597
			 * sent to the database server. The number of question marks must
598
			 * be the same as the number of columns listed in the INSERT
599
			 * statement. The order of the columns must correspond to the
600
			 * order in the save() method, so the correct values are
601
			 * bound to the columns.
602
			 *
603
			 * The SQL statement should conform to the ANSI standard to be
604
			 * compatible with most relational database systems. This also
605
			 * includes using double quotes for table and column names.
606
			 *
607
			 * @param string SQL statement for inserting records
608
			 * @since 2014.03
609
			 * @category Developer
610
			 * @see mshop/order/manager/base/update/ansi
611
			 * @see mshop/order/manager/base/newid/ansi
612
			 * @see mshop/order/manager/base/delete/ansi
613
			 * @see mshop/order/manager/base/search/ansi
614
			 * @see mshop/order/manager/base/count/ansi
615
			 */
616
			$path = 'mshop/order/manager/base/insert';
617
			$sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ) );
0 ignored issues
show
Bug introduced by
It seems like $this->getSqlConfig($path) can also be of type array; however, parameter $sql of Aimeos\MShop\Common\Manager\Base::addSqlColumns() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

617
			$sql = $this->addSqlColumns( array_keys( $columns ), /** @scrutinizer ignore-type */ $this->getSqlConfig( $path ) );
Loading history...
618
		}
619
		else
620
		{
621
			/** mshop/order/manager/base/update/mysql
622
			 * Updates an existing order record in the database
623
			 *
624
			 * @see mshop/order/manager/base/update/ansi
625
			 */
626
627
			/** mshop/order/manager/base/update/ansi
628
			 * Updates an existing order record in the database
629
			 *
630
			 * Items which already have an ID (i.e. the ID is not NULL) will
631
			 * be updated in the database.
632
			 *
633
			 * The SQL statement must be a string suitable for being used as
634
			 * prepared statement. It must include question marks for binding
635
			 * the values from the order item to the statement before they are
636
			 * sent to the database server. The order of the columns must
637
			 * correspond to the order in the save() method, so the
638
			 * correct values are bound to the columns.
639
			 *
640
			 * The SQL statement should conform to the ANSI standard to be
641
			 * compatible with most relational database systems. This also
642
			 * includes using double quotes for table and column names.
643
			 *
644
			 * @param string SQL statement for updating records
645
			 * @since 2014.03
646
			 * @category Developer
647
			 * @see mshop/order/manager/base/insert/ansi
648
			 * @see mshop/order/manager/base/newid/ansi
649
			 * @see mshop/order/manager/base/delete/ansi
650
			 * @see mshop/order/manager/base/search/ansi
651
			 * @see mshop/order/manager/base/count/ansi
652
			 */
653
			$path = 'mshop/order/manager/base/update';
654
			$sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ), false );
655
		}
656
657
		$priceItem = $item->getPrice();
658
		$localeItem = $context->locale();
659
660
		$idx = 1;
661
		$stmt = $this->getCachedStatement( $conn, $path, $sql );
662
663
		foreach( $columns as $name => $entry ) {
664
			$stmt->bind( $idx++, $item->get( $name ), $entry->getInternalType() );
665
		}
666
667
		$stmt->bind( $idx++, $item->getCustomerId() );
668
		$stmt->bind( $idx++, $localeItem->getSiteItem()->getCode() );
669
		$stmt->bind( $idx++, $item->locale()->getLanguageId() );
670
		$stmt->bind( $idx++, $priceItem->getCurrencyId() );
671
		$stmt->bind( $idx++, $priceItem->getValue() );
672
		$stmt->bind( $idx++, $priceItem->getCosts() );
673
		$stmt->bind( $idx++, $priceItem->getRebate() );
674
		$stmt->bind( $idx++, $priceItem->getTaxValue() );
675
		$stmt->bind( $idx++, $priceItem->getTaxFlag(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
676
		$stmt->bind( $idx++, $item->getCustomerReference() );
677
		$stmt->bind( $idx++, $item->getComment() );
678
		$stmt->bind( $idx++, $date ); // mtime
679
		$stmt->bind( $idx++, $context->editor() );
680
		$stmt->bind( $idx++, $localeItem->getSiteId() );
681
682
		if( $id !== null ) {
683
			$stmt->bind( $idx++, $id, \Aimeos\Base\DB\Statement\Base::PARAM_INT );
684
		} else {
685
			$stmt->bind( $idx++, $date ); // ctime
686
		}
687
688
		$stmt->execute()->finish();
689
690
		if( $id === null && $fetch === true )
691
		{
692
			/** mshop/order/manager/base/newid/mysql
693
			 * Retrieves the ID generated by the database when inserting a new record
694
			 *
695
			 * @see mshop/order/manager/base/newid/ansi
696
			 */
697
698
			/** mshop/order/manager/base/newid/ansi
699
			 * Retrieves the ID generated by the database when inserting a new record
700
			 *
701
			 * As soon as a new record is inserted into the database table,
702
			 * the database server generates a new and unique identifier for
703
			 * that record. This ID can be used for retrieving, updating and
704
			 * deleting that specific record from the table again.
705
			 *
706
			 * For MySQL:
707
			 *  SELECT LAST_INSERT_ID()
708
			 * For PostgreSQL:
709
			 *  SELECT currval('seq_mord_id')
710
			 * For SQL Server:
711
			 *  SELECT SCOPE_IDENTITY()
712
			 * For Oracle:
713
			 *  SELECT "seq_mord_id".CURRVAL FROM DUAL
714
			 *
715
			 * There's no way to retrive the new ID by a SQL statements that
716
			 * fits for most database servers as they implement their own
717
			 * specific way.
718
			 *
719
			 * @param string SQL statement for retrieving the last inserted record ID
720
			 * @since 2014.03
721
			 * @category Developer
722
			 * @see mshop/order/manager/base/insert/ansi
723
			 * @see mshop/order/manager/base/update/ansi
724
			 * @see mshop/order/manager/base/delete/ansi
725
			 * @see mshop/order/manager/base/search/ansi
726
			 * @see mshop/order/manager/base/count/ansi
727
			 */
728
			$path = 'mshop/order/manager/base/newid';
729
			$id = $this->newId( $conn, $path );
730
		}
731
732
		$item->setId( $id );
733
734
		return $this->saveBasket( $item );
735
	}
736
737
738
	/**
739
	 * Search for orders based on the given criteria.
740
	 *
741
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria object
742
	 * @param string[] $ref List of domains to fetch list items and referenced items for, e.g.
743
	 *	"order/base/address", "order/base/coupon", "order/base/product", "order/base/service"
744
	 * @param int|null &$total Number of items that are available in total
745
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Order\Item\Base\Iface with ids as keys
746
	 */
747
	public function search( \Aimeos\Base\Criteria\Iface $search, array $ref = [], int &$total = null ) : \Aimeos\Map
748
	{
749
		$context = $this->context();
750
		$priceManager = \Aimeos\MShop::create( $context, 'price' );
751
		$localeManager = \Aimeos\MShop::create( $context, 'locale' );
752
753
		$conn = $context->db( $this->getResourceName() );
754
		$map = $items = $custItems = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $items is dead and can be removed.
Loading history...
755
756
			$required = array( 'order.base' );
757
758
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
759
			$level = $context->config()->get( 'mshop/order/manager/sitemode', $level );
760
761
			/** mshop/order/manager/base/search/mysql
762
			 * Retrieves the records matched by the given criteria in the database
763
			 *
764
			 * @see mshop/order/manager/base/search/ansi
765
			 */
766
767
			/** mshop/order/manager/base/search/ansi
768
			 * Retrieves the records matched by the given criteria in the database
769
			 *
770
			 * Fetches the records matched by the given criteria from the order
771
			 * database. The records must be from one of the sites that are
772
			 * configured via the context item. If the current site is part of
773
			 * a tree of sites, the SELECT statement can retrieve all records
774
			 * from the current site and the complete sub-tree of sites.
775
			 *
776
			 * As the records can normally be limited by criteria from sub-managers,
777
			 * their tables must be joined in the SQL context. This is done by
778
			 * using the "internaldeps" property from the definition of the ID
779
			 * column of the sub-managers. These internal dependencies specify
780
			 * the JOIN between the tables and the used columns for joining. The
781
			 * ":joins" placeholder is then replaced by the JOIN strings from
782
			 * the sub-managers.
783
			 *
784
			 * To limit the records matched, conditions can be added to the given
785
			 * criteria object. It can contain comparisons like column names that
786
			 * must match specific values which can be combined by AND, OR or NOT
787
			 * operators. The resulting string of SQL conditions replaces the
788
			 * ":cond" placeholder before the statement is sent to the database
789
			 * server.
790
			 *
791
			 * If the records that are retrieved should be ordered by one or more
792
			 * columns, the generated string of column / sort direction pairs
793
			 * replaces the ":order" placeholder. In case no ordering is required,
794
			 * the complete ORDER BY part including the "\/*-orderby*\/...\/*orderby-*\/"
795
			 * markers is removed to speed up retrieving the records. Columns of
796
			 * sub-managers can also be used for ordering the result set but then
797
			 * no index can be used.
798
			 *
799
			 * The number of returned records can be limited and can start at any
800
			 * number between the begining and the end of the result set. For that
801
			 * the ":size" and ":start" placeholders are replaced by the
802
			 * corresponding values from the criteria object. The default values
803
			 * are 0 for the start and 100 for the size value.
804
			 *
805
			 * The SQL statement should conform to the ANSI standard to be
806
			 * compatible with most relational database systems. This also
807
			 * includes using double quotes for table and column names.
808
			 *
809
			 * @param string SQL statement for searching items
810
			 * @since 2014.03
811
			 * @category Developer
812
			 * @see mshop/order/manager/base/insert/ansi
813
			 * @see mshop/order/manager/base/update/ansi
814
			 * @see mshop/order/manager/base/newid/ansi
815
			 * @see mshop/order/manager/base/delete/ansi
816
			 * @see mshop/order/manager/base/count/ansi
817
			 */
818
			$cfgPathSearch = 'mshop/order/manager/base/search';
819
820
			/** mshop/order/manager/base/count/mysql
821
			 * Counts the number of records matched by the given criteria in the database
822
			 *
823
			 * @see mshop/order/manager/base/count/ansi
824
			 */
825
826
			/** mshop/order/manager/base/count/ansi
827
			 * Counts the number of records matched by the given criteria in the database
828
			 *
829
			 * Counts all records matched by the given criteria from the order
830
			 * database. The records must be from one of the sites that are
831
			 * configured via the context item. If the current site is part of
832
			 * a tree of sites, the statement can count all records from the
833
			 * current site and the complete sub-tree of sites.
834
			 *
835
			 * As the records can normally be limited by criteria from sub-managers,
836
			 * their tables must be joined in the SQL context. This is done by
837
			 * using the "internaldeps" property from the definition of the ID
838
			 * column of the sub-managers. These internal dependencies specify
839
			 * the JOIN between the tables and the used columns for joining. The
840
			 * ":joins" placeholder is then replaced by the JOIN strings from
841
			 * the sub-managers.
842
			 *
843
			 * To limit the records matched, conditions can be added to the given
844
			 * criteria object. It can contain comparisons like column names that
845
			 * must match specific values which can be combined by AND, OR or NOT
846
			 * operators. The resulting string of SQL conditions replaces the
847
			 * ":cond" placeholder before the statement is sent to the database
848
			 * server.
849
			 *
850
			 * Both, the strings for ":joins" and for ":cond" are the same as for
851
			 * the "search" SQL statement.
852
			 *
853
			 * Contrary to the "search" statement, it doesn't return any records
854
			 * but instead the number of records that have been found. As counting
855
			 * thousands of records can be a long running task, the maximum number
856
			 * of counted records is limited for performance reasons.
857
			 *
858
			 * The SQL statement should conform to the ANSI standard to be
859
			 * compatible with most relational database systems. This also
860
			 * includes using double quotes for table and column names.
861
			 *
862
			 * @param string SQL statement for counting items
863
			 * @since 2014.03
864
			 * @category Developer
865
			 * @see mshop/order/manager/base/insert/ansi
866
			 * @see mshop/order/manager/base/update/ansi
867
			 * @see mshop/order/manager/base/newid/ansi
868
			 * @see mshop/order/manager/base/delete/ansi
869
			 * @see mshop/order/manager/base/search/ansi
870
			 */
871
			$cfgPathCount = 'mshop/order/manager/base/count';
872
873
			$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount,
874
				$required, $total, $level );
875
876
			while( ( $row = $results->fetch() ) !== null ) {
877
				$map[$row['order.base.id']] = $row;
878
			}
879
880
		if( ( isset( $ref['customer'] ) || in_array( 'customer', $ref ) )
881
			&& !( $ids = map( $map )->col( 'order.base.customerid' )->filter() )->empty()
882
		) {
883
			$manager = \Aimeos\MShop::create( $context, 'customer' );
884
			$search = $manager->filter()->slice( 0, count( $ids ) )->add( ['customer.id' => $ids] );
885
			$custItems = $manager->search( $search, $ref );
886
		}
887
888
		foreach( $map as $id => $row )
889
		{
890
			// don't use fromArray() or set*() methods to avoid recalculation of tax value
891
			$price = $priceManager->create( [
892
				'price.currencyid' => $row['order.base.currencyid'],
893
				'price.value' => $row['order.base.price'],
894
				'price.costs' => $row['order.base.costs'],
895
				'price.rebate' => $row['order.base.rebate'],
896
				'price.taxflag' => $row['order.base.taxflag'],
897
				'price.taxvalue' => $row['order.base.taxvalue'],
898
			] );
899
900
			// you may need the site object! take care!
901
			$localeItem = $localeManager->create( [
902
				'locale.currencyid' => $row['order.base.currencyid'],
903
				'locale.languageid' => $row['order.base.languageid'],
904
				'locale.siteid' => $row['order.base.siteid'],
905
			] );
906
907
			$map[$id] = [$price, $localeItem, $row, $custItems[$row['order.base.customerid'] ?? null] ?? null];
908
		}
909
910
		return $this->buildItems( $map, $ref );
911
	}
912
913
914
	/**
915
	 * Creates a new basket containing the items from the order excluding the coupons.
916
	 * If the last parameter is ture, the items will be marked as new and
917
	 * modified so an additional order is stored when the basket is saved.
918
	 *
919
	 * @param string $id Base ID of the order to load
920
	 * @param array $ref Basket parts that should be loaded too
921
	 * @param bool $fresh Create a new basket by copying the existing one and remove IDs
922
	 * @param bool $default True to use default criteria, false for no limitation
923
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Basket including all items
924
	 */
925
	public function load( string $id, array $ref = ['order/base/address', 'order/base/coupon', 'order/base/product', 'order/base/service'],
926
		bool $fresh = false, bool $default = false ) : \Aimeos\MShop\Order\Item\Base\Iface
927
	{
928
		$search = $this->object()->filter( $default );
929
		$expr = [
930
			$search->compare( '==', 'order.base.id', $id ),
931
			$search->getConditions(),
932
		];
933
		$search->setConditions( $search->and( $expr ) );
934
935
		$context = $this->context();
936
		$conn = $context->db( $this->getResourceName() );
937
938
		$sitelevel = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
939
		$sitelevel = $context->config()->get( 'mshop/order/manager/sitemode', $sitelevel );
940
941
		$cfgPathSearch = 'mshop/order/manager/base/search';
942
		$cfgPathCount = 'mshop/order/manager/base/count';
943
		$required = array( 'order.base' );
944
		$total = null;
945
946
		$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount, $required, $total, $sitelevel );
947
948
		if( ( $row = $results->fetch() ) === null )
949
		{
950
			$msg = $this->context()->translate( 'mshop', 'Order base item with order ID "%1$s" not found' );
951
			throw new \Aimeos\MShop\Order\Exception( sprintf( $msg, $id ) );
952
		}
953
		$results->finish();
954
955
		$priceManager = \Aimeos\MShop::create( $context, 'price' );
956
		$localeManager = \Aimeos\MShop::create( $context, 'locale' );
957
958
		$price = $priceManager->create( [
959
			'price.currencyid' => $row['order.base.currencyid'],
960
			'price.value' => $row['order.base.price'],
961
			'price.costs' => $row['order.base.costs'],
962
			'price.rebate' => $row['order.base.rebate'],
963
			'price.taxflag' => $row['order.base.taxflag'],
964
			'price.taxvalue' => $row['order.base.taxvalue'],
965
		] );
966
967
		// you may need the site object! take care!
968
		$localeItem = $localeManager->create( [
969
			'locale.languageid' => $row['order.base.languageid'],
970
			'locale.currencyid' => $row['order.base.currencyid'],
971
			'locale.siteid' => $row['order.base.siteid'],
972
		] );
973
974
		if( $fresh === false ) {
975
			$basket = $this->loadItems( $id, $price, $localeItem, $row, $ref );
976
		} else {
977
			$basket = $this->loadFresh( $id, $price, $localeItem, $row, $ref );
978
		}
979
980
		return $basket;
981
	}
982
983
984
	/**
985
	 * Creates the order base item objects from the map and adds the referenced items
986
	 *
987
	 * @param array $map Associative list of order base IDs as keys and list of price/locale/row as values
988
	 * @param string[] $ref Domain items that should be added as well, e.g.
989
	 *	"order/base/address", "order/base/coupon", "order/base/product", "order/base/service"
990
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Order\Item\Base\Iface with IDs as keys
991
	 */
992
	protected function buildItems( array $map, array $ref ) : \Aimeos\Map
993
	{
994
		$items = [];
995
		$baseIds = array_keys( $map );
996
		$addressMap = $couponMap = $productMap = $serviceMap = [];
997
998
		if( in_array( 'order/base/address', $ref ) ) {
999
			$addressMap = $this->getAddresses( $baseIds );
1000
		}
1001
1002
		if( in_array( 'order/base/product', $ref ) ) {
1003
			$productMap = $this->getProducts( $baseIds );
1004
		}
1005
1006
		if( in_array( 'order/base/coupon', $ref ) ) {
1007
			$couponMap = $this->getCoupons( $baseIds, false, $productMap );
1008
		}
1009
1010
		if( in_array( 'order/base/service', $ref ) ) {
1011
			$serviceMap = $this->getServices( $baseIds );
1012
		}
1013
1014
		foreach( $map as $id => $list )
1015
		{
1016
			list( $price, $locale, $row, $custItem ) = $list;
1017
1018
			$addresses = $addressMap[$id] ?? [];
1019
			$coupons = $couponMap[$id] ?? [];
1020
			$products = $productMap[$id] ?? [];
1021
			$services = $serviceMap[$id] ?? [];
1022
1023
			$item = $this->createItemBase( $price, $locale, $row, $products, $addresses, $services, $coupons, $custItem );
1024
1025
			if( $item = $this->applyFilter( $item ) ) {
1026
				$items[$id] = $item;
1027
			}
1028
		}
1029
1030
		return map( $items );
1031
	}
1032
1033
1034
	/**
1035
	 * Returns a new and empty order base item (shopping basket).
1036
	 *
1037
	 * @param \Aimeos\MShop\Price\Item\Iface $price Default price of the basket (usually 0.00)
1038
	 * @param \Aimeos\MShop\Locale\Item\Iface $locale Locale item containing the site, language and currency
1039
	 * @param array $values Associative list of key/value pairs containing, e.g. the order or user ID
1040
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface[] $products List of ordered product items
1041
	 * @param \Aimeos\MShop\Order\Item\Base\Address\Iface[] $addresses List of order address items
1042
	 * @param \Aimeos\MShop\Order\Item\Base\Service\Iface[] $services List of order serviceitems
1043
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface[] $coupons Associative list of coupon codes as keys and items as values
1044
	 * @param \Aimeos\MShop\Customer\Item\Iface|null $custItem Customer item object if requested
1045
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order base object
1046
	 */
1047
	protected function createItemBase( \Aimeos\MShop\Price\Item\Iface $price, \Aimeos\MShop\Locale\Item\Iface $locale,
1048
		array $values = [], array $products = [], array $addresses = [], array $services = [], array $coupons = [],
1049
		?\Aimeos\MShop\Customer\Item\Iface $custItem = null ) : \Aimeos\MShop\Order\Item\Base\Iface
1050
	{
1051
		return new \Aimeos\MShop\Order\Item\Base\Standard( $price, $locale,
1052
			$values, $products, $addresses, $services, $coupons, $custItem );
1053
	}
1054
1055
1056
	/**
1057
	 * Saves the modified basket content
1058
	 *
1059
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket content
1060
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Saved basket content
1061
	 */
1062
	protected function saveBasket( \Aimeos\MShop\Order\Item\Base\Iface $basket ) : \Aimeos\MShop\Order\Item\Base\Iface
1063
	{
1064
		$this->saveAddresses( $basket );
1065
		$this->saveServices( $basket );
1066
		$this->saveProducts( $basket );
1067
		$this->saveCoupons( $basket );
1068
1069
		return $basket;
1070
	}
1071
}
1072