Completed
Push — master ( 6ac7fe...04d955 )
by Antony
19:55
created

Order::statusTransition()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 17
cts 17
cp 1
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 13
nc 6
nop 2
crap 7
1
<?php
2
3
/**
4
 * The order class is a databound object for handling Orders
5
 * within SilverStripe.
6
 *
7
 * @property string|float Currency
8
 * @property string Reference
9
 * @property string Placed
10
 * @property string Paid
11
 * @property string ReceiptSent
12
 * @property string Printed
13
 * @property string Dispatched
14
 * @property string Status
15
 * @property string FirstName
16
 * @property string Surname
17
 * @property string Email
18
 * @property string Notes
19
 * @property string IPAddress
20
 * @property string|bool SeparateBillingAddress
21
 * @property string Locale
22
 * @property string|int MemberID
23
 * @property string|int ShippingAddressID
24
 * @property string|int BillingAddressID
25
 * @method Member|ShopMember Member
26
 * @method Address BillingAddress
27
 * @method Address ShippingAddress
28
 * @method OrderItem[]|HasManyList Items
29
 * @method OrderModifier[]|HasManyList Modifiers
30
 * @method OrderStatusLog[]|HasManyList OrderStatusLogs
31
 *
32
 * @package shop
33
 */
34
class Order extends DataObject
0 ignored issues
show
Complexity introduced by
This class has 24 fields which exceeds the configured maximum of 15.

Too many fields generally indicate a class which does too much and does not follow the single responsibility principle.

We suggest taking a look at the “Code” section for further suggestions on how to fix this.

Loading history...
Complexity introduced by
This class has a complexity of 98 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

You can also find more detailed suggestions for refactoring in the “Code” section of your repository.

Loading history...
35
{
36
    /**
37
     * Status codes and what they mean:
38
     *
39
     * Unpaid (default): Order created but no successful payment by customer yet
40
     * Query: Order not being processed yet (customer has a query, or could be out of stock)
41
     * Paid: Order successfully paid for by customer
42
     * Processing: Order paid for, package is currently being processed before shipping to customer
43
     * Sent: Order paid for, processed for shipping, and now sent to the customer
44
     * Complete: Order completed (paid and shipped). Customer assumed to have received their goods
45
     * AdminCancelled: Order cancelled by the administrator
46
     * MemberCancelled: Order cancelled by the customer (Member)
47
     */
48
    private static $db                = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
49
        'Total'                  => 'Currency',
50
        'Reference'              => 'Varchar', //allow for customised order numbering schemes
51
        //status
52
        'Placed'                 => "SS_Datetime", //date the order was placed (went from Cart to Order)
53
        'Paid'                   => 'SS_Datetime', //no outstanding payment left
54
        'ReceiptSent'            => 'SS_Datetime', //receipt emailed to customer
55
        'Printed'                => 'SS_Datetime',
56
        'Dispatched'             => 'SS_Datetime', //products have been sent to customer
57
        'Status'                 => "Enum('Unpaid,Paid,Processing,Sent,Complete,AdminCancelled,MemberCancelled,Cart','Cart')",
58
        //customer (for guest orders)
59
        'FirstName'              => 'Varchar',
60
        'Surname'                => 'Varchar',
61
        'Email'                  => 'Varchar',
62
        'Notes'                  => 'Text',
63
        'IPAddress'              => 'Varchar(15)',
64
        //separate shipping
65
        'SeparateBillingAddress' => 'Boolean',
66
        // keep track of customer locale
67
        'Locale'                 => 'DBLocale',
68
    );
69
70
    private static $has_one           = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
71
        'Member'          => 'Member',
72
        'ShippingAddress' => 'Address',
73
        'BillingAddress'  => 'Address',
74
    );
75
76
    private static $has_many          = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
77
        'Items'           => 'OrderItem',
78
        'Modifiers'       => 'OrderModifier',
79
        'OrderStatusLogs' => 'OrderStatusLog',
80
    );
81
82
    private static $defaults          = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
83
        'Status' => 'Cart',
84
    );
85
86
    private static $casting           = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
87
        'FullBillingAddress'  => 'Text',
