Completed
Push — master ( 364b5f...db455e )
by
unknown
04:01
created

code/CartPage.php (32 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
 * @description: This is a page that shows the cart content,
5
 * without "leading to" checking out. That is, there is no "next step" functionality
6
 * or a way to submit the order.
7
 * NOTE: both the Account and the Checkout Page extend from this class as they
8
 * share some functionality.
9
 *
10
 *
11
 * @authors: Nicolaas [at] Sunny Side Up .co.nz
12
 * @package: ecommerce
13
 * @sub-package: Pages
14
 * @inspiration: Silverstripe Ltd, Jeremy
15
 **/
16
class CartPage extends Page
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...
17
{
18
    /**
19
     * Standard SS variable.
20
     *
21
     * @var string
22
     */
23
    private static $icon = 'ecommerce/images/icons/CartPage';
0 ignored issues
show
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...
24
25
    /**
26
     * Standard SS variable.
27
     *
28
     * @var array
29
     */
30
    private static $db = array(
0 ignored issues
show
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...
31
        'ContinueShoppingLabel' => 'Varchar(100)',
32
        'ProceedToCheckoutLabel' => 'Varchar(100)',
33
        'ShowAccountLabel' => 'Varchar(100)',
34
        'CurrentOrderLinkLabel' => 'Varchar(100)',
35
        'LoginToOrderLinkLabel' => 'Varchar(100)',
36
        'SaveOrderLinkLabel' => 'Varchar(100)',
37
        'LoadOrderLinkLabel' => 'Varchar(100)',
38
        'DeleteOrderLinkLabel' => 'Varchar(100)',
39
        'NoItemsInOrderMessage' => 'HTMLText',
40
        'NonExistingOrderMessage' => 'HTMLText'
41
    );
42
43
    /**
44
     * Standard SS variable.
45
     *
46
     * @var array
47
     */
48
    private static $defaults = array(
0 ignored issues
show
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...
49
        'ContinueShoppingLabel' => 'continue shopping',
50
        'ProceedToCheckoutLabel' => 'proceed to checkout',
51
        'ShowAccountLabel' => 'view account details',
52
        'CurrentOrderLinkLabel' => 'view current order',
53
        'LoginToOrderLinkLabel' => 'you must log in to view this order',
54
        'SaveOrderLinkLabel' => 'save current order',
55
        'DeleteOrderLinkLabel' => 'delete this order',
56
        'LoadOrderLinkLabel' => 'finalise this order',
57
        'NoItemsInOrderMessage' => '<p>You do not have any items in your current order.</p>',
58
        'NonExistingOrderMessage' => '<p>Sorry, the order you are trying to open does not exist.</p>',
59
    );
60
61
    /**
62
     * Standard SS variable.
63
     *
64
     * @var array
65
     */
66
    private static $casting = array(
0 ignored issues
show
The property $casting 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...
67
        'MenuTitle' => 'HTMLVarchar',
68
    );
69
70
    /**
71
     * Standard SS variable.
72
     */
73
    private static $singular_name = 'Cart Page';
0 ignored issues
show
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...
74
    public function i18n_singular_name()
75
    {
76
        return _t('CartPage.SINGULARNAME', 'Cart Page');
77
    }
78
79
    /**
80
     * Standard SS variable.
81
     */
82
    private static $plural_name = 'Cart Pages';
0 ignored issues
show
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...
83
    public function i18n_plural_name()
84
    {
85
        return _t('CartPage.PLURALNAME', 'Cart Pages');
86
    }
87
88
    /**
89
     * Standard SS variable.
90
     *
91
     * @var string
92
     */
93
    private static $description = 'A page where the customer can view the current order (cart) without finalising the order.';
0 ignored issues
show
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...
94
95
    /***
96
     * override core function to turn "checkout" into "Checkout (1)"
97
     * @return DBField
98
     */
99
    public function obj($fieldName, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null)
100
    {
101
        if ($fieldName == 'MenuTitle' && !($this instanceof OrderConfirmationPage)) {
102
            return DBField::create_field('HTMLVarchar', strip_tags($this->EcommerceMenuTitle()), 'MenuTitle', $this);
103
        } else {
104
            return parent::obj($fieldName);
105
        }
106
    }
107
108
    /**
109
     * Standard SS function, we only allow for one CartPage page to exist
110
     * but we do allow for extensions to exist at the same time.
111
     *
112
     * @param Member $member
0 ignored issues
show
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...
113
     *
114
     * @return bool
115
     */
116
    public function canCreate($member = null)
117
    {
118
        return CartPage::get()->Filter(array('ClassName' => 'CartPage'))->Count() ? false : $this->canEdit($member);
119
    }
120
121
    /**
122
     * Shop Admins can edit.
123
     *
124
     * @param Member $member
0 ignored issues
show
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...
125
     *
126
     * @return bool
127
     */
128
    public function canEdit($member = null)
129
    {
130
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
131
            return true;
132
        }
133
134
        return parent::canEdit($member);
135
    }
136
137
    /**
138
     * Standard SS method.
139
     *
140
     * @param Member $member
0 ignored issues
show
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...
141
     *
142
     * @return bool
143
     */
144
    public function canDelete($member = null)
145
    {
146
        return $this->canEdit($member);
147
    }
148
149
    /**
150
     * Standard SS method.
151
     *
152
     * @param Member $member
0 ignored issues
show
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...
153
     *
154
     * @return bool
155
     */
156
    public function canPublish($member = null)
157
    {
158
        return $this->canEdit($member);
159
    }
160
161
    /**
162
     *@return FieldList
163
     **/
164
    public function getCMSFields()
165
    {
166
        $fields = parent::getCMSFields();
167
        $fields->addFieldsToTab(
168
            'Root.Messages',
169
            array(
170
                new TabSet(
171
                    'Messages',
172
                    Tab::create(
173
                        'Actions',
174
                        _t('CartPage.ACTIONS', 'Actions'),
175
                        new TextField('ContinueShoppingLabel', _t('CartPage.CONTINUESHOPPINGLABEL', 'Label on link to continue shopping - e.g. click here to continue shopping')),
176
                        new TextField('ProceedToCheckoutLabel', _t('CartPage.PROCEEDTOCHECKOUTLABEL', 'Label on link to proceed to checkout - e.g. click here to finalise your order')),
177
                        new TextField('ShowAccountLabel', _t('CartPage.SHOWACCOUNTLABEL', 'Label on the link \'view account details\' - e.g. click here to view your account details')),
178
                        new TextField('CurrentOrderLinkLabel', _t('CartPage.CURRENTORDERLINKLABEL', 'Label for the link pointing to the current order - e.g. click here to view current order')),
179
                        new TextField('LoginToOrderLinkLabel', _t('CartPage.LOGINTOORDERLINKLABEL', 'Label for the link pointing to the order which requires a log in - e.g. you must login to view this order')),
180
                        new TextField('SaveOrderLinkLabel', _t('CartPage.SAVEORDERLINKLABEL', 'Label for the saving an order - e.g. click here to save current order')),
181
                        new TextField('LoadOrderLinkLabel', _t('CartPage.LOADORDERLINKLABEL', 'Label for the loading an order into the cart - e.g. click here to finalise this order')),
182
                        new TextField('DeleteOrderLinkLabel', _t('CartPage.DELETEORDERLINKLABEL', 'Label for the deleting an order - e.g. click here to delete this order'))
183
                    ),
184
                    Tab::create(
185
                        'Errors',
186
                        _t('CartPage.ERRORS', 'Errors'),
187
                        $htmlEditorField1 = new HTMLEditorField('NoItemsInOrderMessage', _t('CartPage.NOITEMSINORDERMESSAGE', 'No items in order - shown when the customer tries to view an order without items.')),
188
                        $htmlEditorField2 = new HTMLEditorField('NonExistingOrderMessage', _t('CartPage.NONEXISTINGORDERMESSAGE', 'Non-existing Order - shown when the customer tries to load a non-existing order.'))
189
                    )
190
                ),
191
            )
192
        );
193
        $htmlEditorField1->setRows(3);
194
        $htmlEditorField2->setRows(3);
195
196
        return $fields;
197
    }
198
199
    /**
200
     * Returns the Link to the CartPage on this site.
201
     * @param string $action [optional]
0 ignored issues
show
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...
202
     * @return string (URLSegment)
203
     */
204
    public static function find_link($action = null)
205
    {
206
        $page = DataObject::get_one('CartPage', array('ClassName' => 'CartPage'));
207
        if ($page) {
208
            return $page->Link($action);
209
        } else {
210
            return CheckoutPage::find_link($action);
211
        }
212
    }
213
214
    /**
215
     * Returns the "new order" link.
216
     *
217
     * @param int | String $orderID - not used in CartPage
218
     *
219
     * @return string (URLSegment)
220
     */
221
    public static function new_order_link($orderID)
222
    {
223
        return self::find_link().'startneworder/';
224
    }
225
226
    /**
227
     * Returns the "copy order" link.
228
     *
229
     * @param int | String $orderID - not used in CartPage
230
     *
231
     * @return string (URLSegment)
232
     */
233
    public static function copy_order_link($orderID)
234
    {
235
        return OrderConfirmationPage::find_link().'copyorder/'.$orderID.'/';
236
    }
237
238
    /**
239
     * Return a link to view the order on this page.
240
     *
241
     * @param int|string $orderID ID of the order
242
     *
243
     * @return int | String (URLSegment)
0 ignored issues
show
Should the return type not be 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...
244
     */
245
    public static function get_order_link($orderID)
246
    {
247
        return self::find_link().'showorder/'.$orderID.'/';
248
    }
249
250
    /**
251
     * Return a link to view the order on this page.
252
     *
253
     * @param int|string $orderID ID of the order
254
     *
255
     * @return string (URLSegment)
256
     */
257
    public function getOrderLink($orderID)
258
    {
259
        return self::get_order_link($orderID);
260
    }
261
262
    /**
263
     * tells us if the current page is part of e-commerce.
264
     *
265
     * @return bool
266
     */
267
    public function IsEcommercePage()
268
    {
269
        return true;
270
    }
271
272
    /**
273
     *@return string (HTML Snippet)
274
     **/
275
    public function EcommerceMenuTitle()
276
    {
277
        $count = 0;
0 ignored issues
show
$count 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...
278
        $order = ShoppingCart::current_order();
279
        if ($order) {
280
            $count = $order->TotalItems();
281
            $oldSSViewer = Config::inst()->get('SSViewer', 'source_file_comments');
282
            Config::inst()->update('SSViewer', 'source_file_comments', false);
283
            $this->customise(array('Count' => $count, 'OriginalMenuTitle' => $this->MenuTitle));
284
            $s = $this->renderWith('AjaxNumItemsInCart');
285
            Config::inst()->update('SSViewer', 'source_file_comments', $oldSSViewer);
286
287
            return $s;
288
        }
289
290
        return $this->OriginalMenuTitle();
291
    }
292
293
    /**
294
     * The original menu title of the page.
295
     *
296
     * @return string
297
     */
298
    public function OriginalMenuTitle()
299
    {
300
        return $this->MenuTite;
301
    }
302
303
    /***********************
304
     * For use in templates
305
     ***********************/
306
307
    /**
308
     * standard SS method for use in templates.
309
     *
310
     * @return string
311
     */
312
    public function LinkingMode()
313
    {
314
        return parent::LinkingMode().' cartlink cartlinkID_'.$this->ID;
315
    }
316
317
    /**
318
     * standard SS method for use in templates.
319
     *
320
     * @return string
321
     */
322
    public function LinkOrSection()
323
    {
324
        return parent::LinkOrSection().' cartlink';
325
    }
326
327
    /**
328
     * standard SS method for use in templates.
329
     *
330
     * @return string
331
     */
332
    public function LinkOrCurrent()
333
    {
334
        return parent::LinkOrCurrent().' cartlink';
335
    }
336
}
337
338
class CartPage_Controller extends Page_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...
339
{
340
    /**
341
     * @static array
342
     * standard SS variable
343
     * it is important that we list all the options here
344
     */
345
    private static $allowed_actions = array(
0 ignored issues
show
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...
346
        'saveorder',
347
        'CreateAccountForm',
348
        'retrieveorder',
349
        'loadorder',
350
        'deleteorder',
351
        'startneworder',
352
        'showorder',
353
        'share',
354
        'LoginForm'
355
    );
356
357
    /**
358
     * This ArraList holds DataObjects with a Link and Title each....
359
     *
360
     * @var ArraList
361
     **/
362
    protected $actionLinks = null;
363
364
    /**
365
     * to ensure messages and actions links are only worked out once...
366
     *
367
     * @var Boolean
368
     **/
369
    protected $workedOutMessagesAndActions = false;
370
371
    /**
372
     * order currently being shown on this page.
373
     *
374
     * @var DataObject
375
     **/
376
    protected $currentOrder = null;
377
378
    /**
379
     * Message shown (e.g. no current order, could not find order, order updated, etc...).
380
     *
381
     *@var String
382
     * @todo: check if we need this....!
383
     **/
384
    private $message = '';
385
    public static function set_message($s)
386
    {
387
        $sessionCode = EcommerceConfig::get('CartPage_Controller', 'session_code');
388
        Session::set($sessionCode, $s);
389
    }
390
391
    /**
392
     * show the order even if canView returns false.
393
     *
394
     * @var bool
395
     */
396
    protected $overrideCanView = false;
397
398
    /**
399
     * @standard SS method
400
     */
401
    public function init()
0 ignored issues
show
init uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
402
    {
403
        HTTP::set_cache_age(0);
404
        parent::init();
405
        // find the current order if any
406
        $orderID = 0;
0 ignored issues
show
$orderID 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...
407
        //WE HAVE THIS FOR SUBMITTING FORMS!
408
        if (isset($_REQUEST['OrderID'])) {
409
            $orderID = intval($_REQUEST['OrderID']);
410
            if ($orderID) {
411
                $this->currentOrder = Order::get()->byID($orderID);
412
            }
413
        } elseif ($this->request && $this->request->param('ID') && $this->request->param('Action')) {
414
            //we can not do intval here!
415
            $id = $this->request->param('ID');
416
            $action = $this->request->param('Action');
417
            $otherID = intval($this->request->param('OtherID'));
418
            //the code below is for submitted orders, but we still put it here so
419
            //we can do all the retrieval options at once.
420
            if (($action == 'retrieveorder') && $id && $otherID) {
421
                $sessionID = Convert::raw2sql($id);
422
                $retrievedOrder = Order::get()->filter(
423
                    array(
424
                        'SessionID' => $sessionID,
425
                        'ID' => $otherID,
426
                    )
427
                )->first();
428
                if ($retrievedOrder) {
429
                    $this->currentOrder = $retrievedOrder;
430
                    $this->overrideCanView = true;
431
                    $this->setRetrievalOrderID($this->currentOrder->ID);
432
                }
433
            } elseif (intval($id) && in_array($action, $this->stat('allowed_actions'))) {
434
                $this->currentOrder = Order::get()->byID(intval($id));
435
            }
436
        }
437
        if (!$this->currentOrder) {
438
            $this->currentOrder = ShoppingCart::current_order();
439
            if ($this->currentOrder) {
440
                if ($this->currentOrder->IsSubmitted()) {
441
                    $this->overrideCanView = true;
442
                }
443
            }
444
        }
445
        //redirect if we are viewing the order with the wrong page!
446
        if ($this->currentOrder) {
447
            if ($this->overrideCanView) {
448
                $canView = $this->currentOrder->canOverrideCanView();
0 ignored issues
show
The method canOverrideCanView() does not exist on DataObject. Did you maybe mean canView()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
449
            } else {
450
                $canView = $this->currentOrder->canView();
451
            }
452
            //IMPORTANT SECURITY QUESTION!
453
            if ($canView) {
454
                if ($this->currentOrder->IsSubmitted() && $this->onlyShowUnsubmittedOrders()) {
455
                    $this->redirect($this->currentOrder->Link());
456
                } elseif ((!$this->currentOrder->IsSubmitted()) && $this->onlyShowSubmittedOrders()) {
457
                    $this->redirect($this->currentOrder->Link());
458
                }
459
            } else {
460
                if (!$this->LoginToOrderLinkLabel) {
461
                    $this->LoginToOrderLinkLabel = _t('CartPage.LOGINFIRST', 'You will need to log in before you can access the requested order order. ');
462
                }
463
                $messages = array(
464
                    'default' => '<p class="message good">'.$this->LoginToOrderLinkLabel.'</p>',
465
                    'logInAgain' => _t('CartPage.LOGINAGAIN', 'You have been logged out. If you would like to log in again, please do so below.'),
466
                );
467
                Security::permissionFailure($this, $messages);
468
469
                return false;
470
            }
471
            if (!$this->currentOrder->IsSubmitted()) {
472
                //we always want to make sure the order is up-to-date.
473
                $this->currentOrder->init($force = false);
474
                $this->currentOrder->calculateOrderAttributes($force = true);
475
                $this->currentOrder->calculateOrderAttributes($force = true);
476
            }
477
        } else {
478
            $this->message = _t('CartPage.ORDERNOTFOUND', 'Order can not be found.');
479
        }
480
    }
481
482
483
    /**
484
     * We set sesssion ID for retrieval of order in non cart setting
485
     * @param int $orderID
486
     * @param int $validUntilTS timestamp (unix epoch) until which the current Order ID is valid
0 ignored issues
show
Should the type for parameter $validUntilTS not be integer|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...
487
     */
488
    protected function setRetrievalOrderID($orderID, $validUntilTS = null)
489
    {
490
        if (! $validUntilTS) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $validUntilTS of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
491
            $validUntilTS = time() + 3600;
492
        }
493
        Session::set('CheckoutPageCurrentOrderID', $orderID);
494
        Session::set('CheckoutPageCurrentRetrievalTime', $validUntilTS);
495
        Session::save();
496
    }
