Completed
Push — master ( ce2dd0...10a6ac )
by Nicolaas
04:02
created

ShoppingCart::cleanParameters()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
eloc 9
nc 4
nop 1
1
<?php
2
/**
3
 * ShoppingCart - provides a global way to interface with the cart (current order).
4
 *
5
 * This can be used in other code by calling $cart = ShoppingCart::singleton();
6
 *
7
 * The shopping cart can be accessed as an order handler from the back-end
8
 * (e.g. when creating an order programmatically), while the accompagnying controller
9
 * is used by web-users to manipulate their order.
10
 *
11
 * A bunch of core functions are also stored in the order itself.
12
 * Methods and variables are in the shopping cart if they are relevant
13
 * only before (and while) the order is placed (e.g. latest update message),
14
 * and others are in the order because they are relevant even after the
15
 * order has been submitted (e.g. Total Cost).
16
 *
17
 * Key methods:
18
 *
19
 * //get Cart
20
 * $myCart = ShoppingCart::singleton();
21
 *
22
 * //get order
23
 * $myOrder = ShoppingCart::current_order();
24
 *
25
 * //view order (from another controller)
26
 * $this->redirect(ShoppingCart::current_order()->Link());
27
 *
28
 * //add item to cart
29
 * ShoppingCart::singleton()->addBuyable($myProduct);
30
 *
31
 * @authors: Nicolaas [at] Sunny Side Up .co.nz
32
 * @package: ecommerce
33
 * @sub-package: control
34
 * @inspiration: Silverstripe Ltd, Jeremy
35
 **/
