Completed
Push — master ( 0940e8...364b5f )
by Nicolaas
06:04 queued 19s
created

ShoppingCart_Controller   F

Complexity

Total Complexity 105

Size/Duplication

Total Lines 901
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 20

Importance

Changes 0
Metric Value
dl 0
loc 901
rs 1.263
c 0
b 0
f 0
wmc 105
lcom 2
cbo 20

50 Methods

Rating   Name   Duplication   Size   Complexity  
C init() 0 23 8
A index() 0 11 2
A Link() 0 4 1
A create_link() 0 8 1
A add_item_link() 0 4 1
A remove_item_link() 0 4 1
A remove_all_item_link() 0 4 1
A remove_all_item_and_edit_link() 0 4 1
A set_quantity_item_link() 0 4 1
A remove_modifier_link() 0 4 1
A add_modifier_link() 0 4 1
A remove_address_link() 0 4 1
A clear_cart_link() 0 4 1
A save_cart_link() 0 4 1
A clear_cart_and_logout_link() 0 4 1
A delete_order_link() 0 4 1
A copy_order_link() 0 7 3
A set_currency_link() 0 4 1
A remove_from_sale_link() 0 4 1
A json() 0 4 1
A additem() 0 11 2
A setquantityitem() 0 11 2
A removeitem() 0 11 2
A removeallitem() 0 14 2
A removeallitemandedit() 0 11 2
A removemodifier() 0 9 1
A addmodifier() 0 9 1
A setcountry() 0 8 1
A setregion() 0 7 1
A setcurrency() 0 7 1
A removefromsale() 0 21 4
A save() 0 6 1
A clear() 0 7 1
A clearandlogout() 0 10 2
A deleteorder() 0 10 3
A copyorder() 0 9 2
A numberofitemsincart() 0 6 1
A showcart() 0 4 1
A loadorder() 0 10 2
C removeaddress() 0 26 7
C submittedbuyable() 0 33 7
A placeorderformember() 0 21 3
A loginas() 0 19 4
A params_to_get_string() 0 9 2
B buyable() 0 21 7
A quantity() 0 9 2
A parameters() 0 4 2
A goToErrorPage() 0 11 2
A debug() 0 9 3
A ajaxtest() 0 20 4

How to fix   Complexity   

Complex Class

Complex classes like ShoppingCart_Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ShoppingCart_Controller, and based on these observations, apply Extract Interface, too.

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',// no need to set to  => 'ADMIN',
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
        return $this->goToErrorPage();
112
        user_error(_t('Order.NOCARTINITIALISED', 'no 404 page available'), E_USER_ERROR);
0 ignored issues
show
Unused Code introduced by
user_error(_t('Order.NOC...lable'), E_USER_ERROR); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
113
    }
114
115
    /*******************************************************
116
    * CONTROLLER LINKS
117
    *******************************************************/
118
119
    /**
120
     * @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...
121
     *
122
     * @return string (Link)
123
     */
124
    public function Link($action = null)
125
    {
126
        return self::create_link($action);
127
    }
128
129
    /**
130
     * returns ABSOLUTE link to the shopping cart controller.
131
     * @param null | array | string $actionAndOtherLinkVariables
132
     * @return string
133
     */
134
    protected static function create_link($actionAndOtherLinkVariables = null)
135
    {
136
        return Controller::join_links(
137
            Director::baseURL(),
138
            Config::inst()->get('ShoppingCart_Controller', 'url_segment'),
139
            $actionAndOtherLinkVariables
140
        );
141
    }
142
143
    /**
144
     * @param int    $buyableID
145
     * @param string $classNameForBuyable
146
     * @param array  $parameters
147
     *
148
     * @return string
149
     */
150
    public static function add_item_link($buyableID, $classNameForBuyable = 'Product', array $parameters = array())
151
    {
152
        return self::create_link('additem/'.$buyableID.'/'.$classNameForBuyable.'/'.self::params_to_get_string($parameters));
153
    }
154
155
    /**
156
     * @param int    $buyableID
157
     * @param string $classNameForBuyable
158
     * @param array  $parameters
159
     *
160
     * @return string
161
     */
162
    public static function remove_item_link($buyableID, $classNameForBuyable = 'Product', array $parameters = array())
163
    {
164
        return self::create_link('removeitem/'.$buyableID.'/'.$classNameForBuyable.'/'.self::params_to_get_string($parameters));
165
    }
