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

code/api/ShoppingCart.php (75 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
/**
3
 * ShoppingCart - provides a global way to interface with the cart (current order).
4
 *
5
 * This can be used in other code by calling $cart = ShoppingCart::singleton();
6
 *
7
 * The shopping cart can be accessed as an order handler from the back-end
8
 * (e.g. when creating an order programmatically), while the accompagnying controller
9
 * is used by web-users to manipulate their order.
10
 *
11
 * A bunch of core functions are also stored in the order itself.
12
 * Methods and variables are in the shopping cart if they are relevant
13
 * only before (and while) the order is placed (e.g. latest update message),
14
 * and others are in the order because they are relevant even after the
15
 * order has been submitted (e.g. Total Cost).
16
 *
17
 * Key methods:
18
 *
19
 * //get Cart
20
 * $myCart = ShoppingCart::singleton();
21
 *
22
 * //get order
23
 * $myOrder = ShoppingCart::current_order();
24
 *
25
 * //view order (from another controller)
26
 * $this->redirect(ShoppingCart::current_order()->Link());
27
 *
28
 * //add item to cart
29
 * ShoppingCart::singleton()->addBuyable($myProduct);
30
 *
31
 * @authors: Nicolaas [at] Sunny Side Up .co.nz
32
 * @package: ecommerce
33
 * @sub-package: control
34
 * @inspiration: Silverstripe Ltd, Jeremy
35
 **/
36
class ShoppingCart extends Object
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...
37
{
38
    /**
39
     * List of names that can be used as session variables.
40
     * Also @see ShoppingCart::sessionVariableName.
41
     *
42
     * @var array
43
     */
44
    private static $session_variable_names = array('OrderID', 'Messages');
45
46
    /**
47
     * This is where we hold the (singleton) Shoppingcart.
48
     *
49
     * @var object (ShoppingCart)
50
     */
51
    private static $_singletoncart = null;
52
53
    /**
54
     * Feedback message to user (e.g. cart updated, could not delete item, someone in standing behind you).
55
     *
56
     *@var array
57
     **/
58
    protected $messages = array();
59
60
    /**
61
     * stores a reference to the current order object.
62
     *
63
     * @var object
64
     **/
65
    protected $order = null;
66
67
    /**
68
     * This variable is set to YES when we actually need an order (i.e. write it).
69
     *
70
     * @var bool
71
     */
72
    protected $requireSavedOrder = false;
73
74
    /**
75
     * Allows access to the cart from anywhere in code.
76
     *
77
     * @return ShoppingCart Object
78
     */
79
    public static function singleton()
80
    {
81
        if (!self::$_singletoncart) {
82
            self::$_singletoncart = Injector::inst()->get('ShoppingCart');
83
        }
84
85
        return self::$_singletoncart;
86
    }
87
88
    /**
89
     * Allows access to the current order from anywhere in the code..
90
     *
91
     * if you do not like the session Order then you can set it here ...
92
     *
93
     * @param Order $order (optional)
0 ignored issues
show
Should the type for parameter $order not be Order|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...
94
     *
95
     * @return Order
96
     */
97
    public static function current_order($order = null)
98
    {
99
        return self::singleton()->currentOrder(0, $order);
100
    }
101
102
    /**
103
     * useful when the order has been updated ...
104
     */
105
    public static function reset_order_reference()
106
    {
107
        return self::singleton()->order = null;
108
    }
109
110
    /**
111
     * looks up current order id.
112
     * you may supply an ID here, so that it looks up the current order ID
113
     * only when none is supplied.
114
     *
115
     * @param int (optional) | Order $orderOrOrderID
116
     *
117
     * @return int;
0 ignored issues
show
The doc-type int; could not be parsed: Expected "|" or "end of type", but got ";" at position 3. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
118
     */
119
    public static function current_order_id($orderOrOrderID = 0)
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
120
    {
121
        $orderID = 0;
122
        if (! $orderOrOrderID) {
123
            $order = self::current_order();
124
            if ($order && $order->exists()) {
125
                $orderID = $order->ID;
126
            }
127
        }
128
        if ($orderOrOrderID instanceof Order) {
129
            $orderID = $orderOrOrderID->ID;
130
        } elseif (intval($orderOrOrderID)) {
131
            $orderID = intval($orderOrOrderID);
132
        }
133
134
        return $orderID;
135
    }
136
137
    /**
138
     * Allows access to the current order from anywhere in the code..
139
     *
140
     * @return Order
0 ignored issues
show
Should the return type not be Order|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...
141
     */
142
    public static function session_order()
143
    {
144
        $sessionVariableName = self::singleton()->sessionVariableName('OrderID');
145
        $orderIDFromSession = intval(Session::get($sessionVariableName)) - 0;
146
147
        return Order::get()->byID($orderIDFromSession);
148
    }
149
150
    /**
151
     * set a specific order, other than the one from session ....
152
     *
153
     * @param Order $order
154
     *
155
     * @return Order
156
     */
157
    public function setOrder($order)
158
    {
159
        $this->order = $order;
160
        return $this->order;
161
    }
162
163
    /**
164
     * Gets or creates the current order.
165
     * Based on the session ONLY unless the order has been explictely set.
166
     * IMPORTANT FUNCTION!
167
     *
168
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
169
     *
170
     * However, you can pass an order in case you want to manipulate an order that is not in sesssion
171
     *
172
     * @param int $recurseCount (optional)
173
     * @param Order $order (optional)
0 ignored issues
show
Should the type for parameter $order not be Order|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...
174
     *
175
     * @return Order
176
     */
177
    public function currentOrder($recurseCount = 0, $order = null)
178
    {
179
        if ($order) {
180
            $this->order = $order;
181
        }
182
        if ($this->allowWrites()) {
183
            if (!$this->order) {
184
                $this->order = self::session_order();
185
                $loggedInMember = Member::currentUser();
186
                if ($this->order) {
187
                    //first reason to set to null: it is already submitted
188
                    if ($this->order->IsSubmitted()) {
189
                        $this->order = null;
190
                    }
191
                    //second reason to set to null: make sure we have permissions
192
                    elseif (!$this->order->canView()) {
193
                        $this->order = null;
194
                    }
195
                    //logged in, add Member.ID to order->MemberID
196
                    elseif ($loggedInMember && $loggedInMember->exists()) {
197
                        if ($this->order->MemberID != $loggedInMember->ID) {
0 ignored issues
show
The property MemberID does not exist on object<Order>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read 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.");
        }
    }

}

If the property has read access only, you can use the @property-read 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...
198
                            $updateMember = false;
199
                            if (!$this->order->MemberID) {
0 ignored issues
show
The property MemberID does not exist on object<Order>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read 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.");
        }
    }

}

If the property has read access only, you can use the @property-read 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...
200
                                $updateMember = true;
201
                            }
202
                            if (!$loggedInMember->IsShopAdmin()) {
203
                                $updateMember = true;
204
                            }
205
                            if ($updateMember) {
206
                                $this->order->MemberID = $loggedInMember->ID;
0 ignored issues
show
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...
207
                                $this->order->write();
208
                            }
209
                        }
210
                        //IF current order has nothing in it AND the member already has an order: use the old one first
211
                        //first, lets check if the current order is worthwhile keeping
212
                        if ($this->order->StatusID || $this->order->TotalItems()) {
0 ignored issues
show
The property StatusID does not exist on object<Order>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read 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.");
        }
    }

}

