Completed
Push — master ( f490c0...521ac8 )
by Nicolaas
03:24
created

OrderStep::getShowAsSummary()   F

Complexity

Conditions 10
Paths 256

Size

Total Lines 33
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
dl 0
loc 33
rs 3.1304
c 0
b 0
f 0
eloc 23
nc 256
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @description: see OrderStep.md
5
 *
6
 *
7
 * @authors: Nicolaas [at] Sunny Side Up .co.nz
8
 * @package: ecommerce
9
 * @sub-package: model
10
 * @inspiration: Silverstripe Ltd, Jeremy
11
 **/
12
class OrderStep extends DataObject implements EditableEcommerceObject
0 ignored issues
show
Bug introduced by
Possible parse error: class missing opening or closing brace
Loading history...
13
{
14
<<<<<<< HEAD
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_SL, expecting T_FUNCTION or T_CONST
Loading history...
15
    function calculateddefertimeinseconds(){
16
        return $this->DeferTimeInSeconds;
17
    }
18
=======
19
>>>>>>> f490c01eeb47f01d8bc83cd8d7a32172c433e06b
20
21
    /**
22
     * standard SS variable.
23
     *
24
     * @return array
25
     */
26
    private static $db = array(
27
        'Name' => 'Varchar(50)',
28
        'Code' => 'Varchar(50)',
29
        'Description' => 'Text',
30
        'EmailSubject' => 'Varchar(200)',
31
        'CustomerMessage' => 'HTMLText',
32
        //customer privileges
33
        'CustomerCanEdit' => 'Boolean',
34
        'CustomerCanCancel' => 'Boolean',
35
        'CustomerCanPay' => 'Boolean',
36
        //What to show the customer...
37
        'ShowAsUncompletedOrder' => 'Boolean',
38
        'ShowAsInProcessOrder' => 'Boolean',
39
        'ShowAsCompletedOrder' => 'Boolean',
40
        'HideStepFromCustomer' => 'Boolean',
41
        //sorting index
42
        'Sort' => 'Int',
43
        'DeferTimeInSeconds' => 'Int',
44
        'DeferFromSubmitTime' => 'Boolean'
45
    );
46
47
48
49
    /**
50
     * standard SS variable.
51
     *
52
     * @return array
53
     */
54
    private static $indexes = array(
55
        'Code' => true,
56
        'Sort' => true,
57
    );
58
59
    /**
60
     * standard SS variable.
61
     *
62
     * @return array
63
     */
64
    private static $has_many = array(
65
        'Orders' => 'Order',
66
        'OrderEmailRecords' => 'OrderEmailRecord',
67
    );
68
69
    /**
70
     * standard SS variable.
71
     *
72
     * @return array
73
     */
74
    private static $field_labels = array(
75
        'Sort' => 'Sorting Index',
76
        'CustomerCanEdit' => 'Customer can edit order',
77
        'CustomerCanPay' => 'Customer can pay order',
78
        'CustomerCanCancel' => 'Customer can cancel order',
79
    );
80
81
    /**
82
     * standard SS variable.
83
     *
84
     * @return array
85
     */
86
    private static $summary_fields = array(
87
        'NameAndDescription' => 'Step',
88
        'ShowAsSummary' => 'Phase',
89
    );
90
91
    /**
92
     * standard SS variable.
93
     *
94
     * @return array
95
     */
96
    private static $casting = array(
97
        'Title' => 'Varchar',
98
        'CustomerCanEditNice' => 'Varchar',
99
        'CustomerCanPayNice' => 'Varchar',
100
        'CustomerCanCancelNice' => 'Varchar',
101
        'ShowAsUncompletedOrderNice' => 'Varchar',
102
        'ShowAsInProcessOrderNice' => 'Varchar',
103
        'ShowAsCompletedOrderNice' => 'Varchar',
104
        'HideStepFromCustomerNice' => 'Varchar',
105
        'HasCustomerMessageNice' => 'Varchar',
106
        'ShowAsSummary' => 'HTMLText',
107
        'NameAndDescription' => 'HTMLText'
108
    );
109
110
    /**
111
     * standard SS variable.
112
     *
113
     * @return array
114
     */
115
    private static $searchable_fields = array(
116
        'Name' => array(
117
            'title' => 'Name',
118
            'filter' => 'PartialMatchFilter',
119
        ),
120
        'Code' => array(
121
            'title' => 'Code',
122
            'filter' => 'PartialMatchFilter',
123
        ),
124
    );
125
126
127
    /**
128
     * casted variable.
129
     *
130
     * @return string
131
     */
132
    public function Title()
133
    {
134
        return $this->getTitle();
135
    }
136
    public function getTitle()
137
    {
138
        return $this->Name;
139
    }
140
141
    /**
142
     * casted variable.
143
     *
144
     * @return string
145
     */
146
    public function CustomerCanEditNice()
147
    {
148
        return $this->getCustomerCanEditNice();
149
    }
150
    public function getCustomerCanEditNice()
151
    {
152
        if ($this->CustomerCanEdit) {
153
            return _t('OrderStep.YES', 'Yes');
154
        }
155
156
        return _t('OrderStep.NO', 'No');
157
    }
158
159
    /**
160
     * casted variable.
161
     *
162
     * @return string
163
     */
164
    public function CustomerCanPayNice()
165
    {
166
        return $this->getCustomerCanPayNice();
167
    }
168
    public function getCustomerCanPayNice()
169
    {
170
        if ($this->CustomerCanPay) {
171
            return _t('OrderStep.YES', 'Yes');
172
        }
173
174
        return _t('OrderStep.NO', 'No');
175
    }
176
177
    /**
178
     * casted variable.
179
     *
180
     * @return string
181
     */
182
    public function CustomerCanCancelNice()
183
    {
184
        return $this->getCustomerCanCancelNice();
185
    }
186
    public function getCustomerCanCancelNice()
187
    {
188
        if ($this->CustomerCanCancel) {
189
            return _t('OrderStep.YES', 'Yes');
190
        }
191
192
        return _t('OrderStep.NO', 'No');
193
    }
194
195
    public function ShowAsUncompletedOrderNice()
196
    {
197
        return $this->getShowAsUncompletedOrderNice();
198
    }
199
    public function getShowAsUncompletedOrderNice()
200
    {
201
        if ($this->ShowAsUncompletedOrder) {
202
            return _t('OrderStep.YES', 'Yes');
203
        }
204
205
        return _t('OrderStep.NO', 'No');
206
    }
207
208
    /**
209
     * casted variable.
210
     *
211
     * @return string
212
     */
213
    public function ShowAsInProcessOrderNice()
214
    {
215
        return $this->getShowAsInProcessOrderNice();
216
    }
217
    public function getShowAsInProcessOrderNice()
218
    {
219
        if ($this->ShowAsInProcessOrder) {
220
            return _t('OrderStep.YES', 'Yes');
221
        }
222
223
        return _t('OrderStep.NO', 'No');
224
    }
225
226
    /**
227
     * casted variable.
228
     *
229
     * @return string
230
     */
231
    public function ShowAsCompletedOrderNice()
232
    {
233
        return $this->getShowAsCompletedOrderNice();
234
    }
235
    public function getShowAsCompletedOrderNice()
236
    {
237
        if ($this->ShowAsCompletedOrder) {
238
            return _t('OrderStep.YES', 'Yes');
239
        }
240
241
        return _t('OrderStep.NO', 'No');
242
    }
243
244
    /**
245
     * do not show in steps at all.
246
     * @return boolean
247
     */
248
    public function HideFromEveryone()
249
    {
250
        return false;
251
    }
252
253
    /**
254
     * casted variable.
255
     *
256
     * @return string
257
     */
258
    public function HideStepFromCustomerNice()
259
    {
260
        return $this->getHideStepFromCustomerNice();
261
    }
262
263
    public function getHideStepFromCustomerNice()
264
    {
265
        if ($this->HideStepFromCustomer) {
266
            return _t('OrderStep.YES', 'Yes');
267
        }
268
269
        return _t('OrderStep.NO', 'No');
270
    }
271
272
    /**
273
     * standard SS variable.
274
     *
275
     * @return string
276
     */
277
    private static $singular_name = 'Order Step';
278
    public function i18n_singular_name()
279
    {
280
        return _t('OrderStep.ORDERSTEP', 'Order Step');
281
    }
282
283
    /**
284
     * standard SS variable.
285
     *
286
     * @return string
287
     */
288
    private static $plural_name = 'Order Steps';
289
    public function i18n_plural_name()
290
    {
291
        return _t('OrderStep.ORDERSTEPS', 'Order Steps');
292
    }
293
294
    /**
295
     * Standard SS variable.
296
     *
297
     * @var string
298
     */
299
    private static $description = 'A step that any order goes through.';
300
301
    /**
302
     * SUPER IMPORTANT TO KEEP ORDER!
303
     * standard SS variable.
304
     *
305
     * @return string
306
     */
307
    private static $default_sort = '"Sort" ASC';
308
309
    /**
310
     * returns all the order steps
311
     * that the admin should / can edit....
312
     *
313
     * @return DataList
314
     */
315
    public static function admin_manageable_steps()
316
    {
317
        $lastStep = OrderStep::get()->Last();
318
        return OrderStep::get()->filter(array('CustomerCanEdit' => 0))->exclude(array('ID' => $lastStep->ID));
319
    }
320
321
    /**
322
     * return StatusIDs (orderstep IDs) from orders that are bad....
323
     * (basically StatusID values that do not exist)
324
     *
325
     * @return array
326
     */
327
    public static function bad_order_step_ids()
328
    {
329
        $badorderStatus = Order::get()
330
            ->leftJoin('OrderStep', '"OrderStep"."ID" = "Order"."StatusID"')
331
            ->where('"OrderStep"."ID" IS NULL AND "StatusID" > 0')
332
            ->column('StatusID');
333
        if (is_array($badorderStatus)) {
334
            return array_unique(array_values($badorderStatus));
335
        } else {
336
            return array(-1);
337
        }
338
    }
339
340
    /**
341
     * turns code into ID.
342
     *
343
     * @param string $code
344
     * @param int
345
     */
346
    public static function get_status_id_from_code($code)
347
    {
348
        $otherStatus = OrderStep::get()
349
            ->filter(array('Code' => $code))
350
            ->First();
351
        if ($otherStatus) {
352
            return $otherStatus->ID;
353
        }
354
355
        return 0;
356
    }
357
358
    /**
359
     *@return array
360
     **/
361
    public static function get_codes_for_order_steps_to_include()
362
    {
363
        $newArray = array();
364
        $array = EcommerceConfig::get('OrderStep', 'order_steps_to_include');
365
        if (is_array($array) && count($array)) {
366
            foreach ($array as $className) {
367
                $code = singleton($className)->getMyCode();
368
                $newArray[$className] = strtoupper($code);
369
            }
370
        }
371
372
        return $newArray;
373
    }
374
375
    /**
376
     * returns a list of ordersteps that have not been created yet.
377
     *
378
     * @return array
379
     **/
380
    public static function get_not_created_codes_for_order_steps_to_include()
381
    {
382
        $array = EcommerceConfig::get('OrderStep', 'order_steps_to_include');
383
        if (is_array($array) && count($array)) {
384
            foreach ($array as $className) {
385
                $obj = $className::get()->First();
386
                if ($obj) {
387
                    unset($array[$className]);
388
                }
389
            }
390
        }
391
392
        return $array;
393
    }
394
395
    /**
396
     *@return string
397
     **/
398
    public function getMyCode()
399
    {
400
        $array = Config::inst()->get($this->ClassName, 'defaults', Config::UNINHERITED);
401
        if (!isset($array['Code'])) {
402
            user_error($this->class.' does not have a default code specified');
403
        }
404
405
        return $array['Code'];
406
    }
407
408
    /**
409
     * IMPORTANT:: MUST HAVE Code must be defined!!!
410
     * standard SS variable.
411
     *
412
     * @return array
413
     */
414
    private static $defaults = array(
415
        'CustomerCanEdit' => 0,
416
        'CustomerCanCancel' => 0,
417
        'CustomerCanPay' => 1,
418
        'ShowAsUncompletedOrder' => 0,
419
        'ShowAsInProcessOrder' => 0,
420
        'ShowAsCompletedOrder' => 0,
421
        'Code' => 'ORDERSTEP',
422
    );
423
424
    /**
425
     * standard SS method.
426
     */
427
    public function populateDefaults()
428
    {
429
        parent::populateDefaults();
430
        $this->Description = $this->myDescription();
431
    }
432
433
    /**
434
     *@return FieldList
435
     **/
436
    public function getCMSFields()
437
    {
438
        $fields = parent::getCMSFields();
439
        //replacing
440
        if ($this->canBeDefered()) {
441
            if ($this->DeferTimeInSeconds) {
442
                $fields->addFieldToTab(
443
                    'Root.Queue',
444
                    HeaderField::create(
445
                        'WhenWillThisRun',
446
                        $this->humanReadeableDeferTimeInSeconds()
447
                    )
448
                );
449
            }
450
            $fields->addFieldToTab(
451
                'Root.Queue',
452
                $deferTimeInSecondsField = TextField::create(
453
                    'DeferTimeInSeconds',
454
                    _t('OrderStep.DeferTimeInSeconds', 'Seconds in queue')
455
                )
456
                ->setRightTitle(
457
                    _t(
458
                        'OrderStep.TIME_EXPLANATION',
459
                        '86,400 seconds is one day ...
460
                        <br />To make it easier, you can also enter things like <em>1 week</em>, <em>3 hours</em>, or <em>7 minutes</em>.
461
                        <br />Non-second entries will automatically be converted to seconds.'
462
                    )
463
                )
464
            );
465
            if ($this->DeferTimeInSeconds) {
466
                $fields->addFieldToTab(
467
                    'Root.Queue',
468
                    $deferTimeInSecondsField = CheckboxField::create(
469
                        'DeferFromSubmitTime',
470
                        _t('OrderStep.DeferFromSubmitTime', 'Calculated from submit time?')
471
                    )
472
                    ->setDescription(
473
                        _t(
474
                            'OrderStep.DeferFromSubmitTime_HELP',
475
                            'The time in the queue can be calculated from the moment the current orderstep starts or from the moment the order was submitted (in this case, check the box above) '
476
                            )
477
                        )
478
                );
479
            }
480
        }
481
        if ($this->hasCustomerMessage()) {
482
            $rightTitle = _t(
483
                'OrderStep.EXPLAIN_ORDER_NUMBER_IN_SUBJECT',
484
                'You can use [OrderNumber] as a tag that will be replaced with the actual Order Number.'
485
            );
486
            $fields->addFieldToTab(
487
                'Root.CustomerMessage',
488
                TextField::create('EmailSubject', _t('OrderStep.EMAILSUBJECT', 'Email Subject'))
489
                    ->setRightTitle($rightTitle)
490
            );
491
            if ($testEmailLink = $this->testEmailLink()) {
492
                $fields->addFieldToTab('Root.CustomerMessage', new LiteralField('testEmailLink', '<h3><a href="'.$testEmailLink.'" data-popup="true" target="_blank">'._t('OrderStep.VIEW_EMAIL_EXAMPLE', 'View email example in browser').'</a></h3>'));
493
            }
494
            
495
            $fields->addFieldToTab('Root.CustomerMessage', $htmlEditorField = new HTMLEditorField('CustomerMessage', _t('OrderStep.CUSTOMERMESSAGE', 'Customer Message (if any)')));
496
        } else {
497
            $fields->removeFieldFromTab('Root', 'OrderEmailRecords');
498
            $fields->removeFieldFromTab('Root.Main', 'EmailSubject');
499
            $fields->removeFieldFromTab('Root.Main', 'CustomerMessage');
500
        }
501
        //adding
502
        if (!$this->exists() || !$this->isDefaultStatusOption()) {
503
            $fields->removeFieldFromTab('Root.Main', 'Code');
504
            $fields->addFieldToTab('Root.Main', new DropdownField('ClassName', _t('OrderStep.TYPE', 'Type'), self::get_not_created_codes_for_order_steps_to_include()), 'Name');
505
        }
506
        if ($this->isDefaultStatusOption()) {
507
            $fields->replaceField('Code', $fields->dataFieldByName('Code')->performReadonlyTransformation());
508
        }
509
        //headers
510
        $fields->addFieldToTab('Root.Main', new HeaderField('WARNING1', _t('OrderStep.CAREFUL', 'CAREFUL! please edit details below with care'), 2), 'Description');
511
        $fields->addFieldToTab('Root.Main', new HeaderField('WARNING2', _t('OrderStep.CUSTOMERCANCHANGE', 'What can be changed during this step?'), 3), 'CustomerCanEdit');
512
        $fields->addFieldToTab('Root.Main', new HeaderField('WARNING5', _t('OrderStep.ORDERGROUPS', 'Order groups for customer?'), 3), 'ShowAsUncompletedOrder');
513
        $fields->addFieldToTab('Root.Main', new HeaderField('HideStepFromCustomerHeader', _t('OrderStep.HIDE_STEP_FROM_CUSTOMER_HEADER', 'Customer Interaction'), 3), 'HideStepFromCustomer');
514
        //final cleanup
515
        $fields->removeFieldFromTab('Root.Main', 'Sort');
516
        $fields->addFieldToTab('Root.Main', new TextareaField('Description', _t('OrderStep.DESCRIPTION', 'Explanation for internal use only')), 'WARNING1');
517
518
        return $fields;
519
    }
520
521
    /**
522
     * link to edit the record.
523
     *
524
     * @param string | Null $action - e.g. edit
525
     *
526
     * @return string
527
     */
528
    public function CMSEditLink($action = null)
529
    {
530
        return Controller::join_links(
531
            Director::baseURL(),
532
            '/admin/shop/'.$this->ClassName.'/EditForm/field/'.$this->ClassName.'/item/'.$this->ID.'/',
533
            $action
534
        );
535
    }
536
537
    /**
538
     * tells the order to display itself with an alternative display page.
539
     * in that way, orders can be displayed differently for certain steps
540
     * for example, in a print step, the order can be displayed in a
541
     * PRINT ONLY format.
542
     *
543
     * When the method return null, the order is displayed using the standard display page
544
     *
545
     * @see Order::DisplayPage
546
     *
547
     * @return null|object (Page)
548
     **/
549
    public function AlternativeDisplayPage()
550
    {
551
        return;
552
    }
553
554
    /**
555
     * Allows the opportunity for the Order Step to add any fields to Order::getCMSFields
556
     * Usually this is added before ActionNextStepManually.
557
     *
558
     * @param FieldList $fields
559
     * @param Order     $order
560
     *
561
     * @return FieldList
562
     **/
563
    public function addOrderStepFields(FieldList $fields, Order $order)
564
    {
565
        return $fields;
566
    }
567
568
    /**
569
     *@return ValidationResult
570
     **/
571
    public function validate()
572
    {
573
        $result = parent::validate();
574
        $anotherOrderStepWithSameNameOrCode = OrderStep::get()
575
            ->filter(
576
                array(
577
                    'Name' => $this->Name,
578
                    'Code' => strtoupper($this->Code),
579
                )
580
            )
581
            ->exclude(array('ID' => intval($this->ID)))
582
            ->First();
583
        if ($anotherOrderStepWithSameNameOrCode) {
584
            $result->error(_t('OrderStep.ORDERSTEPALREADYEXISTS', 'An order status with this name already exists. Please change the name and try again.'));
585
        }
586
587
        return $result;
588
    }
589
590
/**************************************************
591
* moving between statusses...
592
**************************************************/
593
    /**
594
     *initStep:
595
     * makes sure the step is ready to run.... (e.g. check if the order is ready to be emailed as receipt).
596
     * should be able to run this function many times to check if the step is ready.
597
     *
598
     * @see Order::doNextStatus
599
     *
600
     * @param Order object
601
     *
602
     * @return bool - true if the current step is ready to be run...
603
     **/
604
    public function initStep(Order $order)
605
    {
606
        user_error('Please implement the initStep method in a subclass ('.get_class().') of OrderStep', E_USER_WARNING);
607
608
        return true;
609
    }
610
611
    /**
612
     *doStep:
613
     * should only be able to run this function once
614
     * (init stops you from running it twice - in theory....)
615
     * runs the actual step.
616
     *
617
     * @see Order::doNextStatus
618
     *
619
     * @param Order object
620
     *
621
     * @return bool - true if run correctly.
622
     **/
623
    public function doStep(Order $order)
624
    {
625
        user_error('Please implement the initStep method in a subclass ('.get_class().') of OrderStep', E_USER_WARNING);
626
627
        return true;
628
    }
629
630
    /**
631
     * nextStep:
632
     * returns the next step (after it checks if everything is in place for the next step to run...).
633
     *
634
     * @see Order::doNextStatus
635
     *
636
     * @param Order $order
637
     *
638
     * @return OrderStep | Null (next step OrderStep object)
639
     **/
640
    public function nextStep(Order $order)
641
    {
642
        $nextOrderStepObject = OrderStep::get()
643
            ->filter(array('Sort:GreaterThan' => $this->Sort))
644
            ->First();
645
        if ($nextOrderStepObject) {
646
            return $nextOrderStepObject;
647
        }
648
649
        return;
650
    }
651
652
/**************************************************
653
* Boolean checks
654
**************************************************/
655
656
    /**
657
     * Checks if a step has passed (been completed) in comparison to the current step.
658
     *
659
     * @param string $code:       the name of the step to check
660
     * @param bool   $orIsEqualTo if set to true, this method will return TRUE if the step being checked is the current one
661
     *
662
     * @return bool
663
     **/
664
    public function hasPassed($code, $orIsEqualTo = false)
665
    {
666
        $otherStatus = OrderStep::get()
667
            ->filter(array('Code' => $code))
668
            ->First();
669
        if ($otherStatus) {
670
            if ($otherStatus->Sort < $this->Sort) {
671
                return true;
672
            }
673
            if ($orIsEqualTo && $otherStatus->Code == $this->Code) {
674
                return true;
675
            }
676
        } else {
677
            user_error("could not find $code in OrderStep", E_USER_NOTICE);
678
        }
679
680
        return false;
681
    }
682
683
    /**
684
     * @param string $code
685
     *
686
     * @return bool
687
     **/
688
    public function hasPassedOrIsEqualTo($code)
689
    {
690
        return $this->hasPassed($code, true);
691
    }
692
693
    /**
694
     * @param string $code
695
     *
696
     * @return bool
697
     **/
698
    public function hasNotPassed($code)
699
    {
700
        return (bool) !$this->hasPassed($code, true);
701
    }
702
703
    /**
704
     * Opposite of hasPassed.
705
     *
706
     * @param string $code
707
     *
708
     * @return bool
709
     **/
710
    public function isBefore($code)
711
    {
712
        return (bool) $this->hasPassed($code, false) ? false : true;
713
    }
714
715
    /**
716
     *@return bool
717
     **/
718
    protected function isDefaultStatusOption()
719
    {
720
        return in_array($this->Code, self::get_codes_for_order_steps_to_include());
721
    }
722
723
/**************************************************
724
* Email
725
**************************************************/
726
727
    /**
728
     * @var string
729
     */
730
    protected $emailClassName = '';
731
732
    /**
733
     * returns the email class used for emailing the
734
     * customer during a specific step (IF ANY!).
735
     *
736
     * @return string
737
     */
738
    public function getEmailClassName()
739
    {
740
        return $this->emailClassName;
741
    }
742
743
    /**
744
     * return true if done already or mailed successfully now.
745
     *
746
     * @param order         $order
747
     * @param string        $subject
748
     * @param string        $message
749
     * @param bool          $resend
750
     * @param bool | string $adminOnlyOrToEmail you can set to false = send to customer, true: send to admin, or email = send to email
751
     * @param string        $emailClassName
752
     *
753
     * @return boolean;
754
     */
755
    protected function sendEmailForStep(
756
        $order,
757
        $subject,
758
        $message = '',
759
        $resend = false,
760
        $adminOnlyOrToEmail = false,
761
        $emailClassName = ''
762
    ) {
763
        if (!$this->hasBeenSent($order) || $resend) {
764
            if (!$subject) {
765
                $subject = $this->EmailSubject;
766
            }
767
            if (!$emailClassName) {
768
                $emailClassName = $this->getEmailClassName();
769
            }
770
            $adminOnlyOrToEmailIsEmail = $adminOnlyOrToEmail && filter_var($adminOnlyOrToEmail, FILTER_VALIDATE_EMAIL);
771
            if ($this->hasCustomerMessage() || $adminOnlyOrToEmailIsEmail) {
772
                return $order->sendEmail(
773
                    $subject,
774
                    $message,
775
                    $resend,
776
                    $adminOnlyOrToEmail,
777
                    $emailClassName
778
                );
779
            } else {
780
                if (!$emailClassName) {
781
                    $emailClassName = 'Order_ErrorEmail';
782
                }
783
                //looks like we are sending an error, but we are just using this for notification
784
                $message = _t('OrderStep.THISMESSAGENOTSENTTOCUSTOMER', 'NOTE: This message was not sent to the customer.').'<br /><br /><br /><br />'.$message;
785
                $outcome = $order->sendAdminNotification(
786
                    $subject,
787
                    $message,
788
                    $resend,
789
                    $emailClassName
790
                );
791
            }
792
            if ($outcome || Director::isDev()) {
793
                return true;
794
            }
795
796
            return false;
797
        }
798
799
        return true;
800
    }
801
802
    /**
803
     * sets the email class used for emailing the
804
     * customer during a specific step (IF ANY!).
805
     *
806
     * @param string
807
     */
808
    public function setEmailClassName($s)
809
    {
810
        $this->emailClassName = $s;
811
    }
812
813
    /**
814
     * returns a link that can be used to test
815
     * the email being sent during this step
816
     * this method returns NULL if no email
817
     * is being sent OR if there is no suitable Order
818
     * to test with...
819
     *
820
     * @return string
821
     */
822
    protected function testEmailLink()
823
    {
824
        if ($this->getEmailClassName()) {
825
            $orders = Order::get()
826
                ->where('"OrderStep"."Sort" >= '.$this->Sort)
827
                ->sort('IF("OrderStep"."Sort" > '.$this->Sort.', 0, 1) ASC, "OrderStep"."Sort" ASC, RAND() ASC')
828
                ->innerJoin('OrderStep', '"OrderStep"."ID" = "Order"."StatusID"');
829
            if ($orders->count()) {
830
                if ($order = $orders->First()) {
831
                    return OrderConfirmationPage::get_email_link($order->ID, $this->getEmailClassName(), $actuallySendEmail = false, $alternativeOrderStepID = $this->ID);
832
                }
833
            }
834
        }
835
    }
836
837
    /**
838
     * Has an email been sent to the customer for this
839
     * order step.
840
     *"-10 days".
841
     *
842
     * @param Order $order
843
     * @param bool  $checkDateOfOrder
844
     *
845
     * @return bool
846
     **/
847
    public function hasBeenSent(Order $order, $checkDateOfOrder = true)
848
    {
849
        //if it has been more than a XXX days since the order was last edited (submitted) then we do not send emails as
850
        //this would be embarrasing.
851
        if ($checkDateOfOrder) {
852
            if ($log = $order->SubmissionLog()) {
853
                $lastEditedValue = $log->LastEdited;
854
            } else {
855
                $lastEditedValue = $order->LastEdited;
856
            }
857
            if ((strtotime($lastEditedValue) < strtotime('-'.EcommerceConfig::get('OrderStep', 'number_of_days_to_send_update_email').' days'))) {
858
                return true;
859
            }
860
        }
861
        $count = OrderEmailRecord::get()
862
            ->Filter(array(
863
                'OrderID' => $order->ID,
864
                'OrderStepID' => $this->ID,
865
                'Result' => 1,
866
            ))
867
            ->count();
868
869
        return $count ? true : false;
870
    }
871
872
    /**
873
     * For some ordersteps this returns true...
874
     *
875
     * @return bool
876
     **/
877
    protected function hasCustomerMessage()
878
    {
879
        return false;
880
    }
881
882
883
    /**
884
     * Formatted answer for "hasCustomerMessage".
885
     *
886
     * @return string
887
     */
888
    public function HasCustomerMessageNice()
889
    {
890
        return $this->getHasCustomerMessageNice();
891
    }
892
    public function getHasCustomerMessageNice()
893
    {
894
        return $this->hasCustomerMessage() ?  _t('OrderStep.YES', 'Yes') :  _t('OrderStep.NO', 'No');
895
    }
896
897
    /**
898
     * Formatted answer for "hasCustomerMessage".
899
     *
900
     * @return string
901
     */
902
    public function ShowAsSummary()
903
    {
904
        return $this->getShowAsSummary();
905
    }
906
907
    /**
908
     *
909
     *
910
     * @return string
911
     */
912
    public function getShowAsSummary()
913
    {
914
        $v = '<strong>';
915
        if ($this->ShowAsUncompletedOrder) {
916
            $v .= _t('OrderStep.UNCOMPLETED', 'Uncompleted');
917
        } elseif ($this->ShowAsInProcessOrder) {
918
            $v .= _t('OrderStep.INPROCESS', 'In process');
919
        } elseif ($this->ShowAsCompletedOrder) {
920
            $v .= _t('OrderStep.COMPLETED', 'Completed');
921
        }
922
        $v .= '</strong>';
923
        $canArray = array();
924
        if ($this->CustomerCanEdit) {
925
            $canArray[] = _t('OrderStep.EDITABLE', 'edit');
926
        }
927
        if ($this->CustomerCanPay) {
928
            $canArray[] = _t('OrderStep.PAY', 'pay');
929
        }
930
        if ($this->CustomerCanCancel) {
931
            $canArray[] = _t('OrderStep.CANCEL', 'cancel');
932
        }
933
        if (count($canArray)) {
934
            $v .=  '<br />'._t('OrderStep.CUSTOMER_CAN', 'Customer Can').': '.implode(', ', $canArray).'';
935
        }
936
        if ($this->hasCustomerMessage()) {
937
            $v .= '<br />'._t('OrderStep.CUSTOMER_MESSAGES', 'Includes message to customer');
938
        }
939
        if ($this->DeferTimeInSeconds) {
940
            $v .= '<br />'.$this->humanReadeableDeferTimeInSeconds();
941
        }
942
943
        return DBField::create_field('HTMLText', $v);
944
    }
945
946
    /**
947
     * @return string
948
     */
949
    protected function humanReadeableDeferTimeInSeconds()
950
    {
951
        if ($this->canBeDefered()) {
952
            $field = DBField::create_field('SS_DateTime', strtotime('+ '.$this->DeferTimeInSeconds.' seconds'));
953
            $descr0 = _t('OrderStep.THE', 'The').' '.'<span style="color: #338DC1">'.$this->getTitle().'</span>';
954
            $descr1 = _t('OrderStep.DELAY_VALUE', 'Order Step, for any order, will run');
955
            $descr2 = $field->ago();
956
            $descr3 = $this->DeferFromSubmitTime ?
957
                    _t('OrderStep.FROM_ORDER_SUBMIT_TIME', 'from the order being submitted') :
958
                    _t('OrderStep.FROM_START_OF_ORDSTEP', 'from the order arriving on this step');
959
            return $descr0. ' ' . $descr1.' <span style="color: #338DC1">'.$descr2.'</span> '.$descr3.'.';
960
        }
961
        // $dtF = new \DateTime('@0');
962
        // $dtT = new \DateTime("@".$this->DeferTimeInSeconds);
963
        //
964
        // return $dtF->diff($dtT)->format('%a days, %h hours, %i minutes and %s seconds');
965
    }
966
967
    /**
968
     * Formatted answer for "hasCustomerMessage".
969
     *
970
     * @return string
971
     */
972
    public function NameAndDescription()
973
    {
974
        return $this->getNameAndDescription();
975
    }
976
977
    public function getNameAndDescription()
978
    {
979
        $v = '<strong>'.$this->Name.'</strong><br /><em>'.$this->Description.'</em>';
980
981
        return DBField::create_field('HTMLText', $v);
982
    }
983
984
    /**
985
     * This allows you to set the time to something other than the standard DeferTimeInSeconds
986
     * value based on the order provided.
987
     *
988
     * @param Order
989
     *
990
     * @return int
991
     */
992
    public function CalculatedDeferTimeInSeconds($order)
993
    {
994
        return $this->DeferTimeInSeconds;
995
    }
996
997
    /**
998
     * can this order step be delayed?
999
     * in general, if there is a customer message
1000
     * we should be able to delay it
1001
     *
1002
     * This method can be overridden in any orderstep
1003
     * @return bool
1004
     **/
1005
    protected function canBeDefered()
1006
    {
1007
        return $this->hasCustomerMessage();
1008
    }
1009
1010
1011
/**************************************************
1012
* Order Status Logs
1013
**************************************************/
1014
1015
    /**
1016
     * The OrderStatusLog that is relevant to the particular step.
1017
     *
1018
     * @var string
1019
     */
1020
    protected $relevantLogEntryClassName = '';
1021
1022
    /**
1023
     * @return string
1024
     */
1025
    public function getRelevantLogEntryClassName()
1026
    {
1027
        return $this->relevantLogEntryClassName;
1028
    }
1029
1030
    /**
1031
     * @param string
1032
     */
1033
    public function setRelevantLogEntryClassName($s)
1034
    {
1035
        $this->relevantLogEntryClassName = $s;
1036
    }
1037
1038
    /**
1039
     * returns the OrderStatusLog that is relevant to this step.
1040
     *
1041
     * @param Order $order
1042
     *
1043
     * @return OrderStatusLog | null
1044
     */
1045
    public function RelevantLogEntry(Order $order)
1046
    {
1047
        if ($className = $this->getRelevantLogEntryClassName()) {
1048
            return $this->RelevantLogEntries($order)->Last();
1049
        }
1050
    }
1051
1052
    /**
1053
     * returns the OrderStatusLogs that are relevant to this step.
1054
     *
1055
     * @param Order $order
1056
     *
1057
     * @return DataObjectSet | null
1058
     */
1059
    public function RelevantLogEntries(Order $order)
1060
    {
1061
        if ($className = $this->getRelevantLogEntryClassName()) {
1062
            return $className::get()->filter(array('OrderID' => $order->ID));
1063
        }
1064
    }
1065
1066
/**************************************************
1067
* Silverstripe Standard Data Object Methods
1068
**************************************************/
1069
1070
    /**
1071
     * Standard SS method
1072
     * These are only created programmatically.
1073
     *
1074
     * @param Member $member
1075
     *
1076
     * @return bool
1077
     */
1078
    public function canCreate($member = null)
1079
    {
1080
        return false;
1081
    }
1082
1083
    /**
1084
     * Standard SS method.
1085
     *
1086
     * @param Member $member
1087
     *
1088
     * @return bool
1089
     */
1090
    public function canView($member = null)
1091
    {
1092
        if (! $member) {
1093
            $member = Member::currentUser();
1094
        }
1095
        $extended = $this->extendedCan(__FUNCTION__, $member);
1096
        if ($extended !== null) {
1097
            return $extended;
1098
        }
1099
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
1100
            return true;
1101
        }
1102
1103
        return parent::canEdit($member);
1104
    }
