Completed
Push — 2.0 ( 7f87f2...afdd14 )
by Roman
16:48
created

Order::getStatusI18N()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
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 22 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 80 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
     * Flags to determine when an order can be cancelled.
155
     */
156
    private static $cancel_before_payment    = true;
157
158
    private static $cancel_before_processing = false;
159
160
    private static $cancel_before_sending    = false;
161
162
    private static $cancel_after_sending     = false;
163
164
    /**
165
     * Place an order before payment processing begins
166
     *
167
     * @var boolean
168
     */
169
    private static $place_before_payment = false;
170
171
    /**
172
     * Modifiers represent the additional charges or
173
     * deductions associated to an order, such as
174
     * shipping, taxes, vouchers etc.
175
     */
176
    private static $modifiers            = array();
177
178
    private static $rounding_precision   = 2;
179
180
    private static $reference_id_padding = 5;
181
182
    /**
183
     * @var boolean Will allow completion of orders with GrandTotal=0,
184
     * which could be the case for orders paid with loyalty points or vouchers.
185
     * Will send the "Paid" date on the order, even though no actual payment was taken.
186
     * Will trigger the payment related extension points:
187
     * Order->onPayment, OrderItem->onPayment, Order->onPaid.
188
     */
189
    private static $allow_zero_order_total = false;
190
191
    public static function get_order_status_options()
192
    {
193
        $values = array();
194
        foreach (singleton('Order')->dbObject('Status')->enumValues(false) as $value) {
195
            $values[$value] = _t('Order.STATUS_' . strtoupper($value), $value);
196
        }
197
        return $values;
198
    }
199
200
    /**
201
     * Create CMS fields for cms viewing and editing orders
202
     */
203
    public function getCMSFields()
204
    {
205
        $fields = FieldList::create(TabSet::create('Root', Tab::create('Main')));
206
        $fs = "<div class=\"field\">";
207
        $fe = "</div>";
208
        $parts = array(
209
            DropdownField::create("Status", _t('Order.db_Status', "Status"), self::get_order_status_options()),
210
            LiteralField::create('Customer', $fs . $this->renderWith("OrderAdmin_Customer") . $fe),
211
            LiteralField::create('Addresses', $fs . $this->renderWith("OrderAdmin_Addresses") . $fe),
212
            LiteralField::create('Content', $fs . $this->renderWith("OrderAdmin_Content") . $fe),
213
        );
214
        if ($this->Notes) {
215
            $parts[] = LiteralField::create('Notes', $fs . $this->renderWith("OrderAdmin_Notes") . $fe);
216
        }
217
        $fields->addFieldsToTab('Root.Main', $parts);
218
        $this->extend('updateCMSFields', $fields);
219
        if ($payments = $fields->fieldByName("Root.Payments.Payments")) {
220
            $fields->removeByName("Payments");
221
            $fields->insertAfter($payments, "Content");
222
            $payments->addExtraClass("order-payments");
223
        }
224
225
        return $fields;
226
    }
227
228
    /**
229
     * Adjust scafolded search context
230
     *
231
     * @return SearchContext the updated search context
232
     */
233
    public function getDefaultSearchContext()
234
    {
235
        $context = parent::getDefaultSearchContext();
236
        $fields = $context->getFields();
237
        $fields->push(
238
            ListboxField::create("Status", _t('Order.db_Status', "Status"))
239
                ->setSource(
240
                    array_combine(
241
                        self::config()->placed_status,
242
                        self::config()->placed_status
243
                    )
244
                )
245
                ->setMultiple(true)
246
        );
247
248
        // add date range filtering
249
        $fields->insertBefore(
250
            DateField::create("DateFrom", _t('Order.DateFrom', "Date from"))
251
                ->setConfig('showcalendar', true),
252
            '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...
253
        );
254
        $fields->insertBefore(
255
            DateField::create("DateTo", _t('Order.DateTo', "Date to"))
256
                ->setConfig('showcalendar', true),
257
            '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...
258
        );
259
260
        // get the array, to maniplulate name, and fullname seperately
261
        $filters = $context->getFilters();
262
        $filters['DateFrom'] = GreaterThanFilter::create('Placed');
263
        $filters['DateTo'] = LessThanFilter::create('Placed');
264
265
        // filter customer need to use a bunch of different sources
266
        $filters['FirstName'] = new MultiFieldPartialMatchFilter(
267
            'FirstName', false,
268
            array('SplitWords'),
269
            array(
270
                'Surname',
271
                'Member.FirstName',
272
                'Member.Surname',
273
                'BillingAddress.FirstName',
274
                'BillingAddress.Surname',
275
                'ShippingAddress.FirstName',
276
                'ShippingAddress.Surname',
277
            )
278
        );
279
280
        $context->setFilters($filters);
281
282
        return $context;
283
    }