166
167
    /**
168
     * @param int    $buyableID
169
     * @param string $classNameForBuyable
170
     * @param array  $parameters
171
     *
172
     * @return string
173
     */
174
    public static function remove_all_item_link($buyableID, $classNameForBuyable = 'Product', array $parameters = array())
175
    {
176
        return self::create_link('removeallitem/'.$buyableID.'/'.$classNameForBuyable.'/'.self::params_to_get_string($parameters));
177
    }
178
179
    /**
180
     * @param int    $buyableID
181
     * @param string $classNameForBuyable
182
     * @param array  $parameters
183
     *
184
     * @return string
185
     */
186
    public static function remove_all_item_and_edit_link($buyableID, $classNameForBuyable = 'Product', array $parameters = array())
187
    {
188
        return self::create_link('removeallitemandedit/'.$buyableID.'/'.$classNameForBuyable.'/'.self::params_to_get_string($parameters));
189
    }
190
191
    /**
192
     * @param int    $buyableID
193
     * @param string $classNameForBuyable
194
     * @param array  $parameters
195
     *
196
     * @return string
197
     */
198
    public static function set_quantity_item_link($buyableID, $classNameForBuyable = 'Product', array $parameters = array())
199
    {
200
        return self::create_link('setquantityitem/'.$buyableID.'/'.$classNameForBuyable.'/'.self::params_to_get_string($parameters));
201
    }
202
203
    /**
204
     * @param int   $modifierID
205
     * @param array $parameters
206
     *
207
     * @return string
208
     */
209
    public static function remove_modifier_link($modifierID, array $parameters = array())
210
    {
211
        return self::create_link('removemodifier/'.$modifierID.'/'.self::params_to_get_string($parameters));
212
    }
213
214
    /**
215
     * @param int   $modifierID
216
     * @param array $parameters
217
     *
218
     * @return string
219
     */
220
    public static function add_modifier_link($modifierID, array $parameters = array())
221
    {
222
        return self::create_link('addmodifier/'.$modifierID.'/'.self::params_to_get_string($parameters));
223
    }
224
225
    /**
226
     * @param int    $addressID
227
     * @param string $addressClassName
228
     * @param array  $parameters
229
     *
230
     * @return string
231
     */
232
    public static function remove_address_link($addressID, $addressClassName, array $parameters = array())
233
    {
234
        return self::create_link('removeaddress/'.$addressID.'/'.$addressClassName.'/'.self::params_to_get_string($parameters));
235
    }
236
237
    /**
238
     * @param array $parameters
239
     *
240
     * @return string
241
     */
242
    public static function clear_cart_link($parameters = array())
243
    {
244
        return self::create_link('clear/'.self::params_to_get_string($parameters));
245
    }
246
247
    /**
248
     * @param array $parameters
249
     *
250
     * @return string
251
     */
252
    public static function save_cart_link(array $parameters = array())
253
    {
254
        return self::create_link('save/'.self::params_to_get_string($parameters));
255
    }
256
257
    /**
258
     * @param array $parameters
259
     *
260
     * @return string
261
     */
262
    public static function clear_cart_and_logout_link(array $parameters = array())
263
    {
264
        return self::create_link('clearandlogout/'.self::params_to_get_string($parameters));
265
    }
266
267
    /**
268
     * @param array $parameters
269
     *
270
     * @return string
271
     */
272
    public static function delete_order_link($orderID, array $parameters = array())
273
    {
274
        return self::create_link('deleteorder/'.$orderID.'/'.self::params_to_get_string($parameters));
275
    }
276
277
    /**
278
     *
279
     * @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...
280
     */
281
    public static function copy_order_link($orderID, $parameters = array())
282
    {
283
        $order = Order::get()->byID($orderID);
284
        if ($order && $order->IsSubmitted()) {
285
            return self::create_link('copyorder/'.$orderID.'/'.self::params_to_get_string($parameters));
286
        }
287
    }
288
289
    /**
290
     * returns a link that allows you to set a currency...
291
     * dont be fooled by the set_ part...
292
     *
293
     * @param string $code
294
     *
295
     * @return string
296
     */
297
    public static function set_currency_link($code, array $parameters = array())
298
    {
299
        return self::create_link('setcurrency/'.$code.'/'.self::params_to_get_string($parameters));
300
    }
301
302
    /**
303
     *
304
     *
305
     * @param  int    $id
306
     * @param  string $className
307
     * @return string
308
     */