36
class ShoppingCart extends Object
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
37
{
38
    /**
39
     * List of names that can be used as session variables.
40
     * Also @see ShoppingCart::sessionVariableName.
41
     *
42
     * @var array
43
     */
44
    private static $session_variable_names = array('OrderID', 'Messages');
45
46
    /**
47
     * This is where we hold the (singleton) Shoppingcart.
48
     *
49
     * @var object (ShoppingCart)
50
     */
51
    private static $_singletoncart = null;
52
53
    /**
54
     * Feedback message to user (e.g. cart updated, could not delete item, someone in standing behind you).
55
     *
56
     *@var array
57
     **/
58
    protected $messages = array();
59
60
    /**
61
     * stores a reference to the current order object.
62
     *
63
     * @var object
64
     **/
65
    protected $order = null;
66
67
    /**
68
     * This variable is set to YES when we actually need an order (i.e. write it).
69
     *
70
     * @var bool
71
     */
72
    protected $requireSavedOrder = false;
73
74
    /**
75
     * Allows access to the cart from anywhere in code.
76
     *
77
     * @return ShoppingCart Object
78
     */
79
    public static function singleton()
80
    {
81
        if (!self::$_singletoncart) {
82
            self::$_singletoncart = Injector::inst()->get('ShoppingCart');
83
        }
84
85
        return self::$_singletoncart;
86
    }
87
88
    /**
89
     * Allows access to the current order from anywhere in the code..
90
     *
91
     * if you do not like the session Order then you can set it here ...
92
     *
93
     * @param Order $order (optional)
0 ignored issues
show
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...
94
     *
95
     * @return Order
96
     */
97
    public static function current_order($order = null)
98
    {
99
        return self::singleton()->currentOrder(0, $order);
100
    }
101
102
    /**
103
     * looks up current order id.
104
     * you may supply an ID here, so that it looks up the current order ID
105
     * only when none is supplied.
106
     *
107
     * @param int (optional) | Order $orderOrOrderID
108
     *
109
     * @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...
110
     */
111
    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...
112
    {
113
        $orderID = 0;
114
        if (!$orderOrOrderID) {
115
            $order = self::current_order();
116
            if ($order && $order->exists()) {
117
                $orderID = $order->ID;
118
            }
119
        }
120
        if($orderOrOrderID instanceof Order) {
121
            $orderID = $orderOrOrderID->ID;
122
        } elseif(intval($orderOrOrderID)) {
123
            $orderID = intval($orderOrOrderID);
124
        }
125
126
        return $orderID;
127
    }
128
129
    /**
130
     * Allows access to the current order from anywhere in the code..
131
     *
132
     * @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...
133
     */
134
    public static function session_order()
135
    {
136
        $sessionVariableName = self::singleton()->sessionVariableName('OrderID');
137
        $orderIDFromSession = intval(Session::get($sessionVariableName)) - 0;
138
139
        return Order::get()->byID($orderIDFromSession);
140
    }
141
142
    /**
143
     * set a specific order, other than the one from session ....
144
     *
145
     * @param Order $order
146
     *
147
     * @return Order
148
     */
149
    public function setOrder($order)
150
    {
151
        $this->order = $order;
152
        return $this->order;
153
    }
154
155
    /**
156
     * Gets or creates the current order.
157
     * Based on the session ONLY unless the order has been explictely set.
158
     * IMPORTANT FUNCTION!
159
     *
160
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
161
     *
162
     * However, you can pass an order in case you want to manipulate an order that is not in sesssion
163
     *
164
     * @param int $recurseCount (optional)
165
     * @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...
166
     *
167
     * @return Order
168
     */
169
    public function currentOrder($recurseCount = 0, $order = null)
170
    {
171
        if($order) {
172
            $this->order = $order;
173
        }
174
        if($this->allowWrites()) {
175
            if (!$this->order) {
176
                $this->order = self::session_order();
177
                $loggedInMember = Member::currentUser();
178
                if ($this->order) {
179
                    //first reason to set to null: it is already submitted
180
                    if ($this->order->IsSubmitted()) {
181
                        $this->order = null;
182
                    }
183
                    //second reason to set to null: make sure we have permissions
184
                    elseif (!$this->order->canView()) {
185
                        $this->order = null;
186
                    }
187
                    //logged in, add Member.ID to order->MemberID
188
                    elseif ($loggedInMember && $loggedInMember->exists()) {
189
                        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...
190
                            $updateMember = false;
191
                            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...
192
                                $updateMember = true;
193
                            }
194
                            if (!$loggedInMember->IsShopAdmin()) {
195
                                $updateMember = true;
196
                            }
197
                            if ($updateMember) {
198
                                $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...
199
                                $this->order->write();
200
                            }
201
                        }
202
                        //IF current order has nothing in it AND the member already has an order: use the old one first
203
                        //first, lets check if the current order is worthwhile keeping
204
                        if ($this->order->StatusID || $this->order->TotalItems()) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

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

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

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

could be turned into

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

This is much more concise to read.

Loading history...
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...
205
                            //do NOTHING!
206
                        } else {
207
                            $firstStep = OrderStep::get()->First();
208
                            //we assume the first step always exists.
209
                            //TODO: what sort order?
210
                            $count = 0;
211
                            while (
212
                                $firstStep &&
213
                                $previousOrderFromMember = Order::get()
214
                                    ->where('
215
                                        "MemberID" = '.$loggedInMember->ID.'
216
                                        AND ("StatusID" = '.$firstStep->ID.' OR "StatusID" = 0)
217
                                        AND "Order"."ID" <> '.$this->order->ID
218
                                    )
219
                                    ->First()
220
                            ) {
221
                                //arbritary 12 attempts ...
222
                                if ($count > 12) {
223
                                    break;
224
                                }
225
                                ++$count;
226
                                if ($previousOrderFromMember && $previousOrderFromMember->canView()) {
227
                                    if ($previousOrderFromMember->StatusID || $previousOrderFromMember->TotalItems()) {
228
                                        $this->order->delete();
229
                                        $this->order = $previousOrderFromMember;
230
                                        break;
231
                                    } else {
232
                                        $previousOrderFromMember->delete();
233
                                    }
234
                                }
235
                            }
236
                        }
237
                    }
238
                }
239
                if (! $this->order) {
240
                    if ($loggedInMember) {
241
                        //find previour order...
242
                        $firstStep = OrderStep::get()->First();
243
                        if ($firstStep) {
244
                            $previousOrderFromMember = Order::get()
245
                                ->filter(array(
246
                                    'MemberID' => $loggedInMember->ID,
247
                                    'StatusID' => array($firstStep->ID, 0),
248
                                ))
249
                                ->First();
250
                            if ($previousOrderFromMember) {
251
                                if ($previousOrderFromMember->canView()) {
252
                                    $this->order = $previousOrderFromMember;
253
                                }
254
                            }
255
                        }
256
                    }
257
                    if ($this->order && !$this->order->exists()) {
258
                        $this->order = null;
259
                    }
260
                    if (! $this->order) {
261
                        //here we cleanup old orders, because they should be
262
                        //cleaned at the same rate that they are created...
263
                        if (EcommerceConfig::get('ShoppingCart', 'cleanup_every_time')) {
264
                            $cartCleanupTask = EcommerceTaskCartCleanup::create();
265
                            $cartCleanupTask->runSilently();
266
                        }
267
                        //create new order
268
                        $this->order = Order::create();
269
                        if ($loggedInMember) {
270
                            $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...
271
                        }
272
                        $this->order->write();
273
                    }
274
                    $sessionVariableName = $this->sessionVariableName('OrderID');
275
                    Session::set($sessionVariableName, intval($this->order->ID));
276
                }
277
                if ($this->order){
278
                    if($this->order->exists()) {
279
                        $this->order->calculateOrderAttributes($force = false);
280
                    }
281
                    if (! $this->order->SessionID) {
282
                        $this->order->write();
283
                    }
284
                    //add session ID...
285
                }
286
            }
