ShoppingCart::currentOrder()   F
last analyzed

Complexity

Conditions 37
Paths > 20000

Size

Total Lines 135

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 37
dl 0
loc 135
rs 0
c 0
b 0
f 0
nc 49986
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * ShoppingCart - provides a global way to interface with the cart (current order).
5
 *
6
 * This can be used in other code by calling $cart = ShoppingCart::singleton();
7
 *
8
 * The shopping cart can be accessed as an order handler from the back-end
9
 * (e.g. when creating an order programmatically), while the accompagnying controller
10
 * is used by web-users to manipulate their order.
11
 *
12
 * A bunch of core functions are also stored in the order itself.
13
 * Methods and variables are in the shopping cart if they are relevant
14
 * only before (and while) the order is placed (e.g. latest update message),
15
 * and others are in the order because they are relevant even after the
16
 * order has been submitted (e.g. Total Cost).
17
 *
18
 * Key methods:
19
 *
20
 * //get Cart
21
 * $myCart = ShoppingCart::singleton();
22
 *
23
 * //get order
24
 * $myOrder = ShoppingCart::current_order();
25
 *
26
 * //view order (from another controller)
27
 * $this->redirect(ShoppingCart::current_order()->Link());
28
 *
29
 * //add item to cart
30
 * ShoppingCart::singleton()->addBuyable($myProduct);
31
 *
32
 * @authors: Nicolaas [at] Sunny Side Up .co.nz
33
 * @package: ecommerce
34
 * @sub-package: control
35
 * @inspiration: Silverstripe Ltd, Jeremy
36
 **/