If the property has read access only, you can use the @property-read 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...
This if statement is empty and can be removed.

This check looks for the bodies 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 if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

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

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
213
                            //do NOTHING!
214
                        } else {
215
                            $firstStep = DataObject::get_one('OrderStep');
216
                            //we assume the first step always exists.
217
                            //TODO: what sort order?
218
                            $count = 0;
219
                            while (
220
                                $firstStep &&
221
                                $previousOrderFromMember = DataObject::get_one(
222
                                    'Order',
223
                                    '
224
                                        "MemberID" = '.$loggedInMember->ID.'
225
                                        AND ("StatusID" = '.$firstStep->ID.' OR "StatusID" = 0)
226
                                        AND "Order"."ID" <> '.$this->order->ID
227
                                )
228
                            ) {
229
                                //arbritary 12 attempts ...
230
                                if ($count > 12) {
231
                                    break;
232
                                }
233
                                ++$count;
234
                                if ($previousOrderFromMember && $previousOrderFromMember->canView()) {
235
                                    if ($previousOrderFromMember->StatusID || $previousOrderFromMember->TotalItems()) {
236
                                        $this->order->delete();
237
                                        $this->order = $previousOrderFromMember;
238
                                        break;
239
                                    } else {
240
                                        $previousOrderFromMember->delete();
241
                                    }
242
                                }
243
                            }
244
                        }
245
                    }
246
                }
247
                if (! $this->order) {
248
                    if ($loggedInMember) {
249
                        //find previour order...
250
                        $firstStep = DataObject::get_one('OrderStep');
251
                        if ($firstStep) {
252
                            $previousOrderFromMember = Order::get()
253
                                ->filter(
254
                                    array(
255
                                        'MemberID' => $loggedInMember->ID,
256
                                        'StatusID' => array($firstStep->ID, 0),
257
                                    )
258
                                )->first();
259
                            if ($previousOrderFromMember) {
260
                                if ($previousOrderFromMember->canView()) {
261
                                    $this->order = $previousOrderFromMember;
262
                                }
263
                            }
264
                        }
265
                    }
266
                    if ($this->order && !$this->order->exists()) {
267
                        $this->order = null;
268
                    }
269
                    if (! $this->order) {
270
                        //here we cleanup old orders, because they should be
271
                        //cleaned at the same rate that they are created...
272
                        if (EcommerceConfig::get('ShoppingCart', 'cleanup_every_time')) {
273
                            $cartCleanupTask = Injector::inst()->get('EcommerceTaskCartCleanup');
274
                            $cartCleanupTask->runSilently();
275
                        }
276
                        //create new order
277
                        $this->order = Order::create();
278
                        if ($loggedInMember) {
279
                            $this->order->MemberID = $loggedInMember->ID;
0 ignored issues
show
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...
280
                        }
281
                        $this->order->write();
282
                    }
283
                    $sessionVariableName = $this->sessionVariableName('OrderID');
284
                    Session::set($sessionVariableName, intval($this->order->ID));
285
                }
286
                if ($this->order) {
287
                    if ($this->order->exists()) {
288
                        $this->order->calculateOrderAttributes($force = false);
289
                    }
290
                    if (! $this->order->SessionID) {
291
                        $this->order->write();
292
                    }
293
                    //add session ID...
294
                }
295
            }
296
            //try it again
297
            //but limit to three, just in case ...
298
            //just in case ...
299
            if (!$this->order && $recurseCount < 3) {
300
                ++$recurseCount;
301
302
                return $this->currentOrder($recurseCount, $order);
303
            }
304
305
            return $this->order;
306
        } else {
307
308
            //we still return an order so that we do not end up with errors...
309
            return Order::create();
310
        }
311
    }
312
313
314
    private static $_allow_writes_cache = null;
315
316
    /**
317
     * can the current user use sessions and therefore write to cart???
318
     * the method also returns if an order has explicitely been set
319
     * @return Boolean
320
     */
321
    protected function allowWrites()
322
    {
323
        if (self::$_allow_writes_cache === null) {
324
            if ($this->order) {
325
                self::$_allow_writes_cache = true;
326
            } else {
327
                if (php_sapi_name() !== 'cli') {
328
                    if (version_compare(phpversion(), '5.4.0', '>=')) {
329
                        self::$_allow_writes_cache = (session_status() === PHP_SESSION_ACTIVE ? true : false);
330
                    } else {
331
                        self::$_allow_writes_cache = (session_id() === '' ? false : true);
332
                    }
333
                } else {
334
                    self::$_allow_writes_cache = false;
335
                }
336
            }
337
        }
338
339
        return self::$_allow_writes_cache;
340
    }
