Passed
Push — master ( 62fc03...bcc204 )
by Aimeos
04:26
created

Standard   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 1120
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
wmc 44
eloc 322
c 5
b 1
f 0
dl 0
loc 1120
rs 8.8798

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 23 1
A clear() 0 10 2
A aggregate() 0 87 1
A create() 0 13 1
A getResourceType() 0 6 1
A delete() 0 34 1
A get() 0 3 1
A getSearchAttributes() 0 22 1
C saveItem() 0 188 10
A getSubManager() 0 3 1
A addStatus() 0 21 5
B buildItems() 0 42 10
A search() 0 184 3
A filter() 0 16 3
A createInvoiceNumber() 0 27 2
A saveBasket() 0 8 1

How to fix   Complexity   

Complex Class

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

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

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

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

667
			$sql = $this->addSqlColumns( array_keys( $columns ), /** @scrutinizer ignore-type */ $this->getSqlConfig( $path ) );
Loading history...
668
		}
669
		else
670
		{
671
			/** mshop/order/manager/update/mysql
672
			 * Updates an existing order record in the database
673
			 *
674
			 * @see mshop/order/manager/update/ansi
675
			 */
676
677
			/** mshop/order/manager/update/ansi
678
			 * Updates an existing order record in the database
679
			 *
680
			 * Items which already have an ID (i.e. the ID is not NULL) will
681
			 * be updated in the database.
682
			 *
683
			 * The SQL statement must be a string suitable for being used as
684
			 * prepared statement. It must include question marks for binding
685
			 * the values from the order item to the statement before they are
686
			 * sent to the database server. The order of the columns must
687
			 * correspond to the order in the save() method, so the
688
			 * correct values are bound to the columns.
689
			 *
690
			 * The SQL statement should conform to the ANSI standard to be
691
			 * compatible with most relational database systems. This also
692
			 * includes using double quotes for table and column names.
693
			 *
694
			 * @param string SQL statement for updating records
695
			 * @since 2014.03
696
			 * @see mshop/order/manager/insert/ansi
697
			 * @see mshop/order/manager/newid/ansi
698
			 * @see mshop/order/manager/delete/ansi
699
			 * @see mshop/order/manager/search/ansi
700
			 * @see mshop/order/manager/count/ansi
701
			 */
702
			$path = 'mshop/order/manager/update';
703
			$sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ), false );
704
		}
705
706
		$idx = 1;
707
		$priceItem = $item->getPrice();
708
		$stmt = $this->getCachedStatement( $conn, $path, $sql );
709
710
		foreach( $columns as $name => $entry ) {
711
			$stmt->bind( $idx++, $item->get( $name ), \Aimeos\Base\Criteria\SQL::type( $entry->getType() ) );
712
		}
713
714
		$stmt->bind( $idx++, $item->getInvoiceNumber() );
715
		$stmt->bind( $idx++, $item->getChannel() );
716
		$stmt->bind( $idx++, $item->getDatePayment() );
717
		$stmt->bind( $idx++, $item->getDateDelivery() );