37
class ShoppingCart extends Object
38
{
39
    /**
40
     * List of names that can be used as session variables.
41
     * Also @see ShoppingCart::sessionVariableName.
42
     *
43
     * @var array
44
     */
45
    private static $session_variable_names = array('OrderID', 'Messages');
46
47
    /**
48
     * This is where we hold the (singleton) Shoppingcart.
49
     *
50
     * @var object (ShoppingCart)
51
     */
52
    private static $_singletoncart = null;
53
54
    /**
55
     * Feedback message to user (e.g. cart updated, could not delete item, someone in standing behind you).
56
     *
57
     *@var array
58
     **/
59
    protected $messages = array();
60
61
    /**
62
     * stores a reference to the current order object.
63
     *
64
     * @var object
65
     **/
66
    protected $order = null;
67
68
    /**
69
     * This variable is set to YES when we actually need an order (i.e. write it).
70
     *
71
     * @var bool
72
     */
73
    protected $requireSavedOrder = false;
74
75
    /**
76
     * Allows access to the cart from anywhere in code.
77
     *
78
     * @return ShoppingCart Object
79
     */
80
    public static function singleton()
81
    {
82
        if (!self::$_singletoncart) {
83
            self::$_singletoncart = Injector::inst()->get('ShoppingCart');
84
        }
85
86
        return self::$_singletoncart;
87
    }
88
89
    /**
90
     * Allows access to the current order from anywhere in the code..
91
     *
92
     * if you do not like the session Order then you can set it here ...
93
     *
94
     * @param Order $order (optional)
0 ignored issues
show
Documentation introduced by
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...
95
     *
96
     * @return Order
97
     */
98
    public static function current_order($order = null)
99
    {
100
        return self::singleton()->currentOrder(0, $order);
101
    }
102
103
    /**
104
     * useful when the order has been updated ...
105
     */
106
    public static function reset_order_reference()
107
    {
108
        return self::singleton()->order = null;
109
    }
110
111
    /**
112
     * looks up current order id.
113
     * you may supply an ID here, so that it looks up the current order ID
114
     * only when none is supplied.
115
     *
116
     * @param int (optional) | Order $orderOrOrderID
117
     *
118
     * @return int;
0 ignored issues
show
Documentation introduced by
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...
119
     */
120
    public static function current_order_id($orderOrOrderID = 0)
0 ignored issues
show
Documentation introduced by
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...
121
    {
122
        $orderID = 0;
123
        if (! $orderOrOrderID) {
124
            $order = self::current_order();
125
            if ($order && $order->exists()) {
126
                $orderID = $order->ID;
127
            }
128
        }
129
        if ($orderOrOrderID instanceof Order) {
130
            $orderID = $orderOrOrderID->ID;
131
        } elseif (intval($orderOrOrderID)) {
132
            $orderID = intval($orderOrOrderID);
133
        }
134
135
        return $orderID;
136
    }
137
138
    /**
139
     * Allows access to the current order from anywhere in the code..
140
     *
141
     * @return Order
0 ignored issues
show
Documentation introduced by
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...
142
     */
143
    public static function session_order()
144
    {
145
        $sessionVariableName = self::singleton()->sessionVariableName('OrderID');
146
        $orderIDFromSession = intval(Session::get($sessionVariableName)) - 0;
147
148
        return Order::get()->byID($orderIDFromSession);
149
    }
150
151
    /**
152
     * set a specific order, other than the one from session ....
153
     *
154
     * @param Order $order
155
     *
156
     * @return Order
157
     */
158
    public function setOrder($order)
159
    {
160
        $this->order = $order;
161
        return $this->order;
162
    }
163
164
    /**
165
     * Gets or creates the current order.
166
     * Based on the session ONLY unless the order has been explictely set.
167
     * IMPORTANT FUNCTION!
168
     *
169
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
170
     *
171
     * However, you can pass an order in case you want to manipulate an order that is not in sesssion
172
     *
173
     * @param int $recurseCount (optional)
174
     * @param Order $order (optional)
0 ignored issues
show
Documentation introduced by
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...
175
     *
176
     * @return Order
177
     */
178
    public function currentOrder($recurseCount = 0, $order = null)
179
    {
180
        if ($order) {
181
            $this->order = $order;
182
        }
183
        if ($this->allowWrites()) {
184
            if (!$this->order) {
185
                $this->order = self::session_order();
186
                $loggedInMember = Member::currentUser();
187
                if ($this->order) {
188
                    //first reason to set to null: it is already submitted
189
                    if ($this->order->IsSubmitted()) {
190
                        $this->order = null;
191
                    }
192
                    //second reason to set to null: make sure we have permissions
193
                    elseif (!$this->order->canView()) {
194
                        $this->order = null;
195
                    }
196
                    //logged in, add Member.ID to order->MemberID
197
                    elseif ($loggedInMember && $loggedInMember->exists()) {
198
                        if ($this->order->MemberID != $loggedInMember->ID) {
0 ignored issues
show
Documentation introduced by
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...
199
                            $updateMember = false;
200
                            if (!$this->order->MemberID) {
0 ignored issues
show
Documentation introduced by
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...
201
                                $updateMember = true;
202
                            }
203
                            if (!$loggedInMember->IsShopAdmin()) {
204
                                $updateMember = true;
205
                            }
206
                            if ($updateMember) {
207
                                $this->order->MemberID = $loggedInMember->ID;
0 ignored issues
show
Documentation introduced by
The property MemberID does not exist on object<Order>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
208
                                $this->order->write();
209
                            }
210
                        }
211
                        //IF current order has nothing in it AND the member already has an order: use the old one first
212
                        //first, lets check if the current order is worthwhile keeping
213
                        if ($this->order->StatusID || $this->order->TotalItems()) {
0 ignored issues
show
Documentation introduced by
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...
214
                            //do NOTHING!
215
                        } else {
216
                            $firstStep = DataObject::get_one('OrderStep');
217
                            //we assume the first step always exists.
218
                            //TODO: what sort order?
219
                            $count = 0;
220
                            while (
221
                                $firstStep &&
222
                                $previousOrderFromMember = DataObject::get_one(
223
                                    'Order',
224
                                    '
225
                                        "MemberID" = '.$loggedInMember->ID.'
226
                                        AND ("StatusID" = '.$firstStep->ID.' OR "StatusID" = 0)
227
                                        AND "Order"."ID" <> '.$this->order->ID
228
                                )
229
                            ) {
230
                                //arbritary 12 attempts ...
231
                                if ($count > 12) {
232
                                    break;
233
                                }
234
                                ++$count;
235
                                if ($previousOrderFromMember && $previousOrderFromMember->canView()) {
236
                                    if ($previousOrderFromMember->StatusID || $previousOrderFromMember->TotalItems()) {
237
                                        $this->order->delete();
238
                                        $this->order = $previousOrderFromMember;
239
                                        break;
240
                                    } else {
241
                                        $previousOrderFromMember->delete();
242
                                    }
243
                                }
244
                            }
245
                        }
246
                    }
247
                }
248
                if (! $this->order) {
249
                    if ($loggedInMember) {
250
                        //find previour order...
251
                        $firstStep = DataObject::get_one('OrderStep');
252
                        if ($firstStep) {
253
                            $previousOrderFromMember = Order::get()
254
                                ->filter(
255
                                    array(
256
                                        'MemberID' => $loggedInMember->ID,
257
                                        'StatusID' => array($firstStep->ID, 0),
258
                                    )
259
                                )->first();
260
                            if ($previousOrderFromMember) {
261
                                if ($previousOrderFromMember->canView()) {
262
                                    $this->order = $previousOrderFromMember;
263
                                }
264
                            }
265
                        }
266
                    }
267
                    if ($this->order && !$this->order->exists()) {
268
                        $this->order = null;
269
                    }
270
                    if (! $this->order) {
271
                        //here we cleanup old orders, because they should be
272
                        //cleaned at the same rate that they are created...
273
                        if (EcommerceConfig::get('ShoppingCart', 'cleanup_every_time')) {
274
                            $cartCleanupTask = Injector::inst()->get('EcommerceTaskCartCleanup');
275
                            $cartCleanupTask->runSilently();
276
                        }
277
                        //create new order
278
                        $this->order = Order::create();
279
                        if ($loggedInMember) {
280
                            $this->order->MemberID = $loggedInMember->ID;
0 ignored issues
show
Documentation introduced by
The property MemberID does not exist on object<Order>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
281
                        }
282
                        $this->order->write();
283
                    }
284
                    $sessionVariableName = $this->sessionVariableName('OrderID');
285
                    Session::set($sessionVariableName, intval($this->order->ID));
286
                }
287
                if ($this->order) {
288
                    if ($this->order->exists()) {
289
                        $this->order->calculateOrderAttributes($force = false);
290
                    }
291
                    if (! $this->order->SessionID) {
292
                        $this->order->write();
293
                    }
294
                    //add session ID...
295
                }
296
            }
297
            //try it again
298
            //but limit to three, just in case ...
299
            //just in case ...
300
            if (!$this->order && $recurseCount < 3) {
301
                ++$recurseCount;
302
303
                return $this->currentOrder($recurseCount, $order);
304
            }
305
306
            return $this->order;
307
        } else {
308
309
            //we still return an order so that we do not end up with errors...
310
            return Order::create();
311
        }
312
    }
313
314
315
    private static $_allow_writes_cache = null;
316
317
    /**
318
     * can the current user use sessions and therefore write to cart???
319
     * the method also returns if an order has explicitely been set
320
     * @return bool
321
     */
322
    protected function allowWrites()
323
    {
324
        if (self::$_allow_writes_cache === null) {
325
            if ($this->order) {
326
                self::$_allow_writes_cache = true;
327
            } else {
328
                if (php_sapi_name() !== 'cli') {
329
                    if (version_compare(phpversion(), '5.4.0', '>=')) {
330
                        self::$_allow_writes_cache = (session_status() === PHP_SESSION_ACTIVE ? true : false);
331
                    } else {
332
                        self::$_allow_writes_cache = (session_id() === '' ? false : true);
333
                    }
334
                } else {
335
                    self::$_allow_writes_cache = false;
336
                }
337
            }
338
        }
339
340
        return self::$_allow_writes_cache;
341
    }
342
343
    /**
344
     * Allows access to the current order from anywhere in the code..
345
     *
346
     * @param Order $order (optional)
0 ignored issues
show
Documentation introduced by
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...
347
     *
348
     * @return ShoppingCart Object
349
     */
350
    public function Link($order = null)
0 ignored issues
show
Unused Code introduced by
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...
351
    {
352
        $order = self::singleton()->currentOrder(0, $order = null);
353
        if ($order) {
354
            return $order->Link();
355
        }
356
    }
357
358
    /**
359
     * Adds any number of items to the cart.
360
     * Returns the order item on succes OR false on failure.
361
     *
362
     * @param DataObject $buyable    - the buyable (generally a product) being added to the cart
0 ignored issues
show
Documentation introduced by
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...
363
     * @param float      $quantity   - number of items add.
0 ignored issues
show
Documentation introduced by
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...
364
     * @param mixed      $parameters - array of parameters to target a specific order item. eg: group=1, length=5
365
     *                                 if you make it a form, it will save the form into the orderitem
366
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
367
     *
368
     * @return false | DataObject (OrderItem)
0 ignored issues
show
Documentation introduced by
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...
369
     */
370
    public function addBuyable(BuyableModel $buyable, $quantity = 1, $parameters = array())
371
    {
372
        if ($this->allowWrites()) {
373
            if (!$buyable) {
374
                $this->addMessage(_t('Order.ITEMCOULDNOTBEFOUND', 'This item could not be found.'), 'bad');
375
                return false;
376
            }
377
            if (!$buyable->canPurchase()) {
378
                $this->addMessage(_t('Order.ITEMCOULDNOTBEADDED', 'This item is not for sale.'), 'bad');
379
                return false;
380
            }
381
            $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false);
382
            $quantity = $this->prepareQuantity($buyable, $quantity);
383
            if ($item && $quantity) { //find existing order item or make one
384
                $item->Quantity += $quantity;
0 ignored issues
show
Documentation introduced by
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...
385
                $item->write();
386
                $this->currentOrder()->Attributes()->add($item); //save to current order
0 ignored issues
show
Bug introduced by
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...
387
                //TODO: distinquish between incremented and set
388
                //TODO: use sprintf to allow product name etc to be included in message
389
                if ($quantity > 1) {
390
                    $msg = _t('Order.ITEMSADDED', 'Items added.');
391
                } else {
392
                    $msg = _t('Order.ITEMADDED', 'Item added.');
393
                }
394
                $this->addMessage($msg, 'good');
395
            } elseif (!$item) {
396
                $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad');
397
            } else {
398
                $this->addMessage(_t('Order.ITEMCOULDNOTBEADDED', 'Item could not be added.'), 'bad');
399
            }
400
401
            return $item;
402
        }
403
    }