497
498
    /**
499
     * we clear the retrieval Order ID
500
     */
501
    protected function clearRetrievalOrderID()
502
    {
503
        Session::clear('CheckoutPageCurrentOrderID');
504
        Session::set('CheckoutPageCurrentOrderID', 0);
505
        Session::clear('CheckoutPageCurrentRetrievalTime');
506
        Session::set('CheckoutPageCurrentRetrievalTime', 0);
507
        Session::save();
508
    }
509
510
    /***********************
511
     * Actions
512
     ***********************
513
514
515
516
517
    /**
518
     * shows an order and loads it if it is not submitted.
519
     * @todo: do we still need loadorder controller method????
520
     * @param SS_HTTPRequest
521
     * @return array just so that template shows
522
     **/
523
    public function showorder(SS_HTTPRequest $request)
524
    {
525
        if (!$this->currentOrder) {
526
            $this->message = _t('CartPage.ORDERNOTFOUND', 'Order can not be found.');
527
        } else {
528
            if (!$this->currentOrder->IsSubmitted()) {
529
                $shoppingCart = ShoppingCart::current_order();
530
                if ($shoppingCart->ID != $this->currentOrder->ID) {
531
                    if (ShoppingCart::singleton()->loadOrder($this->currentOrder)) {
532
                        $this->message = _t('CartPage.ORDERHASBEENLOADED', 'Order has been loaded.');
533
                    } else {
534
                        $this->message = _t('CartPage.ORDERNOTLOADED', 'Order could not be loaded.');
535
                    }
536
                }
537
            }
538
        }
539
540
        return array();
541
    }