341
342
    /**
343
     * Allows access to the current order from anywhere in the code..
344
     *
345
     * @param Order $order (optional)
0 ignored issues
show
Should the type for parameter $order not be Order|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...
346
     *
347
     * @return ShoppingCart Object
348
     */
349
    public function Link($order = null)
0 ignored issues
show
The parameter $order 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...
350
    {
351
        $order = self::singleton()->currentOrder(0, $order = null);
352
        if ($order) {
353
            return $order->Link();
354
        }
355
    }
356
357
    /**
358
     * Adds any number of items to the cart.
359
     * Returns the order item on succes OR false on failure.
360
     *
361
     * @param DataObject $buyable    - the buyable (generally a product) being added to the cart
0 ignored issues
show
Should the type for parameter $buyable not be BuyableModel?

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...
362
     * @param float      $quantity   - number of items add.
0 ignored issues
show
Should the type for parameter $quantity not be integer?

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...
363
     * @param mixed      $parameters - array of parameters to target a specific order item. eg: group=1, length=5
364
     *                                 if you make it a form, it will save the form into the orderitem
365
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
366
     *
367
     * @return false | DataObject (OrderItem)
0 ignored issues
show
Should the return type not be false|OrderItem|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...
368
     */
369
    public function addBuyable(BuyableModel $buyable, $quantity = 1, $parameters = array())
370
    {
371
        if ($this->allowWrites()) {
372
            if (!$buyable) {
373
                $this->addMessage(_t('Order.ITEMCOULDNOTBEFOUND', 'This item could not be found.'), 'bad');
374
                return false;
375
            }
376
            if (!$buyable->canPurchase()) {
377
                $this->addMessage(_t('Order.ITEMCOULDNOTBEADDED', 'This item is not for sale.'), 'bad');
378
                return false;
379
            }
380
            $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false);
381
            $quantity = $this->prepareQuantity($buyable, $quantity);
382
            if ($item && $quantity) { //find existing order item or make one
383
                $item->Quantity += $quantity;
0 ignored issues
show
The property Quantity does not exist on object<OrderItem>. 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...
384
                $item->write();
385
                $this->currentOrder()->Attributes()->add($item); //save to current order
0 ignored issues
show
The method Attributes() does not exist on Order. Did you maybe mean getOrderAttributesByType()?

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

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

Loading history...
386
                //TODO: distinquish between incremented and set
387
                //TODO: use sprintf to allow product name etc to be included in message
388
                if ($quantity > 1) {
389
                    $msg = _t('Order.ITEMSADDED', 'Items added.');
390
                } else {
391
                    $msg = _t('Order.ITEMADDED', 'Item added.');
392
                }
393
                $this->addMessage($msg, 'good');
394
            } elseif (!$item) {
395
                $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad');
396
            } else {
397
                $this->addMessage(_t('Order.ITEMCOULDNOTBEADDED', 'Item could not be added.'), 'bad');
398
            }
399
400
            return $item;
401
        }
402
    }
403
404
    /**
405
     * Sets quantity for an item in the cart.
406
     *
407
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
408
     *
409
     * @param DataObject $buyable    - the buyable (generally a product) being added to the cart
0 ignored issues
show
Should the type for parameter $buyable not be BuyableModel?

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...
410
     * @param float      $quantity   - number of items add.
411
     * @param array      $parameters - array of parameters to target a specific order item. eg: group=1, length=5
412
     *
413
     * @return false | DataObject (OrderItem) | null
0 ignored issues
show
Should the return type not be OrderItem|false|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...
414
     */
415
    public function setQuantity(BuyableModel $buyable, $quantity, array $parameters = array())
416
    {
417
        if ($this->allowWrites()) {
418
            $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false);
419
            $quantity = $this->prepareQuantity($buyable, $quantity);
420
            if ($item) {
421
                $item->Quantity = $quantity; //remove quantity
0 ignored issues
show
The property Quantity does not exist on object<OrderItem>. 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...
422
                $item->write();
423
                $this->addMessage(_t('Order.ITEMUPDATED', 'Item updated.'), 'good');
424
425
                return $item;
426
            } else {
427
                $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad');
428
            }
429
430
            return false;
431
        }
432
    }
433
434
    /**
435
     * Removes any number of items from the cart.
436
     *
437
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
438
     *
439
     * @param DataObject $buyable    - the buyable (generally a product) being added to the cart
0 ignored issues
show
Should the type for parameter $buyable not be BuyableModel?

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...
440
     * @param float      $quantity   - number of items add.
0 ignored issues
show
Should the type for parameter $quantity not be integer?

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...
441
     * @param array      $parameters - array of parameters to target a specific order item. eg: group=1, length=5
442
     *
443
     * @return false | OrderItem | null
0 ignored issues
show
Should the return type not be OrderItem|false|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...
444
     */
445
    public function decrementBuyable(BuyableModel $buyable, $quantity = 1, array $parameters = array())
446
    {
447
        if ($this->allowWrites()) {
448
            $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false);
449
            $quantity = $this->prepareQuantity($buyable, $quantity);
450
            if ($item) {
451
                $item->Quantity -= $quantity; //remove quantity
0 ignored issues
show
The property Quantity does not exist on object<OrderItem>. 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...
452
                if ($item->Quantity < 0) {
0 ignored issues
show
The property Quantity does not exist on object<OrderItem>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read 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.");
        }
    }

}

If the property has read access only, you can use the @property-read 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...
453
                    $item->Quantity = 0;
0 ignored issues
show
The property Quantity does not exist on object<OrderItem>. 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...
454
                }
455
                $item->write();
456
                if ($quantity > 1) {
457
                    $msg = _t('Order.ITEMSREMOVED', 'Items removed.');
458
                } else {
459
                    $msg = _t('Order.ITEMREMOVED', 'Item removed.');
460
                }
461
                $this->addMessage($msg, 'good');
462
463
                return $item;
464
            } else {
465
                $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad');
466
            }