404
405
    /**
406
     * Sets quantity for an item in the cart.
407
     *
408
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
409
     *
410
     * @param DataObject $buyable    - the buyable (generally a product) being added to the cart
0 ignored issues
show
Documentation introduced by
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...
411
     * @param float      $quantity   - number of items add.
412
     * @param array      $parameters - array of parameters to target a specific order item. eg: group=1, length=5
413
     *
414
     * @return false | DataObject (OrderItem) | null
0 ignored issues
show
Documentation introduced by
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...
415
     */
416
    public function setQuantity(BuyableModel $buyable, $quantity, array $parameters = array())
417
    {
418
        if ($this->allowWrites()) {
419
            $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false);
420
            $quantity = $this->prepareQuantity($buyable, $quantity);
421
            if ($item) {
422
                $item->Quantity = $quantity; //remove quantity
0 ignored issues
show
Documentation introduced by
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...
423
                $item->write();
424
                $this->addMessage(_t('Order.ITEMUPDATED', 'Item updated.'), 'good');
425
426
                return $item;
427
            } else {
428
                $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad');
429
            }
430
431
            return false;
432
        }
433
    }
434
435
    /**
436
     * Removes any number of items from the cart.
437
     *
438
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
439
     *
440
     * @param DataObject $buyable    - the buyable (generally a product) being added to the cart
0 ignored issues
show
Documentation introduced by
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...
441
     * @param float      $quantity   - number of items add.
0 ignored issues
show
Documentation introduced by
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...
442
     * @param array      $parameters - array of parameters to target a specific order item. eg: group=1, length=5
443
     *
444
     * @return false | OrderItem | null
0 ignored issues
show
Documentation introduced by
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...
445
     */
446
    public function decrementBuyable(BuyableModel $buyable, $quantity = 1, array $parameters = array())
447
    {
448
        if ($this->allowWrites()) {
449
            $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false);
450
            $quantity = $this->prepareQuantity($buyable, $quantity);
451
            if ($item) {
452
                $item->Quantity -= $quantity; //remove quantity
0 ignored issues
show
Documentation introduced by
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...
453
                if ($item->Quantity < 0) {
0 ignored issues
show
Documentation introduced by
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...
454
                    $item->Quantity = 0;
0 ignored issues
show
Documentation introduced by
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...
455
                }
456
                $item->write();
457
                if ($quantity > 1) {
458
                    $msg = _t('Order.ITEMSREMOVED', 'Items removed.');
459
                } else {
460
                    $msg = _t('Order.ITEMREMOVED', 'Item removed.');
461
                }
462
                $this->addMessage($msg, 'good');
463
464
                return $item;
465
            } else {
466
                $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad');
467
            }
468
469
            return false;
470
        }
471
    }
