Completed
Push — master ( 613b2e...a2195f )
by Nicolaas
03:28
created

ShoppingCart_Controller::add_modifier_link()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
eloc 2
nc 1
nop 2
1
<?php
2
3
4
/**
5
 * ShoppingCart_Controller.
6
 *
7
 * Handles the modification of a shopping cart via http requests.
8
 * Provides links for making these modifications.
9
 *
10
 *@author: Jeremy Shipman, Nicolaas Francken
11
 *@package: ecommerce
12
 *
13
 *@todo supply links for adding, removing, and clearing cart items
14
 *@todo link for removing modifier(s)
15
 */
16
class ShoppingCart_Controller extends Controller
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
    /**
20
     * Default URL handlers - (Action)/(ID)/(OtherID).
21
     */
22
    private static $url_handlers = array(
0 ignored issues
show
Unused Code introduced by
The property $url_handlers is not used and could be removed.

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

Loading history...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
23
        '$Action//$ID/$OtherID/$Version' => 'handleAction',
24
    );
25
26
    /**
27
     * We need to only use the Security ID on a few
28
     * actions, these are listed here.
29
     *
30
     * @var array
31
     */
32
    protected $methodsRequiringSecurityID = array(
33
        'additem',
34
        'removeitem',
35
        'removeallitem',
36
        'removeallitemandedit',
37
        'removemodifier',
38
        'addmodifier',
39
        'copyorder',
40
        'deleteorder',
41
        'save',
42
    );
43
44
    /**
45
     * @var ShoppingCart
46
     */
47
    protected $cart = null;
48
49
    public function init()
0 ignored issues
show
Coding Style introduced by
init uses the super-global variable $_GET 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...
50
    {
51
        parent::init();
52
        $action = $this->request->param('Action');
53
        if (!isset($_GET['cached'])) {
54
            if ($action && (in_array($action, $this->methodsRequiringSecurityID))) {
55
                $savedSecurityID = Session::get('SecurityID');
56
                if ($savedSecurityID) {
57
                    if (!isset($_GET['SecurityID'])) {
58
                        $_GET['SecurityID'] = '';
59
                    }
60
                    if ($savedSecurityID) {
61
                        if ($_GET['SecurityID'] != $savedSecurityID) {
62
                            $this->httpError(400, "Security token doesn't match, possible CSRF attack.");
63
                        } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

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

These else branches can be removed.

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

could be turned into

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

This is much more concise to read.

Loading history...
64
                            //all OK!
65
                        }
66
                    }
67
                }
68
            }
69
        }
70
        $this->cart = ShoppingCart::singleton();
71
    }
72
73
    private static $allowed_actions = array(
0 ignored issues
show
Unused Code introduced by
The property $allowed_actions is not used and could be removed.

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

Loading history...
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
74
        'json',
75
        'index',
76
        'additem',
77
        'removeitem',
78
        'removeallitem',
79
        'removeallitemandedit',
80
        'removemodifier',
81
        'addmodifier',
82
        'setcountry',
83
        'setregion',
84
        'setcurrency',
85
        'removefromsale',
86
        'setquantityitem',
87
        'clear',
88
        'clearandlogout',
89
        'save',
90
        'deleteorder',
91
        'numberofitemsincart',
92
        'showcart',
93
        'loadorder',
94
        'copyorder',
95
        'removeaddress',
96
        'submittedbuyable',
97
        'placeorderformember',
98
        'loginas',
99
        'debug', // no need to set to  => 'ADMIN',
100
        'ajaxtest', // no need to set to  => 'ADMIN',
101
    );
102
103
    public function index()