542
543
    /**
544
     * share an order ...
545
     * @todo: do we still need loadorder controller method????
546
     * @param SS_HTTPRequest
547
     * @return array just so that template shows
548
     **/
549
    public function share(SS_HTTPRequest $request)
550
    {
551
        $codes = Convert::raw2sql($request->param('ID'));
552
        if (! $request->getVar('ready') && ! $request->getVar('done')) {
553
            return $this->redirect($this->Link('share/'.$codes).'?ready=1');
554
        }
555
        $titleAppendixArray = array();
556
        $buyables = explode('-', $codes);
557
        if (count($buyables)) {
558
            $sc = ShoppingCart::singleton();
559
            $order = $sc->currentOrder();
560
            foreach ($buyables as $buyable) {
561
                $details = explode(",", $buyable);
562
                if (count($details) == 3) {
563
                    $className = $details[0];
564
                    $className = class_exists($className) ? $className : null;
565
                    $id = intval($details[1]);
566
                    $quantity = floatval($details[2]);
567
                    if ($className && $id && $quantity) {
568
                        $buyable = $className::get()->byID($id);
569
                        if ($buyable && $buyable->canPurchase()) {
570
                            $sc->addBuyable($buyable, $quantity);
571
                            $sc->setQuantity($buyable, $quantity);
572
                            if ($request->getVar('done')) {
573
                                $titleAppendixArray[] = $buyable->getTitle();
574
                            }
575
                        }
576
                    }
577
                }
578
            }
579
            $order->calculateOrderAttributes(false);
580
            if (! $request->getVar('done')) {
581
                return $this->redirect($this->Link('share/'.$codes).'?done=1');
582
            }
583
        }
584
        $this->Title .= ': '.  implode(', ', $titleAppendixArray);
585
        if (strlen($this->Title) > 255) {
586
            $this->Title = substr($this->Title, 0, 255). ' ...';
587
        }
588
        return array();
589
    }