287
            //try it again
288
            //but limit to three, just in case ...
289
            //just in case ...
290
            if (!$this->order && $recurseCount < 3) {
291
                ++$recurseCount;
292
293
                return $this->currentOrder($recurseCount, $order);
294
            }
295
296
            return $this->order;
297
        } else {
298
299
            //we still return an order so that we do not end up with errors...
300
            return Order::create();
301
        }
302
    }
303
304
305
    private static $_allow_writes_cache = null;
306
307
    /**
308
     * can the current user use sessions and therefore write to cart???
309
     * the method also returns if an order has explicitely been set
310
     * @return Boolean
311
     */
312
    protected function allowWrites()
313
    {
314
        if(self::$_allow_writes_cache === null) {
315
            if($this->order) {
316
                self::$_allow_writes_cache = true;
317
            } else {
318
                if ( php_sapi_name() !== 'cli' ) {
319
                    if ( version_compare(phpversion(), '5.4.0', '>=') ) {
320
                        self::$_allow_writes_cache = session_status() === PHP_SESSION_ACTIVE ? true : false;
321
                    } else {
322
                        self::$_allow_writes_cache = session_id() === '' ? true : false;
323
                    }
324
                } else {
325
                    self::$_allow_writes_cache = false;
326
                }
327
            }
328
        }
329
330
        return self::$_allow_writes_cache;
331
332
    }
333
334
    /**
335
     * Allows access to the current order from anywhere in the code..
336
     *
337
     * @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...
338
     *
339
     * @return ShoppingCart Object
340
     */
341
    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...
342
    {
343
        $order = self::singleton()->currentOrder(0, $order = null);
344
        if ($order) {
345
            return $order->Link();
346
        }
347
    }
348
349
    /**
350
     * Adds any number of items to the cart.
351
     * Returns the order item on succes OR false on failure.
352
     *
353
     * @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...
354
     * @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...
355
     * @param mixed      $parameters - array of parameters to target a specific order item. eg: group=1, length=5
356
     *                                 if you make it a form, it will save the form into the orderitem
357
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
358
     *
359
     * @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...
360
     */
361
    public function addBuyable(BuyableModel $buyable, $quantity = 1, $parameters = array())
362
    {
363
        if($this->allowWrites()) {
364
            if (!$buyable) {
365
                $this->addMessage(_t('Order.ITEMCOULDNOTBEFOUND', 'This item could not be found.'), 'bad');
366
                return false;
367
            }
368
            if (!$buyable->canPurchase()) {
369
                $this->addMessage(_t('Order.ITEMCOULDNOTBEADDED', 'This item is not for sale.'), 'bad');
370
                return false;
371
            }
372
            $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false);
373
            $quantity = $this->prepareQuantity($buyable, $quantity);
374
            if ($item && $quantity) { //find existing order item or make one
375
                $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...
376
                $item->write();
377
                $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...
378
                //TODO: distinquish between incremented and set
379
                //TODO: use sprintf to allow product name etc to be included in message
380
                if ($quantity > 1) {
381
                    $msg = _t('Order.ITEMSADDED', 'Items added.');
382
                } else {
383
                    $msg = _t('Order.ITEMADDED', 'Item added.');
384
                }
385
                $this->addMessage($msg, 'good');
386
            } elseif (!$item) {
387
                $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad');
388
            } else {
389
                $this->addMessage(_t('Order.ITEMCOULDNOTBEADDED', 'Item could not be added.'), 'bad');
390
            }
391
392
            return $item;
393
        }
394
    }