718
		$stmt->bind( $idx++, $item->getStatusDelivery(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
719
		$stmt->bind( $idx++, $item->getStatusPayment(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
720
		$stmt->bind( $idx++, $item->getRelatedId(), \Aimeos\Base\DB\Statement\Base::PARAM_STR );
721
		$stmt->bind( $idx++, $item->getCustomerId() );
722
		$stmt->bind( $idx++, $context->locale()->getSiteItem()->getCode() );
723
		$stmt->bind( $idx++, $item->locale()->getLanguageId() );
724
		$stmt->bind( $idx++, $priceItem->getCurrencyId() );
725
		$stmt->bind( $idx++, $priceItem->getValue() );
726
		$stmt->bind( $idx++, $priceItem->getCosts() );
727
		$stmt->bind( $idx++, $priceItem->getRebate() );
728
		$stmt->bind( $idx++, $priceItem->getTaxValue() );
729
		$stmt->bind( $idx++, $priceItem->getTaxFlag(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
730
		$stmt->bind( $idx++, $item->getCustomerReference() );
731
		$stmt->bind( $idx++, $item->getComment() );
732
		$stmt->bind( $idx++, $context->datetime() ); // mtime
733
		$stmt->bind( $idx++, $context->editor() );
734
735
		if( $id !== null ) {
736
			$stmt->bind( $idx++, $context->locale()->getSiteId() . '%' );
737
			$stmt->bind( $idx++, $id, \Aimeos\Base\DB\Statement\Base::PARAM_INT );
738
		} else {
739
			$date = date_create_from_format( 'Y-m-d H:i:s', $context->datetime() );
740
			$stmt->bind( $idx++, $this->siteId( $item->getSiteId(), \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE ) );
741
			$stmt->bind( $idx++, $context->datetime() ); // ctime
742
			$stmt->bind( $idx++, $date->format( 'Y-m-d' ) ); // cdate
743
			$stmt->bind( $idx++, $date->format( 'Y-m' ) ); // cmonth
744
			$stmt->bind( $idx++, $date->format( 'Y-W' ) ); // cweek
745
			$stmt->bind( $idx++, $date->format( 'w' ) ); // cwday
746
			$stmt->bind( $idx++, $date->format( 'G' ) ); // chour
747
		}
748
749
		$stmt->execute()->finish();
750
751
		if( $id === null && $fetch === true )
752
		{
753
			/** mshop/order/manager/newid/mysql
754
			 * Retrieves the ID generated by the database when inserting a new record
755
			 *
756
			 * @see mshop/order/manager/newid/ansi
757
			 */
758
759
			/** mshop/order/manager/newid/ansi
760
			 * Retrieves the ID generated by the database when inserting a new record
761
			 *
762
			 * As soon as a new record is inserted into the database table,
763
			 * the database server generates a new and unique identifier for
764
			 * that record. This ID can be used for retrieving, updating and
765
			 * deleting that specific record from the table again.
766
			 *
767
			 * For MySQL:
768
			 *  SELECT LAST_INSERT_ID()
769
			 * For PostgreSQL:
770
			 *  SELECT currval('seq_mord_id')
771
			 * For SQL Server:
772
			 *  SELECT SCOPE_IDENTITY()
773
			 * For Oracle:
774
			 *  SELECT "seq_mord_id".CURRVAL FROM DUAL
775
			 *
776
			 * There's no way to retrive the new ID by a SQL statements that
777
			 * fits for most database servers as they implement their own
778
			 * specific way.
779
			 *
780
			 * @param string SQL statement for retrieving the last inserted record ID
781
			 * @since 2014.03
782
			 * @see mshop/order/manager/insert/ansi
783
			 * @see mshop/order/manager/update/ansi
784
			 * @see mshop/order/manager/delete/ansi
785
			 * @see mshop/order/manager/search/ansi
786
			 * @see mshop/order/manager/count/ansi
787
			 */
788
			$path = 'mshop/order/manager/newid';
789
			$id = $this->newId( $conn, $path );
790
		}
791
792
		$item->setId( $id );
793
794
		$this->addStatus( $item );
795
796
		return $this->saveBasket( $item );
797
	}
798
799
800
	/**
801
	 * Searches for orders based on the given criteria.
802
	 *
803
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria object
804
	 * @param string[] $ref List of domains to fetch list items and referenced items for
805
	 * @param int|null &$total Number of items that are available in total
806
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Order\Item\Iface with ids as keys
807
	 */
808
	public function search( \Aimeos\Base\Criteria\Iface $search, array $ref = [], int &$total = null ) : \Aimeos\Map
809
	{
810
		$context = $this->context();
811
		$conn = $context->db( $this->getResourceName() );
812
813
		$priceManager = \Aimeos\MShop::create( $context, 'price' );
814
		$localeManager = \Aimeos\MShop::create( $context, 'locale' );
815
816
		$map = $custItems = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $custItems is dead and can be removed.
Loading history...
817
		$required = ['order'];
818
819
		/** mshop/order/manager/sitemode
820
		 * Mode how items from levels below or above in the site tree are handled
821
		 *
822
		 * By default, only items from the current site are fetched from the
823
		 * storage. If the ai-sites extension is installed, you can create a
824
		 * tree of sites. Then, this setting allows you to define for the
825
		 * whole order domain if items from parent sites are inherited,
826
		 * sites from child sites are aggregated or both.
827
		 *
828
		 * Available constants for the site mode are:
829
		 * * 0 = only items from the current site
830
		 * * 1 = inherit items from parent sites
831
		 * * 2 = aggregate items from child sites
832
		 * * 3 = inherit and aggregate items at the same time
833
		 *
834
		 * You also need to set the mode in the locale manager
835
		 * (mshop/locale/manager/sitelevel) to one of the constants.
836
		 * If you set it to the same value, it will work as described but you
837
		 * can also use different modes. For example, if inheritance and
838
		 * aggregation is configured the locale manager but only inheritance
839
		 * in the domain manager because aggregating items makes no sense in
840
		 * this domain, then items wil be only inherited. Thus, you have full
841
		 * control over inheritance and aggregation in each domain.
842
		 *
843
		 * @param int Constant from Aimeos\MShop\Locale\Manager\Base class
844
		 * @since 2018.01
845
		 * @see mshop/locale/manager/sitelevel
846
		 */
847
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
848
		$level = $context->config()->get( 'mshop/order/manager/sitemode', $level );
849
850
		/** mshop/order/manager/search/mysql
851
		 * Retrieves the records matched by the given criteria in the database
852
		 *
853
		 * @see mshop/order/manager/search/ansi
854
		 */
855
856
		/** mshop/order/manager/search/ansi
857
		 * Retrieves the records matched by the given criteria in the database
858
		 *
859
		 * Fetches the records matched by the given criteria from the order
860
		 * database. The records must be from one of the sites that are
861
		 * configured via the context item. If the current site is part of
862
		 * a tree of sites, the SELECT statement can retrieve all records
863
		 * from the current site and the complete sub-tree of sites.
864
		 *
865
		 * As the records can normally be limited by criteria from sub-managers,
866
		 * their tables must be joined in the SQL context. This is done by
867
		 * using the "internaldeps" property from the definition of the ID
868
		 * column of the sub-managers. These internal dependencies specify
869
		 * the JOIN between the tables and the used columns for joining. The
870
		 * ":joins" placeholder is then replaced by the JOIN strings from
871
		 * the sub-managers.
872
		 *
873
		 * To limit the records matched, conditions can be added to the given
874
		 * criteria object. It can contain comparisons like column names that
875
		 * must match specific values which can be combined by AND, OR or NOT
876
		 * operators. The resulting string of SQL conditions replaces the
877
		 * ":cond" placeholder before the statement is sent to the database
878
		 * server.
879
		 *
880
		 * If the records that are retrieved should be ordered by one or more
881
		 * columns, the generated string of column / sort direction pairs
882
		 * replaces the ":order" placeholder. Columns of
883
		 * sub-managers can also be used for ordering the result set but then
884
		 * no index can be used.
885
		 *
886
		 * The number of returned records can be limited and can start at any
887
		 * number between the begining and the end of the result set. For that
888
		 * the ":size" and ":start" placeholders are replaced by the
889
		 * corresponding values from the criteria object. The default values
890
		 * are 0 for the start and 100 for the size value.
891
		 *
892
		 * The SQL statement should conform to the ANSI standard to be
893
		 * compatible with most relational database systems. This also
894
		 * includes using double quotes for table and column names.
895
		 *
896
		 * @param string SQL statement for searching items
897
		 * @since 2014.03
898
		 * @see mshop/order/manager/insert/ansi
899
		 * @see mshop/order/manager/update/ansi
900
		 * @see mshop/order/manager/newid/ansi
901
		 * @see mshop/order/manager/delete/ansi
902
		 * @see mshop/order/manager/count/ansi
903
		 */
904
		$cfgPathSearch = 'mshop/order/manager/search';
905
906
		/** mshop/order/manager/count/mysql
907
		 * Counts the number of records matched by the given criteria in the database
908
		 *
909
		 * @see mshop/order/manager/count/ansi
910
		 */
911
912
		/** mshop/order/manager/count/ansi
913
		 * Counts the number of records matched by the given criteria in the database
914
		 *
915
		 * Counts all records matched by the given criteria from the order
916
		 * database. The records must be from one of the sites that are
917
		 * configured via the context item. If the current site is part of
918
		 * a tree of sites, the statement can count all records from the
919
		 * current site and the complete sub-tree of sites.
920
		 *
921
		 * As the records can normally be limited by criteria from sub-managers,
922
		 * their tables must be joined in the SQL context. This is done by
923
		 * using the "internaldeps" property from the definition of the ID
924
		 * column of the sub-managers. These internal dependencies specify
925
		 * the JOIN between the tables and the used columns for joining. The
926
		 * ":joins" placeholder is then replaced by the JOIN strings from
927
		 * the sub-managers.
928
		 *
929
		 * To limit the records matched, conditions can be added to the given
930
		 * criteria object. It can contain comparisons like column names that
931
		 * must match specific values which can be combined by AND, OR or NOT
932
		 * operators. The resulting string of SQL conditions replaces the
933
		 * ":cond" placeholder before the statement is sent to the database
934
		 * server.
935
		 *
936
		 * Both, the strings for ":joins" and for ":cond" are the same as for
937
		 * the "search" SQL statement.
938
		 *
939
		 * Contrary to the "search" statement, it doesn't return any records
940
		 * but instead the number of records that have been found. As counting
941
		 * thousands of records can be a long running task, the maximum number
942
		 * of counted records is limited for performance reasons.
943
		 *
944
		 * The SQL statement should conform to the ANSI standard to be
945
		 * compatible with most relational database systems. This also
946
		 * includes using double quotes for table and column names.
947
		 *
948
		 * @param string SQL statement for counting items
949
		 * @since 2014.03
950
		 * @see mshop/order/manager/insert/ansi
951
		 * @see mshop/order/manager/update/ansi
952
		 * @see mshop/order/manager/newid/ansi
953
		 * @see mshop/order/manager/delete/ansi
954
		 * @see mshop/order/manager/search/ansi
955
		 */
956
		$cfgPathCount = 'mshop/order/manager/count';
957
958
		$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount,
959
			$required, $total, $level );
960
961
		try
962
		{
963
			while( $row = $results->fetch() )
964
			{
965
				$row['.price'] = $priceManager->create( [
966
					'price.currencyid' => $row['order.currencyid'],
967
					'price.value' => $row['order.price'],
968
					'price.costs' => $row['order.costs'],
969
					'price.rebate' => $row['order.rebate'],
970
					'price.taxflag' => $row['order.taxflag'],
971
					'price.taxvalue' => $row['order.taxvalue'],
972
					'price.siteid' => $row['order.siteid'],
973
				] );
974
975
				// you may need the site object! take care!
976
				$row['.locale'] = $localeManager->create( [
977
					'locale.currencyid' => $row['order.currencyid'],
978
					'locale.languageid' => $row['order.languageid'],
979
					'locale.siteid' => $row['order.siteid'],
980
				] );
981
982
				$map[$row['order.id']] = $row;
983
			}
984
		}
985
		catch( \Exception $e )
986
		{
987
			$results->finish();
988
			throw $e;
989
		}
990
991
		return $this->buildItems( $map, $ref );
992
	}
993
994
995
	/**
996
	 * Returns a new manager for order extensions
997
	 *
998
	 * @param string $manager Name of the sub manager type in lower case
999
	 * @param string|null $name Name of the implementation, will be from configuration (or Default) if null
1000
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager for different extensions, e.g base, etc.
1001
	 */
1002
	public function getSubManager( string $manager, string $name = null ) : \Aimeos\MShop\Common\Manager\Iface
1003
	{
1004
		return $this->getSubManagerBase( 'order', $manager, $name );
1005
	}
1006
1007
1008
	/**
1009
	 * Adds the new payment and delivery values to the order status log.
1010
	 *
1011
	 * @param \Aimeos\MShop\Order\Item\Iface $item Order item object
1012
	 */
1013
	protected function addStatus( \Aimeos\MShop\Order\Item\Iface $item )
1014
	{
1015
		$statusManager = \Aimeos\MShop::create( $this->context(), 'order/status' );
1016
1017
		$statusItem = $statusManager->create();
1018
		$statusItem->setParentId( $item->getId() );
1019
1020
		if( ( $status = $item->get( '.statuspayment' ) ) !== null && $status != $item->getStatusPayment() )
1021
		{
1022
			$statusItem->setId( null )->setValue( $item->getStatusPayment() )
1023
				->setType( \Aimeos\MShop\Order\Item\Status\Base::STATUS_PAYMENT );
1024
1025
			$statusManager->save( $statusItem, false );
1026
		}
1027
1028
		if( ( $status = $item->get( '.statusdelivery' ) ) !== null && $status != $item->getStatusDelivery() )
1029
		{
1030
			$statusItem->setId( null )->setValue( $item->getStatusDelivery() )
1031
				->setType( \Aimeos\MShop\Order\Item\Status\Base::STATUS_DELIVERY );
1032
1033
			$statusManager->save( $statusItem, false );
1034
		}
1035
	}
1036
1037
1038
	/**
1039
	 * Creates the order base item objects from the map and adds the referenced items
1040
	 *
1041
	 * @param array $map Associative list of order base IDs as keys and list of price/locale/row as values
1042
	 * @param string[] $ref Domain items that should be added as well, e.g.
1043
	 *	"order/address", "order/coupon", "order/product", "order/service"
1044
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Order\Item\Iface with IDs as keys
1045
	 */
1046
	protected function buildItems( array $map, array $ref ) : \Aimeos\Map
1047
	{
1048
		$ids = array_keys( $map );
1049
		$items = $addresses = $customers = $coupons = $products = $services = [];
1050
1051
		if( $this->hasRef( $ref, 'customer' ) && !( $cids = map( $map )->col( 'order.customerid' )->filter() )->empty() )
1052
		{
1053
			$manager = \Aimeos\MShop::create( $this->context(), 'customer' );
1054
			$search = $manager->filter()->slice( 0, 0x7fffffff )->add( ['customer.id' => $cids] );
1055
			$customers = $manager->search( $search, $ref );
1056
		}
1057
1058
		if( $this->hasRef( $ref, 'order/address' ) ) {
1059
			$addresses = $this->getAddresses( $ids, $ref );
1060
		}
1061
1062
		if( $this->hasRef( $ref, 'order/product' ) || $this->hasRef( $ref, 'order/coupon' ) ) {
1063
			$products = $this->getProducts( $ids, $ref );
1064
		}
1065
1066
		if( $this->hasRef( $ref, 'order/coupon' ) ) {
1067
			$coupons = $this->getCoupons( $ids, $ref );
1068
		}
1069
1070
		if( $this->hasRef( $ref, 'order/service' ) ) {
1071
			$services = $this->getServices( $ids, $ref );
1072
		}
1073
1074
		foreach( $map as $id => $row )
1075
		{
1076
			$row['.customer'] = $customers[$row['order.customerid']] ?? null;
1077
			$row['.addresses'] = $addresses[$id] ?? [];
1078
			$row['.coupons'] = $coupons[$id] ?? [];
1079
			$row['.products'] = $products[$id] ?? [];
1080
			$row['.services'] = $services[$id] ?? [];
1081
1082
			if( $item = $this->applyFilter( $item = $this->create( $row ) ) ) {
1083
				$items[$id] = $item;
1084
			}
1085
		}
1086
1087
		return map( $items );
1088
	}
1089
1090
1091
	/**
1092
	 * Creates a new invoice number for the passed order and site.
1093
	 *
1094
	 * @param \Aimeos\MShop\Order\Item\Iface $item Order item with necessary values
1095
	 * @return string Unique invoice number for the current site
1096
	 */
1097
	public function createInvoiceNumber( \Aimeos\MShop\Order\Item\Iface $item ) : string
1098
	{
1099
		$context = $this->context();
1100
		$siteId = $context->locale()->getSiteId();
1101
		$conn = $context->db( 'db-locale', true );
1102
1103
		try
1104
		{
1105
			$conn->query( 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE' )->finish();
1106
			$conn->query( 'START TRANSACTION' )->finish();
1107
1108
			$result = $conn->query( 'SELECT "invoiceno" FROM "mshop_locale_site" where "siteid" = ?', [$siteId] );
1109
			$row = $result->fetch();
1110
			$result->finish();
1111
1112
			$conn->create( 'UPDATE "mshop_locale_site" SET "invoiceno" = "invoiceno" + 1 WHERE "siteid" = ?' )
1113
				->bind( 1, $siteId )->execute()->finish();
1114
1115
			$conn->query( 'COMMIT' )->finish();
1116
		}
1117
		catch( \Exception $e )
1118
		{
1119
			$conn->close();
1120
			throw $e;
1121
		}
1122
1123
		return $row['invoiceno'] ?? '';
1124
	}
1125
1126
1127
	/**
1128
	 * Saves the modified basket content
1129
	 *
1130
	 * @param \Aimeos\MShop\Order\Item\Iface $basket Basket content
1131
	 * @return \Aimeos\MShop\Order\Item\Iface Saved basket content
1132
	 */
1133
	protected function saveBasket( \Aimeos\MShop\Order\Item\Iface $basket ) : \Aimeos\MShop\Order\Item\Iface
1134
	{
1135
		$this->saveAddresses( $basket );
1136
		$this->saveServices( $basket );
1137
		$this->saveProducts( $basket );
1138
		$this->saveCoupons( $basket );
1139
1140
		return $basket;
1141
	}
1142
}
1143