Completed
Push — master ( f71c82...1db67b )
by
unknown
03:31
created

CheckoutPage   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 337
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 0
Metric Value
dl 0
loc 337
rs 9.52
c 0
b 0
f 0
wmc 36
lcom 1
cbo 20

15 Methods

Rating   Name   Duplication   Size   Complexity  
A i18n_singular_name() 0 4 1
A i18n_plural_name() 0 4 1
A find_terms_and_conditions_page() 0 7 3
A find_link() 0 10 2
A find_last_step_link() 0 14 5
B find_next_step_link() 0 34 8
A get_checkout_order_link() 0 8 2
A canCreate() 0 4 2
A canEdit() 0 8 2
A canDelete() 0 4 1
A canPublish() 0 4 1
A getCMSFields() 0 40 3
A getOrderModifierDescriptionField() 0 14 1
A getCheckoutStepDescriptionField() 0 14 1
A requireDefaultRecords() 0 15 3
1
<?php
2
3
/**
4
 * CheckoutPage is a CMS page-type that shows the order
5
 * details to the customer for their current shopping
6
 * cart on the site. It also lets the customer review
7
 * the items in their cart, and manipulate them (add more,
8
 * deduct or remove items completely). The most important
9
 * thing is that the {@link CheckoutPage_Controller} handles
10
 * the {@link OrderForm} form instance, allowing the customer
11
 * to fill out their shipping details, confirming their order
12
 * and making a payment.
13
 *
14
 * @see CheckoutPage_Controller->Order()
15
 * @see OrderForm
16
 * @see CheckoutPage_Controller->OrderForm()
17
 *
18
 * The CheckoutPage_Controller is also responsible for setting
19
 * up the modifier forms for each of the OrderModifiers that are
20
 * enabled on the site (if applicable - some don't require a form
21
 * for user input). A usual implementation of a modifier form would
22
 * be something like allowing the customer to enter a discount code
23
 * so they can receive a discount on their order.
24
 * @see OrderModifier
25
 * @see CheckoutPage_Controller->ModifierForms()
26
 *
27
 * TO DO: get rid of all the messages...
28
 *
29
 * @authors: Nicolaas [at] Sunny Side Up .co.nz
30
 * @package: ecommerce
31
 * @sub-package: Pages
32
 * @inspiration: Silverstripe Ltd, Jeremy
33
 **/
