Complex classes like ShoppingCart often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ShoppingCart, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
36 | class ShoppingCart extends Object |
||
|
|||
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) |
||
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; |
||
110 | */ |
||
111 | public static function current_order_id($orderOrOrderID = 0) |
||
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 |
||
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) |
||
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) { |
||
190 | $updateMember = false; |
||
191 | if (!$this->order->MemberID) { |
||
192 | $updateMember = true; |
||
193 | } |
||
194 | if (!$loggedInMember->IsShopAdmin()) { |
||
195 | $updateMember = true; |
||
196 | } |
||
197 | if ($updateMember) { |
||
198 | $this->order->MemberID = $loggedInMember->ID; |
||
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()) { |
||
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; |
||
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($this->order) { |
||
315 | return true; |
||
316 | } |
||
317 | if(self::$_allow_writes_cache === null) { |
||
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 | return self::$_allow_writes_cache; |
||
330 | |||
331 | } |
||
332 | |||
333 | /** |
||
334 | * Allows access to the current order from anywhere in the code.. |
||
335 | * |
||
336 | * @param Order $order (optional) |
||
337 | * |
||
338 | * @return ShoppingCart Object |
||
339 | */ |
||
340 | public function Link($order = null) |
||
341 | { |
||
342 | $order = self::singleton()->currentOrder(0, $order = null); |
||
343 | if ($order) { |
||
344 | return $order->Link(); |
||
345 | } |
||
346 | } |
||
347 | |||
348 | /** |
||
349 | * Adds any number of items to the cart. |
||
350 | * Returns the order item on succes OR false on failure. |
||
351 | * |
||
352 | * @param DataObject $buyable - the buyable (generally a product) being added to the cart |
||
353 | * @param float $quantity - number of items add. |
||
354 | * @param mixed $parameters - array of parameters to target a specific order item. eg: group=1, length=5 |
||
355 | * if you make it a form, it will save the form into the orderitem |
||
356 | * returns null if the current user does not allow order manipulation or saving (e.g. session disabled) |
||
357 | * |
||
358 | * @return false | DataObject (OrderItem) |
||
359 | */ |
||
360 | public function addBuyable(BuyableModel $buyable, $quantity = 1, $parameters = array()) |
||
361 | { |
||
362 | if($this->allowWrites()) { |
||
363 | if (!$buyable) { |
||
364 | $this->addMessage(_t('Order.ITEMCOULDNOTBEFOUND', 'This item could not be found.'), 'bad'); |
||
365 | return false; |
||
366 | } |
||
367 | if (!$buyable->canPurchase()) { |
||
368 | $this->addMessage(_t('Order.ITEMCOULDNOTBEADDED', 'This item is not for sale.'), 'bad'); |
||
369 | return false; |
||
370 | } |
||
371 | $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false); |
||
372 | $quantity = $this->prepareQuantity($buyable, $quantity); |
||
373 | if ($item && $quantity) { //find existing order item or make one |
||
374 | $item->Quantity += $quantity; |
||
375 | $item->write(); |
||
376 | $this->currentOrder()->Attributes()->add($item); //save to current order |
||
377 | //TODO: distinquish between incremented and set |
||
378 | //TODO: use sprintf to allow product name etc to be included in message |
||
379 | if ($quantity > 1) { |
||
380 | $msg = _t('Order.ITEMSADDED', 'Items added.'); |
||
381 | } else { |
||
382 | $msg = _t('Order.ITEMADDED', 'Item added.'); |
||
383 | } |
||
384 | $this->addMessage($msg, 'good'); |
||
385 | } elseif (!$item) { |
||
386 | $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad'); |
||
387 | } else { |
||
388 | $this->addMessage(_t('Order.ITEMCOULDNOTBEADDED', 'Item could not be added.'), 'bad'); |
||
389 | } |
||
390 | |||
391 | return $item; |
||
392 | } |
||
393 | } |
||
394 | |||
395 | /** |
||
396 | * Sets quantity for an item in the cart. |
||
397 | * |
||
398 | * returns null if the current user does not allow order manipulation or saving (e.g. session disabled) |
||
399 | * |
||
400 | * @param DataObject $buyable - the buyable (generally a product) being added to the cart |
||
401 | * @param float $quantity - number of items add. |
||
402 | * @param array $parameters - array of parameters to target a specific order item. eg: group=1, length=5 |
||
403 | * |
||
404 | * @return false | DataObject (OrderItem) | null |
||
405 | */ |
||
406 | public function setQuantity(BuyableModel $buyable, $quantity, array $parameters = array()) |
||
407 | { |
||
408 | if($this->allowWrites()) { |
||
409 | $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false); |
||
410 | $quantity = $this->prepareQuantity($buyable, $quantity); |
||
411 | if ($item) { |
||
412 | $item->Quantity = $quantity; //remove quantity |
||
413 | $item->write(); |
||
414 | $this->addMessage(_t('Order.ITEMUPDATED', 'Item updated.'), 'good'); |
||
415 | |||
416 | return $item; |
||
417 | } else { |
||
418 | $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad'); |
||
419 | } |
||
420 | |||
421 | return false; |
||
422 | } |
||
423 | } |
||
424 | |||
425 | /** |
||
426 | * Removes any number of items from the cart. |
||
427 | * |
||
428 | * returns null if the current user does not allow order manipulation or saving (e.g. session disabled) |
||
429 | * |
||
430 | * @param DataObject $buyable - the buyable (generally a product) being added to the cart |
||
431 | * @param float $quantity - number of items add. |
||
432 | * @param array $parameters - array of parameters to target a specific order item. eg: group=1, length=5 |
||
433 | * |
||
434 | * @return false | OrderItem | null |
||
435 | */ |
||
436 | public function decrementBuyable(BuyableModel $buyable, $quantity = 1, array $parameters = array()) |
||
437 | { |
||
438 | if($this->allowWrites()) { |
||
439 | $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = false); |
||
440 | $quantity = $this->prepareQuantity($buyable, $quantity); |
||
441 | if ($item) { |
||
442 | $item->Quantity -= $quantity; //remove quantity |
||
443 | if ($item->Quantity < 0) { |
||
444 | $item->Quantity = 0; |
||
445 | } |
||
446 | $item->write(); |
||
447 | if ($quantity > 1) { |
||
448 | $msg = _t('Order.ITEMSREMOVED', 'Items removed.'); |
||
449 | } else { |
||
450 | $msg = _t('Order.ITEMREMOVED', 'Item removed.'); |
||
451 | } |
||
452 | $this->addMessage($msg, 'good'); |
||
453 | |||
454 | return $item; |
||
455 | } else { |
||
456 | $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad'); |
||
457 | } |
||
458 | |||
459 | return false; |
||
460 | } |
||
461 | |||
462 | } |
||
463 | |||
464 | /** |
||
465 | * Delete item from the cart. |
||
466 | * |
||
467 | * returns null if the current user does not allow order manipulation or saving (e.g. session disabled) |
||
468 | * |
||
469 | * @param OrderItem $buyable - the buyable (generally a product) being added to the cart |
||
470 | * @param array $parameters - array of parameters to target a specific order item. eg: group=1, length=5 |
||
471 | * |
||
472 | * @return bool | item | null - successfully removed |
||
473 | */ |
||
474 | public function deleteBuyable(BuyableModel $buyable, array $parameters = array()) |
||
475 | { |
||
476 | if($this->allowWrites()) { |
||
477 | $item = $this->prepareOrderItem($buyable, $parameters, $mustBeExistingItem = true); |
||
478 | if ($item) { |
||
479 | $this->currentOrder()->Attributes()->remove($item); |
||
480 | $item->delete(); |
||
481 | $item->destroy(); |
||
482 | $this->addMessage(_t('Order.ITEMCOMPLETELYREMOVED', 'Item removed from cart.'), 'good'); |
||
483 | |||
484 | return $item; |
||
485 | } else { |
||
486 | $this->addMessage(_t('Order.ITEMNOTFOUND', 'Item could not be found.'), 'bad'); |
||
487 | |||
488 | return false; |
||
489 | } |
||
490 | } |
||
491 | } |
||
492 | |||
493 | /** |
||
494 | * Checks and prepares variables for a quantity change (add, edit, remove) for an Order Item. |
||
495 | * |
||
496 | * @param DataObject $buyable - the buyable (generally a product) being added to the cart |
||
497 | * @param float $quantity - number of items add. |
||
498 | * @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. |
||
499 | * @param array | Form $parameters - array of parameters to target a specific order item. eg: group=1, length=5* |
||
500 | * - form saved into item... |
||
501 | * |
||
502 | * @return bool | DataObject ($orderItem) |
||
503 | */ |
||
504 | protected function prepareOrderItem(BuyableModel $buyable, $parameters = array(), $mustBeExistingItem = true) |
||
505 | { |
||
506 | $parametersArray = $parameters; |
||
507 | $form = null; |
||
508 | if ($parameters instanceof Form) { |
||
509 | $parametersArray = array(); |
||
510 | $form = $parameters; |
||
511 | } |
||
512 | if (!$buyable) { |
||
513 | user_error('No buyable was provided', E_USER_WARNING); |
||
514 | } |
||
515 | if (!$buyable->canPurchase()) { |
||
516 | $item = $this->getExistingItem($buyable, $parametersArray); |
||
517 | if ($item && $item->exists()) { |
||
518 | $item->delete(); |
||
519 | $item->destroy(); |
||
520 | } |
||
521 | |||
522 | return false; |
||
523 | } |
||
524 | $item = null; |
||
525 | if ($mustBeExistingItem) { |
||
526 | $item = $this->getExistingItem($buyable, $parametersArray); |
||
527 | } else { |
||
528 | $item = $this->findOrMakeItem($buyable, $parametersArray); //find existing order item or make one |
||
529 | } |
||
530 | if (!$item) { |
||
531 | //check for existence of item |
||
532 | return false; |
||
533 | } |
||
534 | if ($form) { |
||
535 | $form->saveInto($item); |
||
536 | } |
||
537 | |||
538 | return $item; |
||
539 | } |
||
540 | |||
541 | /** |
||
542 | * @todo: what does this method do??? |
||
543 | * |
||
544 | * @return int |
||
545 | * |
||
546 | * @param DataObject ($buyable) |
||
547 | * @param float $quantity |
||
548 | */ |
||
549 | protected function prepareQuantity(BuyableModel $buyable, $quantity) |
||
550 | { |
||
551 | $quantity = round($quantity, $buyable->QuantityDecimals()); |
||
552 | if ($quantity < 0 || (!$quantity && $quantity !== 0)) { |
||
553 | $this->addMessage(_t('Order.INVALIDQUANTITY', 'Invalid quantity.'), 'warning'); |
||
554 | |||
555 | return false; |
||
556 | } |
||
557 | |||
558 | return $quantity; |
||
559 | } |
||
560 | |||
561 | /** |
||
562 | * Helper function for making / retrieving order items. |
||
563 | * we do not need things like "canPurchase" here, because that is with the "addBuyable" method. |
||
564 | * NOTE: does not write! |
||
565 | * |
||
566 | * @param DataObject $buyable |
||
567 | * @param array $parameters |
||
568 | * |
||
569 | * @return OrderItem |
||
570 | */ |
||
571 | public function findOrMakeItem(BuyableModel $buyable, array $parameters = array()) |
||
572 | { |
||
573 | if($this->allowWrites()) { |
||
574 | if ($item = $this->getExistingItem($buyable, $parameters)) { |
||
575 | //do nothing |
||
576 | } else { |
||
577 | //otherwise create a new item |
||
578 | if (!($buyable instanceof BuyableModel)) { |
||
579 | $this->addMessage(_t('ShoppingCart.ITEMNOTFOUND', 'Item is not buyable.'), 'bad'); |
||
580 | |||
581 | return false; |
||
582 | } |
||
583 | $className = $buyable->classNameForOrderItem(); |
||
584 | $item = new $className(); |
||
585 | if ($order = $this->currentOrder()) { |
||
586 | $item->OrderID = $order->ID; |
||
587 | $item->BuyableID = $buyable->ID; |
||
588 | $item->BuyableClassName = $buyable->ClassName; |
||
589 | if (isset($buyable->Version)) { |
||
590 | $item->Version = $buyable->Version; |
||
591 | } |
||
592 | } |
||
593 | } |
||
594 | if ($parameters) { |
||
595 | $item->Parameters = $parameters; |
||
596 | } |
||
597 | |||
598 | return $item; |
||
599 | } else { |
||
600 | return OrderItem::create(); |
||
601 | } |
||
602 | } |
||
603 | |||
604 | /** |
||
605 | * submit the order so that it is no longer available |
||
606 | * in the cart but will continue its journey through the |
||
607 | * order steps. |
||
608 | * |
||
609 | * @return bool |
||
610 | */ |
||
611 | public function submit() |
||
612 | { |
||
613 | if($this->allowWrites()) { |
||
614 | $this->currentOrder()->tryToFinaliseOrder(); |
||
615 | $this->clear(); |
||
616 | //little hack to clear static memory |
||
617 | OrderItem::reset_price_has_been_fixed($this->currentOrder()->ID); |
||
618 | |||
619 | return true; |
||
620 | } |
||
621 | |||
622 | return false; |
||
623 | } |
||
624 | |||
625 | /** |
||
626 | * returns null if the current user does not allow order manipulation or saving (e.g. session disabled) |
||
627 | * |
||
628 | * @return bool | null |
||
629 | */ |
||
630 | public function save() |
||
631 | { |
||
632 | if($this->allowWrites()) { |
||
633 | $this->currentOrder()->write(); |
||
634 | $this->addMessage(_t('Order.ORDERSAVED', 'Order Saved.'), 'good'); |
||
635 | |||
636 | return true; |
||
637 | } |
||
638 | } |
||
639 | |||
640 | /** |
||
641 | * Clears the cart contents completely by removing the orderID from session, and |
||
642 | * thus creating a new cart on next request. |
||
643 | * |
||
644 | * @return bool |
||
645 | */ |
||
646 | public function clear() |
||
647 | { |
||
648 | //we keep this here so that a flush can be added... |
||
649 | set_time_limit(1 * 60); |
||
650 | self::$_singletoncart = null; |
||
651 | $this->order = null; |
||
652 | $this->messages = array(); |
||
653 | foreach (self::$session_variable_names as $name) { |
||
654 | $sessionVariableName = $this->sessionVariableName($name); |
||
655 | Session::set($sessionVariableName, null); |
||
656 | Session::clear($sessionVariableName); |
||
657 | Session::save(); |
||
658 | } |
||
659 | $memberID = Intval(Member::currentUserID()); |
||
660 | if ($memberID) { |
||
661 | $orders = Order::get()->filter(array('MemberID' => $memberID)); |
||
662 | if ($orders && $orders->count()) { |
||
663 | foreach ($orders as $order) { |
||
664 | if (! $order->IsSubmitted()) { |
||
665 | $order->delete(); |
||
666 | } |
||
667 | } |
||
668 | } |
||
669 | } |
||
670 | |||
671 | return true; |
||
672 | } |
||
673 | |||
674 | /** |
||
675 | * alias for clear. |
||
676 | */ |
||
677 | public function reset() |
||
678 | { |
||
679 | return $this->clear(); |
||
680 | } |
||
681 | |||
682 | /** |
||
683 | * Removes a modifier from the cart |
||
684 | * It does not actually remove it, but it just |
||
685 | * sets it as "removed", to avoid that it is being |
||
686 | * added again. |
||
687 | * |
||
688 | * returns null if the current user does not allow order manipulation or saving (e.g. session disabled) |
||
689 | * |
||
690 | * @param OrderModifier $modifier |
||
691 | * |
||
692 | * @return bool | null |
||
693 | */ |
||
694 | public function removeModifier(OrderModifier $modifier) |
||
695 | { |
||
696 | if($this->allowWrites()) { |
||
697 | $modifier = (is_numeric($modifier)) ? OrderModifier::get()->byID($modifier) : $modifier; |
||
698 | if (!$modifier) { |
||
699 | $this->addMessage(_t('Order.MODIFIERNOTFOUND', 'Modifier could not be found.'), 'bad'); |
||
700 | |||
701 | return false; |
||
702 | } |
||
703 | if (!$modifier->CanBeRemoved()) { |
||
704 | $this->addMessage(_t('Order.MODIFIERCANNOTBEREMOVED', 'Modifier can not be removed.'), 'bad'); |
||
705 | |||
706 | return false; |
||
707 | } |
||
708 | $modifier->HasBeenRemoved = 1; |
||
709 | $modifier->onBeforeRemove(); |
||
710 | $modifier->write(); |
||
711 | $modifier->onAfterRemove(); |
||
712 | $this->addMessage(_t('Order.MODIFIERREMOVED', 'Removed.'), 'good'); |
||
713 | |||
714 | return true; |
||
715 | } |
||
716 | } |
||
717 | |||
718 | /** |
||
719 | * Removes a modifier from the cart. |
||
720 | * |
||
721 | * returns null if the current user does not allow order manipulation or saving (e.g. session disabled) |
||
722 | * |
||
723 | * @param Int/ OrderModifier |
||
724 | * |
||
725 | * @return bool |
||
726 | */ |
||
727 | public function addModifier($modifier) |
||
728 | { |
||
729 | if($this->allowWrites()) { |
||
730 | if (is_numeric($modifier)) { |
||
731 | $modifier = OrderModifier::get()->byID($modifier); |
||
732 | } elseif (!(is_a($modifier, Object::getCustomClass('OrderModifier')))) { |
||
733 | user_error('Bad parameter provided to ShoppingCart::addModifier', E_USER_WARNING); |
||
734 | } |
||
735 | if (!$modifier) { |
||
736 | $this->addMessage(_t('Order.MODIFIERNOTFOUND', 'Modifier could not be found.'), 'bad'); |
||
737 | |||
738 | return false; |
||
739 | } |
||
740 | $modifier->HasBeenRemoved = 0; |
||
741 | $modifier->write(); |
||
742 | $this->addMessage(_t('Order.MODIFIERREMOVED', 'Added.'), 'good'); |
||
743 | |||
744 | return true; |
||
745 | } |
||
746 | } |
||
747 | |||
748 | /** |
||
749 | * Sets an order as the current order. |
||
750 | * |
||
751 | * @param int | Order $order |
||
752 | * |
||
753 | * @return bool |
||
754 | */ |
||
755 | public function loadOrder($order) |
||
756 | { |
||
757 | if($this->allowWrites()) { |
||
758 | //TODO: how to handle existing order |
||
759 | //TODO: permission check - does this belong to another member? ...or should permission be assumed already? |
||
760 | if (is_numeric($order)) { |
||
761 | $this->order = Order::get()->byID($order); |
||
762 | } elseif (is_a($order, Object::getCustomClass('Order'))) { |
||
763 | $this->order = $order; |
||
764 | } else { |
||
765 | user_error('Bad order provided as parameter to ShoppingCart::loadOrder()'); |
||
766 | } |
||
767 | if ($this->order) { |
||
768 | //first can view and then, if can view, set as session... |
||
769 | if ($this->order->canView()) { |
||
770 | $this->order->init(true); |
||
771 | $sessionVariableName = $this->sessionVariableName('OrderID'); |
||
772 | //we set session ID after can view check ... |
||
773 | Session::set($sessionVariableName, $this->order->ID); |
||
774 | $this->addMessage(_t('Order.LOADEDEXISTING', 'Order loaded.'), 'good'); |
||
775 | |||
776 | return true; |
||
777 | } else { |
||
778 | $this->addMessage(_t('Order.NOPERMISSION', 'You do not have permission to view this order.'), 'bad'); |
||
779 | |||
780 | return false; |
||
781 | } |
||
782 | } else { |
||
783 | $this->addMessage(_t('Order.NOORDER', 'Order can not be found.'), 'bad'); |
||
784 | |||
785 | return false; |
||
786 | } |
||
787 | } else { |
||
788 | $this->addMessage(_t('Order.NOSAVE', 'You can not load orders as your session functionality is turned off.'), 'bad'); |
||
789 | |||
790 | return false; |
||
791 | } |
||
792 | |||
793 | } |
||
794 | |||
795 | /** |
||
796 | * NOTE: tried to copy part to the Order Class - but that was not much of a go-er. |
||
797 | * |
||
798 | * returns null if the current user does not allow order manipulation or saving (e.g. session disabled) |
||
799 | * |
||
800 | * @param int | Order $order |
||
801 | * |
||
802 | * @return Order | false | null |
||
803 | **/ |
||
804 | public function copyOrder($oldOrder) |
||
805 | { |
||
806 | if($this->allowWrites()) { |
||
807 | if (is_numeric($oldOrder)) { |
||
808 | $oldOrder = Order::get()->byID(intval($oldOrder)); |
||
809 | } elseif (is_a($oldOrder, Object::getCustomClass('Order'))) { |
||
810 | //$oldOrder = $oldOrder; |
||
811 | } else { |
||
812 | user_error('Bad order provided as parameter to ShoppingCart::loadOrder()'); |
||
813 | } |
||
814 | if ($oldOrder) { |
||
815 | if ($oldOrder->canView() && $oldOrder->IsSubmitted()) { |
||
816 | $newOrder = Order::create(); |
||
817 | //copying fields. |
||
818 | $newOrder->UseShippingAddress = $oldOrder->UseShippingAddress; |
||
819 | //important to set it this way... |
||
820 | $newOrder->setCurrency($oldOrder->CurrencyUsed()); |
||
821 | $newOrder->MemberID = $oldOrder->MemberID; |
||
822 | //load the order |
||
823 | $newOrder->write(); |
||
824 | $this->loadOrder($newOrder); |
||
825 | $items = OrderItem::get() |
||
826 | ->filter(array('OrderID' => $oldOrder->ID)); |
||
827 | if ($items->count()) { |
||
828 | foreach ($items as $item) { |
||
829 | $buyable = $item->Buyable($current = true); |
||
830 | if ($buyable->canPurchase()) { |
||
831 | $this->addBuyable($buyable, $item->Quantity); |
||
832 | } |
||
833 | } |
||
834 | } |
||
835 | $newOrder->CreateOrReturnExistingAddress('BillingAddress'); |
||
836 | $newOrder->CreateOrReturnExistingAddress('ShippingAddress'); |
||
837 | $newOrder->write(); |
||
838 | $this->addMessage(_t('Order.ORDERCOPIED', 'Order has been copied.'), 'good'); |
||
839 | |||
840 | return $newOrder; |
||
841 | } else { |
||
842 | $this->addMessage(_t('Order.NOPERMISSION', 'You do not have permission to view this order.'), 'bad'); |
||
843 | |||
844 | return false; |
||
845 | } |
||
846 | } else { |
||
847 | $this->addMessage(_t('Order.NOORDER', 'Order can not be found.'), 'bad'); |
||
848 | |||
849 | return false; |
||
850 | } |
||
851 | } |
||
852 | } |
||
853 | |||
854 | /** |
||
855 | * sets country in order so that modifiers can be recalculated, etc... |
||
856 | * |
||
857 | * @param string - $countryCode |
||
858 | * |
||
859 | * @return bool |
||
860 | **/ |
||
861 | public function setCountry($countryCode) |
||
862 | { |
||
863 | if($this->allowWrites()) { |
||
864 | if (EcommerceCountry::code_allowed($countryCode)) { |
||
865 | $this->currentOrder()->SetCountryFields($countryCode); |
||
866 | $this->addMessage(_t('Order.UPDATEDCOUNTRY', 'Updated country.'), 'good'); |
||
867 | |||
868 | return true; |
||
869 | } else { |
||
870 | $this->addMessage(_t('Order.NOTUPDATEDCOUNTRY', 'Could not update country.'), 'bad'); |
||
871 | |||
872 | return false; |
||
873 | } |
||
874 | } |
||
875 | } |
||
876 | |||
877 | /** |
||
878 | * sets region in order so that modifiers can be recalculated, etc... |
||
879 | * |
||
880 | * @param int | String - $regionID you can use the ID or the code. |
||
881 | * |
||
882 | * @return bool |
||
883 | **/ |
||
884 | public function setRegion($regionID) |
||
885 | { |
||
886 | if (EcommerceRegion::regionid_allowed($regionID)) { |
||
887 | $this->currentOrder()->SetRegionFields($regionID); |
||
888 | $this->addMessage(_t('ShoppingCart.REGIONUPDATED', 'Region updated.'), 'good'); |
||
889 | |||
890 | return true; |
||
891 | } else { |
||
892 | $this->addMessage(_t('ORDER.NOTUPDATEDREGION', 'Could not update region.'), 'bad'); |
||
893 | |||
894 | return false; |
||
895 | } |
||
896 | } |
||
897 | |||
898 | /** |
||
899 | * sets the display currency for the cart. |
||
900 | * |
||
901 | * @param string $currencyCode |
||
902 | * |
||
903 | * @return bool |
||
904 | **/ |
||
905 | public function setCurrency($currencyCode) |
||
906 | { |
||
907 | $currency = EcommerceCurrency::get_one_from_code($currencyCode); |
||
908 | if ($currency) { |
||
909 | if ($this->currentOrder()->MemberID) { |
||
910 | $member = $this->currentOrder()->Member(); |
||
911 | if ($member && $member->exists()) { |
||
912 | $member->SetPreferredCurrency($currency); |
||
913 | } |
||
914 | } |
||
915 | $this->currentOrder()->UpdateCurrency($currency); |
||
916 | $msg = _t('Order.CURRENCYUPDATED', 'Currency updated.'); |
||
917 | $this->addMessage($msg, 'good'); |
||
918 | |||
919 | return true; |
||
920 | } else { |
||
921 | $msg = _t('Order.CURRENCYCOULDNOTBEUPDATED', 'Currency could not be updated.'); |
||
922 | $this->addMessage($msg, 'bad'); |
||
923 | |||
924 | return false; |
||
925 | } |
||
926 | } |
||
927 | |||
928 | /** |
||
929 | * Produces a debug of the shopping cart. |
||
930 | */ |
||
931 | public function debug() |
||
932 | { |
||
933 | if (Director::isDev() || Permission::check('ADMIN')) { |
||
934 | debug::show($this->currentOrder()); |
||
935 | |||
936 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Country</h1>'; |
||
937 | echo 'GEOIP Country: '.EcommerceCountry::get_country_from_ip().'<br />'; |
||
938 | echo 'Calculated Country Country: '.EcommerceCountry::get_country().'<br />'; |
||
939 | |||
940 | echo '<blockquote><blockquote><blockquote><blockquote>'; |
||
941 | |||
942 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Items</h1>'; |
||
943 | $items = $this->currentOrder()->Items(); |
||
944 | echo $items->sql(); |
||
945 | echo '<hr />'; |
||
946 | if ($items->count()) { |
||
947 | foreach ($items as $item) { |
||
948 | Debug::show($item); |
||
949 | } |
||
950 | } else { |
||
951 | echo '<p>there are no items for this order</p>'; |
||
952 | } |
||
953 | |||
954 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Modifiers</h1>'; |
||
955 | $modifiers = $this->currentOrder()->Modifiers(); |
||
956 | if ($modifiers->count()) { |
||
957 | foreach ($modifiers as $modifier) { |
||
958 | Debug::show($modifier); |
||
959 | } |
||
960 | } else { |
||
961 | echo '<p>there are no modifiers for this order</p>'; |
||
962 | } |
||
963 | |||
964 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Addresses</h1>'; |
||
965 | $billingAddress = $this->currentOrder()->BillingAddress(); |
||
966 | if ($billingAddress && $billingAddress->exists()) { |
||
967 | Debug::show($billingAddress); |
||
968 | } else { |
||
969 | echo '<p>there is no billing address for this order</p>'; |
||
970 | } |
||
971 | $shippingAddress = $this->currentOrder()->ShippingAddress(); |
||
972 | if ($shippingAddress && $shippingAddress->exists()) { |
||
973 | Debug::show($shippingAddress); |
||
974 | } else { |
||
975 | echo '<p>there is no shipping address for this order</p>'; |
||
976 | } |
||
977 | |||
978 | $currencyUsed = $this->currentOrder()->CurrencyUsed(); |
||
979 | if ($currencyUsed && $currencyUsed->exists()) { |
||
980 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Currency</h1>'; |
||
981 | Debug::show($currencyUsed); |
||
982 | } |
||
983 | |||
984 | $cancelledBy = $this->currentOrder()->CancelledBy(); |
||
985 | if ($cancelledBy && $cancelledBy->exists()) { |
||
986 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Cancelled By</h1>'; |
||
987 | Debug::show($cancelledBy); |
||
988 | } |
||
989 | |||
990 | $logs = $this->currentOrder()->OrderStatusLogs(); |
||
991 | if ($logs && $logs->count()) { |
||
992 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Logs</h1>'; |
||
993 | foreach ($logs as $log) { |
||
994 | Debug::show($log); |
||
995 | } |
||
996 | } |
||
997 | |||
998 | $payments = $this->currentOrder()->Payments(); |
||
999 | if ($payments && $payments->count()) { |
||
1000 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Payments</h1>'; |
||
1001 | foreach ($payments as $payment) { |
||
1002 | Debug::show($payment); |
||
1003 | } |
||
1004 | } |
||
1005 | |||
1006 | $emails = $this->currentOrder()->Emails(); |
||
1007 | if ($emails && $emails->count()) { |
||
1008 | echo '<hr /><hr /><hr /><hr /><hr /><hr /><h1>Emails</h1>'; |
||
1009 | foreach ($emails as $email) { |
||
1010 | Debug::show($email); |
||
1011 | } |
||
1012 | } |
||
1013 | |||
1014 | echo '</blockquote></blockquote></blockquote></blockquote>'; |
||
1015 | } else { |
||
1016 | echo 'Please log in as admin first'; |
||
1017 | } |
||
1018 | } |
||
1019 | |||
1020 | /** |
||
1021 | * Stores a message that can later be returned via ajax or to $form->sessionMessage();. |
||
1022 | * |
||
1023 | * @param $message - the message, which could be a notification of successful action, or reason for failure |
||
1024 | * @param $type - please use good, bad, warning |
||
1025 | */ |
||
1026 | public function addMessage($message, $status = 'good') |
||
1027 | { |
||
1028 | //clean status for the lazy programmer |
||
1029 | //TODO: remove the awkward replace |
||
1030 | $status = strtolower($status); |
||
1031 | str_replace(array('success', 'failure'), array('good', 'bad'), $status); |
||
1032 | $statusOptions = array('good', 'bad', 'warning'); |
||
1033 | if (!in_array($status, $statusOptions)) { |
||
1034 | user_error('Message status should be one of the following: '.implode(',', $statusOptions), E_USER_NOTICE); |
||
1035 | } |
||
1036 | $this->messages[] = array( |
||
1037 | 'Message' => $message, |
||
1038 | 'Type' => $status, |
||
1039 | ); |
||
1040 | } |
||
1041 | |||
1042 | /******************************************************* |
||
1043 | * HELPER FUNCTIONS |
||
1044 | *******************************************************/ |
||
1045 | |||
1046 | /** |
||
1047 | * Gets an existing order item based on buyable and passed parameters. |
||
1048 | * |
||
1049 | * @param DataObject $buyable |
||
1050 | * @param array $parameters |
||
1051 | * |
||
1052 | * @return OrderItem | null |
||
1053 | */ |
||
1054 | protected function getExistingItem(BuyableModel $buyable, array $parameters = array()) |
||
1055 | { |
||
1056 | $filterString = $this->parametersToSQL($parameters); |
||
1057 | if ($order = $this->currentOrder()) { |
||
1058 | $orderID = $order->ID; |
||
1059 | $obj = OrderItem::get() |
||
1060 | ->where( |
||
1061 | " \"BuyableClassName\" = '".$buyable->ClassName."' AND |
||
1062 | \"BuyableID\" = ".$buyable->ID.' AND |
||
1063 | "OrderID" = '.$orderID.' '. |
||
1064 | $filterString |
||
1065 | ) |
||
1066 | ->First(); |
||
1067 | |||
1068 | return $obj; |
||
1069 | } |
||
1070 | } |
||
1071 | |||
1072 | /** |
||
1073 | * Removes parameters that aren't in the default array, merges with default parameters, and converts raw2SQL. |
||
1074 | * |
||
1075 | * @param array $parameters |
||
1076 | * |
||
1077 | * @return cleaned array |
||
1078 | */ |
||
1079 | protected function cleanParameters(array $params = array()) |
||
1080 | { |
||
1081 | $defaultParamFilters = EcommerceConfig::get('ShoppingCart', 'default_param_filters'); |
||
1082 | $newarray = array_merge(array(), $defaultParamFilters); //clone array |
||
1083 | if (!count($newarray)) { |
||
1084 | return array(); //no use for this if there are not parameters defined |
||
1085 | } |
||
1086 | foreach ($newarray as $field => $value) { |
||
1087 | if (isset($params[$field])) { |
||
1088 | $newarray[$field] = Convert::raw2sql($params[$field]); |
||
1089 | } |
||
1090 | } |
||
1091 | |||
1092 | return $newarray; |
||
1093 | } |
||
1094 | |||
1095 | /** |
||
1096 | * @param array $parameters |
||
1097 | * Converts parameter array to SQL query filter |
||
1098 | */ |
||
1099 | protected function parametersToSQL(array $parameters = array()) |
||
1100 | { |
||
1101 | $defaultParamFilters = EcommerceConfig::get('ShoppingCart', 'default_param_filters'); |
||
1102 | if (!count($defaultParamFilters)) { |
||
1103 | return ''; //no use for this if there are not parameters defined |
||
1104 | } |
||
1105 | $cleanedparams = $this->cleanParameters($parameters); |
||
1106 | $outputArray = array(); |
||
1107 | foreach ($cleanedparams as $field => $value) { |
||
1108 | $outputarray[$field] = '"'.$field.'" = '.$value; |
||
1109 | } |
||
1110 | if (count($outputArray)) { |
||
1111 | return implode(' AND ', $outputArray); |
||
1112 | } |
||
1113 | |||
1228 |
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.