395
396
    /**
397
     * Sets quantity for an item in the cart.
398
     *
399
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
400
     *
401
     * @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...
402
     * @param float      $quantity   - number of items add.
403
     * @param array      $parameters - array of parameters to target a specific order item. eg: group=1, length=5
404
     *
405
     * @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...
406
     */
407
    public function setQuantity(BuyableModel $buyable, $quantity, array $parameters = array())
408
    {
409
        if($this->allowWrites()) {
410
            $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false);
411
            $quantity = $this->prepareQuantity($buyable, $quantity);
412
            if ($item) {
413
                $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...
414
                $item->write();
415
                $this->addMessage(_t('Order.ITEMUPDATED', 'Item updated.'), 'good');
416
417
                return $item;
418
            } else {
419
                $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad');
420
            }
421
422
            return false;
423
        }
424
    }
425
426
    /**
427
     * Removes any number of items from the cart.
428
     *
429
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
430
     *
431
     * @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...
432
     * @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...
433
     * @param array      $parameters - array of parameters to target a specific order item. eg: group=1, length=5
434
     *
435
     * @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...
436
     */
437
    public function decrementBuyable(BuyableModel $buyable, $quantity = 1, array $parameters = array())
438
    {
439
        if($this->allowWrites()) {
440
            $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false);
441
            $quantity = $this->prepareQuantity($buyable, $quantity);
442
            if ($item) {
443
                $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...
444
                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...
445
                    $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...
446
                }
447
                $item->write();
448
                if ($quantity > 1) {
449
                    $msg = _t('Order.ITEMSREMOVED', 'Items removed.');
450
                } else {
451
                    $msg = _t('Order.ITEMREMOVED', 'Item removed.');
452
                }
453
                $this->addMessage($msg, 'good');
454
455
                return $item;
456
            } else {
457
                $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad');
458
            }
459
460
            return false;
461
        }
462
463
    }
464
465
    /**
466
     * Delete item from the cart.
467
     *
468
     * returns null if the current user does not allow order manipulation or saving (e.g. session disabled)
469
     *
470
     * @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...
471
     * @param array     $parameters - array of parameters to target a specific order item. eg: group=1, length=5
472
     *
473
     * @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...
474
     */
475
    public function deleteBuyable(BuyableModel $buyable, array $parameters = array())
476
    {
477
        if($this->allowWrites()) {
478
            $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = true);
479
            if ($item) {
480
                $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...
481
                $item->delete();
482
                $item->destroy();
483
                $this->addMessage(_t('Order.ITEMCOMPLETELYREMOVED', 'Item removed from cart.'), 'good');
484
485
                return $item;
486
            } else {
487
                $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad');
488
489
                return false;
490
            }
491
        }
492
    }
493
494
    /**
495
     * Checks and prepares variables for a quantity change (add, edit, remove) for an Order Item.
496
     *
497
     * @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...
498
     * @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...
499
     * @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...
500
     * @param array | Form  $parameters          - array of parameters to target a specific order item. eg: group=1, length=5*
501
     *                                           - form saved into item...
502
     *
503
     * @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...
504
     */
505
    protected function prepareOrderItem(BuyableModel $buyable, $parameters = array(), $mustBeExistingItem = true)
506
    {
507
        $parametersArray = $parameters;
508
        $form = null;
509
        if ($parameters instanceof Form) {
510
            $parametersArray = array();
511
            $form = $parameters;
512
        }
513
        if (!$buyable) {
514
            user_error('No buyable was provided', E_USER_WARNING);
515
        }
516
        if (!$buyable->canPurchase()) {
517
            $item = $this->getExistingItem($buyable, $parametersArray);
518
            if ($item && $item->exists()) {
519
                $item->delete();
520
                $item->destroy();
521
            }
522
523
            return false;
524
        }
525
        $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...
526
        if ($mustBeExistingItem) {
527
            $item = $this->getExistingItem($buyable, $parametersArray);
528
        } else {
529
            $item = $this->findOrMakeItem($buyable, $parametersArray); //find existing order item or make one
530
        }
531
        if (!$item) {
532
            //check for existence of item
533
            return false;
534
        }
535
        if ($form) {
536
            $form->saveInto($item);
537
        }
538
539
        return $item;
540
    }
541
542
    /**
543
     * @todo: what does this method do???
544
     *
545
     * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be false|double?

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

Loading history...
546
     *
547
     * @param DataObject ($buyable)
548
     * @param float $quantity
549
     */