467
468
            return false;
469
        }
470
    }
471
472
    /**
473
     * Delete item from the cart.
474
     *
475
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
476
     *
477
     * @param OrderItem $buyable    - the buyable (generally a product) being added to the cart
0 ignored issues
show
Should the type for parameter $buyable not be BuyableModel?

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...
478
     * @param array     $parameters - array of parameters to target a specific order item. eg: group=1, length=5
479
     *
480
     * @return bool | item | null - successfully removed
0 ignored issues
show
Should the return type not be OrderItem|false|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...
481
     */
482
    public function deleteBuyable(BuyableModel $buyable, array $parameters = array())
483
    {
484
        if ($this->allowWrites()) {
485
            $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = true);
486
            if ($item) {
487
                $this->currentOrder()->Attributes()->remove($item);
0 ignored issues
show
The method Attributes() does not exist on Order. Did you maybe mean getOrderAttributesByType()?

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

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

Loading history...
488
                $item->delete();
489
                $item->destroy();
490
                $this->addMessage(_t('Order.ITEMCOMPLETELYREMOVED', 'Item removed from cart.'), 'good');
491
492
                return $item;
493
            } else {
494
                $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad');
495
496
                return false;
497
            }
498
        }
499
    }
500
501
    /**
502
     * Checks and prepares variables for a quantity change (add, edit, remove) for an Order Item.
503
     *
504
     * @param DataObject    $buyable             - the buyable (generally a product) being added to the cart
0 ignored issues
show
Should the type for parameter $buyable not be BuyableModel?

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...
505
     * @param float         $quantity            - number of items add.
0 ignored issues
show
There is no parameter named $quantity. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
506
     * @param bool          $mustBeExistingItems - if false, the Order Item gets created if it does not exist - if TRUE the order item is searched for and an error shows if there is no Order item.
0 ignored issues
show
There is no parameter named $mustBeExistingItems. Did you maybe mean $mustBeExistingItem?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
507
     * @param array | Form  $parameters          - array of parameters to target a specific order item. eg: group=1, length=5*
508
     *                                           - form saved into item...
509
     *
510
     * @return bool | DataObject ($orderItem)
0 ignored issues
show
Should the return type not be false|OrderItem?

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...
511
     */
512
    protected function prepareOrderItem(BuyableModel $buyable, $parameters = array(), $mustBeExistingItem = true)
513
    {
514
        $parametersArray = $parameters;
515
        $form = null;
516
        if ($parameters instanceof Form) {
517
            $parametersArray = array();
518
            $form = $parameters;
519
        }
520
        if (!$buyable) {
521
            user_error('No buyable was provided', E_USER_WARNING);
522
        }
523
        if (!$buyable->canPurchase()) {
524
            $item = $this->getExistingItem($buyable, $parametersArray);
525
            if ($item && $item->exists()) {
526
                $item->delete();
527
                $item->destroy();
528
            }
529
530
            return false;
531
        }
532
        $item = null;
0 ignored issues
show
$item 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...
533
        if ($mustBeExistingItem) {
534
            $item = $this->getExistingItem($buyable, $parametersArray);
535
        } else {
536
            $item = $this->findOrMakeItem($buyable, $parametersArray); //find existing order item or make one
537
        }
538
        if (!$item) {
539
            //check for existence of item
540
            return false;
541
        }
542
        if ($form) {
543
            $form->saveInto($item);
544
        }
545
546
        return $item;
547
    }
548
549
    /**
550
     * @todo: what does this method do???
551
     *
552
     * @return int
0 ignored issues
show
Should the return type not be false|double?

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...
553
     *
554
     * @param DataObject ($buyable)
555
     * @param float $quantity
556
     */
557
    protected function prepareQuantity(BuyableModel $buyable, $quantity)
558
    {
559
        $quantity = round($quantity, $buyable->QuantityDecimals());
560
        if ($quantity < 0 || (!$quantity && $quantity !== 0)) {
561
            $this->addMessage(_t('Order.INVALIDQUANTITY', 'Invalid quantity.'), 'warning');
562
563
            return false;
564
        }
565
566
        return $quantity;
567
    }
568
569
    /**
570
     * Helper function for making / retrieving order items.
571
     * we do not need things like "canPurchase" here, because that is with the "addBuyable" method.
572
     * NOTE: does not write!
573
     *
574
     * @param DataObject $buyable
0 ignored issues
show
Should the type for parameter $buyable not be BuyableModel?

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...
575
     * @param array      $parameters
576
     *
577
     * @return OrderItem
578
     */
579
    public function findOrMakeItem(BuyableModel $buyable, array $parameters = array())
580
    {
581
        if ($this->allowWrites()) {
582
            if ($item = $this->getExistingItem($buyable, $parameters)) {
0 ignored issues
show
This if statement is empty and can be removed.

This check looks for the bodies 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 if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

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

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
583
                //do nothing
584
            } else {
585
                //otherwise create a new item
586
                if (!($buyable instanceof BuyableModel)) {
587
                    $this->addMessage(_t('ShoppingCart.ITEMNOTFOUND', 'Item is not buyable.'), 'bad');
588
589
                    return false;
590
                }
591
                $className = $buyable->classNameForOrderItem();
592
                $item = new $className();
593
                if ($order = $this->currentOrder()) {
594
                    $item->OrderID = $order->ID;
595
                    $item->BuyableID = $buyable->ID;
0 ignored issues
show
Accessing ID on the interface BuyableModel suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
596
                    $item->BuyableClassName = $buyable->ClassName;
0 ignored issues
show
Accessing ClassName on the interface BuyableModel suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
597
                    if (isset($buyable->Version)) {
0 ignored issues
show
Accessing Version on the interface BuyableModel suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
598
                        $item->Version = $buyable->Version;
0 ignored issues
show
Accessing Version on the interface BuyableModel suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
599
                    }
600
                }
601
            }
602
            if ($parameters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parameters of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
603
                $item->Parameters = $parameters;
604
            }
605
606
            return $item;
607
        } else {
608
            return OrderItem::create();
609
        }
610
    }
