• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aimeos / aimeos-core / 4f8b7e8d-5595-43b2-ba5c-df9921830178

17 May 2026 07:32AM UTC coverage: 92.581%. Remained the same
4f8b7e8d-5595-43b2-ba5c-df9921830178

push

circleci

aimeos
Fixed PHPStan issues

896 of 980 new or added lines in 165 files covered. (91.43%)

35 existing lines in 29 files now uncovered.

9734 of 10514 relevant lines covered (92.58%)

80.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.91
/src/MShop/Order/Manager/Standard.php
1
<?php
2

3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2026
6
 * @package MShop
7
 * @subpackage Order
8
 */
9

10

11
namespace Aimeos\MShop\Order\Manager;
12

13

14
/**
15
 * Default order manager implementation.
16
 *
17
 * @package MShop
18
 * @subpackage Order
19
 */
20
class Standard extends Base
21
        implements \Aimeos\MShop\Order\Manager\Iface, \Aimeos\MShop\Common\Manager\Factory\Iface
22
{
23
        use Session;
24
        use Update;
25

26

27
        /**
28
         * Counts the number items that are available for the values of the given key.
29
         *
30
         * @param \Aimeos\Base\Criteria\Iface $search Search criteria
31
         * @param array|string $key Search key or list of key to aggregate items for
32
         * @param string|null $value Search key for aggregating the value column
33
         * @param string|null $type Type of the aggregation, empty string for count or "sum" or "avg" (average)
34
         * @return \Aimeos\Map List of the search keys as key and the number of counted items as value
35
         */
36
        public function aggregate( \Aimeos\Base\Criteria\Iface $search, $key, ?string $value = null, ?string $type = null ) : \Aimeos\Map
37
        {
38
                /** mshop/order/manager/aggregate/mysql
39
                 * Counts the number of records grouped by the values in the key column and matched by the given criteria
40
                 *
41
                 * @see mshop/order/manager/aggregate/ansi
42
                 */
43

44
                /** mshop/order/manager/aggregate/ansi
45
                 * Counts the number of records grouped by the values in the key column and matched by the given criteria
46
                 *
47
                 * Groups all records by the values in the key column and counts their
48
                 * occurence. The matched records can be limited by the given criteria
49
                 * from the order database. The records must be from one of the sites
50
                 * that are configured via the context item. If the current site is part
51
                 * of a tree of sites, the statement can count all records from the
52
                 * current site and the complete sub-tree of sites.
53
                 *
54
                 * As the records can normally be limited by criteria from sub-managers,
55
                 * their tables must be joined in the SQL context. This is done by
56
                 * using the "internaldeps" property from the definition of the ID
57
                 * column of the sub-managers. These internal dependencies specify
58
                 * the JOIN between the tables and the used columns for joining. The
59
                 * ":joins" placeholder is then replaced by the JOIN strings from
60
                 * the sub-managers.
61
                 *
62
                 * To limit the records matched, conditions can be added to the given
63
                 * criteria object. It can contain comparisons like column names that
64
                 * must match specific values which can be combined by AND, OR or NOT
65
                 * operators. The resulting string of SQL conditions replaces the
66
                 * ":cond" placeholder before the statement is sent to the database
67
                 * server.
68
                 *
69
                 * This statement doesn't return any records. Instead, it returns pairs
70
                 * of the different values found in the key column together with the
71
                 * number of records that have been found for that key values.
72
                 *
73
                 * The SQL statement should conform to the ANSI standard to be
74
                 * compatible with most relational database systems. This also
75
                 * includes using double quotes for table and column names.
76
                 *
77
                 * @type string SQL statement for aggregating order items
78
                 * @since 2014.09
79
                 * @see mshop/order/manager/insert/ansi
80
                 * @see mshop/order/manager/update/ansi
81
                 * @see mshop/order/manager/newid/ansi
82
                 * @see mshop/order/manager/delete/ansi
83
                 * @see mshop/order/manager/search/ansi
84
                 * @see mshop/order/manager/count/ansi
85
                 */
86

87
                /** mshop/order/manager/aggregateavg/mysql
88
                 * Computes the average of all values grouped by the key column and matched by the given criteria
89
                 *
90
                 * @type string SQL statement for aggregating the order items and computing the average value
91
                 * @since 2017.10
92
                 * @see mshop/order/manager/aggregateavg/ansi
93
                 * @see mshop/order/manager/aggregate/mysql
94
                 */
95

96
                /** mshop/order/manager/aggregateavg/ansi
97
                 * Computes the average of all values grouped by the key column and matched by the given criteria
98
                 *
99
                 * @type string SQL statement for aggregating the order items and computing the average value
100
                 * @since 2017.10
101
                 * @see mshop/order/manager/aggregate/ansi
102
                 */
103

104
                /** mshop/order/manager/aggregatesum/mysql
105
                 * Computes the sum of all values grouped by the key column and matched by the given criteria
106
                 *
107
                 * @type string SQL statement for aggregating the order items and computing the sum
108
                 * @since 2017.10
109
                 * @see mshop/order/manager/aggregatesum/ansi
110
                 * @see mshop/order/manager/aggregate/mysql
111
                 */
112

113
                /** mshop/order/manager/aggregatesum/ansi
114
                 * Computes the sum of all values grouped by the key column and matched by the given criteria
115
                 *
116
                 * @type string SQL statement for aggregating the order items and computing the sum
117
                 * @since 2017.10
118
                 * @see mshop/order/manager/aggregate/ansi
119
                 */
120

121
                $cfgkey = 'mshop/order/manager/aggregate';
11✔
122
                return $this->aggregateBase( $search, $key, $cfgkey, ['order'], $value, $type );
11✔
123
        }
124

125

126
        /**
127
         * Creates a new empty item instance
128
         *
129
         * @param array $values Values the item should be initialized with
130
         * @return \Aimeos\MShop\Order\Item\Iface New order item object
131
         */
132
        public function create( array $values = [] ) : \Aimeos\MShop\Common\Item\Iface
133
        {
134
                $context = $this->context();
466✔
135
                $locale = $context->locale();
466✔
136

137
                $values['.locale'] = $values['.locale'] ?? $locale;
466✔
138
                $values['.price'] = $values['.price'] ?? \Aimeos\MShop::create( $context, 'price' )->create();
466✔
139
                $values['order.siteid'] = $values['order.siteid'] ?? $locale->getSiteId();
466✔
140

141
                $item = new \Aimeos\MShop\Order\Item\Standard( 'order.', $values );
466✔
142
                \Aimeos\MShop::create( $context, 'plugin' )->register( $item, 'order' );
466✔
143

144
                return $item;
466✔
145
        }
146

147

148
        /**
149
         * Creates a new address item instance
150
         *
151
         * @param array $values Values the item should be initialized with
152
         * @return \Aimeos\MShop\Order\Item\Address\Iface New order address item object
153
         */
154
        public function createAddress( array $values = [] ) : \Aimeos\MShop\Order\Item\Address\Iface
155
        {
156
                return $this->object()->getSubManager( 'address' )->create( $values ); // @phpstan-ignore return.type
1✔
157
        }
158

159

160
        /**
161
         * Creates a new coupon item instance
162
         *
163
         * @param array $values Values the item should be initialized with
164
         * @return \Aimeos\MShop\Order\Item\Coupon\Iface New order coupon item object
165
         */
166
        public function createCoupon( array $values = [] ) : \Aimeos\MShop\Order\Item\Coupon\Iface
167
        {
168
                return $this->object()->getSubManager( 'coupon' )->create( $values ); // @phpstan-ignore return.type
1✔
169
        }
170

171

172
        /**
173
         * Creates a new product item instance
174
         *
175
         * @param array $values Values the item should be initialized with
176
         * @return \Aimeos\MShop\Order\Item\Product\Iface New order product item object
177
         */
178
        public function createProduct( array $values = [] ) : \Aimeos\MShop\Order\Item\Product\Iface
179
        {
180
                return $this->object()->getSubManager( 'product' )->create( $values ); // @phpstan-ignore return.type
1✔
181
        }
182

183

184
        /**
185
         * Creates a new product attribute item instance
186
         *
187
         * @param array $values Values the item should be initialized with
188
         * @return \Aimeos\MShop\Order\Item\Product\Attribute\Iface New order product attribute item object
189
         */
190
        public function createProductAttribute( array $values = [] ) : \Aimeos\MShop\Order\Item\Product\Attribute\Iface
191
        {
192
                return $this->object()->getSubManager( 'product' )->getSubManager( 'attribute' )->create( $values ); // @phpstan-ignore return.type
1✔
193
        }
194

195

196
        /**
197
         * Creates a new service item instance
198
         *
199
         * @param array $values Values the item should be initialized with
200
         * @return \Aimeos\MShop\Order\Item\Service\Iface New order service item object
201
         */
202
        public function createService( array $values = [] ) : \Aimeos\MShop\Order\Item\Service\Iface
203
        {
204
                return $this->object()->getSubManager( 'service' )->create( $values ); // @phpstan-ignore return.type
1✔
205
        }
206

207

208
        /**
209
         * Creates a new service attribute item instance
210
         *
211
         * @param array $values Values the item should be initialized with
212
         * @return \Aimeos\MShop\Order\Item\Service\Attribute\Iface New order service attribute item object
213
         */
214
        public function createServiceAttribute( array $values = [] ) : \Aimeos\MShop\Order\Item\Service\Attribute\Iface
215
        {
216
                return $this->object()->getSubManager( 'service' )->getSubManager( 'attribute' )->create( $values ); // @phpstan-ignore return.type
1✔
217
        }
218

219

220
        /**
221
         * Creates a new service transaction item instance
222
         *
223
         * @param array $values Values the item should be initialized with
224
         * @return \Aimeos\MShop\Order\Item\Service\Transaction\Iface New order service transaction item object
225
         */
226
        public function createServiceTransaction( array $values = [] ) : \Aimeos\MShop\Order\Item\Service\Transaction\Iface
227
        {
228
                return $this->object()->getSubManager( 'service' )->getSubManager( 'transaction' )->create( $values ); // @phpstan-ignore return.type
1✔
229
        }
230

231

232
        /**
233
         * Creates a new status item instance
234
         *
235
         * @param array $values Values the item should be initialized with
236
         * @return \Aimeos\MShop\Order\Item\Status\Iface New order item object
237
         */
238
        public function createStatus( array $values = [] ) : \Aimeos\MShop\Order\Item\Status\Iface
239
        {
240
                return $this->object()->getSubManager( 'status' )->create( $values ); // @phpstan-ignore return.type
3✔
241
        }
242

243

244
        /**
245
         * Creates a search critera object
246
         *
247
         * @param bool|null $default Add default criteria or NULL for relaxed default criteria
248
         * @param bool $site TRUE to add site criteria to show orders with available products only
249
         * @return \Aimeos\Base\Criteria\Iface New search criteria object
250
         */
251
        public function filter( ?bool $default = false, bool $site = false ) : \Aimeos\Base\Criteria\Iface
252
        {
253
                $search = parent::filter( $default );
94✔
254

255
                if( $default !== false ) {
94✔
256
                        $search->add( ['order.customerid' => $this->context()->user()] );
5✔
257
                }
258

259
                if( $site === true )
94✔
260
                {
261
                        $level = \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE;
1✔
262
                        $search->add( $this->siteCondition( 'order.product.siteid', $level ) );
1✔
263
                }
264

265
                return $search;
94✔
266
        }
267

268

269
        /**
270
         * Returns the additional column/search definitions
271
         *
272
         * @return array Associative list of column names as keys and items implementing \Aimeos\Base\Criteria\Attribute\Iface
273
         */
274
        public function getSaveAttributes() : array
275
        {
276
                return $this->createAttributes( [
85✔
277
                        'order.invoiceno' => [
85✔
278
                                'label' => 'Invoice number',
85✔
279
                                'internalcode' => 'invoiceno',
85✔
280
                        ],
85✔
281
                        'order.relatedid' => [
85✔
282
                                'label' => 'Related invoice ID',
85✔
283
                                'internalcode' => 'relatedid',
85✔
284
                        ],
85✔
285
                        'order.channel' => [
85✔
286
                                'label' => 'Order channel',
85✔
287
                                'internalcode' => 'channel',
85✔
288
                        ],
85✔
289
                        'order.datepayment' => [
85✔
290
                                'label' => 'Purchase date',
85✔
291
                                'internalcode' => 'datepayment',
85✔
292
                                'type' => 'datetime',
85✔
293
                        ],
85✔
294
                        'order.datedelivery' => [
85✔
295
                                'label' => 'Delivery date',
85✔
296
                                'internalcode' => 'datedelivery',
85✔
297
                                'type' => 'datetime',
85✔
298
                        ],
85✔
299
                        'order.statusdelivery' => [
85✔
300
                                'label' => 'Delivery status',
85✔
301
                                'internalcode' => 'statusdelivery',
85✔
302
                                'type' => 'int',
85✔
303
                        ],
85✔
304
                        'order.statuspayment' => [
85✔
305
                                'label' => 'Payment status',
85✔
306
                                'internalcode' => 'statuspayment',
85✔
307
                                'type' => 'int',
85✔
308
                        ],
85✔
309
                        'order.customerid' => [
85✔
310
                                'label' => 'Order customer ID',
85✔
311
                                'internalcode' => 'customerid',
85✔
312
                        ],
85✔
313
                        'order.customerref' => [
85✔
314
                                'label' => 'Order customer reference',
85✔
315
                                'internalcode' => 'customerref',
85✔
316
                        ],
85✔
317
                        'order.comment' => [
85✔
318
                                'label' => 'Order comment',
85✔
319
                                'internalcode' => 'comment',
85✔
320
                        ],
85✔
321
                ] );
85✔
322
        }
323

324

325
        /**
326
         * Returns the attributes that can be used for searching.
327
         *
328
         * @param bool $withsub Return also attributes of sub-managers if true
329
         * @return \Aimeos\Base\Criteria\Attribute\Iface[] List of search attribute items
330
         */
331
        public function getSearchAttributes( bool $withsub = true ) : array
332
        {
333
                $level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
85✔
334
                $level = $this->context()->config()->get( 'mshop/order/manager/sitemode', $level );
85✔
335
                $expr = $this->siteString( 'mordst_cs."siteid"', (int) $level );
85✔
336

337
                return array_replace( parent::getSearchAttributes( $withsub ), $this->createAttributes( [
85✔
338
                        'order.sitecode' => [
85✔
339
                                'label' => 'Order site code',
85✔
340
                                'internalcode' => 'sitecode',
85✔
341
                                'public' => false,
85✔
342
                        ],
85✔
343
                        'order.languageid' => [
85✔
344
                                'label' => 'Order language code',
85✔
345
                                'internalcode' => 'langid',
85✔
346
                        ],
85✔
347
                        'order.currencyid' => [
85✔
348
                                'label' => 'Order currencyid code',
85✔
349
                                'internalcode' => 'currencyid',
85✔
350
                        ],
85✔
351
                        'order.price' => [
85✔
352
                                'label' => 'Order price amount',
85✔
353
                                'internalcode' => 'price',
85✔
354
                        ],
85✔
355
                        'order.costs' => [
85✔
356
                                'label' => 'Order shipping amount',
85✔
357
                                'internalcode' => 'costs',
85✔
358
                        ],
85✔
359
                        'order.rebate' => [
85✔
360
                                'label' => 'Order rebate amount',
85✔
361
                                'internalcode' => 'rebate',
85✔
362
                        ],
85✔
363
                        'order.taxvalue' => [
85✔
364
                                'label' => 'Order tax amount',
85✔
365
                                'internalcode' => 'tax',
85✔
366
                        ],
85✔
367
                        'order.taxflag' => [
85✔
368
                                'label' => 'Order tax flag (0=net, 1=gross)',
85✔
369
                                'internalcode' => 'taxflag',
85✔
370
                        ],
85✔
371
                        'order.cdate' => [
85✔
372
                                'label' => 'Create date',
85✔
373
                                'internalcode' => 'cdate',
85✔
374
                        ],
85✔
375
                        'order.cmonth' => [
85✔
376
                                'label' => 'Create month',
85✔
377
                                'internalcode' => 'cmonth',
85✔
378
                        ],
85✔
379
                        'order.cweek' => [
85✔
380
                                'label' => 'Create week',
85✔
381
                                'internalcode' => 'cweek',
85✔
382
                        ],
85✔
383
                        'order.cwday' => [
85✔
384
                                'label' => 'Create weekday',
85✔
385
                                'internalcode' => 'cwday',
85✔
386
                        ],
85✔
387
                        'order.chour' => [
85✔
388
                                'label' => 'Create hour',
85✔
389
                                'internalcode' => 'chour',
85✔
390
                        ],
85✔
391
                        'order:status' => [
85✔
392
                                'code' => 'order:status()',
85✔
393
                                'internalcode' => '( SELECT COUNT(mordst_cs."parentid")
85✔
394
                                        FROM "mshop_order_status" AS mordst_cs
395
                                        WHERE mord."id" = mordst_cs."parentid" AND ' . $expr . '
85✔
396
                                        AND mordst_cs."type" = $1 AND mordst_cs."value" IN ( $2 ) )',
85✔
397
                                'label' => 'Number of order status items, parameter(<type>,<value>)',
85✔
398
                                'type' => 'int',
85✔
399
                                'public' => false,
85✔
400
                        ],
85✔
401
                ] ) );
85✔
402
        }