284
285
    /**
286
     * Hack for swapping out relation list with OrderItemList
287
     */
288 33
    public function getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null)
289
    {
290 33
        $components = parent::getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null);
291 33
        if ($componentName === "Items" && get_class($components) !== "UnsavedRelationList") {
292 30
            $query = $components->dataQuery();
293 30
            $components = OrderItemList::create("OrderItem", "OrderID");
294 30
            if ($this->model) {
295 30
                $components->setDataModel($this->model);
296 30
            }
297 30
            $components->setDataQuery($query);
298 30
            $components = $components->forForeignID($this->ID);
299 30
        }
300 33
        return $components;
301
    }
302
303
    /**
304
     * Returns the subtotal of the items for this order.
305
     */
306 12
    public function SubTotal()
307
    {
308 12
        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...
309 11
            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...
310
        }
311
312 1
        return 0;
313
    }
314
315
    /**
316
     * Calculate the total
317
     *
318
     * @return the final total
319
     */
320 8
    public function calculate()
321
    {
322 8
        if (!$this->IsCart()) {
323
            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...
324
        }
325 8
        $calculator = new OrderTotalCalculator($this);
326 8
        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...
327
    }
328
329
    /**
330
     * This is needed to maintain backwards compatiability with
331
     * some subsystems using modifiers. eg discounts
332
     */
333
    public function getModifier($className, $forcecreate = false)
334
    {
335
        $calculator = new OrderTotalCalculator($this);
336
        return $calculator->getModifier($className, $forcecreate);
337
    }
338
339
    /**
340
     * Enforce rounding precision when setting total
341
     */
342 63
    public function setTotal($val)
343
    {
344 63
        $this->setField("Total", round($val, self::$rounding_precision));
345 63
    }
346
347
    /**
348
     * Get final value of order.
349
     * Retrieves value from DataObject's record array.
350
     */
351 14
    public function Total()
352
    {
353 14
        return $this->getField("Total");
354
    }
355
356
    /**
357
     * Alias for Total.
358
     */
359 12
    public function GrandTotal()
360
    {
361 12
        return $this->Total();
362
    }
363
364
    /**
365
     * Calculate how much is left to be paid on the order.
366
     * Enforces rounding precision.
367
     *
368
     * Payments that have been authorized via a non-manual gateway should count towards the total paid amount.
369
     * However, it's possible to exclude these by setting the $includeAuthorized parameter to false, which is
370
     * useful to determine the status of the Order. Order status should only change to 'Paid' when all
371
     * payments are 'Captured'.
372
     *
373
     * @param bool $includeAuthorized whether or not to include authorized payments (excluding manual payments)
374
     * @return float
375
     */
376 10
    public function TotalOutstanding($includeAuthorized = true)
377
    {
378 10
        return round(
379 10
            $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...
380 10
            self::config()->rounding_precision
381 10
        );
382
    }
383
384
    /**
385
     * Get the order status. This will return a localized value if available.
386
     *
387
     * @return string the payment status
388
     */
389 2
    public function getStatusI18N()
390
    {
391 2
        return _t('Order.STATUS_' . strtoupper($this->Status), $this->Status);
392
    }
393
394
    /**
395
     * Get the link for finishing order processing.
396
     */
397 2
    public function Link()
398
    {
399 2
        if (Member::currentUser()) {
400 1
            return Controller::join_links(AccountPage::find_link(), 'order', $this->ID);
401
        }
402 1
        return CheckoutPage::find_link(false, "order", $this->ID);
403
    }
