Completed
Pull Request — master (#599)
by Roman
20:39
created

Order::canPay()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5.0342

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 8
cts 9
cp 0.8889
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 9
nc 4
nop 1
crap 5.0342
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 104 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 3
    public static function get_order_status_options()
205
    {
206
        $values = array();
207 3
        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 45
    public function getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null)
303
    {
304 45
        $components = parent::getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null);
305 45
        if ($componentName === "Items" && get_class($components) !== "UnsavedRelationList") {
306 41
            $query = $components->dataQuery();
307 41
            $components = OrderItemList::create("OrderItem", "OrderID");
308 41
            if ($this->model) {
309 41
                $components->setDataModel($this->model);
310 41
            }
311 41
            $components->setDataQuery($query);
312 41
            $components = $components->forForeignID($this->ID);
313 41
        }
314 45
        return $components;
315
    }
316
317
    /**
318
     * Returns the subtotal of the items for this order.
319
     */
320 33
    public function SubTotal()
321
    {
322 33
        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 20
            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 15
        return 0;
327
    }
328
329
    /**
330
     * Calculate the total
331
     *
332
     * @return the final total
333
     */
334 26
    public function calculate()
335
    {
336 26
        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 25
        $calculator = new OrderTotalCalculator($this);
340 25
        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 78
    public function setTotal($val)
357
    {
358 78
        $this->setField("Total", round($val, self::$rounding_precision));
359 78
    }
360
361
    /**
362
     * Get final value of order.
363
     * Retrieves value from DataObject's record array.
364
     */
365 22
    public function Total()
366
    {
367 22
        return $this->getField("Total");
368
    }
369
370
    /**
371
     * Alias for Total.
372
     */
373 20
    public function GrandTotal()
374
    {
375 20
        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 18
    public function TotalOutstanding($includeAuthorized = true)
391
    {
392 18
        return round(
393 18
            $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 18
            self::config()->rounding_precision
395 18
        );
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($member = null)
426
    {
427 4
        $extended = $this->extendedCan(__FUNCTION__, $member);
428 4
        if($extended !== null) {
429
            return $extended;
430
        }
431
432 4
        switch ($this->Status) {
433 4
            case 'Unpaid' :
434 4
                return self::config()->cancel_before_payment;
435 1
            case 'Paid' :
436 1
                return self::config()->cancel_before_processing;
437 1
            case 'Processing' :
438 1
                return self::config()->cancel_before_sending;
439 1
            case 'Sent' :
440 1
            case 'Complete' :
441 1
                return self::config()->cancel_after_sending;
442 1
        }
443 1
        return false;
444
    }
445
446
    /**
447
     * Check if an order can be paid for.
448
     *
449
     * @return boolean
450
     */
451 7
    public function canPay($member = null)
452
    {
453 7
        $extended = $this->extendedCan(__FUNCTION__, $member);
454 7
        if($extended !== null) {
455
            return $extended;
456
        }
457
458 7
        if (!in_array($this->Status, self::config()->payable_status)) {
459 1
            return false;
460
        }
461 7
        if ($this->TotalOutstanding(true) > 0 && empty($this->Paid)) {
462 5
            return true;
463
        }
464 3
        return false;
465
    }
466
467
    /*
468
     * Prevent deleting orders.
469
     * @return boolean
470
     */
471 1
    public function canDelete($member = null)
472
    {
473 1
        $extended = $this->extendedCan(__FUNCTION__, $member);
474 1
        if($extended !== null) {
475
            return $extended;
476
        }
477
478 1
        return false;
479
    }
480
481
    /**
482
     * Check if an order can be viewed.
483
     *
484
     * @return boolean
485
     */
486
    public function canView($member = null)
487
    {
488
        $extended = $this->extendedCan(__FUNCTION__, $member);
489
        if($extended !== null) {
490
            return $extended;
491
        }
492
493
        return true;
494
    }
495
496
    /**
497
     * Check if an order can be edited.
498
     *
499
     * @return boolean
500
     */
501 1
    public function canEdit($member = null)
502
    {
503 1
        $extended = $this->extendedCan(__FUNCTION__, $member);
504 1
        if($extended !== null) {
505
            return $extended;
506
        }
507
508 1
        return true;
509
    }
510
511
    /**
512
     * Prevent standard creation of orders.
513
     *
514
     * @return boolean
515
     */
516 1
    public function canCreate($member = null, $context = array())
517
    {
518 1
        $extended = $this->extendedCan(__FUNCTION__, $member, $context);
0 ignored issues
show
Unused Code introduced by
The call to Order::extendedCan() has too many arguments starting with $context.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
519 1
        if($extended !== null) {
520
            return $extended;
521
        }
522
523 1
        return false;
524
    }
525
526
    /**
527
     * Return the currency of this order.
528
     * Note: this is a fixed value across the entire site.
529
     *
530
     * @return string
531
     */
532 8
    public function Currency()
533
    {
534 8
        return ShopConfig::get_site_currency();
535
    }
536
537
    /**
538
     * Get the latest email for this order.z
539
     */
540 10
    public function getLatestEmail()
541
    {
542 10
        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...
543 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...
544
        }
545 4
        return $this->getField('Email');
546
    }
547
548
    /**
549
     * Gets the name of the customer.
550
     */
551
    public function getName()
552
    {
553
        $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...
554
        $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...
555
        return implode(" ", array_filter(array($firstname, $surname)));
556
    }
557
558
    public function getTitle()
559
    {
560
        return $this->Reference . " - " . $this->dbObject('Placed')->Nice();
561
    }
562
563
    /**
564
     * Get shipping address, or member default shipping address.
565
     */
566 11
    public function getShippingAddress()
567
    {
568 11
        return $this->getAddress('Shipping');
569
    }
570
571
    /**
572
     * Get billing address, if marked to use seperate address, otherwise use shipping address,
573
     * or the member default billing address.
574
     */
575 9
    public function getBillingAddress()
576
    {
577 9
        if (!$this->SeparateBillingAddress && $this->ShippingAddressID === $this->BillingAddressID) {
578 9
            return $this->getShippingAddress();
579
        } else {
580
            return $this->getAddress('Billing');
581
        }
582
    }
583
584
    /**
585
     * @param string $type - Billing or Shipping
586
     * @return Address
587
     * @throws Exception
588
     */
589 11
    protected function getAddress($type)
590
    {
591 11
        $address = $this->getComponent($type . 'Address');
592
593 11
        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...
594 9
            $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...
595 9
        }
596
597
        if (empty($address->Surname) && empty($address->FirstName)) {
598
            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...
599
                // If there's a member object, use information from the Member.
600
                // The information from Order should have precendence if set though!
601
                $address->FirstName = $this->FirstName ?: $member->FirstName;
602
                $address->Surname = $this->Surname ?: $member->Surname;
603
            } else {
604
                $address->FirstName = $this->FirstName;
605
                $address->Surname = $this->Surname;
606
            }
607
        }
608
609
        return $address;
610
    }