309
    public static function remove_from_sale_link($id, $className)
310
    {
311
        return self::create_link('removefromsale/'.$className.'/'.$id .'/');
312
    }
313
314
    /**
315
     * return json for cart... no further actions.
316
     *
317
     * @param SS_HTTPRequest
318
     *
319
     * @return JSON
320
     */
321
    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...
322
    {
323
        return $this->cart->setMessageAndReturn();
324
    }
325
326
    /**
327
     * Adds item to cart via controller action; one by default.
328
     *
329
     * @param HTTPRequest
330
     *
331
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
332
     *               If it is not AJAX it redirects back to requesting page.
333
     */
334
    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...
335
    {
336
        $buyable = $this->buyable();
337
        if($buyable) {
338
            $this->cart->addBuyable($buyable, $this->quantity(), $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...
339
            return $this->cart->setMessageAndReturn();
340
        } else {
341
            return $this->goToErrorPage();
342
        }
343
344
    }
345
346
    /**
347
     * Sets the exact passed quantity.
348
     * Note: If no ?quantity=x is specified in URL, then quantity will be set to 1.
349
     *
350
     * @param HTTPRequest
351
     *
352
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
353
     *               If it is not AJAX it redirects back to requesting page.
354
     */
355
    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...
356
    {
357
        $buyable = $this->buyable();
358
        if($buyable) {
359
            $this->cart->setQuantity($buyable, $this->quantity(), $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...
360
361
            return $this->cart->setMessageAndReturn();
362
        } else {
363
            return $this->goToErrorPage();
364
        }
365
    }
366
367
    /**
368
     * Removes item from cart via controller action; one by default.
369
     *
370
     * @param HTTPRequest
371
     *
372
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
373
     *               If it is not AJAX it redirects back to requesting page.
374
     */
375
    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...
376
    {
377
        $buyable = $this->buyable();
378
        if($buyable) {
379
            $this->cart->decrementBuyable($buyable, $this->quantity(), $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...
380
381
            return $this->cart->setMessageAndReturn();
382
        } else {
383
            return $this->goToErrorPage();
384
        }
385
    }
386
387
    /**
388
     * Removes all of a specific item.
389
     *
390
     * @param HTTPRequest
391
     *
392
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
393
     *               If it is not AJAX it redirects back to requesting page.
394
     */
395
    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...
396
    {
397
        $buyable = $this->buyable();
398
        if($buyable) {
399
            $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...
400
            //added this because cart was not updating correctly
401
            $order = $this->cart->CurrentOrder();
402
            $order->calculateOrderAttributes($force = true);
403
404
            return $this->cart->setMessageAndReturn();
405
        } else {
406
            return $this->goToErrorPage();
407
        }
408
    }
409
410
    /**
411
     * Removes all of a specific item AND return back.
412
     *
413
     * @param HTTPRequest
414
     *
415
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
416
     *               If it is not AJAX it redirects back to requesting page.
417
     */
418
    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...
419
    {
420
        $buyable = $this->buyable();
421
        if ($buyable) {
422
            $link = $buyable->Link();
423
            $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...
424
            $this->redirect($link);
425
        } else {
426
            $this->redirectBack();
427
        }
428
    }
429
430
    /**
431
     * Removes a specified modifier from 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 removemodifier(SS_HTTPRequest $request)
439
    {
440
        $modifierID = intval($request->param('ID'));
441
        $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...
442
        $order = $this->cart->CurrentOrder();
443
        $order->calculateOrderAttributes($force = true);
444
445
        return $this->cart->setMessageAndReturn();
446
    }
447
448
    /**
449
     * Adds a specified modifier to the cart;.
450
     *
451
     * @param HTTPRequest
452
     *
453
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
454
     *               If it is not AJAX it redirects back to requesting page.
455
     */
456
    public function addmodifier(SS_HTTPRequest $request)
457
    {
458
        $modifierID = intval($request->param('ID'));
459
        $this->cart->addModifier($modifierID);
460
        $order = $this->cart->CurrentOrder();
461
        $order->calculateOrderAttributes($force = true);
462
463
        return $this->cart->setMessageAndReturn();
464
    }
465
466
    /**
467
     * sets the country.
468
     *
469
     * @param SS_HTTPRequest
470
     *
471
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
472
     *               If it is not AJAX it redirects back to requesting page.
473
     **/
474
    public function setcountry(SS_HTTPRequest $request)
475
    {
476
        $countryCode = Convert::raw2sql($request->param('ID'));
477
        //set_country will check if the country code is actually allowed....
478
        $this->cart->setCountry($countryCode);
479
480
        return $this->cart->setMessageAndReturn();
481
    }
482
483
    /**
484
     * @param SS_HTTPRequest
485
     *
486
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
487
     *               If it is not AJAX it redirects back to requesting page.
488
     **/
489
    public function setregion(SS_HTTPRequest $request)
490
    {
491
        $regionID = intval($request->param('ID'));
492
        $this->cart->setRegion($regionID);
493
494
        return $this->cart->setMessageAndReturn();
495
    }
496
497
    /**
498
     * @param SS_HTTPRequest
499
     *
500
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
501
     *               If it is not AJAX it redirects back to requesting page.
502
     **/
503
    public function setcurrency(SS_HTTPRequest $request)
504
    {
505
        $currencyCode = Convert::raw2sql($request->param('ID'));
506
        $this->cart->setCurrency($currencyCode);
0 ignored issues
show
Bug introduced by
It seems like $currencyCode defined by \Convert::raw2sql($request->param('ID')) on line 505 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...
507
508
        return $this->cart->setMessageAndReturn();
509
    }
510
511
    /**
512
     * @param SS_HTTPRequest
513
     *
514
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
515
     *               If it is not AJAX it redirects back to requesting page.
516
     **/
517
    public function removefromsale(SS_HTTPRequest $request)
518
    {
519
        if (EcommerceRole::current_member_is_shop_assistant()) {
520
            $className = Convert::raw2sql($request->param('ID'));
521
            $id = intval($request->param('OtherID'));
522
            if (class_exists($className)) {
523
                $obj = $className::get()->byID($id);
524
                $obj->AllowPurchase = 0;
525
                if ($obj instanceof SiteTree) {
526
                    $obj->writeToStage('Stage');
527
                    $obj->doPublish();
528
                } else {
529
                    $obj->write();
530
                }
531
            }
532
533
            return $this->cart->setMessageAndReturn();
534
        } else {
535
            return Security::permissionFailure($this);
536
        }
537
    }
538
539
    /**
540
     * @param SS_HTTPRequest
541
     *
542
     * @return mixed - if the request is AJAX, it returns JSON - CartResponse::ReturnCartData();
543
     *               If it is not AJAX it redirects back to requesting page.
544
     **/
545
    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...
546
    {
547
        $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...
548
549
        return $this->cart->setMessageAndReturn();
550
    }
551
552
    /**
553
     * @param SS_HTTPRequest
554
     *
555
     * @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...
556
     **/
557
    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...
558
    {
559
        $this->cart->clear();
560
        $this->redirect(Director::baseURL());
561
562
        return array();
563
    }
564
565
    /**
566
     * @param SS_HTTPRequest
567
     *
568
     * @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...
569
     **/
570
    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...
571
    {
572
        $this->cart->clear();
573
        if ($member = Member::currentUser()) {
574
            $member->logout();
575
        }
576
        $this->redirect(Director::baseURL());
577
578
        return array();
579
    }
580
581
    /**
582
     * @param SS_HTTPRequest
583
     *
584
     * @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...
585
     **/
586
    public function deleteorder(SS_HTTPRequest $request)
587
    {
588
        $orderID = intval($request->param('ID'));
589
        if ($order = Order::get_by_id_if_can_view($orderID)) {
590
            if ($order->canDelete()) {
591
                $order->delete();
592
            }
593
        }
594
        $this->redirectBack();
595
    }
596
597
    public function copyorder($request)
598
    {
599
        $orderID = intval($request->param('ID'));
600
        if ($order = Order::get_by_id_if_can_view($orderID)) {
601
            $this->cart->copyOrder($order);
602
        }
603
        $link = CheckoutPage::find_link();
604
        return $this->redirect($link);
605
    }
606
607
    /**
608
     * return number of items in cart.
609
     *
610
     * @param SS_HTTPRequest
611
     *
612
     * @return int
613
     **/
614
    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...
615
    {
616
        $order = $this->cart->CurrentOrder();
617
618
        return $order->TotalItems($recalculate = true);
619
    }
620
621
    /**
622
     * return cart for ajax call.
623
     *
624
     * @param SS_HTTPRequest
625
     *
626
     * @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...
627
     */
628
    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...
629
    {
630
        return $this->customise($this->cart->CurrentOrder())->renderWith('AjaxCart');
631
    }
632
633
    /**
634
     * loads an order.
635
     *
636
     * @param SS_HTTPRequest
637
     *
638
     * @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...
639
     */
640
    public function loadorder(SS_HTTPRequest $request)
641
    {
642
        $this->cart->loadOrder(intval($request->param('ID')));
643
        $cartPageLink = CartPage::find_link();
644
        if ($cartPageLink) {
645
            return $this->redirect($cartPageLink);
646
        } else {
647
            return $this->redirect(Director::baseURL());
648
        }
649
    }
650
651
    /**
652
     * remove address from list of available addresses in checkout.
653
     *
654
     * @param SS_HTTPRequest
655
     *
656
     * @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...
657
     * @TODO: add non-ajax version of this request.
658
     */
659
    public function removeaddress(SS_HTTPRequest $request)
660
    {
661
        $id = intval($request->param('ID'));
662
        $className = Convert::raw2sql($request->param('OtherID'));
663
        if (class_exists($className)) {
664
            $address = $className::get()->byID($id);
665
            if ($address && $address->canView()) {
666
                $member = Member::currentUser();
667
                if ($member) {
668
                    $address->MakeObsolete($member);
669
                    if ($request->isAjax()) {
670
                        return _t('Order.ADDRESSREMOVED', 'Address removed.');
671
                    } else {
672
                        $this->redirectBack();
673
                    }
674
                }
675
            }
676
        }
677
        if ($request->isAjax()) {
678
            return _t('Order.ADDRESSNOTREMOVED', 'Address could not be removed.');
679
        } else {
680
            $this->redirectBack();
681
        }
682
683
        return array();
684
    }
685
686
    /**
687
     * allows us to view out-dated buyables that have been deleted
688
     * where only old versions exist.
689
     * this method should redirect.
690
     *
691
     * @param SS_HTTPRequest
692
     *
693
     * @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...
694
     */
695
    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...
696
    {
697
        $buyableClassName = Convert::raw2sql($this->getRequest()->param('ID'));
698
        $buyableID = intval($this->getRequest()->param('OtherID'));
699
        $version = intval($this->getRequest()->param('Version'));
700
        if ($buyableClassName && $buyableID) {
701
            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 697 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...
702
                $bestBuyable = $buyableClassName::get()->byID($buyableID);
703
                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...
704
                    $link = $bestBuyable->Link('filterforvariations/'.$buyableID.'/?version='.$version.'/');
705
                    $this->redirect($link);
706
707
                    return array();
708
                }
709
                if ($bestBuyable) {
710
                    //show singleton with old version
711
                    $link = $bestBuyable->Link('viewversion/'.$version.'/');
712
                    $this->redirect($link);
713
714
                    return array();
715
                }
716
            }
717
        }
718
        $errorPage404 = DataObject::get_one(
719
            'ErrorPage',
720
            array('ErrorCode' => '404')
721
        );
722
        if ($errorPage404) {
723
            return $this->redirect($errorPage404->Link());
724
        }
725
726
        return;
727
    }
728
729
    /**
730
     * This can be used by admins to log in as customers
731
     * to place orders on their behalf...
732
     *
733
     * @param SS_HTTPRequest
734
     *
735
     * @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...
736
     */
737
    public function placeorderformember(SS_HTTPRequest $request)
738
    {
739
        if (EcommerceRole::current_member_is_shop_admin()) {
740
            $member = Member::get()->byID(intval($request->param('ID')));
741
            if ($member) {
742
                $newOrder = Order::create();
743
                //copying fields.
744
                $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...
745
                //load the order
746
                $newOrder->write();
747
                $this->cart->loadOrder($newOrder);
748
749
                return $this->redirect($newOrder->Link());
750
            } else {
751
                user_error('Can not find this member.');
752
            }
753
        } else {
754
            //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...
755
            return Security::permissionFailure($this);
756
        }
757
    }
758
759
    /**
760
     * This can be used by admins to log in as customers
761
     * to place orders on their behalf...
762
     *
763
     * @param SS_HTTPRequest
764
     *
765
     * @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...
766
     */
767
    public function loginas(SS_HTTPRequest $request)
768
    {
769
        if (Permission::check('ADMIN')) {
770
            $newMember = Member::get()->byID(intval($request->param('ID')));
771
            if ($newMember) {
772
                //$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...
773
                $newMember->logIn();
774
                if ($accountPage = DataObject::get_one('AccountPage')) {
775
                    return $this->redirect($accountPage->Link());
776
                } else {
777
                    return $this->redirect(Director::baseURL());
778
                }
779
            } else {
780
                user_error('Can not find this member.');
781
            }
782
        } else {
783
            return Security::permissionFailure($this);
784
        }
785
    }
786
787
    /**
788
     * Helper function used by link functions
789
     * Creates the appropriate url-encoded string parameters for links from array.
790
     *
791
     * Produces string such as: MyParam%3D11%26OtherParam%3D1
792
     *     ...which decodes to: MyParam=11&OtherParam=1
793
     *
794
     * you will need to decode the url with javascript before using it.
795
     *
796
     * @todo: check that comment description actually matches what it does
797
     *
798
     * @return string (URLSegment)
799
     */
800
    protected static function params_to_get_string(array $array)
801
    {
802
        $token = SecurityToken::inst();
803
        if (!isset($array['SecurityID'])) {
804
            $array['SecurityID'] = $token->getValue();
805
        }
806
807
        return '?'.http_build_query($array);
808
    }
809
810
    /**
811
     * Gets a buyable object based on URL actions.
812
     *
813
     * @return DataObject | Null - returns buyable
814
     */
815
    protected function buyable()
816
    {
817
        $buyableClassName = Convert::raw2sql($this->getRequest()->param('OtherID'));
818
        $buyableID = intval($this->getRequest()->param('ID'));
819
        if ($buyableClassName && $buyableID) {
820
            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 817 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...
821
                $obj = $buyableClassName::get()->byID(intval($buyableID));
822
                if ($obj) {
823
                    if ($obj->ClassName == $buyableClassName) {
824
                        return $obj;
825
                    }
826
                }
827
            } else {
828
                if (strpos($buyableClassName, 'OrderItem')) {
829
                    user_error('ClassName in URL should be buyable and not an orderitem', E_USER_NOTICE);
830
                }
831
            }
832
        }
833
834
        return;
835
    }