404
405
    /**
406
     * Returns TRUE if the order can be cancelled
407
     * PRECONDITION: Order is in the DB.
408
     *
409
     * @return boolean
410
     */
411 1
    public function canCancel()
412
    {
413 1
        switch ($this->Status) {
414 1
            case 'Unpaid' :
415 1
                return self::config()->cancel_before_payment;
416
            case 'Paid' :
417
                return self::config()->cancel_before_processing;
418
            case 'Processing' :
419
                return self::config()->cancel_before_sending;
420
            case 'Sent' :
421
            case 'Complete' :
422
                return self::config()->cancel_after_sending;
423
        }
424
        return false;
425
    }
426
427
    /**
428
     * Check if an order can be paid for.
429
     *
430
     * @return boolean
431
     */
432 2
    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...
433
    {
434 2
        if (!in_array($this->Status, self::config()->payable_status)) {
435
            return false;
436
        }
437 2
        if ($this->TotalOutstanding(true) > 0 && empty($this->Paid)) {
438 2
            return true;
439
        }
440
        return false;
441
    }
442
443
    /*
444
     * Prevent deleting orders.
445
     * @return boolean
446
     */
447
    public function canDelete($member = null)
448
    {
449
        return false;
450
    }
451
452
    /**
453
     * Check if an order can be viewed.
454
     *
455
     * @return boolean
456
     */
457
    public function canView($member = null)
458
    {
459
        return true;
460
    }
461
462
    /**
463
     * Check if an order can be edited.
464
     *
465
     * @return boolean
466
     */
467
    public function canEdit($member = null)
468
    {
469
        return true;
470
    }
471
472
    /**
473
     * Prevent standard creation of orders.
474
     *
475
     * @return boolean
476
     */
477
    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...
478
    {
479
        return false;
480
    }
481
482
    /**
483
     * Return the currency of this order.
484
     * Note: this is a fixed value across the entire site.
485
     *
486
     * @return string
487
     */
488 3
    public function Currency()
489
    {
490 3
        return ShopConfig::get_site_currency();
491
    }
492
493
    /**
494
     * Get the latest email for this order.
495
     */
496 3
    public function getLatestEmail()
497
    {
498 3
        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...
499 3
            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...
500
        }
501
        return $this->getField('Email');
502
    }
503
504
    /**
505
     * Gets the name of the customer.
506
     */
507
    public function getName()
508
    {
509
        $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...
510
        $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...
511
        return implode(" ", array_filter(array($firstname, $surname)));
512
    }
513
514
    public function getTitle()
515
    {
516
        return $this->Reference . " - " . $this->dbObject('Placed')->Nice();
517
    }
518
519
    /**
520
     * Get shipping address, or member default shipping address.
521
     */
522 1
    public function getShippingAddress()
523
    {
524 1
        return $this->getAddress('Shipping');
525
    }
526
527
    /**
528
     * Get billing address, if marked to use seperate address, otherwise use shipping address,
529
     * or the member default billing address.
530
     */
531 1
    public function getBillingAddress()
532
    {
533 1
        if (!$this->SeparateBillingAddress && $this->ShippingAddressID === $this->BillingAddressID) {
534 1
            return $this->getShippingAddress();
535
        } else {
536
            return $this->getAddress('Billing');
537
        }
538
    }
539
540
    /**
541
     * @param string $type - Billing or Shipping
542
     * @return Address
543
     * @throws Exception
544
     */
545 1
    protected function getAddress($type)
546
    {
547 1
        $address = $this->getComponent($type . 'Address');
548
549 1
        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...
550 1
            $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...
551 1
        }
552
553
        if (empty($address->Surname) && empty($address->FirstName)) {
554
            $address->FirstName = $this->FirstName;
555
            $address->Surname = $this->Surname;
556
        }
557
558
        return $address;
559
    }
560
561
    /**
562
     * Check if the two addresses saved differ.
563
     *
564
     * @return boolean
565
     */
566
    public function getAddressesDiffer()
567
    {
568
        return $this->SeparateBillingAddress || $this->ShippingAddressID !== $this->BillingAddressID;
569
    }
