These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * ShoppingCart - provides a global way to interface with the cart (current order). |
||
4 | * |
||
5 | * This can be used in other code by calling $cart = ShoppingCart::singleton(); |
||
6 | * |
||
7 | * The shopping cart can be accessed as an order handler from the back-end |
||
8 | * (e.g. when creating an order programmatically), while the accompagnying controller |
||
9 | * is used by web-users to manipulate their order. |
||
10 | * |
||
11 | * A bunch of core functions are also stored in the order itself. |
||
12 | * Methods and variables are in the shopping cart if they are relevant |
||
13 | * only before (and while) the order is placed (e.g. latest update message), |
||
14 | * and others are in the order because they are relevant even after the |
||
15 | * order has been submitted (e.g. Total Cost). |
||
16 | * |
||
17 | * Key methods: |
||
18 | * |
||
19 | * //get Cart |
||
20 | * $myCart = ShoppingCart::singleton(); |
||
21 | * |
||
22 | * //get order |
||
23 | * $myOrder = ShoppingCart::current_order(); |
||
24 | * |
||
25 | * //view order (from another controller) |
||
26 | * $this->redirect(ShoppingCart::current_order()->Link()); |
||
27 | * |
||
28 | * //add item to cart |
||
29 | * ShoppingCart::singleton()->addBuyable($myProduct); |
||
30 | * |
||
31 | * @authors: Nicolaas [at] Sunny Side Up .co.nz |
||
32 | * @package: ecommerce |
||
33 | * @sub-package: control |
||
34 | * @inspiration: Silverstripe Ltd, Jeremy |
||
35 | **/ |
||
36 | class ShoppingCart extends Object |
||
0 ignored issues
–
show
|
|||
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 | * @return Order |
||
92 | */ |
||
93 | public static function current_order() |
||
94 | { |
||
95 | return self::singleton()->currentOrder(); |
||
96 | } |
||
97 | |||
98 | /** |
||
99 | * looks up current order id. |
||
100 | * you may supply an ID here, so that it looks up the current order ID |
||
101 | * only when none is supplied. |
||
102 | * |
||
103 | * @param int (optional) $orderID |
||
104 | * |
||
105 | * @return int; |
||
106 | */ |
||
107 | public static function current_order_id($orderID = 0) |
||
108 | { |
||
109 | if (!$orderID) { |
||
110 | $order = self::current_order(); |
||
111 | if ($order && $order->exists()) { |
||
112 | $orderID = $order->ID; |
||
113 | } |
||
114 | } |
||
115 | |||
116 | return $orderID; |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * Allows access to the current order from anywhere in the code.. |
||
121 | * |
||
122 | * @return Order |
||
123 | */ |
||
124 | public static function session_order() |
||
125 | { |
||
126 | $sessionVariableName = self::singleton()->sessionVariableName('OrderID'); |
||
127 | $orderIDFromSession = intval(Session::get($sessionVariableName)) - 0; |
||
128 | |||
129 | return Order::get()->byID($orderIDFromSession); |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * Gets or creates the current order. |
||
134 | * Based on the session ONLY! |
||
135 | * IMPORTANT FUNCTION! |
||
136 | * |
||
137 | * @todo - does this need to be public???? |
||
138 | * |
||
139 | * @return Order |
||
140 | */ |
||
141 | public function currentOrder($recurseCount = 0) |
||
142 | { |
||
143 | if (!$this->order) { |
||
144 | $this->order = self::session_order(); |
||
145 | $loggedInMember = Member::currentUser(); |
||
146 | if ($this->order) { |
||
147 | //first reason to set to null: it is already submitted |
||
148 | if ($this->order->IsSubmitted()) { |
||
149 | $this->order = null; |
||
150 | } |
||
151 | //second reason to set to null: make sure we have permissions |
||
152 | elseif (!$this->order->canView()) { |
||
153 | $this->order = null; |
||
154 | } |
||
155 | //logged in, add Member.ID to order->MemberID |
||
156 | elseif ($loggedInMember && $loggedInMember->exists()) { |
||
157 | if ($this->order->MemberID != $loggedInMember->ID) { |
||
158 | $updateMember = false; |
||
159 | if (!$this->order->MemberID) { |
||
160 | $updateMember = true; |
||
161 | } |
||
162 | if (!$loggedInMember->IsShopAdmin()) { |
||
163 | $updateMember = true; |
||
164 | } |
||
165 | if ($updateMember) { |
||
166 | $this->order->MemberID = $loggedInMember->ID; |
||
167 | $this->order->write(); |
||
168 | } |
||
169 | } |
||
170 | //IF current order has nothing in it AND the member already has an order: use the old one first |
||
171 | //first, lets check if the current order is worthwhile keeping |
||
172 | if ($this->order->StatusID || $this->order->TotalItems()) { |
||
173 | //do NOTHING! |
||
174 | } else { |
||
175 | $firstStep = OrderStep::get()->First(); |
||
176 | //we assume the first step always exists. |
||
177 | //TODO: what sort order? |
||
178 | $count = 0; |
||
179 | while ( |
||
180 | $firstStep && |
||
181 | $previousOrderFromMember = Order::get() |
||
182 | ->where(' |
||
183 | "MemberID" = '.$loggedInMember->ID.' |
||
184 | AND ("StatusID" = '.$firstStep->ID.' OR "StatusID" = 0) |
||
185 | AND "Order"."ID" <> '.$this->order->ID |
||
186 | ) |
||
187 | ->First() |
||
188 | ) { |
||
189 | //arbritary 12 attempts ... |
||
190 | if ($count > 12) { |
||
191 | break; |
||
192 | } |
||
193 | ++$count; |
||
194 | if ($previousOrderFromMember && $previousOrderFromMember->canView()) { |
||
195 | if ($previousOrderFromMember->StatusID || $previousOrderFromMember->TotalItems()) { |
||
196 | $this->order->delete(); |
||
197 | $this->order = $previousOrderFromMember; |
||
198 | break; |
||
199 | } else { |
||
200 | $previousOrderFromMember->delete(); |
||
201 | } |
||
202 | } |
||
203 | } |
||
204 | } |
||
205 | } |
||
206 | } |
||
207 | if (!$this->order) { |
||
208 | if ($loggedInMember) { |
||
209 | //find previour order... |
||
210 | $firstStep = OrderStep::get()->First(); |
||
211 | if ($firstStep) { |
||
212 | $previousOrderFromMember = Order::get() |
||
213 | ->filter(array( |
||
214 | 'MemberID' => $loggedInMember->ID, |
||
215 | 'StatusID' => array($firstStep->ID, 0), |
||
216 | )) |
||
217 | ->First(); |
||
218 | if ($previousOrderFromMember) { |
||
219 | if ($previousOrderFromMember->canView()) { |
||
220 | $this->order = $previousOrderFromMember; |
||
221 | } |
||
222 | } |
||
223 | } |
||
224 | } |
||
225 | if ($this->order && !$this->order->exists()) { |
||
226 | $this->order = null; |
||
227 | } |
||
228 | if (!$this->order) { |
||
229 | //here we cleanup old orders, because they should be |
||
230 | //cleaned at the same rate that they are created... |
||
231 | if (EcommerceConfig::get('ShoppingCart', 'cleanup_every_time')) { |
||
232 | $cartCleanupTask = EcommerceTaskCartCleanup::create(); |
||
233 | $cartCleanupTask->runSilently(); |
||
234 | } |
||
235 | //create new order |
||
236 | $this->order = Order::create(); |
||
237 | if ($loggedInMember) { |
||
238 | $this->order->MemberID = $loggedInMember->ID; |
||
239 | } |
||
240 | $this->order->write(); |
||
241 | } |
||
242 | $sessionVariableName = $this->sessionVariableName('OrderID'); |
||
243 | Session::set($sessionVariableName, intval($this->order->ID)); |
||
244 | } |
||
245 | if ($this->order && $this->order->exists()) { |
||
246 | $this->order->calculateOrderAttributes($force = false); |
||
247 | } |
||
248 | if ($this->order && !$this->order->SessionID) { |
||
249 | //add session ID... |
||
250 | $this->order->write(); |
||
251 | } |
||
252 | } |
||
253 | //try it again |
||
254 | //but limit to three, just in case ... |
||
255 | //just in case ... |
||
256 | if (!$this->order && $recurseCount < 3) { |
||
257 | ++$recurseCount; |
||
258 | |||
259 | return $this->currentOrder(); |
||
260 | } |
||
261 | |||
262 | return $this->order; |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * Allows access to the current order from anywhere in the code.. |
||
267 | * |
||
268 | * @return ShoppingCart Object |
||
269 | */ |
||
270 | public function Link() |
||
271 | { |
||
272 | $order = self::singleton()->currentOrder(); |
||
273 | if ($order) { |
||
274 | return $order->Link(); |
||
275 | } |
||
276 | } |
||
277 | |||
278 | /** |
||
279 | * Adds any number of items to the cart. |
||
280 | * Returns the order item on succes OR false on failure. |
||
281 | * |
||
282 | * @param DataObject $buyable - the buyable (generally a product) being added to the cart |
||
283 | * @param float $quantity - number of items add. |
||
284 | * @param mixed $parameters - array of parameters to target a specific order item. eg: group=1, length=5 |
||
285 | * if you make it a form, it will save the form into the orderitem |
||
286 | * |
||
287 | * @return false | DataObject (OrderItem) |
||
288 | */ |
||
289 | public function addBuyable(BuyableModel $buyable, $quantity = 1, $parameters = array()) |
||
290 | { |
||
291 | if (!$buyable) { |
||
292 | $this->addMessage(_t('Order.ITEMCOULDNOTBEFOUND', 'This item could not be found.'), 'bad'); |
||
293 | return false; |
||
294 | } |
||
295 | if (!$buyable->canPurchase()) { |
||
296 | $this->addMessage(_t('Order.ITEMCOULDNOTBEADDED', 'This item is not for sale.'), 'bad'); |
||
297 | return false; |
||
298 | } |
||
299 | $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false); |
||
300 | $quantity = $this->prepareQuantity($buyable, $quantity); |
||
301 | if ($item && $quantity) { //find existing order item or make one |
||
302 | $item->Quantity += $quantity; |
||
303 | $item->write(); |
||
304 | $this->currentOrder()->Attributes()->add($item); //save to current order |
||
305 | //TODO: distinquish between incremented and set |
||
306 | //TODO: use sprintf to allow product name etc to be included in message |
||
307 | if ($quantity > 1) { |
||
308 | $msg = _t('Order.ITEMSADDED', 'Items added.'); |
||
309 | } else { |
||
310 | $msg = _t('Order.ITEMADDED', 'Item added.'); |
||
311 | } |
||
312 | $this->addMessage($msg, 'good'); |
||
313 | } elseif (!$item) { |
||
314 | $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad'); |
||
315 | } else { |
||
316 | $this->addMessage(_t('Order.ITEMCOULDNOTBEADDED', 'Item could not be added.'), 'bad'); |
||
317 | } |
||
318 | |||
319 | return $item; |
||
320 | } |
||
321 | |||
322 | /** |
||
323 | * Sets quantity for an item in the cart. |
||
324 | * |
||
325 | * @param DataObject $buyable - the buyable (generally a product) being added to the cart |
||
326 | * @param float $quantity - number of items add. |
||
327 | * @param array $parameters - array of parameters to target a specific order item. eg: group=1, length=5 |
||
328 | * |
||
329 | * @return false | DataObject (OrderItem) |
||
330 | */ |
||
331 | public function setQuantity(BuyableModel $buyable, $quantity, array $parameters = array()) |
||
332 | { |
||
333 | $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false); |
||
334 | $quantity = $this->prepareQuantity($buyable, $quantity); |
||
335 | if ($item) { |
||
336 | $item->Quantity = $quantity; //remove quantity |
||
337 | $item->write(); |
||
338 | $this->addMessage(_t('Order.ITEMUPDATED', 'Item updated.'), 'good'); |
||
339 | |||
340 | return $item; |
||
341 | } else { |
||
342 | $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad'); |
||
343 | } |
||
344 | |||
345 | return false; |
||
346 | } |
||
347 | |||
348 | /** |
||
349 | * Removes any number of items from the cart. |
||
350 | * |
||
351 | * @param DataObject $buyable - the buyable (generally a product) being added to the cart |
||
352 | * @param float $quantity - number of items add. |
||
353 | * @param array $parameters - array of parameters to target a specific order item. eg: group=1, length=5 |
||
354 | * |
||
355 | * @return false | DataObject (OrderItem) |
||
356 | */ |
||
357 | public function decrementBuyable(BuyableModel $buyable, $quantity = 1, array $parameters = array()) |
||
358 | { |
||
359 | $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false); |
||
360 | $quantity = $this->prepareQuantity($buyable, $quantity); |
||
361 | if ($item) { |
||
362 | $item->Quantity -= $quantity; //remove quantity |
||
363 | if ($item->Quantity < 0) { |
||
364 | $item->Quantity = 0; |
||
365 | } |
||
366 | $item->write(); |
||
367 | if ($quantity > 1) { |
||
368 | $msg = _t('Order.ITEMSREMOVED', 'Items removed.'); |
||
369 | } else { |
||
370 | $msg = _t('Order.ITEMREMOVED', 'Item removed.'); |
||
371 | } |
||
372 | $this->addMessage($msg, 'good'); |
||
373 | |||
374 | return $item; |
||
375 | } else { |
||
376 | $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad'); |
||
377 | } |
||
378 | |||
379 | return false; |
||
380 | } |
||
381 | |||
382 | /** |
||
383 | * Delete item from the cart. |
||
384 | * |
||
385 | * @param OrderItem $buyable - the buyable (generally a product) being added to the cart |
||
386 | * @param array $parameters - array of parameters to target a specific order item. eg: group=1, length=5 |
||
387 | * |
||
388 | * @return bool | item - successfully removed |
||
389 | */ |
||
390 | public function deleteBuyable(BuyableModel $buyable, array $parameters = array()) |
||
391 | { |
||
392 | $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = true); |
||
393 | if ($item) { |
||
394 | $this->currentOrder()->Attributes()->remove($item); |
||
395 | $item->delete(); |
||
396 | $item->destroy(); |
||
397 | $this->addMessage(_t('Order.ITEMCOMPLETELYREMOVED', 'Item removed from cart.'), 'good'); |
||
398 | |||
399 | return $item; |
||
400 | } else { |
||
401 | $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad'); |
||
402 | |||
403 | return false; |
||
404 | } |
||
405 | } |
||
406 | |||
407 | /** |
||
408 | * Checks and prepares variables for a quantity change (add, edit, remove) for an Order Item. |
||
409 | * |
||
410 | * @param DataObject $buyable - the buyable (generally a product) being added to the cart |
||
411 | * @param float $quantity - number of items add. |
||
412 | * @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. |
||
413 | * @param array | Form $parameters - array of parameters to target a specific order item. eg: group=1, length=5* |
||
414 | * - form saved into item... |
||
415 | * |
||
416 | * @return bool | DataObject ($orderItem) |
||
417 | */ |
||
418 | protected function prepareOrderItem(BuyableModel $buyable, $parameters = array(), $mustBeExistingItem = true) |
||
419 | { |
||
420 | $parametersArray = $parameters; |
||
421 | $form = null; |
||
422 | if ($parameters instanceof Form) { |
||
423 | $parametersArray = array(); |
||
424 | $form = $parameters; |
||
425 | } |
||
426 | if (!$buyable) { |
||
427 | user_error('No buyable was provided', E_USER_WARNING); |
||
428 | } |
||
429 | if (!$buyable->canPurchase()) { |
||
430 | $item = $this->getExistingItem($buyable, $parametersArray); |
||
431 | if ($item && $item->exists()) { |
||
432 | $item->delete(); |
||
433 | $item->destroy(); |
||
434 | } |
||
435 | |||
436 | return false; |
||
437 | } |
||
438 | $item = null; |
||
439 | if ($mustBeExistingItem) { |
||
440 | $item = $this->getExistingItem($buyable, $parametersArray); |
||
441 | } else { |
||
442 | $item = $this->findOrMakeItem($buyable, $parametersArray); //find existing order item or make one |
||
443 | } |
||
444 | if (!$item) { |
||
445 | //check for existence of item |
||
446 | return false; |
||
447 | } |
||
448 | if ($form) { |
||
449 | $form->saveInto($item); |
||
450 | } |
||
451 | |||
452 | return $item; |
||
453 | } |
||
454 | |||
455 | /** |
||
456 | * @todo: what does this method do??? |
||
457 | * |
||
458 | * @return int |
||
459 | * |
||
460 | * @param DataObject ($buyable) |
||
461 | * @param float $quantity |
||
462 | */ |
||
463 | protected function prepareQuantity(BuyableModel $buyable, $quantity) |
||
464 | { |
||
465 | $quantity = round($quantity, $buyable->QuantityDecimals()); |
||
466 | if ($quantity < 0 || (!$quantity && $quantity !== 0)) { |
||
467 | $this->addMessage(_t('Order.INVALIDQUANTITY', 'Invalid quantity.'), 'warning'); |
||
468 | |||
469 | return false; |
||
470 | } |
||
471 | |||
472 | return $quantity; |
||
473 | } |
||
474 | |||
475 | /** |
||
476 | * Helper function for making / retrieving order items. |
||
477 | * we do not need things like "canPurchase" here, because that is with the "addBuyable" method. |
||
478 | * NOTE: does not write! |
||
479 | * |
||
480 | * @param DataObject $buyable |
||
481 | * @param array $parameters |
||
482 | * |
||
483 | * @return OrderItem |
||
484 | */ |
||
485 | public function findOrMakeItem(BuyableModel $buyable, array $parameters = array()) |
||
486 | { |
||
487 | if ($item = $this->getExistingItem($buyable, $parameters)) { |
||
488 | //do nothing |
||
489 | } else { |
||
490 | //otherwise create a new item |
||
491 | if (!($buyable instanceof BuyableModel)) { |
||
492 | $this->addMessage(_t('ShoppingCart.ITEMNOTFOUND', 'Item is not buyable.'), 'bad'); |
||
493 | |||
494 | return false; |
||
495 | } |
||
496 | $className = $buyable->classNameForOrderItem(); |
||
497 | $item = new $className(); |
||
498 | if ($order = $this->currentOrder()) { |
||
499 | $item->OrderID = $order->ID; |
||
500 | $item->BuyableID = $buyable->ID; |
||
501 | $item->BuyableClassName = $buyable->ClassName; |
||
502 | if (isset($buyable->Version)) { |
||
503 | $item->Version = $buyable->Version; |
||
504 | } |
||
505 | } |
||
506 | } |
||
507 | if ($parameters) { |
||
508 | $item->Parameters = $parameters; |
||
509 | } |
||
510 | |||
511 | return $item; |
||
512 | } |
||
513 | |||
514 | /** |
||
515 | * submit the order so that it is no longer available |
||
516 | * in the cart but will continue its journey through the |
||
517 | * order steps. |
||
518 | * |
||
519 | * @return bool |
||
520 | */ |
||
521 | public function submit() |
||
522 | { |
||
523 | $this->currentOrder()->tryToFinaliseOrder(); |
||
524 | $this->clear(); |
||
525 | //little hack to clear static memory |
||
526 | OrderItem::reset_price_has_been_fixed($this->currentOrder()->ID); |
||
527 | |||
528 | return true; |
||
529 | } |
||
530 | |||
531 | /** |
||
532 | * @return bool |
||
533 | */ |
||
534 | public function save() |
||
535 | { |
||
536 | $this->currentOrder()->write(); |
||
537 | $this->addMessage(_t('Order.ORDERSAVED', 'Order Saved.'), 'good'); |
||
538 | |||
539 | return true; |
||
540 | } |
||
541 | |||
542 | /** |
||
543 | * Clears the cart contents completely by removing the orderID from session, and |
||
544 | * thus creating a new cart on next request. |
||
545 | * |
||
546 | * @return bool |
||
547 | */ |
||
548 | public function clear() |
||
549 | { |
||
550 | //we keep this here so that a flush can be added... |
||
551 | set_time_limit(1 * 60); |
||
552 | self::$_singletoncart = null; |
||
553 | $this->order = null; |
||
554 | $this->messages = array(); |
||
555 | foreach (self::$session_variable_names as $name) { |
||
556 | $sessionVariableName = $this->sessionVariableName($name); |
||
557 | Session::set($sessionVariableName, null); |
||
558 | Session::clear($sessionVariableName); |
||
559 | Session::save(); |
||
560 | } |
||
561 | $memberID = Intval(Member::currentUserID()); |
||
562 | if ($memberID) { |
||
563 | $orders = Order::get()->filter(array('MemberID' => $memberID)); |
||
564 | if ($orders && $orders->count()) { |
||
565 | foreach ($orders as $order) { |
||
566 | if (!$order->IsSubmitted()) { |
||
567 | $order->delete(); |
||
568 | } |
||
569 | } |
||
570 | } |
||
571 | } |
||
572 | |||
573 | return true; |
||
574 | } |
||
575 | |||
576 | /** |
||
577 | * alias for clear. |
||
578 | */ |
||
579 | public function reset() |
||
580 | { |
||
581 | return $this->clear(); |
||
582 | } |
||
583 | |||
584 | /** |
||
585 | * Removes a modifier from the cart |
||
586 | * It does not actually remove it, but it just |
||
587 | * sets it as "removed", to avoid that it is being |
||
588 | * added again. |
||
589 | * |
||
590 | * @param OrderModifier $modifier |
||
591 | * |
||
592 | * @return bool |
||
593 | */ |
||
594 | public function removeModifier(OrderModifier $modifier) |
||
595 | { |
||
596 | $modifier = (is_numeric($modifier)) ? OrderModifier::get()->byID($modifier) : $modifier; |
||
597 | if (!$modifier) { |
||
598 | $this->addMessage(_t('Order.MODIFIERNOTFOUND', 'Modifier could not be found.'), 'bad'); |
||
599 | |||
600 | return false; |
||
601 | } |
||
602 | if (!$modifier->CanBeRemoved()) { |
||
603 | $this->addMessage(_t('Order.MODIFIERCANNOTBEREMOVED', 'Modifier can not be removed.'), 'bad'); |
||
604 | |||
605 | return false; |
||
606 | } |
||
607 | $modifier->HasBeenRemoved = 1; |
||
608 | $modifier->onBeforeRemove(); |
||
609 | $modifier->write(); |
||
610 | $modifier->onAfterRemove(); |
||
611 | $this->addMessage(_t('Order.MODIFIERREMOVED', 'Removed.'), 'good'); |
||
612 | |||
613 | return true; |
||
614 | } |
||
615 | |||
616 | /** |
||
617 | * Removes a modifier from the cart. |
||
618 | * |
||
619 | * @param Int/ OrderModifier |
||
620 | * |
||
621 | * @return bool |
||
622 | */ |
||
623 | public function addModifier($modifier) |
||
624 | { |
||
625 | if (is_numeric($modifier)) { |
||
626 | $modifier = OrderModifier::get()->byID($modifier); |
||
627 | } elseif (!(is_a($modifier, Object::getCustomClass('OrderModifier')))) { |
||
628 | user_error('Bad parameter provided to ShoppingCart::addModifier', E_USER_WARNING); |
||
629 | } |
||
630 | if (!$modifier) { |
||
631 | $this->addMessage(_t('Order.MODIFIERNOTFOUND', 'Modifier could not be found.'), 'bad'); |
||
632 | |||
633 | return false; |
||
634 | } |
||
635 | $modifier->HasBeenRemoved = 0; |
||
636 | $modifier->write(); |
||
637 | $this->addMessage(_t('Order.MODIFIERREMOVED', 'Added.'), 'good'); |
||
638 | |||
639 | return true; |
||
640 | } |
||
641 | |||
642 | /** |
||
643 | * Sets an order as the current order. |
||
644 | * |
||
645 | * @param int | Order $order |
||
646 | * |
||
647 | * @return bool |
||
648 | */ |
||
649 | public function loadOrder($order) |
||
650 | { |
||
651 | //TODO: how to handle existing order |
||
652 | //TODO: permission check - does this belong to another member? ...or should permission be assumed already? |
||
653 | if (is_numeric($order)) { |
||
654 | $this->order = Order::get()->byID($order); |
||
655 | } elseif (is_a($order, Object::getCustomClass('Order'))) { |
||
656 | $this->order = $order; |
||
657 | } else { |
||
658 | user_error('Bad order provided as parameter to ShoppingCart::loadOrder()'); |
||
659 | } |
||
660 | if ($this->order) { |
||
661 | //first can view and then, if can view, set as session... |
||
662 | if ($this->order->canView()) { |
||
663 | $this->order->init(true); |
||
664 | $sessionVariableName = $this->sessionVariableName('OrderID'); |
||
665 | //we set session ID after can view check ... |
||
666 | Session::set($sessionVariableName, $this->order->ID); |
||
667 | $this->addMessage(_t('Order.LOADEDEXISTING', 'Order loaded.'), 'good'); |
||
668 | |||
669 | return true; |
||
670 | } else { |
||
671 | $this->addMessage(_t('Order.NOPERMISSION', 'You do not have permission to view this order.'), 'bad'); |
||
672 | |||
673 | return false; |
||
674 | } |
||
675 | } else { |
||
676 | $this->addMessage(_t('Order.NOORDER', 'Order can not be found.'), 'bad'); |
||
677 | |||
678 | return false; |
||
679 | } |
||
680 | } |
||
681 | |||
682 | /** |
||
683 | * NOTE: tried to copy part to the Order Class - but that was not much of a go-er. |
||
684 | * |
||
685 | * @param int | Order $order |
||
686 | * |
||
687 | * @return Order | false |
||
688 | **/ |
||
689 | public function copyOrder($oldOrder) |
||
690 | { |
||
691 | if (is_numeric($oldOrder)) { |
||
692 | $oldOrder = Order::get()->byID(intval($oldOrder)); |
||
693 | } elseif (is_a($oldOrder, Object::getCustomClass('Order'))) { |
||
694 | //$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...
|
|||
695 | } else { |
||
696 | user_error('Bad order provided as parameter to ShoppingCart::loadOrder()'); |
||
697 | } |
||
698 | if ($oldOrder) { |
||
699 | if ($oldOrder->canView() && $oldOrder->IsSubmitted()) { |
||
700 | $newOrder = Order::create(); |
||
701 | //copying fields. |
||
702 | $newOrder->UseShippingAddress = $oldOrder->UseShippingAddress; |
||
703 | //important to set it this way... |
||
704 | $newOrder->setCurrency($oldOrder->CurrencyUsed()); |
||
705 | $newOrder->MemberID = $oldOrder->MemberID; |
||
706 | //load the order |
||
707 | $newOrder->write(); |
||
708 | $this->loadOrder($newOrder); |
||
709 | $items = OrderItem::get() |
||
710 | ->filter(array('OrderID' => $oldOrder->ID)); |
||
711 | if ($items->count()) { |
||
712 | foreach ($items as $item) { |
||
713 | $buyable = $item->Buyable($current = true); |
||
714 | if ($buyable->canPurchase()) { |
||
715 | $this->addBuyable($buyable, $item->Quantity); |
||
716 | } |
||
717 | } |
||
718 | } |
||
719 | $newOrder->CreateOrReturnExistingAddress('BillingAddress'); |
||
720 | $newOrder->CreateOrReturnExistingAddress('ShippingAddress'); |
||
721 | $newOrder->write(); |
||
722 | $this->addMessage(_t('Order.ORDERCOPIED', 'Order has been copied.'), 'good'); |
||
723 | |||
724 | return $newOrder; |
||
725 | } else { |
||
726 | $this->addMessage(_t('Order.NOPERMISSION', 'You do not have permission to view this order.'), 'bad'); |
||
727 | |||
728 | return false; |
||
729 | } |
||
730 | } else { |
||
731 | $this->addMessage(_t('Order.NOORDER', 'Order can not be found.'), 'bad'); |
||
732 | |||
733 | return false; |
||
734 | } |
||
735 | } |
||
736 | |||
737 | /** |
||
738 | * sets country in order so that modifiers can be recalculated, etc... |
||
739 | * |
||
740 | * @param string - $countryCode |
||
741 | * |
||
742 | * @return bool |
||
743 | **/ |
||
744 | public function setCountry($countryCode) |
||
745 | { |
||
746 | if (EcommerceCountry::code_allowed($countryCode)) { |
||
747 | $this->currentOrder()->SetCountryFields($countryCode); |
||
748 | $this->addMessage(_t('Order.UPDATEDCOUNTRY', 'Updated country.'), 'good'); |
||
749 | |||
750 | return true; |
||
751 | } else { |
||
752 | $this->addMessage(_t('Order.NOTUPDATEDCOUNTRY', 'Could not update country.'), 'bad'); |
||
753 | |||
754 | return false; |
||
755 | } |
||
756 | } |
||
757 | |||
758 | /** |
||
759 | * sets region in order so that modifiers can be recalculated, etc... |
||
760 | * |
||
761 | * @param int | String - $regionID you can use the ID or the code. |
||
762 | * |
||
763 | * @return bool |
||
764 | **/ |
||
765 | public function setRegion($regionID) |
||
766 | { |
||
767 | if (EcommerceRegion::regionid_allowed($regionID)) { |
||
768 | $this->currentOrder()->SetRegionFields($regionID); |
||
769 | $this->addMessage(_t('ShoppingCart.REGIONUPDATED', 'Region updated.'), 'good'); |
||
770 | |||
771 | return true; |
||
772 | } else { |
||
773 | $this->addMessage(_t('ORDER.NOTUPDATEDREGION', 'Could not update region.'), 'bad'); |
||
774 | |||
775 | return false; |
||
776 | } |
||
777 | } |
||
778 | |||
779 | /** |
||
780 | * sets the display currency for the cart. |
||
781 | * |
||
782 | * @param string $currencyCode |
||
783 | * |
||
784 | * @return bool |
||
785 | **/ |
||
786 | public function setCurrency($currencyCode) |
||
787 | { |
||
788 | $currency = EcommerceCurrency::get_one_from_code($currencyCode); |
||
789 | if ($currency) { |
||
790 | if ($this->currentOrder()->MemberID) { |
||
791 | $member = $this->currentOrder()->Member(); |
||
792 | if ($member && $member->exists()) { |
||
793 | $member->SetPreferredCurrency($currency); |
||
794 | } |
||
795 | } |
||
796 | $this->currentOrder()->UpdateCurrency($currency); |
||
797 | $msg = _t('Order.CURRENCYUPDATED', 'Currency updated.'); |
||
798 | $this->addMessage($msg, 'good'); |
||
799 | |||
800 | return true; |
||
801 | } else { |
||
802 | $msg = _t('Order.CURRENCYCOULDNOTBEUPDATED', 'Currency could not be updated.'); |
||
803 | $this->addMessage($msg, 'bad'); |
||
804 | |||
805 | return false; |
||
806 | } |
||
807 | } |
||
808 | |||
809 | /** |
||
810 | * Produces a debug of the shopping cart. |
||
811 | */ |
||
812 | public function debug() |
||
813 | { |
||
814 | if (Director::isDev() || Permission::check('ADMIN')) { |
||
815 | debug::show($this->currentOrder()); |
||
816 | |||
817 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Country</h1>'; |
||
818 | echo 'GEOIP Country: '.EcommerceCountry::get_country_from_ip().'<br />'; |
||
819 | echo 'Calculated Country Country: '.EcommerceCountry::get_country().'<br />'; |
||
820 | |||
821 | echo '<blockquote><blockquote><blockquote><blockquote>'; |
||
822 | |||
823 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Items</h1>'; |
||
824 | $items = $this->currentOrder()->Items(); |
||
825 | echo $items->sql(); |
||
826 | echo '<hr />'; |
||
827 | if ($items->count()) { |
||
828 | foreach ($items as $item) { |
||
829 | Debug::show($item); |
||
830 | } |
||
831 | } else { |
||
832 | echo '<p>there are no items for this order</p>'; |
||
833 | } |
||
834 | |||
835 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Modifiers</h1>'; |
||
836 | $modifiers = $this->currentOrder()->Modifiers(); |
||
837 | if ($modifiers->count()) { |
||
838 | foreach ($modifiers as $modifier) { |
||
839 | Debug::show($modifier); |
||
840 | } |
||
841 | } else { |
||
842 | echo '<p>there are no modifiers for this order</p>'; |
||
843 | } |
||
844 | |||
845 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Addresses</h1>'; |
||
846 | $billingAddress = $this->currentOrder()->BillingAddress(); |
||
847 | if ($billingAddress && $billingAddress->exists()) { |
||
848 | Debug::show($billingAddress); |
||
849 | } else { |
||
850 | echo '<p>there is no billing address for this order</p>'; |
||
851 | } |
||
852 | $shippingAddress = $this->currentOrder()->ShippingAddress(); |
||
853 | if ($shippingAddress && $shippingAddress->exists()) { |
||
854 | Debug::show($shippingAddress); |
||
855 | } else { |
||
856 | echo '<p>there is no shipping address for this order</p>'; |
||
857 | } |
||
858 | |||
859 | $currencyUsed = $this->currentOrder()->CurrencyUsed(); |
||
860 | if ($currencyUsed && $currencyUsed->exists()) { |
||
861 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Currency</h1>'; |
||
862 | Debug::show($currencyUsed); |
||
863 | } |
||
864 | |||
865 | $cancelledBy = $this->currentOrder()->CancelledBy(); |
||
866 | if ($cancelledBy && $cancelledBy->exists()) { |
||
867 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Cancelled By</h1>'; |
||
868 | Debug::show($cancelledBy); |
||
869 | } |
||
870 | |||
871 | $logs = $this->currentOrder()->OrderStatusLogs(); |
||
872 | if ($logs && $logs->count()) { |
||
873 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Logs</h1>'; |
||
874 | foreach ($logs as $log) { |
||
875 | Debug::show($log); |
||
876 | } |
||
877 | } |
||
878 | |||
879 | $payments = $this->currentOrder()->Payments(); |
||
880 | if ($payments && $payments->count()) { |
||
881 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Payments</h1>'; |
||
882 | foreach ($payments as $payment) { |
||
883 | Debug::show($payment); |
||
884 | } |
||
885 | } |
||
886 | |||
887 | $emails = $this->currentOrder()->Emails(); |
||
888 | if ($emails && $emails->count()) { |
||
889 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Emails</h1>'; |
||
890 | foreach ($emails as $email) { |
||
891 | Debug::show($email); |
||
892 | } |
||
893 | } |
||
894 | |||
895 | echo '</blockquote></blockquote></blockquote></blockquote>'; |
||
896 | } else { |
||
897 | echo 'Please log in as admin first'; |
||
898 | } |
||
899 | } |
||
900 | |||
901 | /** |
||
902 | * Stores a message that can later be returned via ajax or to $form->sessionMessage();. |
||
903 | * |
||
904 | * @param $message - the message, which could be a notification of successful action, or reason for failure |
||
905 | * @param $type - please use good, bad, warning |
||
906 | */ |
||
907 | public function addMessage($message, $status = 'good') |
||
908 | { |
||
909 | //clean status for the lazy programmer |
||
910 | //TODO: remove the awkward replace |
||
911 | $status = strtolower($status); |
||
912 | str_replace(array('success', 'failure'), array('good', 'bad'), $status); |
||
913 | $statusOptions = array('good', 'bad', 'warning'); |
||
914 | if (!in_array($status, $statusOptions)) { |
||
915 | user_error('Message status should be one of the following: '.implode(',', $statusOptions), E_USER_NOTICE); |
||
916 | } |
||
917 | $this->messages[] = array( |
||
918 | 'Message' => $message, |
||
919 | 'Type' => $status, |
||
920 | ); |
||
921 | } |
||
922 | |||
923 | /******************************************************* |
||
924 | * HELPER FUNCTIONS |
||
925 | *******************************************************/ |
||
926 | |||
927 | /** |
||
928 | * Gets an existing order item based on buyable and passed parameters. |
||
929 | * |
||
930 | * @param DataObject $buyable |
||
931 | * @param array $parameters |
||
932 | * |
||
933 | * @return OrderItem | null |
||
934 | */ |
||
935 | protected function getExistingItem(BuyableModel $buyable, array $parameters = array()) |
||
936 | { |
||
937 | $filterString = $this->parametersToSQL($parameters); |
||
938 | if ($order = $this->currentOrder()) { |
||
939 | $orderID = $order->ID; |
||
940 | $obj = OrderItem::get() |
||
941 | ->where( |
||
942 | " \"BuyableClassName\" = '".$buyable->ClassName."' AND |
||
943 | \"BuyableID\" = ".$buyable->ID.' AND |
||
944 | "OrderID" = '.$orderID.' '. |
||
945 | $filterString |
||
946 | ) |
||
947 | ->First(); |
||
948 | |||
949 | return $obj; |
||
950 | } |
||
951 | } |
||
952 | |||
953 | /** |
||
954 | * Removes parameters that aren't in the default array, merges with default parameters, and converts raw2SQL. |
||
955 | * |
||
956 | * @param array $parameters |
||
957 | * |
||
958 | * @return cleaned array |
||
959 | */ |
||
960 | protected function cleanParameters(array $params = array()) |
||
961 | { |
||
962 | $defaultParamFilters = EcommerceConfig::get('ShoppingCart', 'default_param_filters'); |
||
963 | $newarray = array_merge(array(), $defaultParamFilters); //clone array |
||
964 | if (!count($newarray)) { |
||
965 | return array(); //no use for this if there are not parameters defined |
||
966 | } |
||
967 | foreach ($newarray as $field => $value) { |
||
968 | if (isset($params[$field])) { |
||
969 | $newarray[$field] = Convert::raw2sql($params[$field]); |
||
970 | } |
||
971 | } |
||
972 | |||
973 | return $newarray; |
||
974 | } |
||
975 | |||
976 | /** |
||
977 | * @param array $parameters |
||
978 | * Converts parameter array to SQL query filter |
||
979 | */ |
||
980 | protected function parametersToSQL(array $parameters = array()) |
||
981 | { |
||
982 | $defaultParamFilters = EcommerceConfig::get('ShoppingCart', 'default_param_filters'); |
||
983 | if (!count($defaultParamFilters)) { |
||
984 | return ''; //no use for this if there are not parameters defined |
||
985 | } |
||
986 | $cleanedparams = $this->cleanParameters($parameters); |
||
987 | $outputArray = array(); |
||
988 | foreach ($cleanedparams as $field => $value) { |
||
989 | $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 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...
|
|||
990 | } |
||
991 | if (count($outputArray)) { |
||
992 | return implode(' AND ', $outputArray); |
||
993 | } |
||
994 | |||
995 | return ''; |
||
996 | } |
||
997 | |||
998 | /******************************************************* |
||
999 | * UI MESSAGE HANDLING |
||
1000 | *******************************************************/ |
||
1001 | |||
1002 | /** |
||
1003 | * Retrieves all good, bad, and ugly messages that have been produced during the current request. |
||
1004 | * |
||
1005 | * @return array of messages |
||
1006 | */ |
||
1007 | public function getMessages() |
||
1008 | { |
||
1009 | $sessionVariableName = $this->sessionVariableName('Messages'); |
||
1010 | //get old messages |
||
1011 | $messages = unserialize(Session::get($sessionVariableName)); |
||
1012 | //clear old messages |
||
1013 | Session::clear($sessionVariableName, ''); |
||
1014 | //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...
|
|||
1015 | if ($messages && count($messages)) { |
||
1016 | $this->messages = array_merge($messages, $this->messages); |
||
1017 | } |
||
1018 | |||
1019 | return $this->messages; |
||
1020 | } |
||
1021 | |||
1022 | /** |
||
1023 | *Saves current messages in session for retrieving them later. |
||
1024 | * |
||
1025 | *@return array of messages |
||
1026 | */ |
||
1027 | protected function StoreMessagesInSession() |
||
1028 | { |
||
1029 | $sessionVariableName = $this->sessionVariableName('Messages'); |
||
1030 | Session::set($sessionVariableName, serialize($this->messages)); |
||
1031 | } |
||
1032 | |||
1033 | /** |
||
1034 | * This method is used to return data after an ajax call was made. |
||
1035 | * When a asynchronious request is made to the shopping cart (ajax), |
||
1036 | * then you will first action the request and then use this function |
||
1037 | * to return some values. |
||
1038 | * |
||
1039 | * It can also be used without ajax, in wich case it will redirects back |
||
1040 | * to the last page. |
||
1041 | * |
||
1042 | * Note that you can set the ajax response class in the configuration file. |
||
1043 | * |
||
1044 | * |
||
1045 | * @param string $message |
||
1046 | * @param string $status |
||
1047 | * @param Form $form |
||
1048 | * @returns String (JSON) |
||
1049 | */ |
||
1050 | public function setMessageAndReturn($message = '', $status = '', Form $form = null) |
||
1051 | { |
||
1052 | if ($message && $status) { |
||
1053 | $this->addMessage($message, $status); |
||
1054 | } |
||
1055 | //TODO: handle passing back multiple messages |
||
1056 | |||
1057 | if (Director::is_ajax()) { |
||
1058 | $responseClass = EcommerceConfig::get('ShoppingCart', 'response_class'); |
||
1059 | $obj = new $responseClass(); |
||
1060 | |||
1061 | return $obj->ReturnCartData($this->getMessages()); |
||
1062 | } else { |
||
1063 | //TODO: handle passing a message back to a form->sessionMessage |
||
1064 | $this->StoreMessagesInSession(); |
||
1065 | if ($form) { |
||
1066 | //lets make sure that there is an order |
||
1067 | $this->currentOrder(); |
||
1068 | //nowe we can (re)calculate the order |
||
1069 | $this->order->calculateOrderAttributes($force = false); |
||
1070 | $form->sessionMessage($message, $status); |
||
1071 | //let the form controller do the redirectback or whatever else is needed. |
||
1072 | } else { |
||
1073 | if (empty($_REQUEST['BackURL']) && Controller::has_curr()) { |
||
1074 | Controller::curr()->redirectBack(); |
||
1075 | } else { |
||
1076 | Controller::curr()->redirect(urldecode($_REQUEST['BackURL'])); |
||
1077 | } |
||
1078 | } |
||
1079 | |||
1080 | return; |
||
1081 | } |
||
1082 | } |
||
1083 | |||
1084 | /** |
||
1085 | * @return EcommerceDBConfig |
||
1086 | */ |
||
1087 | protected function EcomConfig() |
||
1088 | { |
||
1089 | return EcommerceDBConfig::current_ecommerce_db_config(); |
||
1090 | } |
||
1091 | |||
1092 | /** |
||
1093 | * Return the name of the session variable that should be used. |
||
1094 | * |
||
1095 | * @param string $name |
||
1096 | * |
||
1097 | * @return string |
||
1098 | */ |
||
1099 | protected function sessionVariableName($name = '') |
||
1100 | { |
||
1101 | if (!in_array($name, self::$session_variable_names)) { |
||
1102 | user_error("Tried to set session variable $name, that is not in use", E_USER_NOTICE); |
||
1103 | } |
||
1104 | $sessionCode = EcommerceConfig::get('ShoppingCart', 'session_code'); |
||
1105 | |||
1106 | return $sessionCode.'_'.$name; |
||
1107 | } |
||
1108 | } |
||
1109 |
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.