Issues (2002)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

code/CheckoutPage.php (7 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
35
{
36
    /**
37
     * standard SS variable.
38
     *
39
     * @Var Boolean
40
     */
41
    private static $hide_ancestor = 'CartPage';
42
43
    /**
44
     * standard SS variable.
45
     *
46
     * @Var string
47
     */
48
    private static $icon = 'ecommerce/images/icons/CheckoutPage';
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
50
    /**
51
     * standard SS variable.
52
     *
53
     * @Var Array
54
     */
55
    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...
56
        'TermsAndConditionsMessage' => 'Varchar(200)',
57
    );
58
59
    /**
60
     * standard SS variable.
61
     *
62
     * @Var Array
63
     */
64
    private static $has_one = array(
65
        'TermsPage' => 'Page',
66
    );
67
68
    /**
69
     * standard SS variable.
70
     *
71
     * @Var Array
72
     */
73
    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...
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
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
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
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]
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 {
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.'/';
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
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
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
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
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
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...
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) {
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)) {
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
        return null;
447
    }
448
449
    /**
450
     * Returns a form allowing a user to enter their
451
     * details to checkout their order.
452
     *
453
     * @return OrderForm object
454
     */
455
    public function OrderFormAddress()
456
    {
457
        $form = OrderFormAddress::create($this, 'OrderFormAddress');
458
        $this->data()->extend('updateOrderFormAddress', $form);
459
        //load session data
460
        if ($data = Session::get("FormInfo.{$form->FormName()}.data")) {
461
            $form->loadDataFrom($data);
462
        }
463
464
        return $form;
465
    }
466
467
    /**
468
     * Returns a form allowing a user to enter their
469
     * details to checkout their order.
470
     *
471
     * @return OrderForm object
472
     */
473
    public function OrderForm()
474
    {
475
        $form = OrderForm::create($this, 'OrderForm');
476
        $this->data()->extend('updateOrderForm', $form);
477
        //load session data
478
        if ($data = Session :: get("FormInfo.{$form->FormName()}.data")) {
479
            $form->loadDataFrom($data);
480
        }
481
482
        return $form;
483
    }
484
485
    /**
486
     * Can the user proceed? It must be an editable order (see @link CartPage)
487
     * and is must also contain items.
488
     *
489
     * @return bool
490
     */
491
    public function CanCheckout()
492
    {
493
        return $this->currentOrder->getTotalItems() && !$this->currentOrder->IsSubmitted();
494
    }
495
496
    /**
497
     * Catch for incompatable coding only....
498
     */
499
    public function ModifierForm($request)
500
    {
501
        user_error('Make sure that you set the controller for your ModifierForm to a controller directly associated with the Modifier', E_USER_WARNING);
502
503
        return array();
504
    }
505
506
    /**
507
     * STEP STUFF ---------------------------------------------------------------------------.
508
     */
509
510
511
    /**
512
    *@var String
513
    **/
514
    protected $currentStep = '';
515
516
    /**
517
     *@var array
518
     **/
519
    protected $steps = array();
520
521
    /**
522
     * returns a dataobject set of the steps.
523
     * Or just one step if that is more relevant.
524
     *
525
     * @param int $number - if set, it returns that one step.
526
     */
527
    public function CheckoutSteps($number = 0)
528
    {
529
        $steps = EcommerceConfig::get('CheckoutPage_Controller', 'checkout_steps');
530
        if ($number) {
531
            $code = $steps[$number - 1];
532
533
            return CheckoutPage_StepDescription::get()->filter(['Code' => $code])->first();
534
        }
535
        $returnData = ArrayList::create();
536
        $completed = 1;
537
        $completedClass = 'completed';
538
        $seenCodes = [];
539
        foreach ($steps as $code) {
540
            if(! in_array($code, $seenCodes)) {
541
                $seenCodes[$code] = $code;
542
                $do = CheckoutPage_StepDescription::get()->filter(['Code' => $code])->first();
543
                if($do) {
544
                    if ($this->currentStep && $do->Code == $this->currentStep) {
545
                        $do->LinkingMode = 'current';
546
                        $completed = 0;
547
                        $completedClass = 'notCompleted';
548
                    } else {
549
                        if ($completed) {
550
                            $do->Link = $this->Link('checkoutstep').'/'.$do->Code.'/';
551
                        }
552
                        $do->LinkingMode = "link $completedClass";
553
                    }
554
                    $do->Completed = $completed;
555
                    $returnData->push($do);
556
                }
557
            }
558
        }
559
        if (EcommerceConfig::get('OrderConfirmationPage_Controller', 'include_as_checkout_step')) {
560
            $orderConfirmationPage = DataObject::get_one('OrderConfirmationPage');
561
            if ($orderConfirmationPage) {
562
                $do = $orderConfirmationPage->CurrentCheckoutStep(false);
563
                if ($do) {
564
                    $returnData->push($do);
565
                }
566
            }
567
        }
568
569
        return $returnData;
570
    }