1105
1106
    /**
1107
     * the default for this is TRUE, but for completed order steps
1108
     *
1109
     * we do not allow this.
1110
     *
1111
     * @param  Order $order
1112
     * @param  Member $member optional
1113
     * @return bool
1114
     */
1115
    public function canOverrideCanViewForOrder($order, $member = null)
1116
    {
1117
        //return true if the order can have customer input
1118
        // orders recently saved can also be views
1119
        return
1120
            $this->CustomerCanEdit ||
1121
            $this->CustomerCanCancel ||
1122
            $this->CustomerCanPay;
1123
    }
1124
1125
    /**
1126
     * standard SS method.
1127
     *
1128
     * @param Member | NULL
1129
     *
1130
     * @return bool
1131
     */
1132
    public function canEdit($member = null)
1133
    {
1134
        if (! $member) {
1135
            $member = Member::currentUser();
1136
        }
1137
        $extended = $this->extendedCan(__FUNCTION__, $member);
1138
        if ($extended !== null) {
1139
            return $extended;
1140
        }
1141
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
1142
            return true;
1143
        }
1144
1145
        return parent::canEdit($member);
1146
    }
1147
1148
    /**
1149
     * Standard SS method.
1150
     *
1151
     * @param Member $member
1152
     *
1153
     * @return bool
1154
     */