104
    {
105
        if ($this->cart) {
106
            $this->redirect($this->cart->Link());
107
108
            return;
109
        }
110
        user_error(_t('Order.NOCARTINITIALISED', 'no cart initialised'), E_USER_NOTICE);
111
        $errorPage404 = ErrorPage::get()
112
            ->Filter(array('ErrorCode' => '404'))
113
            ->First();
114
        if ($errorPage404) {
115
            $this->redirect($errorPage404->Link());
116
117
            return;
118
        }
119
        user_error(_t('Order.NOCARTINITIALISED', 'no 404 page available'), E_USER_ERROR);
120
    }
121
122
    /*******************************************************
123
    * CONTROLLER LINKS
124
    *******************************************************/
125
126
    /**
127
     * @param string $action
0 ignored issues
show
Documentation introduced by
Should the type for parameter $action not be string|null?

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

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

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

Loading history...
128
     *
129
     * @return string (Link)
130
     */
131
    public function Link($action = null)
132
    {
133
        return self::create_link($action);
134
    }
135
136
    /**
137
     * returns ABSOLUTE link to the shopping cart controller.
138
     * @param null | array | string $actionAndOtherLinkVariables
139
     * @return string
140
     */
141
    protected static function create_link($actionAndOtherLinkVariables = null)
142
    {
143
        return Controller::join_links(
144
            Director::baseURL(),
145
            Config::inst()->get('ShoppingCart_Controller', 'url_segment'),
146
            $actionAndOtherLinkVariables
147
        );
148
    }
149
150
    /**
151
     * @param int    $buyableID
152
     * @param string $classNameForBuyable
153
     * @param array  $parameters
154
     *
155
     * @return string
156
     */
157
    public static function add_item_link($buyableID, $classNameForBuyable = 'Product', array $parameters = array())
158
    {
159
        return self::create_link('additem/'.$buyableID.'/'.$classNameForBuyable.'/'.self::params_to_get_string($parameters));
160
    }
161
162
    /**
163
     * @param int    $buyableID
164
     * @param string $classNameForBuyable
165
     * @param array  $parameters
166
     *
167
     * @return string
168
     */
169
    public static function remove_item_link($buyableID, $classNameForBuyable = 'Product', array $parameters = array())
170
    {
171
        return self::create_link('removeitem/'.$buyableID.'/'.$classNameForBuyable.'/'.self::params_to_get_string($parameters));
172
    }
173
174
    /**
175
     * @param int    $buyableID
176
     * @param string $classNameForBuyable
177
     * @param array  $parameters
178
     *
179
     * @return string
180
     */
181
    public static function remove_all_item_link($buyableID, $classNameForBuyable = 'Product', array $parameters = array())
182
    {
183
        return self::create_link('removeallitem/'.$buyableID.'/'.$classNameForBuyable.'/'.self::params_to_get_string($parameters));
184
    }
185
186
    /**
187
     * @param int    $buyableID
188
     * @param string $classNameForBuyable
189
     * @param array  $parameters
190
     *
191
     * @return string
192
     */
193
    public static function remove_all_item_and_edit_link($buyableID, $classNameForBuyable = 'Product', array $parameters = array())
194
    {
195
        return self::create_link('removeallitemandedit/'.$buyableID.'/'.$classNameForBuyable.'/'.self::params_to_get_string($parameters));
196
    }
197
198
    /**
199
     * @param int    $buyableID
200
     * @param string $classNameForBuyable
201
     * @param array  $parameters
202
     *
203
     * @return string
204
     */
205
    public static function set_quantity_item_link($buyableID, $classNameForBuyable = 'Product', array $parameters = array())
206
    {
207
        return self::create_link('setquantityitem/'.$buyableID.'/'.$classNameForBuyable.'/'.self::params_to_get_string($parameters));
208
    }
209
210
    /**
211
     * @param int   $modifierID
212
     * @param array $parameters
213
     *
214
     * @return string
215
     */
216
    public static function remove_modifier_link($modifierID, array $parameters = array())
217
    {
218
        return self::create_link('removemodifier/'.$modifierID.'/'.self::params_to_get_string($parameters));
219
    }