472
473
    /**
474
     * Delete item from the cart.
475
     *
476
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
477
     *
478
     * @param OrderItem $buyable    - the buyable (generally a product) being added to the cart
0 ignored issues
show
Documentation introduced by
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...
479
     * @param array     $parameters - array of parameters to target a specific order item. eg: group=1, length=5
480
     *
481
     * @return bool | item | null - successfully removed
0 ignored issues
show
Documentation introduced by
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...
482
     */
483
    public function deleteBuyable(BuyableModel $buyable, array $parameters = array())
484
    {
485
        if ($this->allowWrites()) {
486
            $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = true);
487
            if ($item) {
488
                $this->currentOrder()->Attributes()->remove($item);
0 ignored issues
show
Bug introduced by
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...
489
                $item->delete();
490
                $item->destroy();
491
                $this->addMessage(_t('Order.ITEMCOMPLETELYREMOVED', 'Item removed from cart.'), 'good');
492
493
                return $item;
494
            } else {
495
                $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad');
496
497
                return false;
498
            }
499
        }
500
    }
501
502
    /**
503
     * Checks and prepares variables for a quantity change (add, edit, remove) for an Order Item.
504
     *
505
     * @param DataObject    $buyable             - the buyable (generally a product) being added to the cart
0 ignored issues
show
Documentation introduced by
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...
506
     * @param float         $quantity            - number of items add.
0 ignored issues
show
Bug introduced by
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...
507
     * @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
Documentation introduced by
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...
508
     * @param array | Form  $parameters          - array of parameters to target a specific order item. eg: group=1, length=5*
509
     *                                           - form saved into item...
510
     *
511
     * @return bool | DataObject ($orderItem)
0 ignored issues
show
Documentation introduced by
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...
512
     */
513
    public function prepareOrderItem(BuyableModel $buyable, $parameters = array(), $mustBeExistingItem = true)
514
    {
515
        $parametersArray = $parameters;
516
        $form = null;
517
        if ($parameters instanceof Form) {
518
            $parametersArray = array();
519
            $form = $parameters;
520
        }
521
        if (!$buyable) {
522
            user_error('No buyable was provided', E_USER_WARNING);
523
        }
524
        if (! $buyable->canPurchase()) {
525
            return false;
526
        }
527
        $item = null;
0 ignored issues
show
Unused Code introduced by
$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...
528
        if ($mustBeExistingItem) {
529
            $item = $this->getExistingItem($buyable, $parametersArray);
530
        } else {
531
            $item = $this->findOrMakeItem($buyable, $parametersArray); //find existing order item or make one
532
        }
533
        if (!$item) {
534
            //check for existence of item
535
            return false;
536
        }
537
        if ($form) {
538
            $form->saveInto($item);
539
        }
540
541
        return $item;
542
    }
543
544
    /**
545
     * @todo: what does this method do???
546
     *
547
     *
548
     * @param DataObject ($buyable)
549
     * @param float $quantity
550
     *
551
     * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be double|integer?

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...
552
     */
553
    public function prepareQuantity(BuyableModel $buyable, $quantity)
554
    {
555
        $quantity = round($quantity, $buyable->QuantityDecimals());
556
        if ($quantity > 0) {
557
558
            return $quantity;
559
        } else {
560
            $this->addMessage(_t('Order.INVALIDQUANTITY', 'Invalid quantity.'), 'warning');
561
562
            return 0;
563
        }
564
565
    }
566
567
    /**
568
     * Helper function for making / retrieving order items.
569
     * we do not need things like "canPurchase" here, because that is with the "addBuyable" method.
570
     * NOTE: does not write!
571
     *
572
     * @param DataObject $buyable
0 ignored issues
show
Documentation introduced by
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...
573
     * @param array      $parameters
574
     *
575
     * @return OrderItem
576
     */
577
    public function findOrMakeItem(BuyableModel $buyable, array $parameters = array())
578
    {
579
        if ($this->allowWrites()) {
580
            if ($item = $this->getExistingItem($buyable, $parameters)) {
581
                //do nothing
582
            } else {
583
                //otherwise create a new item
584
                if (!($buyable instanceof BuyableModel)) {
585
                    $this->addMessage(_t('ShoppingCart.ITEMNOTFOUND', 'Item is not buyable.'), 'bad');
586
587
                    return false;
588
                }
589
                $className = $buyable->classNameForOrderItem();
590
                $item = new $className();
591
                if ($order = $this->currentOrder()) {
592
                    $item->OrderID = $order->ID;
593
                    $item->BuyableID = $buyable->ID;
0 ignored issues
show
Bug introduced by
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...
594
                    $item->BuyableClassName = $buyable->ClassName;
0 ignored issues
show
Bug introduced by
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...
595
                    if (isset($buyable->Version)) {
0 ignored issues
show
Bug introduced by
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...
596
                        $item->Version = $buyable->Version;
0 ignored issues
show
Bug introduced by
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...
597
                    }
598
                }
599
            }
600
            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...
601
                $item->Parameters = $parameters;
602
            }
603
604
            return $item;
605
        } else {
606
            return OrderItem::create();
607
        }
608
    }
609
610
    /**
611
     * submit the order so that it is no longer available
612
     * in the cart but will continue its journey through the
613
     * order steps.
614
     *
615
     * @return bool
616
     */
617
    public function submit()
618
    {
619
        if ($this->allowWrites()) {
620
            $this->currentOrder()->tryToFinaliseOrder();
621
            $this->clear();
622
            //little hack to clear static memory
623
            OrderItem::reset_price_has_been_fixed($this->currentOrder()->ID);
624
625
            return true;
626
        }
627
628
        return false;
629
    }
630
631
    /**
632
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
633
     *
634
     * @return bool | null
0 ignored issues
show
Documentation introduced by
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...
635
     */
636
    public function save()
637
    {
638
        if ($this->allowWrites()) {
639
            $this->currentOrder()->write();
640
            $this->addMessage(_t('Order.ORDERSAVED', 'Order Saved.'), 'good');
641
642
            return true;
643
        }
644
    }