570
571
    /**
572
     * Has this order been sent to the customer?
573
     * (at "Sent" status).
574
     *
575
     * @return boolean
576
     */
577
    public function IsSent()
578
    {
579 1
        return $this->Status == 'Sent';
580
    }
581
582
    /**
583
     * Is this order currently being processed?
584
     * (at "Sent" OR "Processing" status).
585
     *
586
     * @return boolean
587
     */
588
    public function IsProcessing()
589
    {
590 1
        return $this->IsSent() || $this->Status == 'Processing';
591
    }
592
593
    /**
594
     * Return whether this Order has been paid for (Status == Paid)
595
     * or Status == Processing, where it's been paid for, but is
596
     * currently in a processing state.
597
     *
598
     * @return boolean
599
     */
600
    public function IsPaid()
601
    {
602 1
        return (boolean)$this->Paid || $this->Status == 'Paid';
603
    }
604
605
    public function IsCart()
606
    {
607 81
        return $this->Status == 'Cart';
608
    }
609
610
    /**
611
     * Create a unique reference identifier string for this order.
612
     */
613
    public function generateReference()
614
    {
615 64
        $reference = str_pad($this->ID, self::$reference_id_padding, '0', STR_PAD_LEFT);
616 64
        $this->extend('generateReference', $reference);
617 64
        $candidate = $reference;
618
        //prevent generating references that are the same
619 64
        $count = 0;
620 64
        while (DataObject::get_one('Order', "\"Reference\" = '$candidate'")) {
621 64
            $count++;
622 64
            $candidate = $reference . "" . $count;
623 64
        }
624 64
        $this->Reference = $candidate;
625 64
    }
626
627
    /**
628
     * Get the reference for this order, or fall back to order ID.
629
     */
630
    public function getReference()
631
    {
632 64
        return $this->getField('Reference') ? $this->getField('Reference') : $this->ID;
633
    }
634
635
    /**
636
     * Force creating an order reference
637
     */
638
    public function onBeforeWrite()
639
    {
640 86
        parent::onBeforeWrite();
641 86
        if (!$this->getField("Reference") && in_array($this->Status, self::$placed_status)) {
642 64
            $this->generateReference();
643 64
        }
644
645
        // While the order is unfinished/cart, always store the current locale with the order.
646
        // We do this everytime an order is saved, because the user might change locale (language-switch).
647 86
        if ($this->Status == 'Cart') {
648 86
            $this->Locale = ShopTools::get_current_locale();
649 86
        }
650 86
    }
651
652
    /**
653
     * delete attributes, statuslogs, and payments
654
     */
655
    public function onBeforeDelete()
656
    {
657 1
        $this->Items()->removeAll();
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...
658 1
        $this->Modifiers()->removeAll();
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...
659 1
        $this->OrderStatusLogs()->removeAll();
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...
660 1
        $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...
661 1
        parent::onBeforeDelete();
662 1
    }
663
664
    public function debug()
665
    {
666
        $val = "<div class='order'><h1>$this->class</h1>\n<ul>\n";
667
        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...
668
            foreach ($this->record as $fieldName => $fieldVal) {
669
                $val .= "\t<li>$fieldName: " . Debug::text($fieldVal) . "</li>\n";
670
            }
671
        }
672
        $val .= "</ul>\n";
673
        $val .= "<div class='items'><h2>Items</h2>";
674
        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...
675
            $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...
676
        }
677
        $val .= "</div><div class='modifiers'><h2>Modifiers</h2>";
678
        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...
679
            $val .= $modifiers->debug();
680
        }
681
        $val .= "</div></div>";
682
683
        return $val;
684
    }
685
686
    /**
687
     * Provide i18n entities for the order class
688
     *
689
     * @return array
690
     */
691
    public function provideI18nEntities()
692
    {
693
        $entities = parent::provideI18nEntities();
694
695
        // collect all the payment status values
696
        foreach ($this->dbObject('Status')->enumValues() as $value) {
697
            $key = strtoupper($value);
698
            $entities["Order.STATUS_$key"] = array(
699
                $value,
700
                "Translation of the order status '$value'",
701
            );
702
        }
703
704
        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...
705
    }
706
}
707