220
221
    /**
222
     * @param int   $modifierID
223
     * @param array $parameters
224
     *
225
     * @return string
226
     */
227
    public static function add_modifier_link($modifierID, array $parameters = array())
228
    {
229
        return self::create_link('addmodifier/'.$modifierID.'/'.self::params_to_get_string($parameters));
230
    }
231
232
    /**
233
     * @param int    $addressID
234
     * @param string $addressClassName
235
     * @param array  $parameters
236
     *
237
     * @return string
238
     */
239
    public static function remove_address_link($addressID, $addressClassName, array $parameters = array())
240
    {
241
        return self::create_link('removeaddress/'.$addressID.'/'.$addressClassName.'/'.self::params_to_get_string($parameters));
242
    }
243
244
    /**
245
     * @param array $parameters
246
     *
247
     * @return string
248
     */
249
    public static function clear_cart_link($parameters = array())
250
    {
251
        return self::create_link('clear/'.self::params_to_get_string($parameters));
252
    }
253
254
    /**
255
     * @param array $parameters
256
     *
257
     * @return string
258
     */
259
    public static function save_cart_link(array $parameters = array())
260
    {
261
        return self::create_link('save/'.self::params_to_get_string($parameters));
262
    }
263
264
    /**
265
     * @param array $parameters
266
     *
267
     * @return string
268
     */
269
    public static function clear_cart_and_logout_link(array $parameters = array())
270
    {
271
        return self::create_link('clearandlogout/'.self::params_to_get_string($parameters));
272
    }
273
274
    /**
275
     * @param array $parameters
276
     *
277
     * @return string
278
     */
279
    public static function delete_order_link($orderID, array $parameters = array())
280
    {
281
        return self::create_link('deleteorder/'.$orderID.'/'.self::params_to_get_string($parameters));
282
    }
283
284
    /**
285
     *
286
     * @return null | string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|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...
287
     */
288
    public static function copy_order_link($orderID, $parameters = array())
289
    {
290
        $order = Order::get()->byID($orderID);
291
        if ($order && $order->IsSubmitted()) {
292
            return self::create_link('copyorder/'.$orderID.'/'.self::params_to_get_string($parameters));
293
        }
294
    }
295
296
    /**
297
     * returns a link that allows you to set a currency...
298
     * dont be fooled by the set_ part...
299
     *
300
     * @param string $code
301
     *
302
     * @return string
303
     */
304
    public static function set_currency_link($code, array $parameters = array())
305
    {
306
        return self::create_link('setcurrency/'.$code.'/'.self::params_to_get_string($parameters));
307
    }
308
309
    /**
310
     *
311
     *
312
     * @param  int    $id
313
     * @param  string $className
314
     * @return string
315
     */
316
    public static function remove_from_sale_link($id, $className)
317
    {
318
        return self::create_link('removefromsale/'.$className.'/'.$id .'/');
319
    }
320
321
    /**
322
     * return json for cart... no further actions.
323
     *
324
     * @param SS_HTTPRequest
325
     *
326
     * @return JSON
327
     */