34
class CheckoutPage extends CartPage
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
35
{
36
    /**
37
     * standard SS variable.
38
     *
39
     * @Var Boolean
40
     */
41
    private static $hide_ancestor = 'CartPage';
0 ignored issues
show
Unused Code introduced by
The property $hide_ancestor is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
42
43
    /**
44
     * standard SS variable.
45
     *
46
     * @Var string
47
     */
48
    private static $icon = 'ecommerce/images/icons/CheckoutPage';
0 ignored issues
show
Unused Code introduced by
The property $icon is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
49
50
    /**
51
     * standard SS variable.
52
     *
53
     * @Var Array
54
     */
55
    private static $db = array(
0 ignored issues
show
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
56
        'TermsAndConditionsMessage' => 'Varchar(200)',
57
    );
58
59
    /**
60
     * standard SS variable.
61
     *
62
     * @Var Array
63
     */
64
    private static $has_one = array(
0 ignored issues
show
Unused Code introduced by
The property $has_one is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
65
        'TermsPage' => 'Page',
66
    );
67
68
    /**
69
     * standard SS variable.
70
     *
71
     * @Var Array
72
     */
73
    private static $defaults = array(
0 ignored issues
show
Unused Code introduced by
The property $defaults is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
74
        'TermsAndConditionsMessage' => 'You must agree with the terms and conditions before proceeding.',
75
    );
76
77
    /**
78
     * standard SS variable.
79
     *
80
     * @Var String
81
     */
82
    private static $singular_name = 'Checkout Page';
0 ignored issues
show
Unused Code introduced by
The property $singular_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
83
    public function i18n_singular_name()
84
    {
85
        return _t('CheckoutPage.SINGULARNAME', 'Checkout Page');
86
    }
87
88
    /**
89
     * standard SS variable.
90
     *
91
     * @Var String
92
     */
93
    private static $plural_name = 'Checkout Pages';
0 ignored issues
show
Unused Code introduced by
The property $plural_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
94
    public function i18n_plural_name()
95
    {
96
        return _t('CheckoutPage.PLURALNAME', 'Checkout Pages');
97
    }
98
99
    /**
100
     * Standard SS variable.
101
     *
102
     * @var string
103
     */
104
    private static $description = 'A page where the customer can view the current order (cart) and finalise (submit) the order. Every e-commerce site needs an Order Confirmation Page.';
0 ignored issues
show
Unused Code introduced by
The property $description is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
105
106
    /**
107
     * Returns the Terms and Conditions Page (if there is one).
108
     *
109
     * @return Page | NULL
110
     */
111
    public static function find_terms_and_conditions_page()
112
    {
113
        $checkoutPage = DataObject::get_one('CheckoutPage');
114
        if ($checkoutPage && $checkoutPage->TermsPageID) {
115
            return Page::get()->byID($checkoutPage->TermsPageID);
116
        }
117
    }
118
119
    /**
120
     * Returns the link or the Link to the Checkout page on this site.
121
     *
122
     * @param string $action [optional]
0 ignored issues
show
Documentation introduced by
Should the type for parameter $action not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
123
     *
124
     * @return string (URLSegment)
125
     */
126
    public static function find_link($action = null)
127
    {
128
        $page = DataObject::get_one('CheckoutPage');
129
        if ($page) {
130
            return $page->Link($action);
131
        }
132
        user_error('No Checkout Page has been created - it is recommended that you create this page type for correct functioning of E-commerce.', E_USER_NOTICE);
133
134
        return '';
135
    }
136
137
    /**
138
     * Returns the link or the Link to the Checkout page on this site
139
     * for the last step.
140
     *
141
     * @param string $step
142
     *
143
     * @return string (URLSegment)
144
     */
145
    public static function find_last_step_link($step = '')
146
    {
147
        if (!$step) {
148
            $steps = EcommerceConfig::get('CheckoutPage_Controller', 'checkout_steps');
149
            if ($steps && count($steps)) {
150
                $step = array_pop($steps);
151
            }
152
        }
153
        if ($step) {
154
            $step = 'checkoutstep/'.strtolower($step).'/#'.$step;
155
        }
156
157
        return self::find_link($step);
158
    }
159
160
    /**
161
     * Returns the link to the next step.
162
     *
163
     * @param string - $currentStep       is the step that has just been actioned....
164
     * @param bool -   $doPreviousInstead - return previous rather than next step
165
     *
166
     * @return string (URLSegment)
167
     */
168
    public static function find_next_step_link($currentStep, $doPreviousInstead = false)
169
    {
170
        $nextStep = null;
171
        if ($link = self::find_link()) {
172
            $steps = EcommerceConfig::get('CheckoutPage_Controller', 'checkout_steps');
173
            if (in_array($currentStep, $steps)) {
174
                $key = array_search($currentStep, $steps);
175
                if ($key !== false) {
176
                    if ($doPreviousInstead) {
177
                        --$key;
178
                    } else {
179
                        ++$key;
180
                    }
181
                    if (isset($steps[$key])) {
182
                        $nextStep = $steps[$key];
183
                    }
184
                }
185
            } else {
186
                if ($doPreviousInstead) {
187
                    $nextStep = array_shift($steps);
188
                } else {
189
                    $nextStep = array_pop($steps);
190
                }
191
            }
192
            if ($nextStep) {
193
                return $link.'checkoutstep'.'/'.$nextStep.'/';
194
            } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
195
            }
196
197
            return $link;
198
        }
199
200
        return '';
201
    }
202
203
    /**
204
     * Returns the link to the checkout page on this site, using
205
     * a specific Order ID that already exists in the database.
206
     *
207
     * @param int $orderID ID of the {@link Order}
208
     *
209
     * @return string Link to checkout page
210
     */
211
    public static function get_checkout_order_link($orderID)