611
612
    /**
613
     * Check if the two addresses saved differ.
614
     *
615
     * @return boolean
616
     */
617
    public function getAddressesDiffer()
618
    {
619
        return $this->SeparateBillingAddress || $this->ShippingAddressID !== $this->BillingAddressID;
620
    }
621
622
    /**
623
     * Has this order been sent to the customer?
624
     * (at "Sent" status).
625
     *
626
     * @return boolean
627
     */
628
    public function IsSent()
629
    {
630 1
        return $this->Status == 'Sent';
631
    }
632
633
    /**
634
     * Is this order currently being processed?
635
     * (at "Sent" OR "Processing" status).
636
     *
637
     * @return boolean
638
     */
639
    public function IsProcessing()
640
    {
641 1
        return $this->IsSent() || $this->Status == 'Processing';
642
    }
643
644
    /**
645
     * Return whether this Order has been paid for (Status == Paid)
646
     * or Status == Processing, where it's been paid for, but is
647
     * currently in a processing state.
648
     *
649
     * @return boolean
650
     */
651
    public function IsPaid()
652
    {
653 3
        return (boolean)$this->Paid || $this->Status == 'Paid';
654
    }
655
656
    public function IsCart()
657
    {
658 98
        return $this->Status == 'Cart';
659
    }
660
661
    /**
662
     * Create a unique reference identifier string for this order.
663
     */
664
    public function generateReference()
665
    {
666 81
        $reference = str_pad($this->ID, self::$reference_id_padding, '0', STR_PAD_LEFT);
667 81
        $this->extend('generateReference', $reference);
668 81
        $candidate = $reference;
669
        //prevent generating references that are the same
670 81
        $count = 0;
671 81
        while (DataObject::get_one('Order', "\"Reference\" = '$candidate'")) {
672 78
            $count++;
673 78
            $candidate = $reference . "" . $count;
674 78
        }
675 81
        $this->Reference = $candidate;
676 81
    }
677
678
    /**
679
     * Get the reference for this order, or fall back to order ID.
680
     */
681
    public function getReference()
682
    {
683 81
        return $this->getField('Reference') ? $this->getField('Reference') : $this->ID;
684
    }
685
686
    /**
687
     * Force creating an order reference
688
     */