645
646
    /**
647
     * Clears the cart contents completely by removing the orderID from session, and
648
     * thus creating a new cart on next request.
649
     *
650
     * @return bool
651
     */
652
    public function clear()
653
    {
654
        //we keep this here so that a flush can be added...
655
        set_time_limit(1 * 60);
656
        self::$_singletoncart = null;
657
        $this->order = null;
658
        $this->messages = array();
659
        foreach (self::$session_variable_names as $name) {
660
            $sessionVariableName = $this->sessionVariableName($name);
661
            Session::set($sessionVariableName, null);
662
            Session::clear($sessionVariableName);
663
            Session::save();
664
        }
665
        $memberID = Intval(Member::currentUserID());
666
        if ($memberID) {
667
            $orders = Order::get()->filter(array('MemberID' => $memberID));
668
            if ($orders && $orders->count()) {
669
                foreach ($orders as $order) {
670
                    if (! $order->IsSubmitted()) {
671
                        $order->delete();
672
                    }
673
                }
674
            }
675
        }
676
677
        return true;
678
    }
679
680
    /**
681
     * alias for clear.
682
     */
683
    public function reset()
684
    {
685
        return $this->clear();
686
    }
687
688
    /**
689
     * Removes a modifier from the cart
690
     * It does not actually remove it, but it just
691
     * sets it as "removed", to avoid that it is being
692
     * added again.
693
     *
694
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
695
     *
696
     * @param OrderModifier $modifier
697
     *
698
     * @return bool | null
0 ignored issues
show
Documentation introduced by
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...
699
     */
700
    public function removeModifier(OrderModifier $modifier)
701
    {
702
        if ($this->allowWrites()) {
703
            $modifier = (is_numeric($modifier)) ? OrderModifier::get()->byID($modifier) : $modifier;
704
            if (!$modifier) {
705
                $this->addMessage(_t('Order.MODIFIERNOTFOUND', 'Modifier could not be found.'), 'bad');
706
707
                return false;
708
            }
709
            if (!$modifier->CanBeRemoved()) {
710
                $this->addMessage(_t('Order.MODIFIERCANNOTBEREMOVED', 'Modifier can not be removed.'), 'bad');
711
712
                return false;
713
            }
714
            $modifier->HasBeenRemoved = 1;
715
            $modifier->onBeforeRemove();
716
            $modifier->write();
717
            $modifier->onAfterRemove();
718
            $this->addMessage(_t('Order.MODIFIERREMOVED', 'Removed.'), 'good');
719
720
            return true;
721
        }
722
    }
723
724
    /**
725
     * Removes a modifier from the cart.
726
     *
727
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
728
     *
729
     * @param Int/ OrderModifier
730
     *
731
     * @return bool
0 ignored issues
show
Documentation introduced by
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...
732
     */
733
    public function addModifier($modifier)
734
    {
735
        if ($this->allowWrites()) {
736
            if (is_numeric($modifier)) {
737
                $modifier = OrderModifier::get()->byID($modifier);
738
            } elseif (!(is_a($modifier, Object::getCustomClass('OrderModifier')))) {
739
                user_error('Bad parameter provided to ShoppingCart::addModifier', E_USER_WARNING);
740
            }
741
            if (!$modifier) {
742
                $this->addMessage(_t('Order.MODIFIERNOTFOUND', 'Modifier could not be found.'), 'bad');
743
744
                return false;
745
            }
746
            $modifier->HasBeenRemoved = 0;
747
            $modifier->write();
748
            $this->addMessage(_t('Order.MODIFIERREMOVED', 'Added.'), 'good');
749
750
            return true;
751
        }
752
    }
753
754
    /**
755
     * Sets an order as the current order.
756
     *
757
     * @param int | Order $order
758
     *
759
     * @return bool
760
     */
761
    public function loadOrder($order)
762
    {
763
        if ($this->allowWrites()) {
764
            //TODO: how to handle existing order
765
            //TODO: permission check - does this belong to another member? ...or should permission be assumed already?
766
            if (is_numeric($order)) {
767
                $this->order = Order::get()->byID($order);
768
            } elseif (is_a($order, Object::getCustomClass('Order'))) {
769
                $this->order = $order;
770
            } else {
771
                user_error('Bad order provided as parameter to ShoppingCart::loadOrder()');
772
            }
773
            if ($this->order) {
774
                //first can view and then, if can view, set as session...
775
                if ($this->order->canView()) {
776
                    $this->order->init(true);
777
                    $sessionVariableName = $this->sessionVariableName('OrderID');
778
                    //we set session ID after can view check ...
779
                    Session::set($sessionVariableName, $this->order->ID);
780
                    $this->addMessage(_t('Order.LOADEDEXISTING', 'Order loaded.'), 'good');
781
782
                    return true;
783
                } else {
784
                    $this->addMessage(_t('Order.NOPERMISSION', 'You do not have permission to view this order.'), 'bad');
785
786
                    return false;
787
                }
788
            } else {
789
                $this->addMessage(_t('Order.NOORDER', 'Order can not be found.'), 'bad');
790
791
                return false;
792
            }
793
        } else {
794
            $this->addMessage(_t('Order.NOSAVE', 'You can not load orders as your session functionality is turned off.'), 'bad');
795
796
            return false;
797
        }
798
    }
799
800
    /**
801
     * NOTE: tried to copy part to the Order Class - but that was not much of a go-er.
802
     *
803
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
804
     *
805
     * @param int | Order $order
806
     *
807
     * @return Order | false | null
0 ignored issues
show
Documentation introduced by
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...
808
     **/
809
    public function copyOrder($oldOrder)