212
    {
213
        if ($page = self::find_link()) {
214
            return $page->Link('showorder').'/'.$orderID.'/';
0 ignored issues
show
Bug introduced by
The method Link cannot be called on $page (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
215
        }
216
217
        return '';
218
    }
219
220
    /**
221
     * Standard SS function, we only allow for one checkout page to exist
222
     * but we do allow for extensions to exist at the same time.
223
     *
224
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
225
     *
226
     * @return bool
227
     **/
228
    public function canCreate($member = null)
229
    {
230
        return CheckoutPage::get()->Filter(array('ClassName' => 'CheckoutPage'))->Count() ? false : $this->canEdit($member);
231
    }
232
233
    /**
234
     * Shop Admins can edit.
235
     *
236
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
237
     *
238
     * @return bool
239
     */
240
    public function canEdit($member = null)
241
    {
242
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
243
            return true;
244
        }
245
246
        return parent::canEdit($member);
247
    }
248
249
    /**
250
     * Standard SS method.
251
     *
252
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
253
     *
254
     * @return bool
255
     */
256
    public function canDelete($member = null)
257
    {
258
        return false;
259
    }
260
261
    /**
262
     * Standard SS method.
263
     *
264
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
265
     *
266
     * @return bool
267
     */
268
    public function canPublish($member = null)
269
    {
270
        return $this->canEdit($member);
271
    }
272
273
    /**
274
     * Standard SS function.
275
     *
276
     * @return FieldList
277
     **/
278
    public function getCMSFields()
279
    {
280
        $fields = parent :: getCMSFields();
281
        $fields->removeFieldFromTab('Root.Messages.Messages.Actions', 'ProceedToCheckoutLabel');
282
        $fields->removeFieldFromTab('Root.Messages.Messages.Actions', 'ContinueShoppingLabel');
283
        $fields->removeFieldFromTab('Root.Messages.Messages.Actions', 'ContinuePageID');
284
        $fields->removeFieldFromTab('Root.Messages.Messages.Actions', 'LoadOrderLinkLabel');
285
        $fields->removeFieldFromTab('Root.Messages.Messages.Actions', 'CurrentOrderLinkLabel');
286
        $fields->removeFieldFromTab('Root.Messages.Messages.Actions', 'SaveOrderLinkLabel');
287
        $fields->removeFieldFromTab('Root.Messages.Messages.Actions', 'DeleteOrderLinkLabel');
288
        $termsPageIDField = OptionalTreeDropdownField::create(
289
            'TermsPageID',
290
            _t('CheckoutPage.TERMSANDCONDITIONSPAGE', 'Terms and conditions page'),
291
            'SiteTree'
292
        );
293
        $termsPageIDField->setRightTitle(_t('CheckoutPage.TERMSANDCONDITIONSPAGE_RIGHT', 'This is optional. To remove this page clear the reminder message below.'));
294
        $fields->addFieldToTab('Root.Terms', $termsPageIDField);
295
        $fields->addFieldToTab(
296
            'Root.Terms',
297
            $termsPageIDFieldMessage = new TextField(
298
                'TermsAndConditionsMessage',
299
                _t('CheckoutPage.TERMSANDCONDITIONSMESSAGE', 'Reminder Message')
300
            )
301
        );
302
        $termsPageIDFieldMessage->setRightTitle(
303
            _t('CheckoutPage.TERMSANDCONDITIONSMESSAGE_RIGHT', "Shown if the user does not tick the 'I agree with the Terms and Conditions' box. Leave blank to allow customer to proceed without ticking this box")
304
        );
305
        //The Content field has a slightly different meaning for the Checkout Page.
306
        $fields->removeFieldFromTab('Root.Main', 'Content');
307
        $fields->addFieldToTab('Root.Messages.Messages.AlwaysVisible', $htmlEditorField = new HTMLEditorField('Content', _t('CheckoutPage.CONTENT', 'General note - always visible on the checkout page')));
308
        $htmlEditorField->setRows(3);
309
        if (OrderModifier_Descriptor::get()->count()) {
310
            $fields->addFieldToTab('Root.Messages.Messages.OrderExtras', $this->getOrderModifierDescriptionField());
311
        }
312
        if (CheckoutPage_StepDescription::get()->count()) {
313
            $fields->addFieldToTab('Root.Messages.Messages.CheckoutSteps', $this->getCheckoutStepDescriptionField());
314
        }
315
316
        return $fields;
317
    }
318
319
    /**
320
     * @return GridField
321
     */
322
    protected function getOrderModifierDescriptionField()