88
        'FullShippingAddress' => 'Text',
89
        'Total'               => 'Currency',
90
        'SubTotal'            => 'Currency',
91
        'TotalPaid'           => 'Currency',
92
        'Shipping'            => 'Currency',
93
        'TotalOutstanding'    => 'Currency',
94
    );
95
96
    private static $summary_fields    = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
97
        'Reference'   => 'Order No',
98
        'Placed'      => 'Date',
99
        'Name'        => 'Customer',
100
        'LatestEmail' => 'Email',
101
        'Total'       => 'Total',
102
        'Status'      => 'Status',
103
    );
104
105
    private static $searchable_fields = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
106
        'Reference' => array(),
107
        'FirstName' => array(
108
            'title' => 'Customer Name',
109
        ),
110
        'Email'     => array(
111
            'title' => 'Customer Email',
112
        ),
113
        'Status'    => array(
114
            'filter' => 'ExactMatchFilter',
115
            'field'  => 'CheckboxSetField',
116
        ),
117
    );
118
119
    private static $singular_name     = "Order";
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
120
121
    private static $plural_name       = "Orders";
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
122
123
    private static $default_sort      = "\"Placed\" DESC, \"Created\" DESC";
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
124
125
    /**
126
     * Statuses for orders that have been placed.
127
     */
128
    private static $placed_status = array(
129
        'Paid',
130
        'Unpaid',
131
        'Processing',
132
        'Sent',
133
        'Complete',
134
        'MemberCancelled',
135
        'AdminCancelled',
136
    );
137
138
    /**
139
     * Statuses for which an order can be paid for
140
     */
141
    private static $payable_status = array(
142
        'Cart',
143
        'Unpaid',
144
        'Processing',
145
        'Sent',
146
    );
147
148
    /**
149
     * Statuses that shouldn't show in user account.
150
     */
151
    private static $hidden_status = array('Cart');
152
153
154
    /**
155
     * Status for logging changes
156
     * @var array
157
     */
158
    private static $log_status = array();
159
160
    /**
161
     * Flags to determine when an order can be cancelled.
162
     */
163
    private static $cancel_before_payment    = true;
164
165
    private static $cancel_before_processing = false;
166
167
    private static $cancel_before_sending    = false;
168
169
    private static $cancel_after_sending     = false;
170
171
    /**
172
     * Place an order before payment processing begins
173
     *
174
     * @var boolean
175
     */
176
    private static $place_before_payment = false;
177
178
    /**
179
     * Modifiers represent the additional charges or
180
     * deductions associated to an order, such as
181
     * shipping, taxes, vouchers etc.
182
     */
183
    private static $modifiers            = array();
184
185
    private static $rounding_precision   = 2;
186
187
    private static $reference_id_padding = 5;
188
189
    /**
190
     * @var boolean Will allow completion of orders with GrandTotal=0,
191
     * which could be the case for orders paid with loyalty points or vouchers.
192
     * Will send the "Paid" date on the order, even though no actual payment was taken.
193
     * Will trigger the payment related extension points:
194
     * Order->onPayment, OrderItem->onPayment, Order->onPaid.
195
     */
196
    private static $allow_zero_order_total = false;
197
198
    /**
199
     * A flag indicating that an order-status-log entry should be written
200
     * @var bool
201
     */
202
    protected $flagOrderStatusWrite = false;
203
204
    public static function get_order_status_options()
205
    {
206
        $values = array();
207
        foreach (singleton('Order')->dbObject('Status')->enumValues(false) as $value) {
208
            $values[$value] = _t('Order.STATUS_' . strtoupper($value), $value);
209
        }
210
        return $values;
211
    }
212
213
    /**
214
     * Create CMS fields for cms viewing and editing orders
215
     */
216
    public function getCMSFields()