328
    public function json(SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
329
    {
330
        return $this->cart->setMessageAndReturn();
331
    }
332
333
    /**
334
     * Adds item to cart via controller action; one by default.
335
     *
336
     * @param HTTPRequest
337
     *
338
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
339
     *               If it is not AJAX it redirects back to requesting page.
340
     */
341
    public function additem(SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
342
    {
343
        $this->cart->addBuyable($this->buyable(), $this->quantity(), $this->parameters());
0 ignored issues
show
Documentation introduced by
$this->buyable() is of type object<DataObject>, but the function expects a object<BuyableModel>.

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...
344
345
        return $this->cart->setMessageAndReturn();
346
    }
347
348
    /**
349
     * Sets the exact passed quantity.
350
     * Note: If no ?quantity=x is specified in URL, then quantity will be set to 1.
351
     *
352
     * @param HTTPRequest
353
     *
354
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
355
     *               If it is not AJAX it redirects back to requesting page.
356
     */
357
    public function setquantityitem(SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
358
    {
359
        $this->cart->setQuantity($this->buyable(), $this->quantity(), $this->parameters());
0 ignored issues
show
Documentation introduced by
$this->buyable() is of type object<DataObject>, but the function expects a object<BuyableModel>.

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...
360
361
        return $this->cart->setMessageAndReturn();
362
    }
363
364
    /**
365
     * Removes item from cart via controller action; one by default.
366
     *
367
     * @param HTTPRequest
368
     *
369
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
370
     *               If it is not AJAX it redirects back to requesting page.
371
     */
372
    public function removeitem(SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
373
    {
374
        $this->cart->decrementBuyable($this->buyable(), $this->quantity(), $this->parameters());
0 ignored issues
show
Documentation introduced by
$this->buyable() is of type object<DataObject>, but the function expects a object<BuyableModel>.

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...
375
376
        return $this->cart->setMessageAndReturn();
377
    }
378
379
    /**
380
     * Removes all of a specific item.
381
     *
382
     * @param HTTPRequest
383
     *
384
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
385
     *               If it is not AJAX it redirects back to requesting page.
386
     */
387
    public function removeallitem(SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
388
    {
389
        $this->cart->deleteBuyable($this->buyable(), $this->parameters());
0 ignored issues
show
Documentation introduced by
$this->buyable() is of type object<DataObject>, but the function expects a object<BuyableModel>.

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...
390
391
        return $this->cart->setMessageAndReturn();
392
    }
393
394
    /**
395
     * Removes all of a specific item AND return back.
396
     *
397
     * @param HTTPRequest
398
     *
399
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
400
     *               If it is not AJAX it redirects back to requesting page.
401
     */
402
    public function removeallitemandedit(SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
403
    {
404
        $buyable = $this->buyable();
405
        if ($buyable) {
406
            $link = $buyable->Link();
407
            $this->cart->deleteBuyable($buyable, $this->parameters());
0 ignored issues
show
Documentation introduced by
$buyable is of type object<DataObject>, but the function expects a object<BuyableModel>.

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...
408
            $this->redirect($link);
409
        } else {
410
            $this->redirectBack();
411
        }
412
    }
413
414
    /**
415
     * Removes a specified modifier from the cart;.
416
     *
417
     * @param HTTPRequest
418
     *
419
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
420
     *               If it is not AJAX it redirects back to requesting page.
421
     */
422
    public function removemodifier(SS_HTTPRequest $request)
423
    {
424
        $modifierID = intval($request->param('ID'));
425
        $this->cart->removeModifier($modifierID);
0 ignored issues
show
Documentation introduced by
$modifierID is of type integer, but the function expects a object<OrderModifier>.

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...
426
427
        return $this->cart->setMessageAndReturn();
428
    }
429
430
    /**
431
     * Adds a specified modifier to the cart;.
432
     *
433
     * @param HTTPRequest
434
     *
435
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
436
     *               If it is not AJAX it redirects back to requesting page.
437
     */
438
    public function addmodifier(SS_HTTPRequest $request)
439
    {
440
        $modifierID = intval($request->param('ID'));
441
        $this->cart->addModifier($modifierID);
442
443
        return $this->cart->setMessageAndReturn();
444
    }
445
446
    /**
447
     * sets the country.
448
     *
449
     * @param SS_HTTPRequest
450
     *
451
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
452
     *               If it is not AJAX it redirects back to requesting page.
453
     **/
454
    public function setcountry(SS_HTTPRequest $request)
455
    {
456
        $countryCode = Convert::raw2sql($request->param('ID'));
457
        //set_country will check if the country code is actually allowed....
458
        $this->cart->setCountry($countryCode);
459
460
        return $this->cart->setMessageAndReturn();
461
    }
462
463
    /**
464
     * @param SS_HTTPRequest
465
     *
466
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
467
     *               If it is not AJAX it redirects back to requesting page.
468
     **/
469
    public function setregion(SS_HTTPRequest $request)
470
    {
471
        $regionID = intval($request->param('ID'));
472
        $this->cart->setRegion($regionID);
473
474
        return $this->cart->setMessageAndReturn();
475
    }
476
477
    /**
478
     * @param SS_HTTPRequest
479
     *
480
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
481
     *               If it is not AJAX it redirects back to requesting page.
482
     **/
483
    public function setcurrency(SS_HTTPRequest $request)
484
    {
485
        $currencyCode = Convert::raw2sql($request->param('ID'));
486
        $this->cart->setCurrency($currencyCode);
0 ignored issues
show
Bug introduced by
It seems like $currencyCode defined by \Convert::raw2sql($request->param('ID')) on line 485 can also be of type array; however, ShoppingCart::setCurrency() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
487
488
        return $this->cart->setMessageAndReturn();
489
    }
490
491
    /**
492
     * @param SS_HTTPRequest
493
     *
494
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
495
     *               If it is not AJAX it redirects back to requesting page.
496
     **/
497
    public function removefromsale(SS_HTTPRequest $request)
498
    {
499
        $className = Convert::raw2sql($request->param('ID'));
500
        $id = intval($request->param('OtherID'));
501
        if (class_exists($className)) {
502
            $obj = $className::get()->byID($id);
503
            $obj->AllowPurchase = 0;
504
            if ($obj instanceof SiteTree) {
505
                $obj->writeToStage('Stage');
506
                $obj->doPublish();
507
            } else {
508
                $obj->write();
509
            }
510
        }
511
512
        return $this->cart->setMessageAndReturn();
513
    }
514
515
    /**
516
     * @param SS_HTTPRequest
517
     *
518
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
519
     *               If it is not AJAX it redirects back to requesting page.
520
     **/
521
    public function save(SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
522
    {
523
        $order = $this->cart->save();
0 ignored issues
show
Unused Code introduced by
$order 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...
524
525
        return $this->cart->setMessageAndReturn();
526
    }
527
528
    /**
529
     * @param SS_HTTPRequest
530
     *
531
     * @return REDIRECT
0 ignored issues
show
Documentation introduced by
Should the return type not be array?

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...
532
     **/
533
    public function clear(SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
534
    {
535
        $this->cart->clear();
536
        $this->redirect(Director::baseURL());
537
538
        return array();
539
    }
540
541
    /**
542
     * @param SS_HTTPRequest
543
     *
544
     * @return REDIRECT
0 ignored issues
show
Documentation introduced by
Should the return type not be array?

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...
545
     **/
546
    public function clearandlogout(SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
547
    {
548
        $this->cart->clear();
549
        if ($member = Member::currentUser()) {
550
            $member->logout();
551
        }
552
        $this->redirect(Director::baseURL());
553
554
        return array();
555
    }
556
557
    /**
558
     * @param SS_HTTPRequest
559
     *
560
     * @return REDIRECT
0 ignored issues
show
Documentation introduced by
Should the return type not be REDIRECT|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...
561
     **/
562
    public function deleteorder(SS_HTTPRequest $request)
563
    {
564
        $orderID = intval($request->param('ID'));
565
        if ($order = Order::get_by_id_if_can_view($orderID)) {
566
            if ($order->canDelete()) {
567
                $order->delete();
568
            }
569
        }
570
        $this->redirectBack();
571
    }
572
573
    public function copyorder($request)
574
    {
575
        $orderID = intval($request->param('ID'));
576
        if ($order = Order::get_by_id_if_can_view($orderID)) {
577
            $this->cart->copyOrder($order);
578
        }
579
        $this->redirectBack();
580
    }
581
582
    /**
583
     * return number of items in cart.
584
     *
585
     * @param SS_HTTPRequest
586
     *
587
     * @return int
588
     **/
589
    public function numberofitemsincart(SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
590
    {
591
        $order = $this->cart->CurrentOrder();
592
593
        return $order->TotalItems($recalculate = true);
594
    }
595
596
    /**
597
     * return cart for ajax call.
598
     *
599
     * @param SS_HTTPRequest
600
     *
601
     * @return HTML
0 ignored issues
show
Documentation introduced by
Should the return type not be HTMLText?

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...
602
     */
603
    public function showcart(SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
604
    {
605
        return $this->customise($this->cart->CurrentOrder())->renderWith('AjaxCart');
606
    }
607
608
    /**
609
     * loads an order.
610
     *
611
     * @param SS_HTTPRequest
612
     *
613
     * @return REDIRECT
0 ignored issues
show
Documentation introduced by
Should the return type not be SS_HTTPResponse|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...
614
     */
615
    public function loadorder(SS_HTTPRequest $request)
616
    {
617
        $this->cart->loadOrder(intval($request->param('ID')));
618
        $cartPageLink = CartPage::find_link();
619
        if ($cartPageLink) {
620
            return $this->redirect($cartPageLink);
621
        } else {
622
            return $this->redirect(Director::baseURL());
623
        }
624
    }
625
626
    /**
627
     * remove address from list of available addresses in checkout.
628
     *
629
     * @param SS_HTTPRequest
630
     *
631
     * @return string | REDIRECT
0 ignored issues
show
Documentation introduced by
Should the return type not be string|array?

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...
632
     * @TODO: add non-ajax version of this request.
633
     */
634
    public function removeaddress(SS_HTTPRequest $request)
635
    {
636
        $id = intval($request->param('ID'));
637
        $className = Convert::raw2sql($request->param('OtherID'));
638
        if (class_exists($className)) {
639
            $address = $className::get()->byID($id);
640
            if ($address && $address->canView()) {
641
                $member = Member::currentUser();
642
                if ($member) {
643
                    $address->MakeObsolete($member);
644
                    if ($request->isAjax()) {
645
                        return _t('Order.ADDRESSREMOVED', 'Address removed.');
646
                    } else {
647
                        $this->redirectBack();
648
                    }
649
                }
650
            }
651
        }
652
        if ($request->isAjax()) {
653
            return _t('Order.ADDRESSNOTREMOVED', 'Address could not be removed.');
654
        } else {
655
            $this->redirectBack();
656
        }
657
658
        return array();
659
    }
660
661
    /**
662
     * allows us to view out-dated buyables that have been deleted
663
     * where only old versions exist.
664
     * this method should redirect.
665
     *
666
     * @param SS_HTTPRequest
667
     *
668
     * @return REDIRECT
0 ignored issues
show
Documentation introduced by
Should the return type not be array|SS_HTTPResponse|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...
669
     */
670
    public function submittedbuyable(SS_HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
671
    {
672
        $buyableClassName = Convert::raw2sql($this->getRequest()->param('ID'));
673
        $buyableID = intval($this->getRequest()->param('OtherID'));
674
        $version = intval($this->getRequest()->param('Version'));
675
        if ($buyableClassName && $buyableID) {
676
            if (EcommerceDBConfig::is_buyable($buyableClassName)) {
0 ignored issues
show
Bug introduced by
It seems like $buyableClassName defined by \Convert::raw2sql($this-...Request()->param('ID')) on line 672 can also be of type array; however, EcommerceDBConfig::is_buyable() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
677
                $bestBuyable = $buyableClassName::get()->byID($buyableID);
678
                if ($bestBuyable instanceof ProductVariation) {
0 ignored issues
show
Bug introduced by
The class ProductVariation does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
679
                    $link = $bestBuyable->Link('filterforvariations/'.$buyableID.'/?version='.$version.'/');
680
                    $this->redirect($link);
681
682
                    return array();
683
                }
684
                if ($bestBuyable) {
685
                    //show singleton with old version
686
                    $link = $bestBuyable->Link('viewversion/'.$version.'/');
687
                    $this->redirect($link);
688
689
                    return array();
690
                }
691
            }
692
        }
693
        $errorPage404 = ErrorPage::get()
694
            ->Filter(array('ErrorCode' => '404'))
695
            ->First();
696
        if ($errorPage404) {
697
            return $this->redirect($errorPage404->Link());
698
        }
699
700
        return;
701
    }
702
703
    /**
704
     * This can be used by admins to log in as customers
705
     * to place orders on their behalf...
706
     *
707
     * @param SS_HTTPRequest
708
     *
709
     * @return REDIRECT
0 ignored issues
show
Documentation introduced by
Should the return type not be SS_HTTPResponse|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...
710
     */
711
    public function placeorderformember(SS_HTTPRequest $request)
712
    {
713
        $memberToTest = Member::currentMember();
0 ignored issues
show
Bug introduced by
The method currentMember() does not seem to exist on object<Member>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
714
        if ($memberToTest->IsShopAdmin()) {
715
            $member = Member::get()->byID(intval($request->param('ID')));
716
            if ($member) {
717
                $newOrder = Order::create();
718
                //copying fields.
719
                $newOrder->MemberID = $member->ID;
0 ignored issues
show
Documentation introduced by
The property MemberID does not exist on object<Order>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
720
                //load the order
721
                $newOrder->write();
722
                $this->cart->loadOrder($newOrder);
723
724
                return $this->redirect($newOrder->Link());
725
            } else {
726
                user_error('Can not find this member.');
727
            }
728
        } else {
729
            //echo "please <a href=\"Security/login/?BackURL=".urlencode($this->config()->get("url_segment")."/placeorderformember/".$request->param("ID")."/")."\">log in</a> first.";
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
730
            return Security::permissionFailure($this);
731
        }
732
    }
733
734
    /**
735
     * This can be used by admins to log in as customers
736
     * to place orders on their behalf...
737
     *
738
     * @param SS_HTTPRequest
739
     *
740
     * @return REDIRECT
0 ignored issues
show
Documentation introduced by
Should the return type not be SS_HTTPResponse|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...
741
     */
742
    public function loginas(SS_HTTPRequest $request)
743
    {
744
        $memberToTest = Member::currentUser();
745
        if (Permission::check('ADMIN')) {
746
            $newMember = Member::get()->byID(intval($request->param('ID')));
747
            if ($newMember) {
748
                if ($memberToTest) {
749
                    //$memberToTest->logout();
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
750
                    $newMember->logIn();
751
                    if ($accountPage = AccountPage::get()->first()) {
752
                        return $this->redirect($accountPage->Link());
753
                    } else {
754
                        return $this->redirect(Director::baseURL());
755
                    }
756
                } else {
757
                    user_error('Another error occurred.');
758
                }
759
            } else {
760
                user_error('Can not find this member.');
761
            }
762
        } else {
763
            return Security::permissionFailure($this);
764
        }
765
    }
766
767
    /**
768
     * Helper function used by link functions
769
     * Creates the appropriate url-encoded string parameters for links from array.
770
     *
771
     * Produces string such as: MyParam%3D11%26OtherParam%3D1
772
     *     ...which decodes to: MyParam=11&OtherParam=1
773
     *
774
     * you will need to decode the url with javascript before using it.
775
     *
776
     * @todo: check that comment description actually matches what it does
777
     *
778
     * @return string (URLSegment)
779
     */
780
    protected static function params_to_get_string(array $array)
781
    {
782
        $token = SecurityToken::inst();
783
        if (!isset($array['SecurityID'])) {
784
            $array['SecurityID'] = $token->getValue();
785
        }
786
787
        return '?'.http_build_query($array);
788
    }
789
790
    /**
791
     * Gets a buyable object based on URL actions.
792
     *
793
     * @return DataObject | Null - returns buyable
794
     */
795
    protected function buyable()
796
    {
797
        $buyableClassName = Convert::raw2sql($this->getRequest()->param('OtherID'));
798
        $buyableID = intval($this->getRequest()->param('ID'));
799
        if ($buyableClassName && $buyableID) {
800
            if (EcommerceDBConfig::is_buyable($buyableClassName)) {
0 ignored issues
show
Bug introduced by
It seems like $buyableClassName defined by \Convert::raw2sql($this-...st()->param('OtherID')) on line 797 can also be of type array; however, EcommerceDBConfig::is_buyable() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
801
                $obj = $buyableClassName::get()->byID(intval($buyableID));
802
                if ($obj) {
803
                    if ($obj->ClassName == $buyableClassName) {
804
                        return $obj;
805
                    }
806
                }
807
            } else {
808
                if (strpos($buyableClassName, 'OrderItem')) {
809
                    user_error('ClassName in URL should be buyable and not an orderitem', E_USER_NOTICE);
810
                }
811
            }
812
        }
813
814
        return;
815
    }
816
817
    /**
818
     * Gets the requested quantity.
819
     *
820
     * @return float
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double|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...
821
     */
822
    protected function quantity()
823
    {
824
        $quantity = $this->getRequest()->getVar('quantity');
825
        if (is_numeric($quantity)) {
826
            return $quantity;
827
        }
828
829
        return 1;
830
    }
831
832
    /**
833
     * Gets the request parameters.
834
     *
835
     * @param $getpost - choose between obtaining the chosen parameters from GET or POST
836
     *
837
     * @return array
838
     */
839
    protected function parameters($getpost = 'GET')
0 ignored issues
show
Coding Style introduced by
parameters uses the super-global variable $_POST 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...
840
    {
841
        return ($getpost == 'GET') ? $this->getRequest()->getVars() : $_POST;
842
    }
843
844
    /**
845
     * Handy debugging action visit.
846
     * Log in as an administrator and visit mysite/shoppingcart/debug.
847
     */
848
    public function debug()
849
    {
850
        if (Director::isDev() || Permission::check('ADMIN')) {
851
            return $this->cart->debug();
852
        } else {
853
            return Security::permissionFailure($this);
854
            //echo "please <a href=\"Security/login/?BackURL=".urlencode($this->config()->get("url_segment")."/debug/")."\">log in</a> first.";
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
855
        }
856
    }
857
858
    /**
859
     * test the ajax response
860
     * for developers only.
861
     *
862
     * @return output to buffer
0 ignored issues
show
Documentation introduced by
Should the return type not be output|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...
863
     */
864
    public function ajaxtest(SS_HTTPRequest $request)
0 ignored issues
show
Coding Style introduced by
ajaxtest 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...
865
    {
866
        if (Director::isDev() || Permission::check('ADMIN')) {
867
            header('Content-Type', 'text/plain');
868
            echo '<pre>';
869
            $_REQUEST['ajax'] = 1;
870
            $v = $this->cart->setMessageAndReturn('test only');
871
            $v = str_replace(',', ",\r\n\t\t", $v);
872
            $v = str_replace('}', "\r\n\t}", $v);
873
            $v = str_replace('{', "\t{\r\n\t\t", $v);
874
            $v = str_replace(']', "\r\n]", $v);
875
            echo $v;
876
            echo '</pre>';
877
        } else {
878
            echo 'please <a href="Security/login/?BackURL='.urlencode($this->config()->get('url_segment').'/ajaxtest/').'">log in</a> first.';
879
        }
880
        if (!$request->isAjax()) {
881
            die('---- make sure to add ?ajax=1 to the URL ---');
0 ignored issues
show
Coding Style Compatibility introduced by
The method ajaxtest() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
882
        }
883
    }
884
}
885