323
    {
324
        $gridFieldConfig = GridFieldConfig::create()->addComponents(
325
            new GridFieldToolbarHeader(),
326
            new GridFieldSortableHeader(),
327
            new GridFieldDataColumns(),
328
            new GridFieldEditButton(),
329
            new GridFieldDetailForm()
330
        );
331
        $title = _t('CheckoutPage.ORDERMODIFIERDESCRIPTMESSAGES', 'Messages relating to order form extras (e.g. tax or shipping)');
332
        $source = OrderModifier_Descriptor::get();
333
334
        return new GridField('OrderModifier_Descriptor', $title, $source, $gridFieldConfig);
335
    }
336
337
    /**
338
     * @return GridField
339
     */
340
    protected function getCheckoutStepDescriptionField()
341
    {
342
        $gridFieldConfig = GridFieldConfig::create()->addComponents(
343
            new GridFieldToolbarHeader(),
344
            new GridFieldSortableHeader(),
345
            new GridFieldDataColumns(),
346
            new GridFieldEditButton(),
347
            new GridFieldDetailForm()
348
        );
349
        $title = _t('CheckoutPage.CHECKOUTSTEPESCRIPTIONS', 'Checkout Step Descriptions');
350
        $source = CheckoutPage_StepDescription::get();
351
352
        return new GridField('CheckoutPage_StepDescription', $title, $source, $gridFieldConfig);
353
    }
354
355
    public function requireDefaultRecords()
356
    {
357
        parent::requireDefaultRecords();
358
        if (SiteTree::config()->create_default_pages) {
359
            $checkoutPage = DataObject::get_one('CheckoutPage');
360
            if (! $checkoutPage) {
361
                $checkoutPage = self::create();
362
                $checkoutPage->Title = 'Checkout';
363
                $checkoutPage->MenuTitle = 'Checkout';
364
                $checkoutPage->URLSegment = 'checkout';
365
                $checkoutPage->writeToStage('Stage');
366
                $checkoutPage->publish('Stage', 'Live');
367
            }
368
        }
369
    }
370
}
371
372
class CheckoutPage_Controller extends CartPage_Controller
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
373
{
374
    private static $allowed_actions = 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...
Unused Code introduced by
The property $allowed_actions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
375
        'checkoutstep',
376
        'OrderFormAddress',
377
        'saveorder',
378
        'CreateAccountForm',
379
        'retrieveorder',
380
        'loadorder',
381
        'startneworder',
382
        'showorder',
383
        'LoginForm',
384
        'OrderForm',
385
    );
386
387
    /**
388
     * FOR STEP STUFF SEE BELOW.
389
     **/
390
391
    /**
392
     * Standard SS function
393
     * if set to false, user can edit order, if set to true, user can only review order.
394
     **/
395
    public function init()