550
    protected function prepareQuantity(BuyableModel $buyable, $quantity)
551
    {
552
        $quantity = round($quantity, $buyable->QuantityDecimals());
553
        if ($quantity < 0 || (!$quantity && $quantity !== 0)) {
554
            $this->addMessage(_t('Order.INVALIDQUANTITY', 'Invalid quantity.'), 'warning');
555
556
            return false;
557
        }
558
559
        return $quantity;
560
    }
561
562
    /**
563
     * Helper function for making / retrieving order items.
564
     * we do not need things like "canPurchase" here, because that is with the "addBuyable" method.
565
     * NOTE: does not write!
566
     *
567
     * @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...
568
     * @param array      $parameters
569
     *
570
     * @return OrderItem
571
     */
572
    public function findOrMakeItem(BuyableModel $buyable, array $parameters = array())
573
    {
574
        if($this->allowWrites()) {
575
            if ($item = $this->getExistingItem($buyable, $parameters)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

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

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

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

could be turned into

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

This is much more concise to read.

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

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

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
811
                //$oldOrder = $oldOrder;
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
812
            } else {
813
                user_error('Bad order provided as parameter to ShoppingCart::loadOrder()');
814
            }
815
            if ($oldOrder) {
816
                if ($oldOrder->canView() && $oldOrder->IsSubmitted()) {
817
                    $newOrder = Order::create();
818
                    //copying fields.
819
                    $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...
820
                    //important to set it this way...
821
                    $newOrder->setCurrency($oldOrder->CurrencyUsed());
822
                    $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...
823
                    //load the order
824
                    $newOrder->write();
825
                    $this->loadOrder($newOrder);
826
                    $items = OrderItem::get()
827
                        ->filter(array('OrderID' => $oldOrder->ID));
828
                    if ($items->count()) {
829
                        foreach ($items as $item) {
830
                            $buyable = $item->Buyable($current = true);
831
                            if ($buyable->canPurchase()) {
832
                                $this->addBuyable($buyable, $item->Quantity);
833
                            }
834
                        }
835
                    }
836
                    $newOrder->CreateOrReturnExistingAddress('BillingAddress');
837
                    $newOrder->CreateOrReturnExistingAddress('ShippingAddress');
838
                    $newOrder->write();
839
                    $this->addMessage(_t('Order.ORDERCOPIED', 'Order has been copied.'), 'good');
840
841
                    return $newOrder;
842
                } else {
843
                    $this->addMessage(_t('Order.NOPERMISSION', 'You do not have permission to view this order.'), 'bad');
844
845
                    return false;
846
                }
847
            } else {
848
                $this->addMessage(_t('Order.NOORDER', 'Order can not be found.'), 'bad');
849
850
                return false;
851
            }
852
        }
853
    }
854
855
    /**
856
     * sets country in order so that modifiers can be recalculated, etc...
857
     *
858
     * @param string - $countryCode
859
     *
860
     * @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...
861
     **/
862
    public function setCountry($countryCode)
863
    {
864
        if($this->allowWrites()) {
865
            if (EcommerceCountry::code_allowed($countryCode)) {
866
                $this->currentOrder()->SetCountryFields($countryCode);
867
                $this->addMessage(_t('Order.UPDATEDCOUNTRY', 'Updated country.'), 'good');
868
869
                return true;
870
            } else {
871
                $this->addMessage(_t('Order.NOTUPDATEDCOUNTRY', 'Could not update country.'), 'bad');
872
873
                return false;
874
            }
875
        }
876
    }
877
878
    /**
879
     * sets region in order so that modifiers can be recalculated, etc...
880
     *
881
     * @param int | String - $regionID you can use the ID or the code.
882
     *
883
     * @return bool
884
     **/
885
    public function setRegion($regionID)
886
    {
887
        if (EcommerceRegion::regionid_allowed($regionID)) {
888
            $this->currentOrder()->SetRegionFields($regionID);
889
            $this->addMessage(_t('ShoppingCart.REGIONUPDATED', 'Region updated.'), 'good');
890
891
            return true;
892
        } else {
893
            $this->addMessage(_t('ORDER.NOTUPDATEDREGION', 'Could not update region.'), 'bad');
894
895
            return false;
896
        }
897
    }
898
899
    /**
900
     * sets the display currency for the cart.
901
     *
902
     * @param string $currencyCode
903
     *
904
     * @return bool
905
     **/
906
    public function setCurrency($currencyCode)