403

404

405
        /**
406
         * Saves the dependent items of the item
407
         *
408
         * @param \Aimeos\MShop\Common\Item\Iface $item Item object
409
         * @param bool $fetch True if the new ID should be returned in the item
410
         * @return \Aimeos\MShop\Common\Item\Iface Updated item
411
         */
412
        public function saveRefs( \Aimeos\MShop\Common\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Common\Item\Iface
413
        {
414
                // @phpstan-ignore argument.type
415
                $this->addStatus( $item );
11✔
416
                // @phpstan-ignore argument.type
417
                $this->saveAddresses( $item );
11✔
418
                // @phpstan-ignore argument.type
419
                $this->saveServices( $item );
11✔
420
                // @phpstan-ignore argument.type
421
                $this->saveProducts( $item );
11✔
422
                // @phpstan-ignore argument.type
423
                $this->saveCoupons( $item );
11✔
424
                // @phpstan-ignore argument.type
425
                $this->saveStatuses( $item );
11✔
426

427
                return $item;
11✔
428
        }
429

430

431
        /**
432
         * Merges the data from the given map and the referenced items
433
         *
434
         * @param array $entries Associative list of ID as key and the associative list of property key/value pairs as values
435
         * @param array $ref List of referenced items to fetch and add to the entries
436
         * @return array Associative list of ID as key and the updated entries as value
437
         */
438
        public function searchRefs( array $entries, array $ref ) : array
439
        {
440
                $context = $this->context();
69✔
441
                $ids = array_keys( $entries );
69✔
442
                $addresses = $customers = $coupons = $products = $services = $statuses = [];
69✔
443

444
                $priceManager = \Aimeos\MShop::create( $context, 'price' );
69✔
445
                $localeManager = \Aimeos\MShop::create( $context, 'locale' );
69✔
446

447
                if( $this->hasRef( $ref, 'customer' ) && !( $cids = map( $entries )->col( 'order.customerid' )->filter() )->empty() )
69✔
448
                {
449
                        $manager = \Aimeos\MShop::create( $this->context(), 'customer' );
×
450
                        $search = $manager->filter()->slice( 0, 0x7fffffff )->add( ['customer.id' => $cids] );
×
451
                        // @phpstan-ignore argument.type
UNCOV
452
                        $customers = $manager->search( $search, $ref );
×
453
                }
454

455
                if( $this->hasRef( $ref, 'order/address' ) ) {
69✔
456
                        $addresses = $this->getAddresses( $ids, $ref );
34✔
457
                }
458

459
                if( $this->hasRef( $ref, 'order/product' ) || $this->hasRef( $ref, 'order/coupon' ) ) {
69✔
460
                        $products = $this->getProducts( $ids, $ref );
40✔
461
                }
462

463
                if( $this->hasRef( $ref, 'order/coupon' ) ) {
69✔
464
                        $coupons = $this->getCoupons( $ids, $ref );
28✔
465
                }
466

467
                if( $this->hasRef( $ref, 'order/service' ) ) {
69✔
468
                        $services = $this->getServices( $ids, $ref );
35✔
469
                }
470

471
                if( $this->hasRef( $ref, 'order/status' ) ) {
69✔
472
                        $statuses = $this->getStatuses( $ids, $ref );
1✔
473
                }
474

475
                foreach( $entries as $id => $row )
69✔
476
                {
477
                        $entries[$id]['.price'] = $priceManager->create( [
68✔
478
                                'price.currencyid' => $row['order.currencyid'],
68✔
479
                                'price.value' => $row['order.price'],
68✔
480
                                'price.costs' => $row['order.costs'],
68✔
481
                                'price.rebate' => $row['order.rebate'],
68✔
482
                                'price.taxflag' => $row['order.taxflag'],
68✔
483
                                'price.taxvalue' => $row['order.taxvalue'],
68✔
484
                                'price.siteid' => $row['order.siteid'],
68✔
485
                        ] );
68✔
486

487
                        // you may need the site object! take care!
488
                        $entries[$id]['.locale'] = $localeManager->create( [
68✔
489
                                'locale.currencyid' => $row['order.currencyid'],
68✔
490
                                'locale.languageid' => $row['order.languageid'],
68✔
491
                                'locale.siteid' => $row['order.siteid'],
68✔
492
                        ] );
68✔
493

494
                        $entries[$id]['.customer'] = $customers[$row['order.customerid']] ?? null;
68✔
495
                        $entries[$id]['.addresses'] = $addresses[$id] ?? [];
68✔
496
                        $entries[$id]['.coupons'] = $coupons[$id] ?? [];
68✔
497
                        $entries[$id]['.products'] = $products[$id] ?? [];
68✔
498
                        $entries[$id]['.services'] = $services[$id] ?? [];
68✔
499
                        $entries[$id]['.statuses'] = $statuses[$id] ?? [];
68✔
500
                }
501

502
                return $entries;
69✔
503
        }
504

505

506
        /**
507
         * Adds the new payment and delivery values to the order status log.
508
         *
509
         * @param \Aimeos\MShop\Order\Item\Iface $item Order item object
510
         * @return static Manager object for chaining method calls
511
         */
512
        protected function addStatus( \Aimeos\MShop\Order\Item\Iface $item ) : static
513
        {
514
                $object = $this->object();
11✔
515

516
                if( ( $status = $item->get( '.statuspayment' ) ) !== null && $status != $item->getStatusPayment() ) {
11✔
517
                        // @phpstan-ignore argument.type
518
                        $item->addStatus( $object->createStatus()->setType( 'status-payment' )->setValue( $item->getStatusPayment() ) );
1✔
519
                }
520

521
                if( ( $status = $item->get( '.statusdelivery' ) ) !== null && $status != $item->getStatusDelivery() ) {
11✔
522
                        // @phpstan-ignore argument.type
523
                        $item->addStatus( $object->createStatus()->setType( 'status-delivery' )->setValue( $item->getStatusDelivery() ) );
1✔
524
                }
525

526
                return $this;
11✔
527
        }
528

529

530
        /**
531
         * Binds additional values to the statement before execution.
532
         *
533
         * @param \Aimeos\MShop\Common\Item\Iface $item Item object
534
         * @param \Aimeos\Base\DB\Statement\Iface $stmt Database statement object
535
         * @param int $idx Current bind index
536
         * @return \Aimeos\Base\DB\Statement\Iface Database statement object with bound values
537
         */
538
        protected function bind( \Aimeos\MShop\Common\Item\Iface $item, \Aimeos\Base\DB\Statement\Iface $stmt, int &$idx ) : \Aimeos\Base\DB\Statement\Iface
539
        {
540
                $price = $item->getPrice();
11✔
541
                $context = $this->context();
11✔
542

543
                $stmt->bind( $idx++, $context->locale()->getSiteItem()->getCode() );
11✔
544
                $stmt->bind( $idx++, $item->locale()->getLanguageId() );
11✔
545
                $stmt->bind( $idx++, $price->getCurrencyId() );
11✔
546
                $stmt->bind( $idx++, $price->getValue() ?: '0.00' );
11✔
547
                $stmt->bind( $idx++, $price->getCosts() );
11✔
548
                $stmt->bind( $idx++, $price->getRebate() );
11✔
549
                $stmt->bind( $idx++, $price->getTaxValue() );
11✔
550
                $stmt->bind( $idx++, $price->getTaxFlag(), \Aimeos\Base\DB\Statement\Base::PARAM_INT );
11✔
551

552
                if( $item->getId() === null )
11✔
553
                {
554
                        $date = date_create_from_format( 'Y-m-d H:i:s', $context->datetime() );
11✔
555
                        $stmt->bind( $idx++, $date->format( 'Y-m-d' ) ); // cdate
11✔
556
                        $stmt->bind( $idx++, $date->format( 'Y-m' ) ); // cmonth
11✔
557
                        $stmt->bind( $idx++, $date->format( 'Y-W' ) ); // cweek
11✔
558
                        $stmt->bind( $idx++, $date->format( 'w' ) ); // cwday
11✔
559
                        $stmt->bind( $idx++, $date->format( 'G' ) ); // chour
11✔
560
                }
561

562
                return $stmt;
11✔
563
        }
564

565

566
        /**
567
         * Creates a new invoice number for the passed order and site.
568
         *
569
         * @param \Aimeos\MShop\Order\Item\Iface $item Order item with necessary values
570
         * @return string Unique invoice number for the current site
571
         */
572
        protected function createInvoiceNumber( \Aimeos\MShop\Order\Item\Iface $item ) : string
573
        {
574
                $context = $this->context();
×
575
                $siteId = $context->locale()->getSiteId();
×
576
                $conn = $context->db( 'db-locale', true );
×
577

578
                try
579
                {
580
                        $conn->query( 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE' )->finish();
×
581
                        $conn->query( 'START TRANSACTION' )->finish();
×
582

583
                        $result = $conn->query( 'SELECT "invoiceno" FROM "mshop_locale_site" where "siteid" = ?', [$siteId] );
×
584
                        $row = $result->fetch();
×
585
                        $result->finish();
×
586

587
                        $conn->create( 'UPDATE "mshop_locale_site" SET "invoiceno" = "invoiceno" + 1 WHERE "siteid" = ?' )
×
588
                                ->bind( 1, $siteId )->execute()->finish();
×
589

590
                        $conn->query( 'COMMIT' )->finish();
×
591
                }
592
                catch( \Exception $e )
×
593
                {
594
                        $conn->close();
×
595
                        throw $e;
×
596
                }
597

NEW
598
                return (string) ( $row['invoiceno'] ?? '' ); // @phpstan-ignore offsetAccess.notFound
×
599
        }
600

601

602
        /**
603
         * Returns the prefix for the item properties and search keys.
604
         *
605
         * @return string Prefix for the item properties and search keys
606
         */
607
        protected function prefix() : string
608
        {
609
                return 'order.';
85✔
610
        }
611

612

613
        /**
614
         * Creates a one-time order in the storage from the given invoice object.
615
         *
616
         * @param \Aimeos\MShop\Common\Item\Iface $item Order item with necessary values
617
         * @param bool $fetch True if the new ID should be returned in the item
618
         * @return \Aimeos\MShop\Common\Item\Iface $item Updated item including the generated ID
619
         */
620
        protected function saveBase( \Aimeos\MShop\Common\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Common\Item\Iface
621
        {
622
                if( empty( $item->getInvoiceNumber() ) && $item->getStatusPayment() >= \Aimeos\MShop\Order\Item\Base::PAY_PENDING )
11✔
623
                {
624
                        try {
625
                                // @phpstan-ignore argument.type
626
                                $item->setInvoiceNumber( $this->createInvoiceNumber( $item ) );
×
627
                        } catch( \Exception $e ) { // redo on transaction deadlock
×
628
                                // @phpstan-ignore argument.type
UNCOV
629
                                $item->setInvoiceNumber( $this->createInvoiceNumber( $item ) );
×
630
                        }
631
                }
632

633
                return parent::saveBase( $item, $fetch );
11✔
634
        }
635

636

637
        /** mshop/order/manager/name
638
         * Class name of the used order manager implementation
639
         *
640
         * Each default manager can be replace by an alternative imlementation.
641
         * To use this implementation, you have to set the last part of the class
642
         * name as configuration value so the manager factory knows which class it
643
         * has to instantiate.
644
         *
645
         * For example, if the name of the default class is
646
         *
647
         *  \Aimeos\MShop\Order\Manager\Standard
648
         *
649
         * and you want to replace it with your own version named
650
         *
651
         *  \Aimeos\MShop\Order\Manager\Mymanager
652
         *
653
         * then you have to set the this configuration option:
654
         *
655
         *  mshop/order/manager/name = Mymanager
656
         *
657
         * The value is the last part of your own class name and it's case sensitive,
658
         * so take care that the configuration value is exactly named like the last
659
         * part of the class name.
660
         *
661
         * The allowed characters of the class name are A-Z, a-z and 0-9. No other
662
         * characters are possible! You should always start the last part of the class
663
         * name with an upper case character and continue only with lower case characters
664
         * or numbers. Avoid chamel case names like "MyManager"!
665
         *
666
         * @type string Last part of the class name
667
         * @since 2015.10
668
         */
669

670
        /** mshop/order/manager/decorators/excludes
671
         * Excludes decorators added by the "common" option from the order manager
672
         *
673
         * Decorators extend the functionality of a class by adding new aspects
674
         * (e.g. log what is currently done), executing the methods of the underlying
675
         * class only in certain conditions (e.g. only for logged in users) or
676
         * modify what is returned to the caller.
677
         *
678
         * This option allows you to remove a decorator added via
679
         * "mshop/common/manager/decorators/default" before they are wrapped
680
         * around the order manager.
681
         *
682
         *  mshop/order/manager/decorators/excludes = array( 'decorator1' )
683
         *
684
         * This would remove the decorator named "decorator1" from the list of
685
         * common decorators ("\Aimeos\MShop\Common\Manager\Decorator\*") added via
686
         * "mshop/common/manager/decorators/default" for the order manager.
687
         *
688
         * @type array List of decorator names
689
         * @since 2015.10
690
         * @see mshop/common/manager/decorators/default
691
         * @see mshop/order/manager/decorators/global
692
         * @see mshop/order/manager/decorators/local
693
         */
694

695
        /** mshop/order/manager/decorators/global
696
         * Adds a list of globally available decorators only to the order manager
697
         *
698
         * Decorators extend the functionality of a class by adding new aspects
699
         * (e.g. log what is currently done), executing the methods of the underlying
700
         * class only in certain conditions (e.g. only for logged in users) or
701
         * modify what is returned to the caller.
702
         *
703
         * This option allows you to wrap global decorators
704
         * ("\Aimeos\MShop\Common\Manager\Decorator\*") around the order manager.
705
         *
706
         *  mshop/order/manager/decorators/global = array( 'decorator1' )
707
         *
708
         * This would add the decorator named "decorator1" defined by
709
         * "\Aimeos\MShop\Common\Manager\Decorator\Decorator1" only to the order
710
         * manager.
711
         *
712
         * @type array List of decorator names
713
         * @since 2015.10
714
         * @see mshop/common/manager/decorators/default
715
         * @see mshop/order/manager/decorators/excludes
716
         * @see mshop/order/manager/decorators/local
717
         */
718

719
        /** mshop/order/manager/decorators/local
720
         * Adds a list of local decorators only to the order manager
721
         *
722
         * Decorators extend the functionality of a class by adding new aspects
723
         * (e.g. log what is currently done), executing the methods of the underlying
724
         * class only in certain conditions (e.g. only for logged in users) or
725
         * modify what is returned to the caller.
726
         *
727
         * This option allows you to wrap local decorators
728
         * ("\Aimeos\MShop\Order\Manager\Decorator\*") around the order manager.
729
         *
730
         *  mshop/order/manager/decorators/local = array( 'decorator2' )
731
         *
732
         * This would add the decorator named "decorator2" defined by
733
         * "\Aimeos\MShop\Order\Manager\Decorator\Decorator2" only to the order
734
         * manager.
735
         *
736
         * @type array List of decorator names
737
         * @since 2015.10
738
         * @see mshop/common/manager/decorators/default
739
         * @see mshop/order/manager/decorators/excludes
740
         * @see mshop/order/manager/decorators/global
741
         */
742

743
        /** mshop/order/manager/resource
744
         * Name of the database connection resource to use
745
         *
746
         * You can configure a different database connection for each data domain
747
         * and if no such connection name exists, the "db" connection will be used.
748
         * It's also possible to use the same database connection for different
749
         * data domains by configuring the same connection name using this setting.
750
         *
751
         * @type string Database connection name
752
         * @since 2023.04
753
         */
754

755

756
        /** mshop/order/manager/delete/mysql
757
         * Deletes the items matched by the given IDs from the database
758
         *
759
         * @see mshop/order/manager/delete/ansi
760
         */
761

762
        /** mshop/order/manager/delete/ansi
763
         * Deletes the items matched by the given IDs from the database
764
         *
765
         * Removes the records specified by the given IDs from the order database.
766
         * The records must be from the site that is configured via the
767
         * context item.
768
         *
769
         * The ":cond" placeholder is replaced by the name of the ID column and
770
         * the given ID or list of IDs while the site ID is bound to the question
771
         * mark.
772
         *
773
         * The SQL statement should conform to the ANSI standard to be
774
         * compatible with most relational database systems. This also
775
         * includes using double quotes for table and column names.
776
         *
777
         * @type string SQL statement for deleting items
778
         * @since 2015.10
779
         * @see mshop/order/manager/insert/ansi
780
         * @see mshop/order/manager/update/ansi
781
         * @see mshop/order/manager/newid/ansi
782
         * @see mshop/order/manager/search/ansi
783
         * @see mshop/order/manager/count/ansi
784
         */
785

786
        /** mshop/order/manager/submanagers
787
         * List of manager names that can be instantiated by the order manager
788
         *
789
         * Managers provide a generic interface to the underlying storage.
790
         * Each manager has or can have sub-managers caring about particular
791
         * aspects. Each of these sub-managers can be instantiated by its
792
         * parent manager using the getSubManager() method.
793
         *
794
         * The search keys from sub-managers can be normally used in the
795
         * manager as well. It allows you to search for items of the manager
796
         * using the search keys of the sub-managers to further limit the
797
         * retrieved list of items.
798
         *
799
         * @type array List of sub-manager names
800
         * @since 2015.10
801
         */
802

803
        /** mshop/order/manager/insert/mysql
804
         * Inserts a new order record into the database table
805
         *
806
         * @see mshop/order/manager/insert/ansi
807
         */
808

809
        /** mshop/order/manager/insert/ansi
810
         * Inserts a new order record into the database table
811
         *
812
         * Items with no ID yet (i.e. the ID is NULL) will be created in
813
         * the database and the newly created ID retrieved afterwards
814
         * using the "newid" SQL statement.
815
         *
816
         * The SQL statement must be a string suitable for being used as
817
         * prepared statement. It must include question marks for binding
818
         * the values from the order item to the statement before they are
819
         * sent to the database server. The number of question marks must
820
         * be the same as the number of columns listed in the INSERT
821
         * statement. The order of the columns must correspond to the
822
         * order in the save() method, so the correct values are
823
         * bound to the columns.
824
         *
825
         * The SQL statement should conform to the ANSI standard to be
826
         * compatible with most relational database systems. This also
827
         * includes using double quotes for table and column names.
828
         *
829
         * @type string SQL statement for inserting records
830
         * @since 2015.10
831
         * @see mshop/order/manager/update/ansi
832
         * @see mshop/order/manager/newid/ansi
833
         * @see mshop/order/manager/delete/ansi
834
         * @see mshop/order/manager/search/ansi
835
         * @see mshop/order/manager/count/ansi
836
         */
837

838
        /** mshop/order/manager/update/mysql
839
         * Updates an existing order record in the database
840
         *
841
         * @see mshop/order/manager/update/ansi
842
         */
843

844
        /** mshop/order/manager/update/ansi
845
         * Updates an existing order record in the database
846
         *
847
         * Items which already have an ID (i.e. the ID is not NULL) will
848
         * be updated in the database.
849
         *
850
         * The SQL statement must be a string suitable for being used as
851
         * prepared statement. It must include question marks for binding
852
         * the values from the order item to the statement before they are
853
         * sent to the database server. The order of the columns must
854
         * correspond to the order in the save() method, so the
855
         * correct values are bound to the columns.
856
         *
857
         * The SQL statement should conform to the ANSI standard to be
858
         * compatible with most relational database systems. This also
859
         * includes using double quotes for table and column names.
860
         *
861
         * @type string SQL statement for updating records
862
         * @since 2015.10
863
         * @see mshop/order/manager/insert/ansi
864
         * @see mshop/order/manager/newid/ansi
865
         * @see mshop/order/manager/delete/ansi
866
         * @see mshop/order/manager/search/ansi
867
         * @see mshop/order/manager/count/ansi
868
         */
869

870
        /** mshop/order/manager/newid/mysql
871
         * Retrieves the ID generated by the database when inserting a new record
872
         *
873
         * @see mshop/order/manager/newid/ansi
874
         */
875

876
        /** mshop/order/manager/newid/ansi
877
         * Retrieves the ID generated by the database when inserting a new record
878
         *
879
         * As soon as a new record is inserted into the database table,
880
         * the database server generates a new and unique identifier for
881
         * that record. This ID can be used for retrieving, updating and
882
         * deleting that specific record from the table again.
883
         *
884
         * For MySQL:
885
         *  SELECT LAST_INSERT_ID()
886
         * For PostgreSQL:
887
         *  SELECT currval('seq_mrul_id')
888
         * For SQL Server:
889
         *  SELECT SCOPE_IDENTITY()
890
         * For Oracle:
891
         *  SELECT "seq_mrul_id".CURRVAL FROM DUAL
892
         *
893
         * There's no way to retrive the new ID by a SQL statements that
894
         * fits for most database servers as they implement their own
895
         * specific way.
896
         *
897
         * @type string SQL statement for retrieving the last inserted record ID
898
         * @since 2015.10
899
         * @see mshop/order/manager/insert/ansi
900
         * @see mshop/order/manager/update/ansi
901
         * @see mshop/order/manager/delete/ansi
902
         * @see mshop/order/manager/search/ansi
903
         * @see mshop/order/manager/count/ansi
904
         */
905

906
        /** mshop/order/manager/sitemode
907
         * Mode how items from levels below or above in the site tree are handled
908
         *
909
         * By default, only items from the current site are fetched from the
910
         * storage. If the ai-sites extension is installed, you can create a
911
         * tree of sites. Then, this setting allows you to define for the
912
         * whole order domain if items from parent sites are inherited,
913
         * sites from child sites are aggregated or both.
914
         *
915
         * Available constants for the site mode are:
916
         * * 0 = only items from the current site
917
         * * 1 = inherit items from parent sites
918
         * * 2 = aggregate items from child sites
919
         * * 3 = inherit and aggregate items at the same time
920
         *
921
         * You also need to set the mode in the locale manager
922
         * (mshop/locale/manager/sitelevel) to one of the constants.
923
         * If you set it to the same value, it will work as described but you
924
         * can also use different modes. For example, if inheritance and
925
         * aggregation is configured the locale manager but only inheritance
926
         * in the domain manager because aggregating items makes no sense in
927
         * this domain, then items wil be only inherited. Thus, you have full
928
         * control over inheritance and aggregation in each domain.
929
         *
930
         * @type int Constant from Aimeos\MShop\Locale\Manager\Base class
931
         * @since 2018.01
932
         * @see mshop/locale/manager/sitelevel
933
         */
934

935
        /** mshop/order/manager/search/mysql
936
         * Retrieves the records matched by the given criteria in the database
937
         *
938
         * @see mshop/order/manager/search/ansi
939
         */
940

941
        /** mshop/order/manager/search/ansi
942
         * Retrieves the records matched by the given criteria in the database
943
         *
944
         * Fetches the records matched by the given criteria from the order
945
         * database. The records must be from one of the sites that are
946
         * configured via the context item. If the current site is part of
947
         * a tree of sites, the SELECT statement can retrieve all records
948
         * from the current site and the complete sub-tree of sites.
949
         *
950
         * As the records can normally be limited by criteria from sub-managers,
951
         * their tables must be joined in the SQL context. This is done by
952
         * using the "internaldeps" property from the definition of the ID
953
         * column of the sub-managers. These internal dependencies specify
954
         * the JOIN between the tables and the used columns for joining. The
955
         * ":joins" placeholder is then replaced by the JOIN strings from
956
         * the sub-managers.
957
         *
958
         * To limit the records matched, conditions can be added to the given
959
         * criteria object. It can contain comparisons like column names that
960
         * must match specific values which can be combined by AND, OR or NOT
961
         * operators. The resulting string of SQL conditions replaces the
962
         * ":cond" placeholder before the statement is sent to the database
963
         * server.
964
         *
965
         * If the records that are retrieved should be ordered by one or more
966
         * columns, the generated string of column / sort direction pairs
967
         * replaces the ":order" placeholder.
968
         *
969
         * The number of returned records can be limited and can start at any
970
         * number between the begining and the end of the result set. For that
971
         * the ":size" and ":start" placeholders are replaced by the
972
         * corresponding values from the criteria object. The default values
973
         * are 0 for the start and 100 for the size value.
974
         *
975
         * The SQL statement should conform to the ANSI standard to be
976
         * compatible with most relational database systems. This also
977
         * includes using double quotes for table and column names.
978
         *
979
         * @type string SQL statement for searching items
980
         * @since 2015.10
981
         * @see mshop/order/manager/insert/ansi
982
         * @see mshop/order/manager/update/ansi
983
         * @see mshop/order/manager/newid/ansi
984
         * @see mshop/order/manager/delete/ansi
985
         * @see mshop/order/manager/count/ansi
986
         */
987

988
        /** mshop/order/manager/count/mysql
989
         * Counts the number of records matched by the given criteria in the database
990
         *
991
         * @see mshop/order/manager/count/ansi
992
         */
993

994
        /** mshop/order/manager/count/ansi
995
         * Counts the number of records matched by the given criteria in the database
996
         *
997
         * Counts all records matched by the given criteria from the order
998
         * database. The records must be from one of the sites that are
999
         * configured via the context item. If the current site is part of
1000
         * a tree of sites, the statement can count all records from the
1001
         * current site and the complete sub-tree of sites.
1002
         *
1003
         * As the records can normally be limited by criteria from sub-managers,
1004
         * their tables must be joined in the SQL context. This is done by
1005
         * using the "internaldeps" property from the definition of the ID
1006
         * column of the sub-managers. These internal dependencies specify
1007
         * the JOIN between the tables and the used columns for joining. The
1008
         * ":joins" placeholder is then replaced by the JOIN strings from
1009
         * the sub-managers.
1010
         *
1011
         * To limit the records matched, conditions can be added to the given
1012
         * criteria object. It can contain comparisons like column names that
1013
         * must match specific values which can be combined by AND, OR or NOT
1014
         * operators. The resulting string of SQL conditions replaces the
1015
         * ":cond" placeholder before the statement is sent to the database
1016
         * server.
1017
         *
1018
         * Both, the strings for ":joins" and for ":cond" are the same as for
1019
         * the "search" SQL statement.
1020
         *
1021
         * Contrary to the "search" statement, it doesn't return any records
1022
         * but instead the number of records that have been found. As counting
1023
         * thousands of records can be a long running task, the maximum number
1024
         * of counted records is limited for performance reasons.
1025
         *
1026
         * The SQL statement should conform to the ANSI standard to be
1027
         * compatible with most relational database systems. This also
1028
         * includes using double quotes for table and column names.
1029
         *
1030
         * @type string SQL statement for counting items
1031
         * @since 2015.10
1032
         * @see mshop/order/manager/insert/ansi
1033
         * @see mshop/order/manager/update/ansi
1034
         * @see mshop/order/manager/newid/ansi
1035
         * @see mshop/order/manager/delete/ansi
1036
         * @see mshop/order/manager/search/ansi
1037
         */
1038
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc