Completed
Push — master ( 3b65eb...dc665d )
by Nicolaas
11:00 queued 02:51
created

code/CartPage.php (29 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 = CartPage::get()->Filter(array('ClassName' => 'CartPage'))->First();
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 in once.
420
            if (($action == 'retrieveorder') && $id && $otherID) {
421
                $sessionID = Convert::raw2sql($id);
422
                $retrievedOrder = Order::get()
423
                    ->Filter(array(
424
                        'SessionID' => $sessionID,
425
                        'ID' => $otherID,
426
                    ))
427
                    ->First();
428
                $this->currentOrder = $retrievedOrder;
429
                $this->overrideCanView = true;
430
            } elseif (intval($id) && in_array($action, $this->stat('allowed_actions'))) {
431
                $this->currentOrder = Order::get()->byID(intval($id));
432
            }
433
        }
434
        if (!$this->currentOrder) {
435
            $this->currentOrder = ShoppingCart::current_order();
436
            if ($this->currentOrder) {
437
                if ($this->currentOrder->IsSubmitted()) {
438
                    $this->overrideCanView = true;
439
                }
440
            }
441
        }
442
        //redirect if we are viewing the order with the wrong page!
443
        if ($this->currentOrder) {
444
            if ($this->overrideCanView) {
445
                $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...
446
            } else {
447
                $canView = $this->currentOrder->canView();
448
            }
449
            //IMPORTANT SECURITY QUESTION!
450
            if ($canView) {
451
                if ($this->currentOrder->IsSubmitted() && $this->onlyShowUnsubmittedOrders()) {
452
                    $this->redirect($this->currentOrder->Link());
453
                } elseif ((!$this->currentOrder->IsSubmitted()) && $this->onlyShowSubmittedOrders()) {
454
                    $this->redirect($this->currentOrder->Link());
455
                }
456
            } else {
457
                if (!$this->LoginToOrderLinkLabel) {
458
                    $this->LoginToOrderLinkLabel = _t('CartPage.LOGINFIRST', 'You will need to log in before you can access the requested order order. ');
459
                }
460
                $messages = array(
461
                    'default' => '<p class="message good">'.$this->LoginToOrderLinkLabel.'</p>',
462
                    'logInAgain' => _t('CartPage.LOGINAGAIN', 'You have been logged out. If you would like to log in again, please do so below.'),
463
                );
464
                Security::permissionFailure($this, $messages);
465
466
                return false;
467
            }
468
            if (!$this->currentOrder->IsSubmitted()) {
469
                //we always want to make sure the order is up-to-date.
470
                $this->currentOrder->init($force = false);
471
                $this->currentOrder->calculateOrderAttributes($force = true);
472
                $this->currentOrder->calculateOrderAttributes($force = true);
473
            }
474
        } else {
475
            $this->message = _t('CartPage.ORDERNOTFOUND', 'Order can not be found.');
476
        }
477
    }
478
479
    /***********************
480
     * Actions
481
     ***********************
482
483
484
485
486
    /**
487
     * shows an order and loads it if it is not submitted.
488
     * @todo: do we still need loadorder controller method????
489
     * @param SS_HTTPRequest
490
     * @return array just so that template shows
491
     **/
492
    public function showorder(SS_HTTPRequest $request)
493
    {
494
        if (!$this->currentOrder) {
495
            $this->message = _t('CartPage.ORDERNOTFOUND', 'Order can not be found.');
496
        } else {
497
            if (!$this->currentOrder->IsSubmitted()) {
498
                $shoppingCart = ShoppingCart::current_order();
499
                if ($shoppingCart->ID != $this->currentOrder->ID) {
500
                    if (ShoppingCart::singleton()->loadOrder($this->currentOrder)) {
501
                        $this->message = _t('CartPage.ORDERHASBEENLOADED', 'Order has been loaded.');
502
                    } else {
503
                        $this->message = _t('CartPage.ORDERNOTLOADED', 'Order could not be loaded.');
504
                    }
505
                }
506
            }
507
        }
508
509
        return array();
510
    }
511
512
    /**
513
     * share an order ...
514
     * @todo: do we still need loadorder controller method????
515
     * @param SS_HTTPRequest
516
     * @return array just so that template shows
517
     **/
518
    public function share(SS_HTTPRequest $request)
519
    {
520
        $codes = Convert::raw2sql($request->param('ID'));
521
        if (! $request->getVar('ready') && ! $request->getVar('done')) {
522
            return $this->redirect($this->Link('share/'.$codes).'?ready=1');
523
        }
524
        $titleAppendixArray = array();
525
        $buyables = explode('-', $codes);
526
        if (count($buyables)) {
527
            $sc = ShoppingCart::singleton();
528
            $order = $sc->currentOrder();
529
            foreach ($buyables as $buyable) {
530
                $details = explode(",", $buyable);
531
                if (count($details) == 3) {
532
                    $className = $details[0];
533
                    $className = class_exists($className) ? $className : null;
534
                    $id = intval($details[1]);
535
                    $quantity = floatval($details[2]);
536
                    if ($className && $id && $quantity) {
537
                        $buyable = $className::get()->byID($id);
538
                        if ($buyable && $buyable->canPurchase()) {
539
                            $sc->addBuyable($buyable, $quantity);
540
                            $sc->setQuantity($buyable, $quantity);
541
                            if ($request->getVar('done')) {
542
                                $titleAppendixArray[] = $buyable->getTitle();
543
                            }
544
                        }
545
                    }
546
                }
547
            }
548
            $order->calculateOrderAttributes(false);
549
            if (! $request->getVar('done')) {
550
                return $this->redirect($this->Link('share/'.$codes).'?done=1');
551
            }
552
        }
553
        $this->Title .= ': '.  implode(', ', $titleAppendixArray);
554
        if(strlen($this->Title) > 255 ) {
555
            $this->Title = substr($this->Title, 0, 255). ' ...';
556
        }
557
        return array();
558
    }
559
560
    /**
561
     * Loads either the "current order""into the shopping cart.
562
     *
563
     * TO DO: untested
564
     * TO DO: what to do with old order
565
     *
566
     * @param SS_HTTPRequest
567
     *
568
     * @return array
569
     */
570
    public function loadorder(SS_HTTPRequest $request)
571
    {
572
        self::set_message(_t('CartPage.ORDERLOADED', 'Order has been loaded.'));
573
        ShoppingCart::singleton()->loadOrder($this->currentOrder->ID);
574
        $this->redirect($this->Link());
575
576
        return array();
577
    }
578
579
    /**
580
     * save the order to a member. If no member exists then create the member first using the ShopAccountForm.
581
     *
582
     * @param SS_HTTPRequest
583
     *
584
     * @return array
585
     *               TO DO: untested
586
     */
587
    public function saveorder(SS_HTTPRequest $request)
588
    {
589
        $member = Member::currentUser();
590
        if (!$member) {
591
            $this->showCreateAccountForm = true;
592
593
            return array();
594
        }
595
        if ($this->currentOrder && $this->currentOrder->getTotalItems()) {
596
            $this->currentOrder->write();
597
            self::set_message(_t('CartPage.ORDERSAVED', 'Your order has been saved.'));
598
        } else {
599
            self::set_message(_t('CartPage.ORDERCOULDNOTBESAVED', 'Your order could not be saved.'));
600
        }
601
        $this->redirectBack();
602
603
        return array();
604
    }
605
606
    /**
607
     * Delete the currently viewed order.
608
     *
609
     * TO DO: untested
610
     *
611
     * @param SS_HTTPRequest
612
     *
613
     * @return array
614
     */
615
    public function deleteorder(SS_HTTPRequest $request)
616
    {
617
        if (!$this->CurrentOrderIsInCart()) {
618
            if ($this->currentOrder->canDelete()) {
619
                $this->currentOrder->delete();
620
                self::set_message(_t('CartPage.ORDERDELETED', 'Order has been deleted.'));
621
            }
622
        }
623
        self::set_message(_t('CartPage.ORDERNOTDELETED', 'Order could not be deleted.'));
624
625
        return array();
626
    }
627
628
    /**
629
     * Start a new order.
630
     *
631
     * @param SS_HTTPRequest
632
     *
633
     * @return array
634
     *               TO DO: untested
635
     */
636
    public function startneworder(SS_HTTPRequest $request)
637
    {
638
        ShoppingCart::singleton()->clear();
639
        self::set_message(_t('CartPage.NEWORDERSTARTED', 'New order has been started.'));
640
        $this->redirect($this->Link());
641
642
        return array();
643
    }
644
645
    /**
646
     * This returns a ArraList, each dataobject has two vars: Title and Link.
647
     *
648
     * @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...
649
     **/
650
    public function ActionLinks()
651
    {
652
        $this->workOutMessagesAndActions();
653
        if ($this->actionLinks && $this->actionLinks->count()) {
654
            return $this->actionLinks;
655
        }
656
657
        return;
658
    }
659
660
    /**
661
     * @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...
662
     **/
663
    public function Message()
664
    {
665
        $this->workOutMessagesAndActions();
666
        if (!$this->message) {
667
            $sessionCode = EcommerceConfig::get('CartPage_Controller', 'session_code');
668
            if ($sessionMessage = Session::get($sessionCode)) {
669
                $this->message = $sessionMessage;
670
                Session::set($sessionCode, '');
671
                Session::clear($sessionCode);
672
            }
673
        }
674
        $field = DBField::create_field('HTMLText', $this->message);
675
676
        return $field;
677
    }
678
679
    /**
680
     * @return DataObject | Null - Order
681
     **/
682
    public function Order()
683
    {
684
        return $this->currentOrder;
685
    }
686
687
    /**
688
     * @return bool
689
     **/
690
    public function CanEditOrder()
691
    {
692
        if ($this->currentOrder) {
693
            if ($this->currentOrder->canEdit()) {
694
                if ($this->currentOrder->getTotalItems()) {
695
                    return true;
696
                }
697
            }
698
        }
699
700
        return false;
701
    }
702
703
    /**
704
     * Tells you if the order you are viewing at the moment is also in the cart.
705
     *
706
     * @return bool
707
     **/
708
    public function CurrentOrderIsInCart()
709
    {
710
        $viewingRealCurrentOrder = false;
711
        $realCurrentOrder = ShoppingCart::current_order();
712
        if ($this->currentOrder && $realCurrentOrder) {
713
            if ($realCurrentOrder->ID == $this->currentOrder->ID) {
714
                $viewingRealCurrentOrder = true;
715
            }
716
        }
717
718
        return $viewingRealCurrentOrder;
719
    }
720
721
    /**
722
     * @var bool
723
     */
724
    protected $showCreateAccountForm = false;
725
726
    /**
727
     * Do we need to show the Create Account Form?
728
     *
729
     * @return bool
730
     */
731
    public function ShowCreateAccountForm()
732
    {
733
        if (Session::get('CartPageCreateAccountForm')) {
734
            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...
735
736
            return true;
737
        }
738
        if (Member::currentUser() || $this->currentOrder->MemberID) {
739
            return false;
740
        } else {
741
            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...
742
743
            return true;
744
        }
745
    }
746
747
    /**
748
     * Returns the CreateAccountForm.
749
     *
750
     * @return ShopAccountForm
751
     */
752
    public function CreateAccountForm()
753
    {
754
        return ShopAccountForm::create($this, 'CreateAccountForm');
755
    }
756
757
    /**
758
     * work out the options for the user.
759
     **/
760
    protected function workOutMessagesAndActions()
761
    {
762
        if (!$this->workedOutMessagesAndActions) {
763
            $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...
764
            //what order are we viewing?
765
            $viewingRealCurrentOrder = $this->CurrentOrderIsInCart();
766
            $currentUserID = Member::currentUserID();
767
768
            //Continue Shopping
769
            if (isset($this->ContinueShoppingLabel) && $this->ContinueShoppingLabel) {
770
                if ($viewingRealCurrentOrder) {
771
                    if ($this->isCartPage()) {
772
                        $continueLink = $this->ContinueShoppingLink();
773
                        if ($continueLink) {
774
                            $this->actionLinks->push(
775
                                ArrayData::create(
776
                                    array(
777
                                        'Title' => $this->ContinueShoppingLabel,
778
                                        'Link' => $continueLink,
779
                                    )
780
                                )
781
                            );
782
                        }
783
                    }
784
                }
785
            }
786
787
            //Proceed To CheckoutLabel
788
            if (isset($this->ProceedToCheckoutLabel) && $this->ProceedToCheckoutLabel) {
789
                if ($viewingRealCurrentOrder) {
790
                    if ($this->isCartPage()) {
791
                        $checkoutPageLink = CheckoutPage::find_link();
792
                        if ($checkoutPageLink && $this->currentOrder && $this->currentOrder->getTotalItems()) {
793
                            $this->actionLinks->push(new ArrayData(array(
794
                                'Title' => $this->ProceedToCheckoutLabel,
795
                                'Link' => $checkoutPageLink,
796
                            )));
797
                        }
798
                    }
799
                }
800
            }
801
802
            //view account details
803
            if (isset($this->ShowAccountLabel) && $this->ShowAccountLabel) {
804
                if ($this->isOrderConfirmationPage() || $this->isCartPage()) {
805
                    if (AccountPage::find_link()) {
806
                        if ($currentUserID) {
807
                            $this->actionLinks->push(new ArrayData(array(
808
                                'Title' => $this->ShowAccountLabel,
809
                                'Link' => AccountPage::find_link(),
810
                            )));
811
                        }
812
                    }
813
                }
814
            }
815
            //go to current order
816
            if (isset($this->CurrentOrderLinkLabel) && $this->CurrentOrderLinkLabel) {
817
                if ($this->isCartPage()) {
818
                    if (!$viewingRealCurrentOrder) {
819
                        $this->actionLinks->push(new ArrayData(array(
820
                            'Title' => $this->CurrentOrderLinkLabel,
821
                            'Link' => ShoppingCart::current_order()->Link(),
822
                        )));
823
                    }
824
                }
825
            }
826
827
            //Save order - we assume only current ones can be saved.
828
            if (isset($this->SaveOrderLinkLabel) && $this->SaveOrderLinkLabel) {
829
                if ($viewingRealCurrentOrder) {
830
                    if ($currentUserID && $this->currentOrder->MemberID == $currentUserID) {
831
                        if ($this->isCartPage()) {
832
                            if ($this->currentOrder && $this->currentOrder->getTotalItems() && !$this->currentOrder->IsSubmitted()) {
833
                                $this->actionLinks->push(new ArrayData(array(
834
                                    'Title' => $this->SaveOrderLinkLabel,
835
                                    'Link' => $this->Link('saveorder').'/'.$this->currentOrder->ID.'/',
836
                                )));
837
                            }
838
                        }
839
                    }
840
                }
841
            }
842
843
            //load order
844
            if (isset($this->LoadOrderLinkLabel) && $this->LoadOrderLinkLabel) {
845
                if ($this->isCartPage() && $this->currentOrder) {
846
                    if (!$viewingRealCurrentOrder) {
847
                        $this->actionLinks->push(new ArrayData(array(
848
                            'Title' => $this->LoadOrderLinkLabel,
849
                            'Link' => $this->Link('loadorder').'/'.$this->currentOrder->ID.'/',
850
                        )));
851
                    }
852
                }
853
            }
854
855
            //delete order
856
            if (isset($this->DeleteOrderLinkLabel) && $this->DeleteOrderLinkLabel) {
857
                if ($this->isCartPage() && $this->currentOrder) {
858
                    if (!$viewingRealCurrentOrder) {
859
                        $this->actionLinks->push(new ArrayData(array(
860
                            'Title' => $this->DeleteOrderLinkLabel,
861
                            'Link' => $this->Link('deleteorder').'/'.$this->currentOrder->ID.'/',
862
                        )));
863
                    }
864
                }
865
            }
866
867
            //Start new order
868
            //Strictly speaking this is only part of the
869
            //OrderConfirmationPage but we put it here for simplicity's sake
870
            if (isset($this->StartNewOrderLinkLabel) && $this->StartNewOrderLinkLabel) {
871
                if ($this->isOrderConfirmationPage()) {
872
                    $this->actionLinks->push(new ArrayData(array(
873
                        'Title' => $this->StartNewOrderLinkLabel,
874
                        'Link' => CartPage::new_order_link($this->currentOrder->ID),
875
                    )));
876
                }
877
            }
878
879
            //copy order
880
            //Strictly speaking this is only part of the
881
            //OrderConfirmationPage but we put it here for simplicity's sake
882
            if (isset($this->CopyOrderLinkLabel) && $this->CopyOrderLinkLabel) {
883
                if ($this->isOrderConfirmationPage() && $this->currentOrder->ID) {
884
                    $this->actionLinks->push(new ArrayData(array(
885
                        'Title' => $this->CopyOrderLinkLabel,
886
                        'Link' => OrderConfirmationPage::copy_order_link($this->currentOrder->ID),
887
                    )));
888
                }
889
            }
890
891
            //actions from modifiers
892
            if ($this->isOrderConfirmationPage() && $this->currentOrder->ID) {
893
                $modifiers = $this->currentOrder->Modifiers();
894
                if ($modifiers->count()) {
895
                    foreach ($modifiers as $modifier) {
896
                        $array = $modifier->PostSubmitAction();
897
                        if (is_array($array) && count($array)) {
898
                            $this->actionLinks->push(new ArrayData($array));
899
                        }
900
                    }
901
                }
902
            }
903
904
            //log out
905
            //Strictly speaking this is only part of the
906
            //OrderConfirmationPage but we put it here for simplicity's sake
907
            if (Member::currentUser()) {
908
                if ($this->isOrderConfirmationPage()) {
909
                    $this->actionLinks->push(new ArrayData(array(
910
                        'Title' => _t('CartPage.LOGOUT', 'log out'),
911
                        'Link' => '/Security/logout/',
912
                    )));
913
                }
914
            }
915
916
            //no items
917
            if ($this->currentOrder) {
918
                if (!$this->currentOrder->getTotalItems()) {
919
                    $this->message = $this->NoItemsInOrderMessage;
920
                }
921
            } else {
922
                $this->message = $this->NonExistingOrderMessage;
923
            }
924
925
            $this->workedOutMessagesAndActions = true;
926
            //does nothing at present....
927
        }
928
    }
929
930
    /***********************
931
     * HELPER METHOD (PROTECTED)
932
     ***********************
933
934
935
936
937
938
939
    /**
940
     * Is this a CartPage or is it another type (Checkout / OrderConfirmationPage)?
941
     * @return Boolean
942
     */
943
    protected function isCartPage()
944
    {
945
        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...
946
            return false;
947
        }
948
949
        return true;
950
    }
951
952
    /**
953
     * Is this a CheckoutPage or is it another type (CartPage / OrderConfirmationPage)?
954
     *
955
     * @return bool
956
     */
957
    protected function isCheckoutPage()
958
    {
959
        if ($this->dataRecord instanceof CheckoutPage) {
0 ignored issues
show
The if-else statement can be simplified to return $this->dataRecord...stanceof \CheckoutPage;.
Loading history...
960
            return true;
961
        } else {
962
            return false;
963
        }
964
    }
965
966
    /**
967
     * Is this a OrderConfirmationPage or is it another type (CartPage / CheckoutPage)?
968
     *
969
     * @return bool
970
     */
971
    protected function isOrderConfirmationPage()
972
    {
973
        if ($this->dataRecord instanceof OrderConfirmationPage) {
0 ignored issues
show
The if-else statement can be simplified to return $this->dataRecord...\OrderConfirmationPage;.
Loading history...
974
            return true;
975
        } else {
976
            return false;
977
        }
978
    }
979
980
    /**
981
     * Can this page only show Submitted Orders (e.g. OrderConfirmationPage) ?
982
     *
983
     * @return bool
984
     */
985
    protected function onlyShowSubmittedOrders()
986
    {
987
        return false;
988
    }
989
990
    /**
991
     * Can this page only show Unsubmitted Orders (e.g. CartPage) ?
992
     *
993
     * @return bool
994
     */
995
    protected function onlyShowUnsubmittedOrders()
996
    {
997
        return true;
998
    }
999
}
1000