689
    protected function onBeforeWrite()
690
    {
691 101
        parent::onBeforeWrite();
692 101
        if (!$this->getField("Reference") && in_array($this->Status, self::$placed_status)) {
693 81
            $this->generateReference();
694 81
        }
695
696
        // perform status transition
697 101
        if ($this->isInDB() && $this->isChanged('Status')) {
698 10
            $this->statusTransition(
699 10
                empty($this->original['Status']) ? 'Cart' : $this->original['Status'],
700 10
                $this->Status
701 10
            );
702 10
        }
703
704
        // While the order is unfinished/cart, always store the current locale with the order.
705
        // We do this everytime an order is saved, because the user might change locale (language-switch).
706 101
        if ($this->Status == 'Cart') {
707 101
            $this->Locale = ShopTools::get_current_locale();
708 101
        }
709 101
    }
710
711
    /**
712
     * Called from @see onBeforeWrite whenever status changes
713
     * @param string $fromStatus status to transition away from
714
     * @param string $toStatus target status
715
     */
716
    protected function statusTransition($fromStatus, $toStatus)
717
    {
718
        // Add extension hook to react to order status transitions.
719 10
        $this->extend('onStatusChange', $fromStatus, $toStatus);
720
721 10
        if ($toStatus == 'Paid' && !$this->Paid) {
722 5
            $this->Paid = SS_Datetime::now()->Rfc2822();
723 5
            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...
724 4
                $item->onPayment();
725 5
            }
726
            //all payment is settled
727 5
            $this->extend('onPaid');
728
729 5
            if (!$this->ReceiptSent) {
730 5
                OrderEmailNotifier::create($this)->sendReceipt();
731 5
                $this->ReceiptSent = SS_Datetime::now()->Rfc2822();
732 5
            }
733 5
        }
734
735 10
        $logStatus = $this->config()->log_status;
736 10
        if (!empty($logStatus) && in_array($toStatus, $logStatus)) {
737 3
            $this->flagOrderStatusWrite = $fromStatus != $toStatus;
738 3
        }
739 10
    }
740
741
    /**
742
     * delete attributes, statuslogs, and payments
743
     */
744
    protected function onBeforeDelete()
745
    {
746 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...
747 1
            $item->delete();
748 2
        }
749
750 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...
751 1
            $modifier->delete();
752 2
        }
753
754 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...
755 1
            $logEntry->delete();
756 2
        }
757
758
        // just remove the payment relations…
759
        // that way payment objects still persist (they might be relevant for book-keeping?)
760 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...
761
762 2
        parent::onBeforeDelete();
763 2
    }
764
765
    public function onAfterWrite()
766
    {
767 101
        parent::onAfterWrite();
768
769
        //create an OrderStatusLog
770 101
        if ($this->flagOrderStatusWrite) {
771 3
            $this->flagOrderStatusWrite = false;
772 3
            $log = OrderStatusLog::create();
773
774
            // populate OrderStatusLog
775 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...
776 3
                'ShopEmail.StatusChanged',
777 3
                'Status for order #{OrderNo} changed to "{OrderStatus}"',
778 3
                '',
779 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...
780 3
            );
781 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...
782 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...
783 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...
784 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...
785 3
            $this->extend('updateOrderStatusLog', $log);
786 3
            $log->write();
787 3
        }
788 101
    }
789
790
    public function debug()
791
    {
792
        $val = "<div class='order'><h1>$this->class</h1>\n<ul>\n";
793
        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...
794
            foreach ($this->record as $fieldName => $fieldVal) {
795
                $val .= "\t<li>$fieldName: " . Debug::text($fieldVal) . "</li>\n";
796
            }
797
        }
798
        $val .= "</ul>\n";
799
        $val .= "<div class='items'><h2>Items</h2>";
800
        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...
801
            $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...
802
        }
803
        $val .= "</div><div class='modifiers'><h2>Modifiers</h2>";
804
        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...
805
            $val .= $modifiers->debug();
806
        }
807
        $val .= "</div></div>";
808
809
        return $val;
810
    }
811
812
    /**
813
     * Provide i18n entities for the order class
814
     *
815
     * @return array
816
     */
817
    public function provideI18nEntities()
818
    {
819
        $entities = parent::provideI18nEntities();
820
821
        // collect all the payment status values
822
        foreach ($this->dbObject('Status')->enumValues() as $value) {
823
            $key = strtoupper($value);
824
            $entities["Order.STATUS_$key"] = array(
825
                $value,
826
                "Translation of the order status '$value'",
827
            );
828
        }
829
830
        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...
831
    }
832
}
833