217
    {
218
        $fields = FieldList::create(TabSet::create('Root', Tab::create('Main')));
219
        $fs = "<div class=\"field\">";
220
        $fe = "</div>";
221
        $parts = array(
222
            DropdownField::create("Status", _t('Order.db_Status', "Status"), self::get_order_status_options()),
223
            LiteralField::create('Customer', $fs . $this->renderWith("OrderAdmin_Customer") . $fe),
224
            LiteralField::create('Addresses', $fs . $this->renderWith("OrderAdmin_Addresses") . $fe),
225
            LiteralField::create('Content', $fs . $this->renderWith("OrderAdmin_Content") . $fe),
226
        );
227
        if ($this->Notes) {
228
            $parts[] = LiteralField::create('Notes', $fs . $this->renderWith("OrderAdmin_Notes") . $fe);
229
        }
230
        $fields->addFieldsToTab('Root.Main', $parts);
231
        $this->extend('updateCMSFields', $fields);
232
        if ($payments = $fields->fieldByName("Root.Payments.Payments")) {
233
            $fields->removeByName("Payments");
234
            $fields->insertAfter($payments, "Content");
235
            $payments->addExtraClass("order-payments");
236
        }
237
238
        return $fields;
239
    }
240
241
    /**
242
     * Adjust scafolded search context
243
     *
244
     * @return SearchContext the updated search context
245
     */
246
    public function getDefaultSearchContext()
247
    {
248
        $context = parent::getDefaultSearchContext();
249
        $fields = $context->getFields();
250
        $fields->push(
251
            ListboxField::create("Status", _t('Order.db_Status', "Status"))
252
                ->setSource(
253
                    array_combine(
254
                        self::config()->placed_status,
255
                        self::config()->placed_status
256
                    )
257
                )
258
                ->setMultiple(true)
259
        );
260
261
        // add date range filtering
262
        $fields->insertBefore(
263
            DateField::create("DateFrom", _t('Order.DateFrom', "Date from"))
264
                ->setConfig('showcalendar', true),
265
            'Status'
0 ignored issues
show
Documentation introduced by
'Status' is of type string, but the function expects a object<FormField>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
266
        );
267
        $fields->insertBefore(
268
            DateField::create("DateTo", _t('Order.DateTo', "Date to"))
269
                ->setConfig('showcalendar', true),
270
            'Status'
0 ignored issues
show
Documentation introduced by
'Status' is of type string, but the function expects a object<FormField>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
271
        );
272
273
        // get the array, to maniplulate name, and fullname seperately
274
        $filters = $context->getFilters();
275
        $filters['DateFrom'] = GreaterThanFilter::create('Placed');
276
        $filters['DateTo'] = LessThanFilter::create('Placed');
277
278
        // filter customer need to use a bunch of different sources
279
        $filters['FirstName'] = new MultiFieldPartialMatchFilter(
280
            'FirstName', false,
281
            array('SplitWords'),
282
            array(
283
                'Surname',
284
                'Member.FirstName',
285
                'Member.Surname',
286
                'BillingAddress.FirstName',
287
                'BillingAddress.Surname',
288
                'ShippingAddress.FirstName',
289
                'ShippingAddress.Surname',
290
            )
291
        );
292
293
        $context->setFilters($filters);
294
295
        $this->extend('updateDefaultSearchContext', $context);
296
        return $context;
297
    }
298
299
    /**
300
     * Hack for swapping out relation list with OrderItemList
301
     */
302 43
    public function getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null)
303
    {
304 43
        $components = parent::getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null);
305 43
        if ($componentName === "Items" && get_class($components) !== "UnsavedRelationList") {
306 39
            $query = $components->dataQuery();
307 39
            $components = OrderItemList::create("OrderItem", "OrderID");
308 39
            if ($this->model) {
309 39
                $components->setDataModel($this->model);
310 39
            }
311 39
            $components->setDataQuery($query);
312 39
            $components = $components->forForeignID($this->ID);
313 39
        }
314 43
        return $components;
315
    }
316
317
    /**
318
     * Returns the subtotal of the items for this order.
319
     */
320 25
    public function SubTotal()
321
    {
322 25
        if ($this->Items()->exists()) {
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
323 19
            return $this->Items()->SubTotal();
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
324
        }
325
326 8
        return 0;
327
    }
328
329
    /**
330
     * Calculate the total
331
     *
332
     * @return the final total
333
     */
334 18
    public function calculate()