836
837
    /**
838
     * Gets the requested quantity.
839
     *
840
     * @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...
841
     */
842
    protected function quantity()
843
    {
844
        $quantity = $this->getRequest()->getVar('quantity');
845
        if (is_numeric($quantity)) {
846
            return $quantity;
847
        }
848
849
        return 1;
850
    }
851
852
    /**
853
     * Gets the request parameters.
854
     *
855
     * @param $getpost - choose between obtaining the chosen parameters from GET or POST
856
     *
857
     * @return array
858
     */
859
    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...
860
    {
861
        return ($getpost == 'GET') ? $this->getRequest()->getVars() : $_POST;
862
    }
863
864
    protected function goToErrorPage()
865
    {
866
        $errorPage404 = DataObject::get_one(
867
            'ErrorPage',
868
            array('ErrorCode' => '404')
869
        );
870
        if ($errorPage404) {
871
            return $this->redirect($errorPage404->Link());
872
        }
873
        return $this->redirect('page-not-found');
874
    }
875
876
    /**
877
     * Handy debugging action visit.
878
     * Log in as an administrator and visit mysite/shoppingcart/debug.
879
     */
880
    public function debug()
881
    {
882
        if (Director::isDev() || EcommerceRole::current_member_is_shop_admin()) {
883
            return $this->cart->debug();
884
        } else {
885
            return Security::permissionFailure($this);
886
            //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...
887
        }
888
    }
889
890
    /**
891
     * test the ajax response
892
     * for developers only.
893
     *
894
     * @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...
895
     */
896
    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...
897
    {
898
        if (Director::isDev() || Permission::check('ADMIN')) {
899
            header('Content-Type', 'text/plain');
900
            echo '<pre>';
901
            $_REQUEST['ajax'] = 1;
902
            $v = $this->cart->setMessageAndReturn('test only');
903
            $v = str_replace(',', ",\r\n\t\t", $v);
904
            $v = str_replace('}', "\r\n\t}", $v);
905
            $v = str_replace('{', "\t{\r\n\t\t", $v);
906
            $v = str_replace(']', "\r\n]", $v);
907
            echo $v;
908
            echo '</pre>';
909
        } else {
910
            echo 'please <a href="Security/login/?BackURL='.urlencode($this->config()->get('url_segment').'/ajaxtest/').'">log in</a> first.';
911
        }
912
        if (!$request->isAjax()) {
913
            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...
914
        }
915
    }
916
}
917