590
591
    /**
592
     * Loads either the "current order""into the shopping cart.
593
     *
594
     * TO DO: untested
595
     * TO DO: what to do with old order
596
     *
597
     * @param SS_HTTPRequest
598
     *
599
     * @return array
600
     */
601
    public function loadorder(SS_HTTPRequest $request)
602
    {
603
        self::set_message(_t('CartPage.ORDERLOADED', 'Order has been loaded.'));
604
        ShoppingCart::singleton()->loadOrder($this->currentOrder->ID);
605
        $this->redirect($this->Link());
606
607
        return array();
608
    }
609
610
    /**
611
     * save the order to a member. If no member exists then create the member first using the ShopAccountForm.
612
     *
613
     * @param SS_HTTPRequest
614
     *
615
     * @return array
616
     *               TO DO: untested
617
     */
618
    public function saveorder(SS_HTTPRequest $request)
619
    {
620
        $member = Member::currentUser();
621
        if (!$member) {
622
            $this->showCreateAccountForm = true;
623
624
            return array();
625
        }
626
        if ($this->currentOrder && $this->currentOrder->getTotalItems()) {
627
            $this->currentOrder->write();
628
            self::set_message(_t('CartPage.ORDERSAVED', 'Your order has been saved.'));
629
        } else {
630
            self::set_message(_t('CartPage.ORDERCOULDNOTBESAVED', 'Your order could not be saved.'));
631
        }
632
        $this->redirectBack();
633
634
        return array();
635
    }