1155
    public function canDelete($member = null)
1156
    {
1157
        //cant delete last status if there are orders with this status
1158
        $nextOrderStepObject = $this->NextOrderStep();
1159
        if ($nextOrderStepObject) {
1160
            //do nothing
1161
        } else {
1162
            $orderCount = Order::get()
1163
                ->filter(array('StatusID' => intval($this->ID) - 0))
1164
                ->count();
1165
            if ($orderCount) {
1166
                return false;
1167
            }
1168
        }
1169
        if ($this->isDefaultStatusOption()) {
1170
            return false;
1171
        }
1172
        if (! $member) {
1173
            $member = Member::currentUser();
1174
        }
1175
        $extended = $this->extendedCan(__FUNCTION__, $member);
1176
        if ($extended !== null) {
1177
            return $extended;
1178
        }
1179
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
1180
            return true;
1181
        }
1182
1183
        return parent::canEdit($member);
1184
    }
1185
1186
    /**
1187
     * standard SS method.
1188
     */
1189
    public function onBeforeWrite()
1190
    {
1191
        parent::onBeforeWrite();
1192
        //make sure only one of three conditions applies ...
1193
        if ($this->ShowAsUncompletedOrder) {
1194
            $this->ShowAsInProcessOrder = false;
1195
            $this->ShowAsCompletedOrder = false;
1196
        } elseif ($this->ShowAsInProcessOrder) {
1197
            $this->ShowAsUncompletedOrder = false;
1198
            $this->ShowAsCompletedOrder = false;
1199
        } elseif ($this->ShowAsCompletedOrder) {
1200
            $this->ShowAsUncompletedOrder = false;
1201
            $this->ShowAsInProcessOrder = false;
1202
        }
1203
        if (! $this->canBeDefered()) {
1204
            $this->DeferTimeInSeconds = 0;
1205
            $this->DeferFromSubmitTime = 0;
1206
        } else {
1207
            if (is_numeric($this->DeferTimeInSeconds)) {
1208
                $this->DeferTimeInSeconds = intval($this->DeferTimeInSeconds);
1209
            } else {
1210
                $this->DeferTimeInSeconds = strtotime('+'.$this->DeferTimeInSeconds);
1211
                if ($this->DeferTimeInSeconds > 0) {
1212
                    $this->DeferTimeInSeconds = $this->DeferTimeInSeconds - time();
1213
                }
1214
            }
1215
        }
1216
        $this->Code = strtoupper($this->Code);
1217
    }