396
    {
397
        parent::init();
398
399
        Requirements::themedCSS('CheckoutPage', 'ecommerce');
400
        $ajaxifyArray = EcommerceConfig::get('CheckoutPage_Controller', 'ajaxify_steps');
401
        if (count($ajaxifyArray)) {
402
            foreach ($ajaxifyArray as $js) {
0 ignored issues
show
Bug introduced by
The expression $ajaxifyArray of type array|integer|double|string|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
403
                Requirements::javascript($js);
404
            }
405
        }
406
        Requirements::javascript('ecommerce/javascript/EcomPayment.js');
407
        Requirements::customScript(
408
            '
409
            if (typeof EcomOrderForm != "undefined") {
410
                EcomOrderForm.set_TermsAndConditionsMessage(\''.convert::raw2js($this->TermsAndConditionsMessage).'\');
411
            }',
412
            'TermsAndConditionsMessage'
413
        );
414
        $this->steps = EcommerceConfig::get('CheckoutPage_Controller', 'checkout_steps');
415
        $this->currentStep = $this->request->Param('ID');
416
        if ($this->currentStep && in_array($this->currentStep, $this->steps)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
417
            //do nothing
418
        } else {
419
            $this->currentStep = array_shift($this->steps);
420
        }
421
        //redirect to current order -
422
        // this is only applicable when people submit order (start to pay)
423
        // and then return back
424
        if ($checkoutPageCurrentOrderID = Session::get('CheckoutPageCurrentOrderID')) {
425
            if ($this->currentOrder->ID != $checkoutPageCurrentOrderID) {
426
                $this->clearRetrievalOrderID();
427
            }
428
        }
429
        if ($this->currentOrder) {
430
            $this->setRetrievalOrderID($this->currentOrder->ID);
431
        }
432
    }
433
434
    /**
435
     * Returns a ArrayList of {@link OrderModifierForm} objects. These
436
     * forms are used in the OrderInformation HTML table for the user to fill
437
     * in as needed for each modifier applied on the site.
438
     *
439
     * @return ArrayList (ModifierForms) | Null
440
     */
441
    public function ModifierForms()
442
    {
443
        if ($this->currentOrder) {
444
            return $this->currentOrder->getModifierForms();
445
        }
446
    }
447
448
    /**
449
     * Returns a form allowing a user to enter their
450
     * details to checkout their order.
451
     *
452
     * @return OrderForm object
453
     */
454
    public function OrderFormAddress()
455
    {
456
        $form = OrderFormAddress::create($this, 'OrderFormAddress');
457
        $this->data()->extend('updateOrderFormAddress', $form);
458
        //load session data
459
        if ($data = Session::get("FormInfo.{$form->FormName()}.data")) {
460
            $form->loadDataFrom($data);
461
        }
462
463
        return $form;
464
    }
465
466
    /**
467
     * Returns a form allowing a user to enter their
468
     * details to checkout their order.
469
     *
470
     * @return OrderForm object
471
     */
472
    public function OrderForm()
473
    {
474
        $form = OrderForm::create($this, 'OrderForm');
475
        $this->data()->extend('updateOrderForm', $form);
476
        //load session data
477
        if ($data = Session :: get("FormInfo.{$form->FormName()}.data")) {
478
            $form->loadDataFrom($data);
479
        }
480
481
        return $form;
482
    }
483
484
    /**
485
     * Can the user proceed? It must be an editable order (see @link CartPage)
486
     * and is must also contain items.
487
     *
488
     * @return bool
489
     */
490
    public function CanCheckout()
491
    {
492
        return $this->currentOrder->getTotalItems() && !$this->currentOrder->IsSubmitted();
493
    }
494
495
    /**
496
     * Catch for incompatable coding only....
497
     */
498
    public function ModifierForm($request)
499
    {
500
        user_error('Make sure that you set the controller for your ModifierForm to a controller directly associated with the Modifier', E_USER_WARNING);
501
502
        return array();
503
    }
504
505
    /**
506
     * STEP STUFF ---------------------------------------------------------------------------.
507
     */
508
509
510
    /**
511
    *@var String
512
    **/
513
    protected $currentStep = '';
514
515
    /**
516
     *@var array
517
     **/
518
    protected $steps = array();
519
520
    /**
521
     * returns a dataobject set of the steps.
522
     * Or just one step if that is more relevant.
523
     *
524
     * @param int $number - if set, it returns that one step.
525
     */
526
    public function CheckoutSteps($number = 0)
527
    {
528
        $where = '';
0 ignored issues
show
Unused Code introduced by
$where 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...
529
        $dos = CheckoutPage_StepDescription::get()
530
            ->Sort('ID', 'ASC');
531
        if ($number) {
532
            $dos = $dos->Filter(array('ID' => $number));
533
        }
534
        if ($number) {
535
            if ($dos->count()) {
536
                return $dos->First();
537
            }
538
        }
539
        $returnData = new ArrayList(array());
540
        $completed = 1;
541
        $completedClass = 'completed';
542
        foreach ($dos as $do) {
543
            if ($this->currentStep && $do->Code() == $this->currentStep) {
544
                $do->LinkingMode = 'current';
545
                $completed = 0;
546
                $completedClass = 'notCompleted';
547
            } else {
548
                if ($completed) {
549
                    $do->Link = $this->Link('checkoutstep').'/'.$do->Code.'/';
550
                }
551
                $do->LinkingMode = "link $completedClass";
552
            }
553
            $do->Completed = $completed;
554
            $returnData->push($do);
555
        }
556
        if (EcommerceConfig::get('OrderConfirmationPage_Controller', 'include_as_checkout_step')) {
557
            $orderConfirmationPage = DataObject::get_one('OrderConfirmationPage');
558
            if ($orderConfirmationPage) {
559
                $do = $orderConfirmationPage->CurrentCheckoutStep(false);
560
                if ($do) {
561
                    $returnData->push($do);
562
                }
563
            }
564
        }
565
566
        return $returnData;
567
    }
568
569
    /**
570
     * returns the heading for the Checkout Step.
571
     *
572
     * @param int $number
573
     *
574
     * @return string
575
     */
576
    public function StepsContentHeading($number)
577
    {
578
        $do = $this->CheckoutSteps($number);
579
        if ($do) {
580
            return $do->Heading;
581
        }
582
583
        return '';
584
    }
585
586
    /**
587
     * returns the top of the page content for the Checkout Step.
588
     *
589
     * @param int $number
590
     *
591
     * @return string
592
     */
593
    public function StepsContentAbove($number)
594
    {
595
        $do = $this->CheckoutSteps($number);
596
        if ($do) {
597
            return $do->Above;
598
        }
599
600
        return '';
601
    }
602
603
    /**
604
     * returns the bottom of the page content for the Checkout Step.
605
     *
606
     * @param int $number
607
     *
608
     * @return string
609
     */
610
    public function StepsContentBelow($number)
611
    {
612
        $do = $this->CheckoutSteps($number);
613
        if ($do) {
614
            return $do->Below;
615
        }
616
617
        return '';
618
    }
619
620
    /**
621
     * sets the current checkout step
622
     * if it is ajax it returns the current controller
623
     * as the inner for the page.
624
     *
625
     * @param SS_HTTPRequest $request
626
     *
627
     * @return array
628
     */
629
    public function checkoutstep(SS_HTTPRequest $request)
630
    {
631
        if ($this->request->isAjax()) {
632
            Requirements::clear();
633
634
            return $this->renderWith('LayoutCheckoutPageInner');
635
        }
636
637
        return array();
638
    }
639
640
    /**
641
     * when you extend the CheckoutPage you can change this...
642
     *
643
     * @return bool
644
     */
645
    public function HasCheckoutSteps()
646
    {
647
        return true;
648
    }
649
650
    /**
651
     * @param string $step
652
     *
653
     * @return bool
654
     **/
655
    public function CanShowStep($step)
656
    {
657
        if ($this->ShowOnlyCurrentStep()) {
658
            $outcome = $step == $this->currentStep;
659
        } else {
660
            $outcome = in_array($step, $this->steps);
661
        }
662
663
        // die($step.'sadf'.$outcome);
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
664
        return $outcome;
665
    }
666
667
    /**
668
     * Is this the final step in the process.
669
     *
670
     * @return bool
671
     */
672
    public function ShowOnlyCurrentStep()
673
    {
674
        return $this->currentStep ? true : false;
675
    }
676
677
    /**
678
     * Is this the final step in the process?
679
     *
680
     * @return bool
681
     */
682
    public function IsFinalStep()
683
    {
684
        foreach ($this->steps as $finalStep) {
0 ignored issues
show
Unused Code introduced by
This foreach statement is empty and can be removed.

This check looks for foreach loops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

Consider removing the loop.

Loading history...
685
            //do nothing...
686
        }
687
688
        return $this->currentStep == $finalStep;
0 ignored issues
show
Bug introduced by
The variable $finalStep seems to be defined by a foreach iteration on line 684. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
689
    }
690
691
    /**
692
     * returns the percentage of steps done (0 - 100).
693
     *
694
     * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
695
     */
696
    public function PercentageDone()
697
    {
698
        return round($this->currentStepNumber() / $this->numberOfSteps(), 2) * 100;
699
    }
700
701
    /**
702
     * returns the number of the current step (e.g. step 1).
703
     *
704
     * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be false|integer|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
705
     */
706
    protected function currentStepNumber()
707
    {
708
        $key = 1;
709
        if ($this->currentStep) {
710
            $key = array_search($this->currentStep, $this->steps);
711
            ++$key;
712
        }
713
714
        return $key;
715
    }
716
717
    /**
718
     * returns the total number of steps (e.g. 3)
719
     * we add one for the confirmation page.
720
     *
721
     * @return int
722
     */
723
    protected function numberOfSteps()
724
    {
725
        return count($this->steps) + 1;
726
    }
727
728
    /**
729
     * Here are some additional rules that can be applied to steps.
730
     * If you extend the checkout page, you canm overrule these rules.
731
     */
732
    protected function applyStepRules()
733
    {
734
        //no items, back to beginning.
735
        //has step xxx been completed? if not go back one?
736
        //extend
737
        //reset current step if different
738
    }
739
}
740