810
    {
811
        if ($this->allowWrites()) {
812
            if (is_numeric($oldOrder)) {
813
                $oldOrder = Order::get()->byID(intval($oldOrder));
814
            } elseif (is_a($oldOrder, Object::getCustomClass('Order'))) {
815
                //$oldOrder = $oldOrder;
816
            } else {
817
                user_error('Bad order provided as parameter to ShoppingCart::loadOrder()');
818
            }
819
            if ($oldOrder) {
820
                if ($oldOrder->canView() && $oldOrder->IsSubmitted()) {
821
822
                    $this->addMessage(_t('Order.ORDERCOPIED', 'Order has been copied.'), 'good');
823
                    $newOrder = Order::create();
824
                    $newOrder = $this->CopyOrderOnly($oldOrder, $newOrder);
825
826
                    $buyables = [];
827
                    $items = OrderItem::get()
828
                        ->filter(array('OrderID' => $oldOrder->ID));
829
                    if ($items->count()) {
830
                        foreach ($items as $item) {
831
                            $buyables[] = $item->Buyable($current = true);
832
                        }
833
                    }
834
                    if(count($buyables)) {
835
                        $newOrder = $this->CopyBuyablesToNewOrder($newOrder, $buyables, $parameters = []);
836
                    }
837
                    $this->loadOrder($newOrder);
838
839
                    return $newOrder;
840
841
                } else {
842
                    $this->addMessage(_t('Order.NOPERMISSION', 'You do not have permission to view this order.'), 'bad');
843
844
                    return false;
845
                }
846
            } else {
847
                $this->addMessage(_t('Order.NOORDER', 'Order can not be found.'), 'bad');
848
849
                return false;
850
            }
851
        }
852
    }
853
854
    /**
855
     *
856
     * @param Order $oldOrder
857
     * @param Order $newOrder
858
     *
859
     * @return Ordeer (the new order)
0 ignored issues
show
Documentation introduced by
Should the return type not be Order?

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...
860
     */
861
    public function CopyOrderOnly($oldOrder, $newOrder)
862
    {
863
864
        //copying fields.
865
        $newOrder->UseShippingAddress = $oldOrder->UseShippingAddress;
0 ignored issues
show
Documentation introduced by
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...
866
        //important to set it this way...
867
        $newOrder->setCurrency($oldOrder->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...
868
        $newOrder->MemberID = $oldOrder->MemberID;
0 ignored issues
show
Documentation introduced by
The property MemberID does not exist on object<Order>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
869
        //load the order
870
        $newOrder->write();
871
        $newOrder->CreateOrReturnExistingAddress('BillingAddress');
872
        $newOrder->CreateOrReturnExistingAddress('ShippingAddress');
873
        $newOrder->write();
874
875
        return $newOrder;
876
    }
877
878
    /**
879
     * add buyables into new Order
880
     *
881
     * @param  Order $newOrder
882
     * @param  array $buyables   can also be another iterable object (e.g. ArrayList)
883
     * @param  array  $parameters
884
     *
885
     * @return Order (same order as was passed)
886
     */
887
    public function CopyBuyablesToNewOrder($newOrder, $buyables, $parameters = [])
888
    {
889
        foreach ($buyables as $buyable) {
890
            if ($buyable && $buyable->canPurchase()) {
891
                $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false);
892
                $quantity = $this->prepareQuantity($buyable, $quantity);
0 ignored issues
show
Bug introduced by
The variable $quantity does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
893
                if ($item && $quantity) {
894
                    $item->Quantity = $quantity;
0 ignored issues
show
Documentation introduced by
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...
895
                    $item->write();
896
                    $newOrder->Attributes()->add($item); //save to new order order
0 ignored issues
show
Bug introduced by
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...
897
                }
898
            }
899
            $newOrder->write();
900
        }
901
902
        return $newOrder;
903
    }
904
905
    /**
906
     * sets country in order so that modifiers can be recalculated, etc...
907
     *
908
     * @param string - $countryCode
909
     *
910
     * @return bool
0 ignored issues
show
Documentation introduced by
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...
911
     **/
912
    public function setCountry($countryCode)
913
    {
914
        if ($this->allowWrites()) {
915
            if (EcommerceCountry::code_allowed($countryCode)) {
916
                $this->currentOrder()->SetCountryFields($countryCode);
917
                $this->addMessage(_t('Order.UPDATEDCOUNTRY', 'Updated country.'), 'good');
918
919
                return true;
920
            } else {
921
                $this->addMessage(_t('Order.NOTUPDATEDCOUNTRY', 'Could not update country.'), 'bad');
922
923
                return false;
924
            }
925
        }
926
    }
927
928
    /**
929
     * sets region in order so that modifiers can be recalculated, etc...
930
     *
931
     * @param int | String - $regionID you can use the ID or the code.
932
     *
933
     * @return bool
934
     **/
935
    public function setRegion($regionID)
936
    {
937
        if (EcommerceRegion::regionid_allowed($regionID)) {
938
            $this->currentOrder()->SetRegionFields($regionID);
939
            $this->addMessage(_t('ShoppingCart.REGIONUPDATED', 'Region updated.'), 'good');
940
941
            return true;
942
        } else {
943
            $this->addMessage(_t('ORDER.NOTUPDATEDREGION', 'Could not update region.'), 'bad');
944
945
            return false;
946
        }
947
    }
948
949
    /**
950
     * sets the display currency for the cart.
951
     *
952
     * @param string $currencyCode
953
     *
954
     * @return bool
955
     **/
956
    public function setCurrency($currencyCode)
957
    {
958
        $currency = EcommerceCurrency::get_one_from_code($currencyCode);
959
        if ($currency) {
960
            if ($this->currentOrder()->MemberID) {
0 ignored issues
show
Documentation introduced by
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...
961
                $member = $this->currentOrder()->Member();
0 ignored issues
show
Bug introduced by
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...
962
                if ($member && $member->exists()) {
963
                    $member->SetPreferredCurrency($currency);
964
                }
965
            }
966
            $this->currentOrder()->UpdateCurrency($currency);
967
            $msg = _t('Order.CURRENCYUPDATED', 'Currency updated.');
968
            $this->addMessage($msg, 'good');
969
970
            return true;
971
        } else {
972
            $msg = _t('Order.CURRENCYCOULDNOTBEUPDATED', 'Currency could not be updated.');
973
            $this->addMessage($msg, 'bad');
974
975
            return false;
976
        }
977
    }