1218
1219
    /**
1220
     * move linked orders to the next status
1221
     * standard SS method.
1222
     */
1223
    public function onBeforeDelete()
1224
    {
1225
        parent::onBeforeDelete();
1226
        $previousOrderStepObject = null;
1227
        $nextOrderStepObject = $this->NextOrderStep();
1228
        //backup
1229
        if ($nextOrderStepObject) {
1230
            //do nothing
1231
        } else {
1232
            $previousOrderStepObject = $this->PreviousOrderStep();
1233
        }
1234
        if ($previousOrderStepObject) {
1235
            $ordersWithThisStatus = Order::get()->filter(array('StatusID' => $this->ID));
1236
            if ($ordersWithThisStatus && $ordersWithThisStatus->count()) {
1237
                foreach ($ordersWithThisStatus as $orderWithThisStatus) {
1238
                    $orderWithThisStatus->StatusID = $previousOrderStepObject->ID;
1239
                    $orderWithThisStatus->write();
1240
                }
1241
            }
1242
        }
1243
    }
1244
1245
    /**
1246
     * standard SS method.
1247
     */
1248
    public function onAfterDelete()
1249
    {
1250
        parent::onAfterDelete();
1251
        $this->requireDefaultRecords();
1252
    }
1253
1254
    protected function NextOrderStep()