335
    {
336 18
        if (!$this->IsCart()) {
337 1
            return $this->Total;
0 ignored issues
show
Documentation introduced by
The property Total does not exist on object<Order>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
338
        }
339 17
        $calculator = new OrderTotalCalculator($this);
340 17
        return $this->Total = $calculator->calculate();
0 ignored issues
show
Documentation introduced by
The property Total does not exist on object<Order>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
341
    }
342
343
    /**
344
     * This is needed to maintain backwards compatiability with
345
     * some subsystems using modifiers. eg discounts
346
     */
347
    public function getModifier($className, $forcecreate = false)
348
    {
349
        $calculator = new OrderTotalCalculator($this);
350
        return $calculator->getModifier($className, $forcecreate);
351
    }
352
353
    /**
354
     * Enforce rounding precision when setting total
355
     */
356 76
    public function setTotal($val)
357
    {
358 76
        $this->setField("Total", round($val, self::$rounding_precision));
359 76
    }
360
361
    /**
362
     * Get final value of order.
363
     * Retrieves value from DataObject's record array.
364
     */
365 21
    public function Total()
366
    {
367 21
        return $this->getField("Total");
368
    }
369
370
    /**
371
     * Alias for Total.
372
     */
373 19
    public function GrandTotal()
374
    {
375 19
        return $this->Total();
376
    }
377
378
    /**
379
     * Calculate how much is left to be paid on the order.
380
     * Enforces rounding precision.
381
     *
382
     * Payments that have been authorized via a non-manual gateway should count towards the total paid amount.
383
     * However, it's possible to exclude these by setting the $includeAuthorized parameter to false, which is
384
     * useful to determine the status of the Order. Order status should only change to 'Paid' when all
385
     * payments are 'Captured'.
386
     *
387
     * @param bool $includeAuthorized whether or not to include authorized payments (excluding manual payments)
388
     * @return float
389
     */
390 17
    public function TotalOutstanding($includeAuthorized = true)
391
    {
392 17
        return round(
393 17
            $this->GrandTotal() - ($includeAuthorized ? $this->TotalPaidOrAuthorized() : $this->TotalPaid()),
0 ignored issues
show
Documentation Bug introduced by
The method TotalPaidOrAuthorized does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
Documentation Bug introduced by
The method TotalPaid does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
394 17
            self::config()->rounding_precision
395 17
        );
396
    }
397
398
    /**
399
     * Get the order status. This will return a localized value if available.
400
     *
401
     * @return string the payment status
402
     */
403 5
    public function getStatusI18N()
404
    {
405 5
        return _t('Order.STATUS_' . strtoupper($this->Status), $this->Status);
406
    }
407
408
    /**
409
     * Get the link for finishing order processing.
410
     */
411 3
    public function Link()
412
    {
413 3
        if (Member::currentUser()) {
414 1
            return Controller::join_links(AccountPage::find_link(), 'order', $this->ID);
415
        }
416 2
        return CheckoutPage::find_link(false, "order", $this->ID);
417
    }
418
419
    /**
420
     * Returns TRUE if the order can be cancelled
421
     * PRECONDITION: Order is in the DB.
422
     *
423
     * @return boolean
424
     */
425 4
    public function canCancel()
426
    {
427 4
        switch ($this->Status) {
428 4
            case 'Unpaid' :
429 4
                return self::config()->cancel_before_payment;
430 1
            case 'Paid' :
431 1
                return self::config()->cancel_before_processing;
432 1
            case 'Processing' :
433 1
                return self::config()->cancel_before_sending;
434 1
            case 'Sent' :
435 1
            case 'Complete' :
436 1
                return self::config()->cancel_after_sending;
437 1
        }
438 1
        return false;
439
    }
440
441
    /**
442
     * Check if an order can be paid for.
443
     *
444
     * @return boolean
445
     */
446 7
    public function canPay($member = null)
0 ignored issues
show
Unused Code introduced by
The parameter $member is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
447
    {
448 7
        if (!in_array($this->Status, self::config()->payable_status)) {
449 1
            return false;
450
        }
451 7
        if ($this->TotalOutstanding(true) > 0 && empty($this->Paid)) {
452 5
            return true;
453
        }
454 3
        return false;
455
    }