978
979
    /**
980
     * Produces a debug of the shopping cart.
981
     */
982
    public function debug()
983
    {
984
        if (Director::isDev() || Permission::check('ADMIN')) {
985
            debug::show($this->currentOrder());
986
987
            echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Country</h1>';
988
            echo 'GEOIP Country: '.EcommerceCountry::get_country_from_ip().'<br />';
989
            echo 'Calculated Country: '.EcommerceCountry::get_country().'<br />';
990
991
            echo '<blockquote><blockquote><blockquote><blockquote>';
992
993
            echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Items</h1>';
994
            $items = $this->currentOrder()->Items();
995
            echo $items->sql();
996
            echo '<hr />';
997
            if ($items->count()) {
998
                foreach ($items as $item) {
999
                    Debug::show($item);
1000
                }
1001
            } else {
1002
                echo '<p>there are no items for this order</p>';
1003
            }
1004
1005
            echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Modifiers</h1>';
1006
            $modifiers = $this->currentOrder()->Modifiers();
1007
            if ($modifiers->count()) {
1008
                foreach ($modifiers as $modifier) {
1009
                    Debug::show($modifier);
1010
                }
1011
            } else {
1012
                echo '<p>there are no modifiers for this order</p>';
1013
            }
1014
1015
            echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Addresses</h1>';
1016
            $billingAddress = $this->currentOrder()->BillingAddress();
0 ignored issues
show
Bug introduced by
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...
1017
            if ($billingAddress && $billingAddress->exists()) {
1018
                Debug::show($billingAddress);
1019
            } else {
1020
                echo '<p>there is no billing address for this order</p>';
1021
            }
1022
            $shippingAddress = $this->currentOrder()->ShippingAddress();
0 ignored issues
show
Bug introduced by
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...
1023
            if ($shippingAddress && $shippingAddress->exists()) {
1024
                Debug::show($shippingAddress);
1025
            } else {
1026
                echo '<p>there is no shipping address for this order</p>';
1027
            }
1028
1029
            $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...
1030
            if ($currencyUsed && $currencyUsed->exists()) {
1031
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Currency</h1>';
1032
                Debug::show($currencyUsed);
1033
            }
1034
1035
            $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...
1036
            if ($cancelledBy && $cancelledBy->exists()) {
1037
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Cancelled By</h1>';
1038
                Debug::show($cancelledBy);
1039
            }
1040
1041
            $logs = $this->currentOrder()->OrderStatusLogs();
0 ignored issues
show
Bug introduced by
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...
1042
            if ($logs && $logs->count()) {
1043
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Logs</h1>';
1044
                foreach ($logs as $log) {
1045
                    Debug::show($log);
1046
                }
1047
            }
1048
1049
            $payments = $this->currentOrder()->Payments();
0 ignored issues
show
Bug introduced by
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...
1050
            if ($payments  && $payments->count()) {
1051
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Payments</h1>';
1052
                foreach ($payments as $payment) {
1053
                    Debug::show($payment);
1054
                }
1055
            }
1056
1057
            $emails = $this->currentOrder()->Emails();
0 ignored issues
show
Bug introduced by
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...
1058
            if ($emails && $emails->count()) {
1059
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Emails</h1>';
1060
                foreach ($emails as $email) {
1061
                    Debug::show($email);
1062
                }
1063
            }
1064
1065
            echo '</blockquote></blockquote></blockquote></blockquote>';
1066
        } else {
1067
            echo 'Please log in as admin first';
1068
        }
1069
    }
1070
1071
    /**
1072
     * Stores a message that can later be returned via ajax or to $form->sessionMessage();.
1073
     *
1074
     * @param $message - the message, which could be a notification of successful action, or reason for failure
1075
     * @param $type - please use good, bad, warning
1076
     */
1077
    public function addMessage($message, $status = 'good')
1078
    {
1079
        //clean status for the lazy programmer
1080
        //TODO: remove the awkward replace
1081
        $status = strtolower($status);
1082
        str_replace(array('success', 'failure'), array('good', 'bad'), $status);
1083
        $statusOptions = array('good', 'bad', 'warning');
1084
        if (!in_array($status, $statusOptions)) {
1085
            user_error('Message status should be one of the following: '.implode(',', $statusOptions), E_USER_NOTICE);
1086
        }
1087
        $this->messages[] = array(
1088
            'Message' => $message,
1089
            'Type' => $status,
1090
        );
1091
    }
1092
1093
    /*******************************************************
1094
    * HELPER FUNCTIONS
1095
    *******************************************************/
1096
1097
    /**
1098
     * Gets an existing order item based on buyable and passed parameters.
1099
     *
1100
     * @param DataObject $buyable
0 ignored issues
show
Documentation introduced by
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...
1101
     * @param array      $parameters
1102
     *
1103
     * @return OrderItem | null
0 ignored issues
show
Documentation introduced by
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...
1104
     */
1105
    protected function getExistingItem(BuyableModel $buyable, array $parameters = array())
1106
    {
1107
        $filterString = $this->parametersToSQL($parameters);
1108
        if ($order = $this->currentOrder()) {
1109
            $orderID = $order->ID;
1110
            $obj = DataObject::get_one(
1111
                'OrderItem',
1112
                " \"BuyableClassName\" = '".$buyable->ClassName."' AND
0 ignored issues
show
Bug introduced by
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...
1113
                \"BuyableID\" = ".$buyable->ID.' AND
0 ignored issues
show
Bug introduced by
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...
1114
                "OrderID" = '.$orderID.' '.
1115
                $filterString,
1116
                $cacheDataObjectGetOne = false
1117
            );
1118
            return $obj;
1119
        }
1120
    }