611
612
    /**
613
     * submit the order so that it is no longer available
614
     * in the cart but will continue its journey through the
615
     * order steps.
616
     *
617
     * @return bool
618
     */
619
    public function submit()
620
    {
621
        if ($this->allowWrites()) {
622
            $this->currentOrder()->tryToFinaliseOrder();
623
            $this->clear();
624
            //little hack to clear static memory
625
            OrderItem::reset_price_has_been_fixed($this->currentOrder()->ID);
626
627
            return true;
628
        }
629
630
        return false;
631
    }
632
633
    /**
634
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
635
     *
636
     * @return bool | null
0 ignored issues
show
Should the return type not be boolean|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...
637
     */
638
    public function save()
639
    {
640
        if ($this->allowWrites()) {
641
            $this->currentOrder()->write();
642
            $this->addMessage(_t('Order.ORDERSAVED', 'Order Saved.'), 'good');
643
644
            return true;
645
        }
646
    }
647
648
    /**
649
     * Clears the cart contents completely by removing the orderID from session, and
650
     * thus creating a new cart on next request.
651
     *
652
     * @return bool
653
     */
654
    public function clear()
655
    {
656
        //we keep this here so that a flush can be added...
657
        set_time_limit(1 * 60);
658
        self::$_singletoncart = null;
659
        $this->order = null;
660
        $this->messages = array();
661
        foreach (self::$session_variable_names as $name) {
662
            $sessionVariableName = $this->sessionVariableName($name);
663
            Session::set($sessionVariableName, null);
664
            Session::clear($sessionVariableName);
665
            Session::save();
666
        }
667
        $memberID = Intval(Member::currentUserID());
668
        if ($memberID) {
669
            $orders = Order::get()->filter(array('MemberID' => $memberID));
670
            if ($orders && $orders->count()) {
671
                foreach ($orders as $order) {
672
                    if (! $order->IsSubmitted()) {
673
                        $order->delete();
674
                    }
675
                }
676
            }
677
        }
678
679
        return true;
680
    }
681
682
    /**
683
     * alias for clear.
684
     */
685
    public function reset()
686
    {
687
        return $this->clear();
688
    }
689
690
    /**
691
     * Removes a modifier from the cart
692
     * It does not actually remove it, but it just
693
     * sets it as "removed", to avoid that it is being
694
     * added again.
695
     *
696
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
697
     *
698
     * @param OrderModifier $modifier
699
     *
700
     * @return bool | null
0 ignored issues
show
Should the return type not be boolean|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...
701
     */
702
    public function removeModifier(OrderModifier $modifier)
703
    {
704
        if ($this->allowWrites()) {
705
            $modifier = (is_numeric($modifier)) ? OrderModifier::get()->byID($modifier) : $modifier;
706
            if (!$modifier) {
707
                $this->addMessage(_t('Order.MODIFIERNOTFOUND', 'Modifier could not be found.'), 'bad');
708
709
                return false;
710
            }
711
            if (!$modifier->CanBeRemoved()) {
712
                $this->addMessage(_t('Order.MODIFIERCANNOTBEREMOVED', 'Modifier can not be removed.'), 'bad');
713
714
                return false;
715
            }
716
            $modifier->HasBeenRemoved = 1;
717
            $modifier->onBeforeRemove();
718
            $modifier->write();
719
            $modifier->onAfterRemove();
720
            $this->addMessage(_t('Order.MODIFIERREMOVED', 'Removed.'), 'good');
721
722
            return true;
723
        }
724
    }
725
726
    /**
727
     * Removes a modifier from the cart.
728
     *
729
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
730
     *
731
     * @param Int/ OrderModifier
732
     *
733
     * @return bool
0 ignored issues
show
Should the return type not be boolean|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...
734
     */
735
    public function addModifier($modifier)
736
    {
737
        if ($this->allowWrites()) {
738
            if (is_numeric($modifier)) {
739
                $modifier = OrderModifier::get()->byID($modifier);
740
            } elseif (!(is_a($modifier, Object::getCustomClass('OrderModifier')))) {
741
                user_error('Bad parameter provided to ShoppingCart::addModifier', E_USER_WARNING);
742
            }
743
            if (!$modifier) {
744
                $this->addMessage(_t('Order.MODIFIERNOTFOUND', 'Modifier could not be found.'), 'bad');
745
746
                return false;
747
            }
748
            $modifier->HasBeenRemoved = 0;
749
            $modifier->write();
750
            $this->addMessage(_t('Order.MODIFIERREMOVED', 'Added.'), 'good');
751
752
            return true;
753
        }
754
    }
755
756
    /**
757
     * Sets an order as the current order.
758
     *
759
     * @param int | Order $order
760
     *
761
     * @return bool
762
     */
763
    public function loadOrder($order)
764
    {
765
        if ($this->allowWrites()) {
766
            //TODO: how to handle existing order
767
            //TODO: permission check - does this belong to another member? ...or should permission be assumed already?
768
            if (is_numeric($order)) {
769
                $this->order = Order::get()->byID($order);
770
            } elseif (is_a($order, Object::getCustomClass('Order'))) {
771
                $this->order = $order;
772
            } else {
773
                user_error('Bad order provided as parameter to ShoppingCart::loadOrder()');
774
            }
775
            if ($this->order) {
776
                //first can view and then, if can view, set as session...
777
                if ($this->order->canView()) {
778
                    $this->order->init(true);
779
                    $sessionVariableName = $this->sessionVariableName('OrderID');
780
                    //we set session ID after can view check ...
781
                    Session::set($sessionVariableName, $this->order->ID);
782
                    $this->addMessage(_t('Order.LOADEDEXISTING', 'Order loaded.'), 'good');
783
784
                    return true;
785
                } else {
786
                    $this->addMessage(_t('Order.NOPERMISSION', 'You do not have permission to view this order.'), 'bad');
787
788
                    return false;
789
                }
790
            } else {
791
                $this->addMessage(_t('Order.NOORDER', 'Order can not be found.'), 'bad');
792
793
                return false;
794
            }
795
        } else {
796
            $this->addMessage(_t('Order.NOSAVE', 'You can not load orders as your session functionality is turned off.'), 'bad');
797
798
            return false;
799
        }
800
    }