456
457
    /*
458
     * Prevent deleting orders.
459
     * @return boolean
460
     */
461 1
    public function canDelete($member = null)
462
    {
463 1
        return false;
464
    }
465
466
    /**
467
     * Check if an order can be viewed.
468
     *
469
     * @return boolean
470
     */
471
    public function canView($member = null)
472
    {
473
        return true;
474
    }
475
476
    /**
477
     * Check if an order can be edited.
478
     *
479
     * @return boolean
480
     */
481 1
    public function canEdit($member = null)
482
    {
483 1
        return true;
484
    }
485
486
    /**
487
     * Prevent standard creation of orders.
488
     *
489
     * @return boolean
490
     */
491 1
    public function canCreate($member = null, $context = array())
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
492
    {
493 1
        return false;
494
    }
495
496
    /**
497
     * Return the currency of this order.
498
     * Note: this is a fixed value across the entire site.
499
     *
500
     * @return string
501
     */
502 7
    public function Currency()
503
    {
504 7
        return ShopConfig::get_site_currency();
505
    }
506
507
    /**
508
     * Get the latest email for this order.z
509
     */
510 9
    public function getLatestEmail()
511
    {
512 9
        if ($this->MemberID && ($this->Member()->LastEdited > $this->LastEdited || !$this->Email)) {
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
513 6
            return $this->Member()->Email;
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
514
        }
515 3
        return $this->getField('Email');
516
    }
517
518
    /**
519
     * Gets the name of the customer.
520
     */
521
    public function getName()
522
    {
523
        $firstname = $this->FirstName ? $this->FirstName : $this->Member()->FirstName;
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
524
        $surname = $this->FirstName ? $this->Surname : $this->Member()->Surname;
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
525
        return implode(" ", array_filter(array($firstname, $surname)));
526
    }
527
528
    public function getTitle()
529
    {
530
        return $this->Reference . " - " . $this->dbObject('Placed')->Nice();
531
    }
532
533
    /**
534
     * Get shipping address, or member default shipping address.
535
     */
536 9
    public function getShippingAddress()
537
    {
538 9
        return $this->getAddress('Shipping');
539
    }
540
541
    /**
542
     * Get billing address, if marked to use seperate address, otherwise use shipping address,
543
     * or the member default billing address.
544
     */
545 8
    public function getBillingAddress()
546
    {
547 8
        if (!$this->SeparateBillingAddress && $this->ShippingAddressID === $this->BillingAddressID) {
548 8
            return $this->getShippingAddress();
549
        } else {
550
            return $this->getAddress('Billing');
551
        }
552
    }
553
554
    /**
555
     * @param string $type - Billing or Shipping
556
     * @return Address
557
     * @throws Exception
558
     */
559 9
    protected function getAddress($type)
560
    {
561 9
        $address = $this->getComponent($type . 'Address');
562
563 9
        if (!$address || !$address->exists() && $this->Member()) {
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
564 7
            $address = $this->Member()->{"Default${type}Address"}();
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
565 7
        }
566
567
        if (empty($address->Surname) && empty($address->FirstName)) {
568
            if ($member = $this->Member()) {
0 ignored issues
show
Documentation Bug introduced by
The method Member does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
569
                // If there's a member object, use information from the Member.
570
                // The information from Order should have precendence if set though!
571
                $address->FirstName = $this->FirstName ?: $member->FirstName;
572
                $address->Surname = $this->Surname ?: $member->Surname;
573
            } else {
574
                $address->FirstName = $this->FirstName;
575
                $address->Surname = $this->Surname;
576
            }
577
        }
578
579
        return $address;
580
    }
581
582
    /**
583
     * Check if the two addresses saved differ.
584
     *
585
     * @return boolean
586
     */
587
    public function getAddressesDiffer()
588
    {
589
        return $this->SeparateBillingAddress || $this->ShippingAddressID !== $this->BillingAddressID;
590
    }
591
592
    /**
593
     * Has this order been sent to the customer?
594
     * (at "Sent" status).
595
     *
596
     * @return boolean
597
     */
598
    public function IsSent()