636
637
    /**
638
     * Delete the currently viewed order.
639
     *
640
     * TO DO: untested
641
     *
642
     * @param SS_HTTPRequest
643
     *
644
     * @return array
645
     */
646
    public function deleteorder(SS_HTTPRequest $request)
647
    {
648
        if (!$this->CurrentOrderIsInCart()) {
649
            if ($this->currentOrder->canDelete()) {
650
                $this->currentOrder->delete();
651
                self::set_message(_t('CartPage.ORDERDELETED', 'Order has been deleted.'));
652
            }
653
        }
654
        self::set_message(_t('CartPage.ORDERNOTDELETED', 'Order could not be deleted.'));
655
656
        return array();
657
    }
658
659
    /**
660
     * Start a new order.
661
     *
662
     * @param SS_HTTPRequest
663
     *
664
     * @return array
665
     *               TO DO: untested
666
     */
667
    public function startneworder(SS_HTTPRequest $request)
668
    {
669
        ShoppingCart::singleton()->clear();
670
        self::set_message(_t('CartPage.NEWORDERSTARTED', 'New order has been started.'));
671
        $this->redirect($this->Link());
672
673
        return array();
674
    }
675
676
    /**
677
     * This returns a ArraList, each dataobject has two vars: Title and Link.
678
     *
679
     * @return ArraList
0 ignored issues
show
Should the return type not be ArraList|null?

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...
680
     **/