801
802
    /**
803
     * NOTE: tried to copy part to the Order Class - but that was not much of a go-er.
804
     *
805
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
806
     *
807
     * @param int | Order $order
808
     *
809
     * @return Order | false | null
0 ignored issues
show
Should the return type not be Order|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...
810
     **/
811
    public function copyOrder($oldOrder)
812
    {
813
        if ($this->allowWrites()) {
814
            if (is_numeric($oldOrder)) {
815
                $oldOrder = Order::get()->byID(intval($oldOrder));
816
            } elseif (is_a($oldOrder, Object::getCustomClass('Order'))) {
0 ignored issues
show
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif 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 elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
817
                //$oldOrder = $oldOrder;
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
818
            } else {
819
                user_error('Bad order provided as parameter to ShoppingCart::loadOrder()');
820
            }
821
            if ($oldOrder) {
822
                if ($oldOrder->canView() && $oldOrder->IsSubmitted()) {
823
                    $newOrder = Order::create();
824
                    //copying fields.
825
                    $newOrder->UseShippingAddress = $oldOrder->UseShippingAddress;
0 ignored issues
show
The property UseShippingAddress 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...
826
                    //important to set it this way...
827
                    $newOrder->setCurrency($oldOrder->CurrencyUsed());
828
                    $newOrder->MemberID = $oldOrder->MemberID;
0 ignored issues
show
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...
829
                    //load the order
830
                    $newOrder->write();
831
                    $this->loadOrder($newOrder);
832
                    $items = OrderItem::get()
833
                        ->filter(array('OrderID' => $oldOrder->ID));
834
                    if ($items->count()) {
835
                        foreach ($items as $item) {
836
                            $buyable = $item->Buyable($current = true);
837
                            if ($buyable->canPurchase()) {
838
                                $this->addBuyable($buyable, $item->Quantity);
839
                            }
840
                        }
841
                    }
842
                    $newOrder->CreateOrReturnExistingAddress('BillingAddress');
843
                    $newOrder->CreateOrReturnExistingAddress('ShippingAddress');
844
                    $newOrder->write();
845
                    $this->addMessage(_t('Order.ORDERCOPIED', 'Order has been copied.'), 'good');
846
847
                    return $newOrder;
848
                } else {
849
                    $this->addMessage(_t('Order.NOPERMISSION', 'You do not have permission to view this order.'), 'bad');
850
851
                    return false;
852
                }
853
            } else {
854
                $this->addMessage(_t('Order.NOORDER', 'Order can not be found.'), 'bad');
855
856
                return false;
857
            }
858
        }
859
    }
860
861
    /**
862
     * sets country in order so that modifiers can be recalculated, etc...
863
     *
864
     * @param string - $countryCode
865
     *
866
     * @return bool
0 ignored issues
show
Should the return type not be boolean|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...
867
     **/
868
    public function setCountry($countryCode)
869
    {
870
        if ($this->allowWrites()) {
871
            if (EcommerceCountry::code_allowed($countryCode)) {
872
                $this->currentOrder()->SetCountryFields($countryCode);
873
                $this->addMessage(_t('Order.UPDATEDCOUNTRY', 'Updated country.'), 'good');
874
875
                return true;
876
            } else {
877
                $this->addMessage(_t('Order.NOTUPDATEDCOUNTRY', 'Could not update country.'), 'bad');
878
879
                return false;
880
            }
881
        }
882
    }
883
884
    /**
885
     * sets region in order so that modifiers can be recalculated, etc...
886
     *
887
     * @param int | String - $regionID you can use the ID or the code.
888
     *
889
     * @return bool
890
     **/
891
    public function setRegion($regionID)
892
    {
893
        if (EcommerceRegion::regionid_allowed($regionID)) {
894
            $this->currentOrder()->SetRegionFields($regionID);
895
            $this->addMessage(_t('ShoppingCart.REGIONUPDATED', 'Region updated.'), 'good');
896
897
            return true;
898
        } else {
899
            $this->addMessage(_t('ORDER.NOTUPDATEDREGION', 'Could not update region.'), 'bad');
900
901
            return false;
902
        }
903
    }
904
905
    /**
906
     * sets the display currency for the cart.
907
     *
908
     * @param string $currencyCode
909
     *
910
     * @return bool
911
     **/
912
    public function setCurrency($currencyCode)
913
    {
914
        $currency = EcommerceCurrency::get_one_from_code($currencyCode);
915
        if ($currency) {
916
            if ($this->currentOrder()->MemberID) {
0 ignored issues
show
The property MemberID does not exist on object<Order>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read 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.");
        }
    }

}

If the property has read access only, you can use the @property-read 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...
917
                $member = $this->currentOrder()->Member();
0 ignored issues
show
The method Member() does not exist on Order. Did you maybe mean CreateOrReturnExistingMember()?

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

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

Loading history...
918
                if ($member && $member->exists()) {
919
                    $member->SetPreferredCurrency($currency);
920
                }
921
            }
922
            $this->currentOrder()->UpdateCurrency($currency);
923
            $msg = _t('Order.CURRENCYUPDATED', 'Currency updated.');
924
            $this->addMessage($msg, 'good');
925
926
            return true;
927
        } else {
928
            $msg = _t('Order.CURRENCYCOULDNOTBEUPDATED', 'Currency could not be updated.');
929
            $this->addMessage($msg, 'bad');
930
931
            return false;
932
        }
933
    }
934
935
    /**
936
     * Produces a debug of the shopping cart.
937
     */
938
    public function debug()