907
    {
908
        $currency = EcommerceCurrency::get_one_from_code($currencyCode);
909
        if ($currency) {
910
            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...
911
                $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...
912
                if ($member && $member->exists()) {
913
                    $member->SetPreferredCurrency($currency);
914
                }
915
            }
916
            $this->currentOrder()->UpdateCurrency($currency);
917
            $msg = _t('Order.CURRENCYUPDATED', 'Currency updated.');
918
            $this->addMessage($msg, 'good');
919
920
            return true;
921
        } else {
922
            $msg = _t('Order.CURRENCYCOULDNOTBEUPDATED', 'Currency could not be updated.');
923
            $this->addMessage($msg, 'bad');
924
925
            return false;
926
        }
927
    }
928
929
    /**
930
     * Produces a debug of the shopping cart.
931
     */
932
    public function debug()
933
    {
934
        if (Director::isDev() || Permission::check('ADMIN')) {
935
            debug::show($this->currentOrder());
936
937
            echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Country</h1>';
938
            echo 'GEOIP Country: '.EcommerceCountry::get_country_from_ip().'<br />';
939
            echo 'Calculated Country Country: '.EcommerceCountry::get_country().'<br />';
940
941
            echo '<blockquote><blockquote><blockquote><blockquote>';
942
943
            echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Items</h1>';
944
            $items = $this->currentOrder()->Items();
945
            echo $items->sql();
946
            echo '<hr />';
947
            if ($items->count()) {
948
                foreach ($items as $item) {
949
                    Debug::show($item);
950
                }
951
            } else {
952
                echo '<p>there are no items for this order</p>';
953
            }
954
955
            echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Modifiers</h1>';
956
            $modifiers = $this->currentOrder()->Modifiers();
957
            if ($modifiers->count()) {
958
                foreach ($modifiers as $modifier) {
959
                    Debug::show($modifier);
960
                }
961
            } else {
962
                echo '<p>there are no modifiers for this order</p>';
963
            }
964
965
            echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Addresses</h1>';
966
            $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...
967
            if ($billingAddress && $billingAddress->exists()) {
968
                Debug::show($billingAddress);
969
            } else {
970
                echo '<p>there is no billing address for this order</p>';
971
            }
972
            $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...
973
            if ($shippingAddress && $shippingAddress->exists()) {
974
                Debug::show($shippingAddress);
975
            } else {
976
                echo '<p>there is no shipping address for this order</p>';
977
            }
978
979
            $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...
980
            if ($currencyUsed && $currencyUsed->exists()) {
981
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Currency</h1>';
982
                Debug::show($currencyUsed);
983
            }
984
985
            $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...
986
            if ($cancelledBy && $cancelledBy->exists()) {
987
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Cancelled By</h1>';
988
                Debug::show($cancelledBy);
989
            }
990
991
            $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...
992
            if ($logs && $logs->count()) {
993
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Logs</h1>';
994
                foreach ($logs as $log) {
995
                    Debug::show($log);
996
                }
997
            }
998
999
            $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...
1000
            if ($payments  && $payments->count()) {
1001
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Payments</h1>';
1002
                foreach ($payments as $payment) {
1003
                    Debug::show($payment);
1004
                }
1005
            }
1006
1007
            $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...
1008
            if ($emails && $emails->count()) {
1009
                echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Emails</h1>';
1010
                foreach ($emails as $email) {
1011
                    Debug::show($email);
1012
                }
1013
            }
1014
1015
            echo '</blockquote></blockquote></blockquote></blockquote>';
1016
        } else {
1017
            echo 'Please log in as admin first';
1018
        }
1019
    }
1020
1021
    /**
1022
     * Stores a message that can later be returned via ajax or to $form->sessionMessage();.
1023
     *
1024
     * @param $message - the message, which could be a notification of successful action, or reason for failure
1025
     * @param $type - please use good, bad, warning
1026
     */
1027
    public function addMessage($message, $status = 'good')
1028
    {
1029
        //clean status for the lazy programmer
1030
        //TODO: remove the awkward replace
1031
        $status = strtolower($status);
1032
        str_replace(array('success', 'failure'), array('good', 'bad'), $status);
1033
        $statusOptions = array('good', 'bad', 'warning');
1034
        if (!in_array($status, $statusOptions)) {
1035
            user_error('Message status should be one of the following: '.implode(',', $statusOptions), E_USER_NOTICE);
1036
        }
1037
        $this->messages[] = array(
1038
            'Message' => $message,
1039
            'Type' => $status,
1040
        );
1041
    }