1255
    {
1256
        return OrderStep::get()
1257
            ->filter(array('Sort:GreaterThan' => $this->Sort))
1258
            ->First();
1259
    }
1260
1261
    protected function PreviousOrderStep()
1262
    {
1263
        return OrderStep::get()
1264
            ->filter(array('Sort:LessThan' => $this->Sort))
1265
            ->First();
1266
    }
1267
1268
    /**
1269
     * standard SS method
1270
     * USED TO BE: Unpaid,Query,Paid,Processing,Sent,Complete,AdminCancelled,MemberCancelled,Cart.
1271
     */
1272
    public function requireDefaultRecords()
1273
    {
1274
        parent::requireDefaultRecords();
1275
        $orderStepsToInclude = EcommerceConfig::get('OrderStep', 'order_steps_to_include');
1276
        $codesToInclude = self::get_codes_for_order_steps_to_include();
1277
        $indexNumber = 0;
1278
        if ($orderStepsToInclude && count($orderStepsToInclude)) {
1279
            if ($codesToInclude && count($codesToInclude)) {
1280
                foreach ($codesToInclude as $className => $code) {
1281
                    $code = strtoupper($code);
1282
                    $filter = array('ClassName' => $className);
1283
                    $indexNumber += 10;
1284
                    $itemCount = OrderStep::get()->filter($filter)->Count();
1285
                    if ($itemCount) {
1286
                        $obj = OrderStep::get()->filter($filter)->First();
1287
                        if ($obj->Code != $code) {
1288
                            $obj->Code = $code;
1289
                            $obj->write();
1290
                        }
1291
                        $parentObj = singleton('OrderStep');
1292
                        if ($obj->Description == $parentObj->myDescription()) {
1293
                            $obj->Description = $obj->myDescription();
1294
                            $obj->write();
1295
                        }
1296
                    } else {
1297
                        $obj = $className::create();
1298
                        $obj->Code = $code;
1299
                        $obj->Description = $obj->myDescription();
1300
                        $obj->write();
1301
                        DB::alteration_message("Created \"$code\" as $className.", 'created');
1302
                    }
1303
                    $obj = OrderStep::get()
1304
                        ->filter(array('Code' => strtoupper($code)))
1305
                        ->First();
1306
                    if ($obj) {
1307
                        if ($obj->Sort != $indexNumber) {
1308
                            $obj->Sort = $indexNumber;
1309
                            $obj->write();
1310
                        }
1311
                    } else {
1312
                        user_error("There was an error in creating the $code OrderStep");
1313
                    }
1314
                }
1315
            }
1316
        }
1317
        $steps = OrderStep::get();
1318
        foreach ($steps as $step) {
1319
            if (!$step->Description) {
1320
                $step->Description = $step->myDescription();
1321
                $step->write();
1322
            }
1323
        }
1324
    }
1325
1326
    /**
1327
     * returns the standard EcommerceDBConfig for use within OrderSteps.
1328
     *
1329
     * @return EcommerceDBConfig
1330
     */
1331
    protected function EcomConfig()
1332
    {
1333
        return EcommerceDBConfig::current_ecommerce_db_config();
1334
    }
1335
1336
    /**
1337
     * Explains the current order step.
1338
     *
1339
     * @return string
1340
     */
1341
    protected function myDescription()
1342
    {
1343
        return _t('OrderStep.DESCRIPTION', 'No description has been provided for this step.');
1344
    }
1345
}
1346