939
    {
940
        if (Director::isDev() || Permission::check('ADMIN')) {
941
            debug::show($this->currentOrder());
942
943
            echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Country</h1>';
944
            echo 'GEOIP Country: '.EcommerceCountry::get_country_from_ip().'<br />';
945
            echo 'Calculated Country: '.EcommerceCountry::get_country().'<br />';
946
947
            echo '<blockquote><blockquote><blockquote><blockquote>';
948
949
            echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Items</h1>';
950
            $items = $this->currentOrder()->Items();
951
            echo $items->sql();
952
            echo '<hr />';
953
            if ($items->count()) {
954
                foreach ($items as $item) {
955
                    Debug::show($item);
956
                }
957
            } else {
958
                echo '<p>there are no items for this order</p>';
959
            }
960
961
            echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Modifiers</h1>';
962
            $modifiers = $this->currentOrder()->Modifiers();
963
            if ($modifiers->count()) {
964
                foreach ($modifiers as $modifier) {
965
                    Debug::show($modifier);
966
                }
967
            } else {
968
                echo '<p>there are no modifiers for this order</p>';
969
            }
970
971
            echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Addresses</h1>';
972
            $billingAddress = $this->currentOrder()->BillingAddress();
0 ignored issues
show
The method BillingAddress() does not exist on Order. Did you maybe mean getBillingAddressField()?

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

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

Loading history...
973
            if ($billingAddress && $billingAddress->exists()) {
974
                Debug::show($billingAddress);
975
            } else {
976
                echo '<p>there is no billing address for this order</p>';
977
            }
978
            $shippingAddress = $this->currentOrder()->ShippingAddress();
0 ignored issues
show
The method ShippingAddress() does not exist on Order. Did you maybe mean getShippingAddressField()?

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

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

Loading history...
979
            if ($shippingAddress && $shippingAddress->exists()) {
980
                Debug::show($shippingAddress);
981
            } else {
982
                echo '<p>there is no shipping address for this order</p>';
983
            }
984
985
            $currencyUsed = $this->currentOrder()->CurrencyUsed();
0 ignored issues
show
Documentation Bug introduced by
The method CurrencyUsed does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
986
            if ($currencyUsed && $currencyUsed->exists()) {
987
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Currency</h1>';
988
                Debug::show($currencyUsed);
989
            }
990
991
            $cancelledBy = $this->currentOrder()->CancelledBy();
0 ignored issues
show
Documentation Bug introduced by
The method CancelledBy does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
992
            if ($cancelledBy && $cancelledBy->exists()) {
993
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Cancelled By</h1>';
994
                Debug::show($cancelledBy);
995
            }
996
997
            $logs = $this->currentOrder()->OrderStatusLogs();
0 ignored issues
show
The method OrderStatusLogs() does not exist on Order. Did you maybe mean getOrderStatusLogsTableField()?

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

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

Loading history...
998
            if ($logs && $logs->count()) {
999
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Logs</h1>';
1000
                foreach ($logs as $log) {
1001
                    Debug::show($log);
1002
                }
1003
            }
1004
1005
            $payments = $this->currentOrder()->Payments();
0 ignored issues
show
The method Payments() does not exist on Order. Did you maybe mean getPaymentsField()?

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

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

Loading history...
1006
            if ($payments  && $payments->count()) {
1007
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Payments</h1>';
1008
                foreach ($payments as $payment) {
1009
                    Debug::show($payment);
1010
                }
1011
            }
1012
1013
            $emails = $this->currentOrder()->Emails();
0 ignored issues
show
The method Emails() does not exist on Order. Did you maybe mean getEmailsTableField()?

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

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

Loading history...
1014
            if ($emails && $emails->count()) {
1015
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Emails</h1>';
1016
                foreach ($emails as $email) {
1017
                    Debug::show($email);
1018
                }
1019
            }
1020
1021
            echo '</blockquote></blockquote></blockquote></blockquote>';
1022
        } else {
1023
            echo 'Please log in as admin first';
1024
        }
1025
    }
1026
1027
    /**
1028
     * Stores a message that can later be returned via ajax or to $form->sessionMessage();.
1029
     *
1030
     * @param $message - the message, which could be a notification of successful action, or reason for failure
1031
     * @param $type - please use good, bad, warning
1032
     */
1033
    public function addMessage($message, $status = 'good')
1034
    {
1035
        //clean status for the lazy programmer
1036
        //TODO: remove the awkward replace
1037
        $status = strtolower($status);
1038
        str_replace(array('success', 'failure'), array('good', 'bad'), $status);
1039
        $statusOptions = array('good', 'bad', 'warning');
1040
        if (!in_array($status, $statusOptions)) {
1041
            user_error('Message status should be one of the following: '.implode(',', $statusOptions), E_USER_NOTICE);
1042
        }
1043
        $this->messages[] = array(
1044
            'Message' => $message,
1045
            'Type' => $status,
1046
        );
1047
    }
1048
1049
    /*******************************************************
1050
    * HELPER FUNCTIONS
1051
    *******************************************************/
1052
1053
    /**
1054
     * Gets an existing order item based on buyable and passed parameters.
1055
     *
1056
     * @param DataObject $buyable
0 ignored issues
show
Should the type for parameter $buyable not be BuyableModel?

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...
1057
     * @param array      $parameters
1058
     *
1059
     * @return OrderItem | null
0 ignored issues
show
Should the return type not be OrderItem|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...
1060
     */
1061
    protected function getExistingItem(BuyableModel $buyable, array $parameters = array())
1062
    {
1063
        $filterString = $this->parametersToSQL($parameters);
1064
        if ($order = $this->currentOrder()) {
1065
            $orderID = $order->ID;
1066
            $obj = DataObject::get_one(
1067
                'OrderItem',
1068
                " \"BuyableClassName\" = '".$buyable->ClassName."' AND
0 ignored issues
show
Accessing ClassName on the interface BuyableModel suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1069
                \"BuyableID\" = ".$buyable->ID.' AND
0 ignored issues
show
Accessing ID on the interface BuyableModel suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1070
                "OrderID" = '.$orderID.' '.
1071
                $filterString,
1072
                $cacheDataObjectGetOne = false
1073
            );
1074
            return $obj;
1075
        }
1076
    }