681
    public function ActionLinks()
682
    {
683
        $this->workOutMessagesAndActions();
684
        if ($this->actionLinks && $this->actionLinks->count()) {
685
            return $this->actionLinks;
686
        }
687
688
        return;
689
    }
690
691
692
    /**
693
     * The link that Google et al. need to index.
694
     * @return string
695
     */
696
    public function CanonicalLink()
697
    {
698
        $link = $checkoutPageLink = CheckoutPage::find_link();
0 ignored issues
show
$checkoutPageLink 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...
699
        $this->extend('UpdateCanonicalLink', $link);
700
        
701
        return $link;
702
    }
703
704
705
    /**
706
     * @return string
0 ignored issues
show
Should the return type not be DBField?

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...
707
     **/
708
    public function Message()
709
    {
710
        $this->workOutMessagesAndActions();
711
        if (!$this->message) {
712
            $sessionCode = EcommerceConfig::get('CartPage_Controller', 'session_code');
713
            if ($sessionMessage = Session::get($sessionCode)) {
714
                $this->message = $sessionMessage;
715
                Session::set($sessionCode, '');
716
                Session::clear($sessionCode);
717
            }
718
        }
719
        $field = DBField::create_field('HTMLText', $this->message);
720
721
        return $field;
722
    }
723
724
    /**
725
     * @return DataObject | Null - Order
726
     **/
727
    public function Order()
728
    {
729
        return $this->currentOrder;
730
    }
731
732
    /**
733
     * @return bool
734
     **/
735
    public function CanEditOrder()
736
    {
737
        if ($this->currentOrder) {
738
            if ($this->currentOrder->canEdit()) {
739
                if ($this->currentOrder->getTotalItems()) {
740
                    return true;
741
                }
742
            }
743
        }
744
745
        return false;
746
    }
747
748
    /**
749
     * Tells you if the order you are viewing at the moment is also in the cart.
750
     *
751
     * @return bool
752
     **/
753
    public function CurrentOrderIsInCart()
754
    {
755
        $viewingRealCurrentOrder = false;
756
        $realCurrentOrder = ShoppingCart::current_order();
757
        if ($this->currentOrder && $realCurrentOrder) {
758
            if ($realCurrentOrder->ID == $this->currentOrder->ID) {
759
                $viewingRealCurrentOrder = true;
760
            }
761
        }
762
763
        return $viewingRealCurrentOrder;
764
    }
765
766
    /**
767
     * @var bool
768
     */
769
    protected $showCreateAccountForm = false;
770
771
    /**
772
     * Do we need to show the Create Account Form?
773
     *
774
     * @return bool
775
     */
776
    public function ShowCreateAccountForm()
777
    {
778
        if (Session::get('CartPageCreateAccountForm')) {
779
            Session::set('CartPageCreateAccountForm', false);
0 ignored issues
show
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
780
781
            return true;
782
        }
783
        if (Member::currentUser() || $this->currentOrder->MemberID) {
784
            return false;
785
        } else {
786
            Session::set('CartPageCreateAccountForm', true);
0 ignored issues
show
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
787
788
            return true;
789
        }
790
    }
791
792
    /**
793
     * Returns the CreateAccountForm.
794
     *
795
     * @return ShopAccountForm
796
     */
797
    public function CreateAccountForm()
798
    {
799
        return ShopAccountForm::create($this, 'CreateAccountForm');
800
    }
801
802
    /**
803
     * work out the options for the user.
804
     **/
805
    protected function workOutMessagesAndActions()