571
572
    /**
573
     * returns the heading for the Checkout Step.
574
     *
575
     * @param int $number
576
     *
577
     * @return string
578
     */
579
    public function StepsContentHeading($number)
580
    {
581
        $do = $this->CheckoutSteps($number);
582
        if ($do) {
583
            return $do->Heading;
584
        }
585
586
        return '';
587
    }
588
589
    /**
590
     * returns the top of the page content for the Checkout Step.
591
     *
592
     * @param int $number
593
     *
594
     * @return string
595
     */
596
    public function StepsContentAbove($number)
597
    {
598
        $do = $this->CheckoutSteps($number);
599
        if ($do) {
600
            return $do->Above;
601
        }
602
603
        return '';
604
    }
605
606
    /**
607
     * returns the bottom of the page content for the Checkout Step.
608
     *
609
     * @param int $number
610
     *
611
     * @return string
612
     */
613
    public function StepsContentBelow($number)
614
    {
615
        $do = $this->CheckoutSteps($number);
616
        if ($do) {
617
            return $do->Below;
618
        }
619
620
        return '';
621
    }
622
623
    /**
624
     * sets the current checkout step
625
     * if it is ajax it returns the current controller
626
     * as the inner for the page.
627
     *
628
     * @param SS_HTTPRequest $request
629
     *
630
     * @return array
631
     */
632
    public function checkoutstep(SS_HTTPRequest $request)
633
    {
634
        if ($this->request->isAjax()) {
635
            Requirements::clear();
636
637
            return $this->renderWith('LayoutCheckoutPageInner');
638
        }
639
640
        return array();
641
    }
642
643
    /**
644
     * when you extend the CheckoutPage you can change this...
645
     *
646
     * @return bool
647
     */
648
    public function HasCheckoutSteps()
649
    {
650
        return true;
651
    }
652
653
    /**
654
     * @param string $step
655
     *
656
     * @return bool
657
     **/
658
    public function CanShowStep($step)
659
    {
660
        if ($this->ShowOnlyCurrentStep()) {
661
            $outcome = $step == $this->currentStep;
662
        } else {
663
            $outcome = in_array($step, $this->steps);
664
        }
665
666
        // die($step.'sadf'.$outcome);
667
        return $outcome;
668
    }
669
670
    /**
671
     * Is this the final step in the process.
672
     *
673
     * @return bool
674
     */
675
    public function ShowOnlyCurrentStep()
676
    {
677
        return $this->currentStep ? true : false;
678
    }
679
680
    /**
681
     * Is this the final step in the process?
682
     *
683
     * @return bool
684
     */
685
    public function IsFinalStep()
686
    {
687
        foreach ($this->steps as $finalStep) {
688
            //do nothing...
689
        }
690
691
        return $this->currentStep == $finalStep;
692
    }
693
694
    /**
695
     * returns the percentage of steps done (0 - 100).
696
     *
697
     * @return int
698
     */
699
    public function PercentageDone()
700
    {
701
        return round($this->currentStepNumber() / $this->numberOfSteps(), 2) * 100;
702
    }
703
704
    /**
705
     * returns the number of the current step (e.g. step 1).
706
     *
707
     * @return int
708
     */
709
    protected function currentStepNumber()
710
    {
711
        $key = 1;
712
        if ($this->currentStep) {
713
            $key = array_search($this->currentStep, $this->steps);
714
            ++$key;
715
        }
716
717
        return $key;
718
    }
719
720
    /**
721
     * returns the total number of steps (e.g. 3)
722
     * we add one for the confirmation page.
723
     *
724
     * @return int
725
     */
726
    protected function numberOfSteps()
727
    {
728
        return count($this->steps) + 1;
729
    }
730
731
    /**
732
     * Here are some additional rules that can be applied to steps.
733
     * If you extend the checkout page, you canm overrule these rules.
734
     */
735
    protected function applyStepRules()
736
    {
737
        //no items, back to beginning.
738
        //has step xxx been completed? if not go back one?
739
        //extend
740
        //reset current step if different
741
    }
742
}
743