599
    {
600 1
        return $this->Status == 'Sent';
601
    }
602
603
    /**
604
     * Is this order currently being processed?
605
     * (at "Sent" OR "Processing" status).
606
     *
607
     * @return boolean
608
     */
609
    public function IsProcessing()
610
    {
611 1
        return $this->IsSent() || $this->Status == 'Processing';
612
    }
613
614
    /**
615
     * Return whether this Order has been paid for (Status == Paid)
616
     * or Status == Processing, where it's been paid for, but is
617
     * currently in a processing state.
618
     *
619
     * @return boolean
620
     */
621
    public function IsPaid()
622
    {
623 2
        return (boolean)$this->Paid || $this->Status == 'Paid';
624
    }
625
626
    public function IsCart()
627
    {
628 96
        return $this->Status == 'Cart';
629
    }
630
631
    /**
632
     * Create a unique reference identifier string for this order.
633
     */
634
    public function generateReference()
635
    {
636 79
        $reference = str_pad($this->ID, self::$reference_id_padding, '0', STR_PAD_LEFT);
637 79
        $this->extend('generateReference', $reference);
638 79
        $candidate = $reference;
639
        //prevent generating references that are the same
640 79
        $count = 0;
641 79
        while (DataObject::get_one('Order', "\"Reference\" = '$candidate'")) {
642 76
            $count++;
643 76
            $candidate = $reference . "" . $count;
644 76
        }
645 79
        $this->Reference = $candidate;
646 79
    }
647
648
    /**
649
     * Get the reference for this order, or fall back to order ID.
650
     */
651
    public function getReference()
652
    {
653 79
        return $this->getField('Reference') ? $this->getField('Reference') : $this->ID;
654
    }
655
656
    /**
657
     * Force creating an order reference
658
     */
659
    protected function onBeforeWrite()
660
    {
661 99
        parent::onBeforeWrite();
662 99
        if (!$this->getField("Reference") && in_array($this->Status, self::$placed_status)) {
663 79
            $this->generateReference();
664 79
        }
665
666
        // perform status transition
667 99
        if ($this->isInDB() && $this->isChanged('Status')) {
668 9
            $this->statusTransition(
669 9
                empty($this->original['Status']) ? 'Cart' : $this->original['Status'],
670 9
                $this->Status
671 9
            );
672 9
        }
673
674
        // While the order is unfinished/cart, always store the current locale with the order.
675
        // We do this everytime an order is saved, because the user might change locale (language-switch).
676 99
        if ($this->Status == 'Cart') {
677 99
            $this->Locale = ShopTools::get_current_locale();
678 99
        }
679 99
    }
680
681
    /**
682
     * Called from @see onBeforeWrite whenever status changes
683
     * @param string $fromStatus status to transition away from
684
     * @param string $toStatus target status
685
     */
686
    protected function statusTransition($fromStatus, $toStatus)
687
    {
688
        // Add extension hook to react to order status transitions.
689 9
        $this->extend('onStatusChange', $fromStatus, $toStatus);
690
691 9
        if ($toStatus == 'Paid' && !$this->Paid) {
692 4
            $this->Paid = SS_Datetime::now()->Rfc2822();
693 4
            foreach ($this->Items() as $item) {
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
694 3
                $item->onPayment();
695 4
            }
696
            //all payment is settled
697 4
            $this->extend('onPaid');
698
699 4
            if (!$this->ReceiptSent) {
700 4
                OrderEmailNotifier::create($this)->sendReceipt();
701 4
                $this->ReceiptSent = SS_Datetime::now()->Rfc2822();
702 4
            }
703 4
        }
704
705 9
        $logStatus = $this->config()->log_status;
706 9
        if (!empty($logStatus) && in_array($toStatus, $logStatus)) {
707 3
            $this->flagOrderStatusWrite = $fromStatus != $toStatus;
708 3
        }
709 9
    }
710
711
    /**
712
     * delete attributes, statuslogs, and payments
713
     */
714
    protected function onBeforeDelete()