806
    {
807
        if (!$this->workedOutMessagesAndActions) {
808
            $this->actionLinks = new ArrayList(array());
0 ignored issues
show
Documentation Bug introduced by
It seems like new \ArrayList(array()) of type object<ArrayList> is incompatible with the declared type object<ArraList> of property $actionLinks.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
809
            //what order are we viewing?
810
            $viewingRealCurrentOrder = $this->CurrentOrderIsInCart();
811
            $currentUserID = Member::currentUserID();
812
813
            //Continue Shopping
814
            if (isset($this->ContinueShoppingLabel) && $this->ContinueShoppingLabel) {
815
                if ($viewingRealCurrentOrder) {
816
                    if ($this->isCartPage()) {
817
                        $continueLink = $this->ContinueShoppingLink();
818
                        if ($continueLink) {
819
                            $this->actionLinks->push(
820
                                ArrayData::create(
821
                                    array(
822
                                        'Title' => $this->ContinueShoppingLabel,
823
                                        'Link' => $continueLink,
824
                                    )
825
                                )
826
                            );
827
                        }
828
                    }
829
                }
830
            }
831
832
            //Proceed To CheckoutLabel
833
            if (isset($this->ProceedToCheckoutLabel) && $this->ProceedToCheckoutLabel) {
834
                if ($viewingRealCurrentOrder) {
835
                    if ($this->isCartPage()) {
836
                        $checkoutPageLink = CheckoutPage::find_link();
837
                        if ($checkoutPageLink && $this->currentOrder && $this->currentOrder->getTotalItems()) {
838
                            $this->actionLinks->push(new ArrayData(array(
839
                                'Title' => $this->ProceedToCheckoutLabel,
840
                                'Link' => $checkoutPageLink,
841
                            )));
842
                        }
843
                    }
844
                }
845
            }
846
847
            //view account details
848
            if (isset($this->ShowAccountLabel) && $this->ShowAccountLabel) {
849
                if ($this->isOrderConfirmationPage() || $this->isCartPage()) {
850
                    if (AccountPage::find_link()) {
851
                        if ($currentUserID) {
852
                            $this->actionLinks->push(new ArrayData(array(
853
                                'Title' => $this->ShowAccountLabel,
854
                                'Link' => AccountPage::find_link(),
855
                            )));
856
                        }
857
                    }
858
                }
859
            }
860
            //go to current order
861
            if (isset($this->CurrentOrderLinkLabel) && $this->CurrentOrderLinkLabel) {
862
                if ($this->isCartPage()) {
863
                    if (!$viewingRealCurrentOrder) {
864
                        $this->actionLinks->push(new ArrayData(array(
865
                            'Title' => $this->CurrentOrderLinkLabel,
866
                            'Link' => ShoppingCart::current_order()->Link(),
867
                        )));
868
                    }
869
                }
870
            }
871
872
            //Save order - we assume only current ones can be saved.
873
            if (isset($this->SaveOrderLinkLabel) && $this->SaveOrderLinkLabel) {
874
                if ($viewingRealCurrentOrder) {
875
                    if ($currentUserID && $this->currentOrder->MemberID == $currentUserID) {
876
                        if ($this->isCartPage()) {
877
                            if ($this->currentOrder && $this->currentOrder->getTotalItems() && !$this->currentOrder->IsSubmitted()) {
878
                                $this->actionLinks->push(new ArrayData(array(
879
                                    'Title' => $this->SaveOrderLinkLabel,
880
                                    'Link' => $this->Link('saveorder').'/'.$this->currentOrder->ID.'/',
881
                                )));
882
                            }
883
                        }
884
                    }
885
                }
886
            }
887
888
            //load order
889
            if (isset($this->LoadOrderLinkLabel) && $this->LoadOrderLinkLabel) {
890
                if ($this->isCartPage() && $this->currentOrder) {
891
                    if (!$viewingRealCurrentOrder) {
892
                        $this->actionLinks->push(new ArrayData(array(
893
                            'Title' => $this->LoadOrderLinkLabel,
894
                            'Link' => $this->Link('loadorder').'/'.$this->currentOrder->ID.'/',
895
                        )));
896
                    }
897
                }
898
            }
899
900
            //delete order
901
            if (isset($this->DeleteOrderLinkLabel) && $this->DeleteOrderLinkLabel) {
902
                if ($this->isCartPage() && $this->currentOrder) {
903
                    if (!$viewingRealCurrentOrder) {
904
                        $this->actionLinks->push(new ArrayData(array(
905
                            'Title' => $this->DeleteOrderLinkLabel,
906
                            'Link' => $this->Link('deleteorder').'/'.$this->currentOrder->ID.'/',
907
                        )));
908
                    }
909
                }
910
            }
911
912
            //Start new order
913
            //Strictly speaking this is only part of the
914
            //OrderConfirmationPage but we put it here for simplicity's sake
915
            if (isset($this->StartNewOrderLinkLabel) && $this->StartNewOrderLinkLabel) {
916
                if ($this->isOrderConfirmationPage()) {
917
                    $this->actionLinks->push(new ArrayData(array(
918
                        'Title' => $this->StartNewOrderLinkLabel,
919
                        'Link' => CartPage::new_order_link($this->currentOrder->ID),
920
                    )));
921
                }
922
            }
923
924
            //copy order
925
            //Strictly speaking this is only part of the
926
            //OrderConfirmationPage but we put it here for simplicity's sake
927
            if (isset($this->CopyOrderLinkLabel) && $this->CopyOrderLinkLabel) {
928
                if ($this->isOrderConfirmationPage() && $this->currentOrder->ID) {
929
                    $this->actionLinks->push(new ArrayData(array(
930
                        'Title' => $this->CopyOrderLinkLabel,
931
                        'Link' => OrderConfirmationPage::copy_order_link($this->currentOrder->ID),
932
                    )));
933
                }
934
            }
935
936
            //actions from modifiers
937
            if ($this->isOrderConfirmationPage() && $this->currentOrder->ID) {
938
                $modifiers = $this->currentOrder->Modifiers();
939
                if ($modifiers->count()) {
940
                    foreach ($modifiers as $modifier) {
941
                        $array = $modifier->PostSubmitAction();
942
                        if (is_array($array) && count($array)) {
943
                            $this->actionLinks->push(new ArrayData($array));
944
                        }
945
                    }
946
                }
947
            }
948
949
            //log out
950
            //Strictly speaking this is only part of the
951
            //OrderConfirmationPage but we put it here for simplicity's sake
952
            if (Member::currentUser()) {
953
                if ($this->isOrderConfirmationPage()) {
954
                    $this->actionLinks->push(new ArrayData(array(
955
                        'Title' => _t('CartPage.LOGOUT', 'log out'),
956
                        'Link' => '/Security/logout/',
957
                    )));
958
                }
959
            }
960
961
            //no items
962
            if ($this->currentOrder) {
963
                if (!$this->currentOrder->getTotalItems()) {
964
                    $this->message = $this->NoItemsInOrderMessage;
965
                }
966
            } else {
967
                $this->message = $this->NonExistingOrderMessage;
968
            }
969
970
            $this->workedOutMessagesAndActions = true;
971
            //does nothing at present....
972
        }
973
    }