1121
1122
    /**
1123
     * Removes parameters that aren't in the default array, merges with default parameters, and converts raw2SQL.
1124
     *
1125
     * @param array $parameters
0 ignored issues
show
Bug introduced by
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...
1126
     *
1127
     * @return cleaned array
0 ignored issues
show
Documentation introduced by
Should the return type not be array?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1128
     */
1129
    protected function cleanParameters(array $params = array())
1130
    {
1131
        $defaultParamFilters = EcommerceConfig::get('ShoppingCart', 'default_param_filters');
1132
        $newarray = array_merge(array(), $defaultParamFilters); //clone array
1133
        if (!count($newarray)) {
1134
            return array(); //no use for this if there are not parameters defined
1135
        }
1136
        foreach ($newarray as $field => $value) {
1137
            if (isset($params[$field])) {
1138
                $newarray[$field] = Convert::raw2sql($params[$field]);
1139
            }
1140
        }
1141
1142
        return $newarray;
1143
    }
1144
1145
    /**
1146
     * @param array $parameters
1147
     *                          Converts parameter array to SQL query filter
1148
     */
1149
    protected function parametersToSQL(array $parameters = array())
1150
    {
1151
        $defaultParamFilters = EcommerceConfig::get('ShoppingCart', 'default_param_filters');
1152
        if (!count($defaultParamFilters)) {
1153
            return ''; //no use for this if there are not parameters defined
1154
        }
1155
        $cleanedparams = $this->cleanParameters($parameters);
1156
        $outputArray = array();
1157
        foreach ($cleanedparams as $field => $value) {
1158
            $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...
1159
        }
1160
        if (count($outputArray)) {
1161
            return implode(' AND ', $outputArray);
1162
        }
1163
1164
        return '';
1165
    }
1166
1167
    /*******************************************************
1168
    * UI MESSAGE HANDLING
1169
    *******************************************************/
1170
1171
    /**
1172
     * Retrieves all good, bad, and ugly messages that have been produced during the current request.
1173
     *
1174
     * @return array of messages
1175
     */
1176
    public function getMessages()
1177
    {
1178
        $sessionVariableName = $this->sessionVariableName('Messages');
1179
        //get old messages
1180
        $messages = unserialize(Session::get($sessionVariableName));
1181
        //clear old messages
1182
        Session::clear($sessionVariableName, '');
0 ignored issues
show
Unused Code introduced by
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...
1183
        //set to form????
1184
        if ($messages && count($messages)) {
1185
            $this->messages = array_merge($messages, $this->messages);
1186
        }
1187
1188
        return $this->messages;
1189
    }
1190
1191
    /**
1192
     *Saves current messages in session for retrieving them later.
1193
     *
1194
     *@return array of messages
0 ignored issues
show
Documentation introduced by
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...
1195
     */
1196
    protected function StoreMessagesInSession()
1197
    {
1198
        $sessionVariableName = $this->sessionVariableName('Messages');
1199
        Session::set($sessionVariableName, serialize($this->messages));
1200
    }
1201
1202
    /**
1203
     * This method is used to return data after an ajax call was made.
1204
     * When a asynchronious request is made to the shopping cart (ajax),
1205
     * then you will first action the request and then use this function
1206
     * to return some values.
1207
     *
1208
     * It can also be used without ajax, in wich case it will redirects back
1209
     * to the last page.
1210
     *
1211
     * Note that you can set the ajax response class in the configuration file.
1212
     *
1213
     *
1214
     * @param string $message
1215
     * @param string $status
1216
     * @param Form   $form
0 ignored issues
show
Documentation introduced by
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...
1217
     * @returns String (JSON)
1218
     */
1219
    public function setMessageAndReturn($message = '', $status = '', Form $form = null)
0 ignored issues
show
Documentation introduced by
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...
1220
    {
1221
        if ($message && $status) {
1222
            $this->addMessage($message, $status);
1223
        }
1224
        //TODO: handle passing back multiple messages
1225
1226
        if (Director::is_ajax()) {
1227
            $responseClass = EcommerceConfig::get('ShoppingCart', 'response_class');
1228
            $obj = new $responseClass();
1229
1230
            return $obj->ReturnCartData($this->getMessages());
1231
        } else {
1232
            //TODO: handle passing a message back to a form->sessionMessage
1233
            $this->StoreMessagesInSession();
1234
            if ($form) {
1235
                // lets make sure that there is an order
1236
                $this->currentOrder();
1237
                // now we can (re)calculate the order
1238
                $this->order->calculateOrderAttributes($force = false);
1239
                $form->sessionMessage($message, $status);
1240
            // let the form controller do the redirectback or whatever else is needed.
1241
            } else {
1242
                if (empty($_REQUEST['BackURL']) && Controller::has_curr()) {
1243
                    Controller::curr()->redirectBack();
1244
                } else {
1245
                    Controller::curr()->redirect(urldecode($_REQUEST['BackURL']));
1246
                }
1247
            }
1248
1249
            return;
1250
        }
1251
    }
1252
1253
    /**
1254
     * @return EcommerceDBConfig
1255
     */
1256
    protected function EcomConfig()
1257
    {
1258
        return EcommerceDBConfig::current_ecommerce_db_config();
1259
    }
1260
1261
    /**
1262
     * Return the name of the session variable that should be used.
1263
     *
1264
     * @param string $name
1265
     *
1266
     * @return string
1267
     */
1268
    protected function sessionVariableName($name = '')
1269
    {
1270
        if (!in_array($name, self::$session_variable_names)) {
1271
            user_error("Tried to set session variable $name, that is not in use", E_USER_NOTICE);
1272
        }
1273
        $sessionCode = EcommerceConfig::get('ShoppingCart', 'session_code');
1274
1275
        return $sessionCode.'_'.$name;
1276
    }
1277
}
1278