1042
1043
    /*******************************************************
1044
    * HELPER FUNCTIONS
1045
    *******************************************************/
1046
1047
    /**
1048
     * Gets an existing order item based on buyable and passed parameters.
1049
     *
1050
     * @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...
1051
     * @param array      $parameters
1052
     *
1053
     * @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...
1054
     */
1055
    protected function getExistingItem(BuyableModel $buyable, array $parameters = array())
1056
    {
1057
        $filterString = $this->parametersToSQL($parameters);
1058
        if ($order = $this->currentOrder()) {
1059
            $orderID = $order->ID;
1060
            $obj = OrderItem::get()
1061
                ->where(
1062
                    " \"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...
1063
                    \"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...
1064
                    "OrderID" = '.$orderID.' '.
1065
                    $filterString
1066
                )
1067
                ->First();
1068
1069
            return $obj;
1070
        }
1071
    }
1072
1073
    /**
1074
     * Removes parameters that aren't in the default array, merges with default parameters, and converts raw2SQL.
1075
     *
1076
     * @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...
1077
     *
1078
     * @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...
1079
     */
1080
    protected function cleanParameters(array $params = array())
1081
    {
1082
        $defaultParamFilters = EcommerceConfig::get('ShoppingCart', 'default_param_filters');
1083
        $newarray = array_merge(array(), $defaultParamFilters); //clone array
1084
        if (!count($newarray)) {
1085
            return array(); //no use for this if there are not parameters defined
1086
        }
1087
        foreach ($newarray as $field => $value) {
1088
            if (isset($params[$field])) {
1089
                $newarray[$field] = Convert::raw2sql($params[$field]);
1090
            }
1091
        }
1092
1093
        return $newarray;
1094
    }
1095
1096
    /**
1097
     * @param array $parameters
1098
     *                          Converts parameter array to SQL query filter
1099
     */
1100
    protected function parametersToSQL(array $parameters = array())
1101
    {
1102
        $defaultParamFilters = EcommerceConfig::get('ShoppingCart', 'default_param_filters');
1103
        if (!count($defaultParamFilters)) {
1104
            return ''; //no use for this if there are not parameters defined
1105
        }
1106
        $cleanedparams = $this->cleanParameters($parameters);
1107
        $outputArray = array();
1108
        foreach ($cleanedparams as $field => $value) {
1109
            $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...
1110
        }
1111
        if (count($outputArray)) {
1112
            return implode(' AND ', $outputArray);
1113
        }
1114
1115
        return '';
1116
    }
1117
1118
    /*******************************************************
1119
    * UI MESSAGE HANDLING
1120
    *******************************************************/
1121
1122
    /**
1123
     * Retrieves all good, bad, and ugly messages that have been produced during the current request.
1124
     *
1125
     * @return array of messages
1126
     */
1127
    public function getMessages()
1128
    {
1129
        $sessionVariableName = $this->sessionVariableName('Messages');
1130
        //get old messages
1131
        $messages = unserialize(Session::get($sessionVariableName));
1132
        //clear old messages
1133
        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...
1134
        //set to form????
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1135
        if ($messages && count($messages)) {
1136
            $this->messages = array_merge($messages, $this->messages);
1137
        }
1138
1139
        return $this->messages;
1140
    }
1141
1142
    /**
1143
     *Saves current messages in session for retrieving them later.
1144
     *
1145
     *@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...
1146
     */
1147
    protected function StoreMessagesInSession()
1148
    {
1149
        $sessionVariableName = $this->sessionVariableName('Messages');
1150
        Session::set($sessionVariableName, serialize($this->messages));
1151
    }
1152
1153
    /**
1154
     * This method is used to return data after an ajax call was made.
1155
     * When a asynchronious request is made to the shopping cart (ajax),
1156
     * then you will first action the request and then use this function
1157
     * to return some values.
1158
     *
1159
     * It can also be used without ajax, in wich case it will redirects back
1160
     * to the last page.
1161
     *
1162
     * Note that you can set the ajax response class in the configuration file.
1163
     *
1164
     *
1165
     * @param string $message
1166
     * @param string $status
1167
     * @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...
1168
     * @returns String (JSON)
1169
     */
1170
    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...
Coding Style introduced by
setMessageAndReturn uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

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