974
975
    /***********************
976
     * HELPER METHOD (PROTECTED)
977
     ***********************
978
979
980
981
982
983
984
    /**
985
     * Is this a CartPage or is it another type (Checkout / OrderConfirmationPage)?
986
     * @return Boolean
987
     */
988
    protected function isCartPage()
989
    {
990
        if (($this->isCheckoutPage()) || ($this->isOrderConfirmationPage())) {
0 ignored issues
show
This if statement, and the following return statement can be replaced with return !($this->isChecko...derConfirmationPage());.
Loading history...
991
            return false;
992
        }
993
994
        return true;
995
    }
996
997
    /**
998
     * Is this a CheckoutPage or is it another type (CartPage / OrderConfirmationPage)?
999
     *
1000
     * @return bool
1001
     */
1002
    protected function isCheckoutPage()
1003
    {
1004
        if ($this->dataRecord instanceof CheckoutPage) {
0 ignored issues
show
The if-else statement can be simplified to return $this->dataRecord...stanceof \CheckoutPage;.
Loading history...
1005
            return true;
1006
        } else {
1007
            return false;
1008
        }
1009
    }
1010
1011
    /**
1012
     * Is this a OrderConfirmationPage or is it another type (CartPage / CheckoutPage)?
1013
     *
1014
     * @return bool
1015
     */
1016
    protected function isOrderConfirmationPage()
1017
    {
1018
        if ($this->dataRecord instanceof OrderConfirmationPage) {
0 ignored issues
show
The if-else statement can be simplified to return $this->dataRecord...\OrderConfirmationPage;.
Loading history...
1019
            return true;
1020
        } else {
1021
            return false;
1022
        }
1023
    }
1024
1025
    /**
1026
     * Can this page only show Submitted Orders (e.g. OrderConfirmationPage) ?
1027
     *
1028
     * @return bool
1029
     */
1030
    protected function onlyShowSubmittedOrders()
1031
    {
1032
        return false;
1033
    }
1034
1035
    /**
1036
     * Can this page only show Unsubmitted Orders (e.g. CartPage) ?
1037
     *
1038
     * @return bool
1039
     */
1040
    protected function onlyShowUnsubmittedOrders()
1041
    {
1042
        return true;
1043
    }
1044
}
1045