1077
1078
    /**
1079
     * Removes parameters that aren't in the default array, merges with default parameters, and converts raw2SQL.
1080
     *
1081
     * @param array $parameters
0 ignored issues
show
There is no parameter named $parameters. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1082
     *
1083
     * @return cleaned array
0 ignored issues
show
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...
1084
     */
1085
    protected function cleanParameters(array $params = array())
1086
    {
1087
        $defaultParamFilters = EcommerceConfig::get('ShoppingCart', 'default_param_filters');
1088
        $newarray = array_merge(array(), $defaultParamFilters); //clone array
1089
        if (!count($newarray)) {
1090
            return array(); //no use for this if there are not parameters defined
1091
        }
1092
        foreach ($newarray as $field => $value) {
1093
            if (isset($params[$field])) {
1094
                $newarray[$field] = Convert::raw2sql($params[$field]);
1095
            }
1096
        }
1097
1098
        return $newarray;
1099
    }
1100
1101
    /**
1102
     * @param array $parameters
1103
     *                          Converts parameter array to SQL query filter
1104
     */
1105
    protected function parametersToSQL(array $parameters = array())
1106
    {
1107
        $defaultParamFilters = EcommerceConfig::get('ShoppingCart', 'default_param_filters');
1108
        if (!count($defaultParamFilters)) {
1109
            return ''; //no use for this if there are not parameters defined
1110
        }
1111
        $cleanedparams = $this->cleanParameters($parameters);
1112
        $outputArray = array();
1113
        foreach ($cleanedparams as $field => $value) {
1114
            $outputarray[$field] = '"'.$field.'" = '.$value;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$outputarray was never initialized. Although not strictly required by PHP, it is generally a good practice to add $outputarray = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1115
        }
1116
        if (count($outputArray)) {
1117
            return implode(' AND ', $outputArray);
1118
        }
1119
1120
        return '';
1121
    }
1122
1123
    /*******************************************************
1124
    * UI MESSAGE HANDLING
1125
    *******************************************************/
1126
1127
    /**
1128
     * Retrieves all good, bad, and ugly messages that have been produced during the current request.
1129
     *
1130
     * @return array of messages
1131
     */
1132
    public function getMessages()
1133
    {
1134
        $sessionVariableName = $this->sessionVariableName('Messages');
1135
        //get old messages
1136
        $messages = unserialize(Session::get($sessionVariableName));
1137
        //clear old messages
1138
        Session::clear($sessionVariableName, '');
0 ignored issues
show
The call to Session::clear() has too many arguments starting with ''.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1139
        //set to form????
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% 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...
1140
        if ($messages && count($messages)) {
1141
            $this->messages = array_merge($messages, $this->messages);
1142
        }
1143
1144
        return $this->messages;
1145
    }
1146
1147
    /**
1148
     *Saves current messages in session for retrieving them later.
1149
     *
1150
     *@return array of messages
0 ignored issues
show
Should the return type not be array|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...
1151
     */
1152
    protected function StoreMessagesInSession()
1153
    {
1154
        $sessionVariableName = $this->sessionVariableName('Messages');
1155
        Session::set($sessionVariableName, serialize($this->messages));
1156
    }
1157
1158
    /**
1159
     * This method is used to return data after an ajax call was made.
1160
     * When a asynchronious request is made to the shopping cart (ajax),
1161
     * then you will first action the request and then use this function
1162
     * to return some values.
1163
     *
1164
     * It can also be used without ajax, in wich case it will redirects back
1165
     * to the last page.
1166
     *
1167
     * Note that you can set the ajax response class in the configuration file.
1168
     *
1169
     *
1170
     * @param string $message
1171
     * @param string $status
1172
     * @param Form   $form
0 ignored issues
show
Should the type for parameter $form not be null|Form?

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...
1173
     * @returns String (JSON)
1174
     */
1175
    public function setMessageAndReturn($message = '', $status = '', Form $form = null)
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
setMessageAndReturn 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...
1176
    {
1177
        if ($message && $status) {
1178
            $this->addMessage($message, $status);
1179
        }
1180
        //TODO: handle passing back multiple messages
1181
1182
        if (Director::is_ajax()) {
1183
            $responseClass = EcommerceConfig::get('ShoppingCart', 'response_class');
1184
            $obj = new $responseClass();
1185
1186
            return $obj->ReturnCartData($this->getMessages());
1187
        } else {
1188
            //TODO: handle passing a message back to a form->sessionMessage
1189
            $this->StoreMessagesInSession();
1190
            if ($form) {
1191
                //lets make sure that there is an order
1192
                $this->currentOrder();
1193
                //nowe we can (re)calculate the order
1194
                $this->order->calculateOrderAttributes($force = false);
1195
                $form->sessionMessage($message, $status);
1196
            //let the form controller do the redirectback or whatever else is needed.
1197
            } else {
1198
                if (empty($_REQUEST['BackURL']) && Controller::has_curr()) {
1199
                    Controller::curr()->redirectBack();
1200
                } else {
1201
                    Controller::curr()->redirect(urldecode($_REQUEST['BackURL']));
1202
                }
1203
            }
1204
1205
            return;
1206
        }
1207
    }
1208
1209
    /**
1210
     * @return EcommerceDBConfig
1211
     */
1212
    protected function EcomConfig()
1213
    {
1214
        return EcommerceDBConfig::current_ecommerce_db_config();
1215
    }
1216
1217
    /**
1218
     * Return the name of the session variable that should be used.
1219
     *
1220
     * @param string $name
1221
     *
1222
     * @return string
1223
     */
1224
    protected function sessionVariableName($name = '')
1225
    {
1226
        if (!in_array($name, self::$session_variable_names)) {
1227
            user_error("Tried to set session variable $name, that is not in use", E_USER_NOTICE);
1228
        }
1229
        $sessionCode = EcommerceConfig::get('ShoppingCart', 'session_code');
1230
1231
        return $sessionCode.'_'.$name;
1232
    }
1233
}
1234