715
    {
716 2
        foreach ($this->Items() as $item) {
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
717 1
            $item->delete();
718 2
        }
719
720 2
        foreach ($this->Modifiers() as $modifier) {
0 ignored issues
show
Documentation Bug introduced by
The method Modifiers does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
721 1
            $modifier->delete();
722 2
        }
723
724 2
        foreach ($this->OrderStatusLogs() as $logEntry) {
0 ignored issues
show
Documentation Bug introduced by
The method OrderStatusLogs does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
725 1
            $logEntry->delete();
726 2
        }
727
728
        // just remove the payment relations…
729
        // that way payment objects still persist (they might be relevant for book-keeping?)
730 2
        $this->Payments()->removeAll();
0 ignored issues
show
Documentation Bug introduced by
The method Payments does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
731
732 2
        parent::onBeforeDelete();
733 2
    }
734
735
    public function onAfterWrite()
736
    {
737 99
        parent::onAfterWrite();
738
739
        //create an OrderStatusLog
740 99
        if ($this->flagOrderStatusWrite) {
741 3
            $this->flagOrderStatusWrite = false;
742 3
            $log = OrderStatusLog::create();
743
744
            // populate OrderStatusLog
745 3
            $log->Title = _t(
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<OrderStatusLog>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
746 3
                'ShopEmail.StatusChanged',
747 3
                'Status for order #{OrderNo} changed to "{OrderStatus}"',
748 3
                '',
749 3
                array('OrderNo' => $this->Reference, 'OrderStatus' => $this->getStatusI18N())
0 ignored issues
show
Documentation introduced by
array('OrderNo' => $this...$this->getStatusI18N()) is of type array<string,string,{"Or...OrderStatus":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
750 3
            );
751 3
            $log->Note = _t('ShopEmail.StatusChange' . $this->Status . 'Note');
0 ignored issues
show
Documentation introduced by
The property Note does not exist on object<OrderStatusLog>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
752 3
            $log->OrderID = $this->ID;
0 ignored issues
show
Documentation introduced by
The property OrderID does not exist on object<OrderStatusLog>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
753 3
            OrderEmailNotifier::create($this)->sendStatusChange($log->Title, $log->Note);
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<OrderStatusLog>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property Note does not exist on object<OrderStatusLog>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
754 3
            $log->SentToCustomer = true;
0 ignored issues
show
Documentation introduced by
The property SentToCustomer does not exist on object<OrderStatusLog>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
755 3
            $this->extend('updateOrderStatusLog', $log);
756 3
            $log->write();
757 3
        }
758 99
    }
759
760
    public function debug()
761
    {
762
        $val = "<div class='order'><h1>$this->class</h1>\n<ul>\n";
763
        if ($this->record) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->record of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
764
            foreach ($this->record as $fieldName => $fieldVal) {
765
                $val .= "\t<li>$fieldName: " . Debug::text($fieldVal) . "</li>\n";
766
            }
767
        }
768
        $val .= "</ul>\n";
769
        $val .= "<div class='items'><h2>Items</h2>";
770
        if ($items = $this->Items()) {
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
Unused Code introduced by
$items is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
771
            $val .= $this->Items()->debug();
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
772
        }
773
        $val .= "</div><div class='modifiers'><h2>Modifiers</h2>";
774
        if ($modifiers = $this->Modifiers()) {
0 ignored issues
show
Documentation Bug introduced by
The method Modifiers does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
775
            $val .= $modifiers->debug();
776
        }
777
        $val .= "</div></div>";
778
779
        return $val;
780
    }
781
782
    /**
783
     * Provide i18n entities for the order class
784
     *
785
     * @return array
786
     */
787
    public function provideI18nEntities()
788
    {
789
        $entities = parent::provideI18nEntities();
790
791
        // collect all the payment status values
792
        foreach ($this->dbObject('Status')->enumValues() as $value) {
793
            $key = strtoupper($value);
794
            $entities["Order.STATUS_$key"] = array(
795
                $value,
796
                "Translation of the order status '$value'",
797
            );
798
        }
799
800
        return $entities;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $entities; (array<*,array>) is incompatible with the return type of the parent method DataObject::provideI18nEntities of type array<*,array<array|inte...double|string|boolean>>.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
801
    }
802
}
803