Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
ecommerce_cart_processor.inc
1 <?php
18 /*
19 * Ecommerce Package Cart Processor Object
20 *
21 * This object handles the interactions with the ecommerce cart, either stored in
22 * $_SESSION (the default), or passed in by the caller
23 *
24 * @author Robert Howard <rhoward@squiz.net>
25 * @author Luke Wright <lwright@squiz.net>
26 * @author Andrei Railean <arailean@squiz.net>
27 * @version $Revision: 1.29.2.1 $
28 * @package MySource_Matrix_Packages
29 * @subpackage ecommerce
30 */
31 
32 
34 {
35 
36  var $cart = NULL;
37 
38 
42  function Ecommerce_Cart_Processor($checkout_id = 0)
43  {
44  // set, but rely on external objects to initialise
45  $this->setCartContainer($_SESSION['sq_cart_contents'][$checkout_id]);
46 
47  if (is_null($this->cart)) $this->initialiseCart();
48 
49  }//end constructor
50 
51 
60  function setCartContainer(&$cart)
61  {
62  $this->cart =& $cart;
63 
64  }//end setCartContainer()
65 
66 
76  function initialiseCart()
77  {
78  $ret_val = 0;
79 
80  if (is_null($this->cart)) {
81  $this->cart = Array();
82  $ret_val = 1;
83  }
84 
85  return $ret_val;
86 
87  }//end initialiseCart()
88 
89 
96  function clearCart()
97  {
98  $this->cart = Array();
99  return TRUE;
100 
101  }//end clearCart()
102 
103 
117  function &getItem($assetid)
118  {
119  if (!empty($this->cart[$assetid])) {
120  return $this->cart[$assetid];
121  } else {
122  return FALSE;
123  }
124 
125  }//end getItem()
126 
127 
145  function &getCart()
146  {
147  // remove any invalid cart items
148  $this->validateCart();
149 
150  return $this->cart;
151 
152  }//end getCart()
153 
154 
167  function getTotal($taxation_cost=0.0, $taxation_rate=0.0)
168  {
169  $total = 0.00;
170  foreach ($this->cart as $assetid => $details) {
171  // -1 == value not known
172  if ($details['price'] != -1) {
173  // adjust product prices
174  $has_tax = array_get_index($details, 'has_tax', FALSE);
175  $calculate_tax = array_get_index($details, 'taxable', FALSE);
176  if ($calculate_tax) {
177  $item_costs = $this->_calculateItemCosts($details['price'], $taxation_rate, $has_tax);
178  $cost = $item_costs['tax_adjusted_cost'];
179  } else {
180  $cost = $details['price'];
181  }
182 
183  $total += $details['quantity'] * $cost;
184  }
185  }
186 
187  $total += $taxation_cost;
188  return $total;
189 
190  }//end getTotal()
191 
192 
205  function getGrandTotalExDelivery($taxation_rate, $remove_tax, $flat_charges)
206  {
207  $total_tax = $this->getTotalTax($taxation_rate);
208  $flat_charges_total = $this->getFlatChargesTotal($flat_charges);
209 
210  if ($remove_tax) {
211  $grand_total = ($this->getTotal($total_tax, $taxation_rate) + $flat_charges_total) - $total_tax;
212  } else {
213  $grand_total = $this->getTotal($total_tax, $taxation_rate) + $flat_charges_total;
214  }
215 
216  return $grand_total;
217 
218  }//end getGrandTotalExDelivery()
219 
220 
229  function getFlatChargesTotal($flat_charges)
230  {
231  $flat_charges_total = 0.0;
232  foreach ($flat_charges as $name => $price) {
233  $flat_charges_total += $price;
234  }
235 
236  return $flat_charges_total;
237 
238  }//end getFlatChargesTotal()
239 
240 
249  function getTotalDeliveryCosts($grand_total)
250  {
251  $total_delivery_cost = 0.00;
252  if (isset($_SESSION['sq_region_specific'])) {
253  foreach ($this->cart as $assetid => $details) {
254  //if the asset (product) is not a donation, calculate delivery prices per weight metric
255  if (!array_get_index($details, 'is_donation', FALSE) && isset($_SESSION['sq_region_specific']['delivery_weight_fee'])) {
256  $delivery_weight_fee = $_SESSION['sq_region_specific']['delivery_weight_fee'];
257  // it will either be a metadata field or an asset attribute that we have to get weight info from
258  if (isset($_SESSION['sq_region_specific']['delivery_weight_fee']['metadata_fieldid'])) {
259  $fieldid = $_SESSION['sq_region_specific']['delivery_weight_fee']['metadata_fieldid'];
260  $mm =& $GLOBALS['SQ_SYSTEM']->getMetadataManager();
261  $weight = (float) $mm->getMetadataValueByAssetid($assetid, $fieldid);
262  } else if (isset($_SESSION['sq_region_specific']['delivery_weight_fee']['attribute_name'])) {
263  $attribute_name = $_SESSION['sq_region_specific']['delivery_weight_fee']['attribute_name'];
264  $asset =& $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
265  $weight = (float) $asset->attr($attribute_name);
266  }
267 
268  if (isset($weight) && !empty($weight)) {
269  $product_quantity = array_get_index($details, 'quantity', 0);
270 
271  if (isset($delivery_weight_fee['max_quantity']) && ($delivery_weight_fee['max_quantity'] < $product_quantity)) {
272  $product_quantity = $delivery_weight_fee['max_quantity'];
273  }
274 
275  $total_delivery_cost += $weight * $delivery_weight_fee['fee'] * $product_quantity;
276  }
277  }//end if
278  }//end for
279  }//end if
280 
281  // calculate international delivery fee
282  if (isset($_SESSION['sq_region_specific']['international_delivery_fee'])) {
283  $total_delivery_cost += $_SESSION['sq_region_specific']['international_delivery_fee'];
284  }
285 
286  // calculate additional conditional delivery fees
287  // based on total of cart contents plus all existing delivery fees
288  if (isset($_SESSION['sq_region_specific']['conditions'])) {
289  $grand_total_plus_delivery = $grand_total + $total_delivery_cost;
290  $conditions = $_SESSION['sq_region_specific']['conditions'];
291  foreach ($conditions as $key => $data) {
292  // evaluate each condition
293  $operator = print_r($data['operator'], TRUE);
294  if (eval('$satisfied = $grand_total_plus_delivery'.$operator.'$data[\'comparison_price\']; return $satisfied;')) {
295  if ($data['remove_fees']) {
296  $total_delivery_cost = 0;
297  } else {
298  $total_delivery_cost += $data['additional_cost'];
299  }
300  // evaluate only the first condition satisfied
301  break;
302  }
303  }
304  }
305 
306  //cap the delivery fee if the maximum delivery fee is set
307  if (isset($_SESSION['sq_region_specific']['max_delivery_fee']) && ($_SESSION['sq_region_specific']['max_delivery_fee'] < $total_delivery_cost)) {
308  $total_delivery_cost = $_SESSION['sq_region_specific']['max_delivery_fee'];
309  }
310 
311  return $total_delivery_cost;
312 
313  }//end getTotalDeliveryCosts()
314 
315 
325  function getTotalTax($taxation_rate=0.0)
326  {
327  $total_tax = 0.00;
328  foreach ($this->cart as $assetid => $details) {
329  // -1 == value not known
330  if (($details['price'] != -1) && ($details['taxable'])) {
331  $has_tax = array_get_index($details, 'has_tax', FALSE);
332  $item_costs = $this->_calculateItemCosts($details['price'], $taxation_rate, $has_tax);
333  $tax_cost = $item_costs['tax_cost'];
334 
335  $total_tax += $details['quantity'] * $tax_cost;
336  }
337  }
338 
339  return $total_tax;
340 
341  }//end getTotalTax()
342 
343 
355  function _calculateItemCosts($cost, $taxation_rate, $has_tax)
356  {
357  // deduct tax or add tax depending on whether item currently has tax included in its price
358  if ($has_tax) {
359  // deduct tax and alter product cost as well to reflect this
360  $tax_cost = $cost - ($cost / (1 + $taxation_rate));
361  $cost = $cost - $tax_cost;
362  } else {
363  // add tax
364  $tax_cost = $cost * $taxation_rate;
365  }
366  $item_costs = Array(
367  'tax_adjusted_cost' => $cost,
368  'tax_cost' => $tax_cost,
369  );
370 
371  return $item_costs;
372 
373  }// end _calculateItemCosts()
374 
375 
382  function getCount()
383  {
384  if (is_null($this->cart)) return 0;
385  $count = 0;
386  foreach ($this->cart as $assetid => $details) {
387  if (!array_get_index($details, 'is_donation', FALSE)) {
388  $count += $details['quantity'];
389  }
390  }
391 
392  return $count;
393 
394  }//end getCount()
395 
396 
403  function getUniqueCount()
404  {
405  return count($this->cart);
406 
407  }//end getUniqueCount()
408 
409 
423  function addItem($assetid, $quantity=1)
424  {
425  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
426  if (is_null($asset)) {
427  return FALSE;
428  }
429 
430  if (!($asset instanceof Donation) && !($asset instanceof Buyable)) {
431  trigger_localised_error('ECOM0005', E_USER_WARNING, $assetid);
432  return FALSE;
433  }
434 
435  if ($quantity <= 0) {
436  if ($asset instanceof Donation) {
437  trigger_localised_error('ECOM0025', E_USER_WARNING);
438  } else if ($quantity < 0) { // quantity is less than zero, throw a hissy fit
439  trigger_localised_error('ECOM0004', E_USER_WARNING);
440  }
441  return FALSE;
442  }
443 
444 
445  if (!isset($this->cart[$assetid])) {
446  // ugly, but we need to work around the backwards way Files store names
447  if ($asset instanceof File) {
448  $name = $asset->attr('title');
449  } else {
450  $name = $asset->name;
451  }
452 
453  // only include a price if the object is a product
454  // only include taxable boolean if the object is a product
455  $taxable = FALSE;
456  $has_tax = FALSE;
457  $is_donation = FALSE;
458  $refund_type = 0;
459  $product_code = '';
460  $financial_code = '';
461 
462  if ($asset instanceof Donation) {
463  $price = 1;
464  $is_donation = TRUE;
465  } else if ($asset instanceof Product) {
466  $price = $asset->attr('price');
467 
468  $refund_type = $asset->attr('refund_type');
469  $product_code = $asset->attr('product_code');
470  $financial_code = $asset->attr('financial_code');
471 
472  // get the taxable boolean
473  $tmp_price_obj = $asset->attr('price_obj');
474  if (!empty($tmp_price_obj) ) {
475  if ((!empty($tmp_price_obj->value_has_tax)) && ($tmp_price_obj->value_has_tax == 1)) {
476  $has_tax = TRUE;
477  }
478  if ((!empty($tmp_price_obj->calculate_tax)) && ($tmp_price_obj->calculate_tax == 1)) {
479  $taxable = TRUE;
480  }
481  }
482  } else {
483  $price = -1;
484  }
485 
486  $this->cart[$assetid] = Array(
487  'price' => $price,
488  'quantity' => $quantity,
489  'name' => $name,
490  'has_tax' => $has_tax,
491  'taxable' => $taxable,
492  'is_donation' => $is_donation,
493  'refund_type' => $refund_type,
494  'product_code' => $product_code,
495  'financial_code' => $financial_code,
496  );
497  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($asset);
498 
499  } else {
500  if ($asset instanceof Donation) {
501  $this->cart[$assetid]['quantity'] = $quantity;
502  } else {
503  $this->cart[$assetid]['quantity'] += $quantity;
504  }
505  }//end if
506 
507  return TRUE;
508 
509  }//end addItem()
510 
511 
526  function updateItem($assetid, $quantity)
527  {
528  // item doesn't exist in cart, then silently redirect to addItem()
529  if (!isset($this->cart[$assetid])) {
530  return $this->addItem($assetid, $quantity);
531  }
532 
533  // if we are not updating anything, silently return
534  if (array_get_index($this->cart[$assetid], 'quantity', 0) == $quantity) {
535  return FALSE;
536  }
537 
538  // Qty < 0, throw an error
539  if ($quantity < 0) {
540  trigger_localised_error('ECOM0010', E_USER_WARNING);
541  return FALSE;
542  } else if ($quantity == 0) {
543  $this->removeItem($assetid);
544  } else if (isset($this->cart[$assetid]['quantity']) && $this->cart[$assetid]['quantity'] == $quantity) {
545  // not updating, so fail
546  return FALSE;
547  } else {
548  $this->cart[$assetid]['quantity'] = $quantity;
549  }
550 
551  return TRUE;
552 
553  }//end updateItem()
554 
555 
570  function removeItem($assetid, $quantity=NULL)
571  {
572  // item doesn't exist in cart - if the quantity is zero (or ALL)
573  // then silently return, otherwise error
574  if (!isset($this->cart[$assetid])) {
575  if ($quantity > 0) {
576  trigger_localised_error('ECOM0006', E_USER_WARNING);
577  }
578  return FALSE;
579  }
580 
581  // after this point we can assume that asset exists in the cart
582  // quantity is null, remove everything
583  if (is_null($quantity)) {
584  $quantity = $this->cart[$assetid]['quantity'];
585  }
586 
587  // qty == 0, silently return - Qty < 0, throw an error
588  if ($quantity <= 0) {
589  if ($quantity < 0) {
590  trigger_localised_error('ECOM0007', E_USER_WARNING);
591  }
592  return FALSE;
593  }
594 
595  if ($quantity > $this->cart[$assetid]['quantity']) {
596  trigger_localised_error('ECOM0008', E_USER_WARNING);
597  return FALSE;
598  } else if ($quantity == $this->cart[$assetid]['quantity']) {
599  unset($this->cart[$assetid]);
600  } else {
601  $this->cart[$assetid]['quantity'] -= $quantity;
602  }
603 
604  return TRUE;
605 
606  }//end removeItem()
607 
608 
617  function hashCart()
618  {
619  // don't grab by reference, because we're sorting and mucking around with the array
620  $tmp_cart = $this->cart;
621  ksort($tmp_cart);
622  $hash = md5(serialize($tmp_cart));
623 
624  return $hash;
625 
626  }//end hashCart()
627 
628 
644  function printReceipt($taxation_name='GST', $taxation_rate=0, $flat_charges=Array(), $remove_tax=FALSE, $currency_symbol='$', $float_precision=2, $refund_info='')
645  {
646  if (empty($this->cart)) return;
647 
648  // ensure the float precision is an int
649  $float_precision = (int) $float_precision;
650  $price_format = $currency_symbol.'%.'.$float_precision.'f';
651 
652  $name_length = 0;
653  $has_price = FALSE;
654  $has_products = FALSE;
655 
656  // TRUE if any item in cart is non-refundable
657  $non_refundable_flag = FALSE;
658 
659  // formatting preparation
660  foreach ($this->cart as $assetid => $details) {
661  // determine longest item name
662 
663  $tmp_taxable = array_get_index($details, 'taxable', FALSE);
664  $product_name = array_get_index($details, 'name', '');
665 
666  if (!$non_refundable_flag) {
667  $refund_type = array_get_index($details, 'refund_type', 'NonRefundable');
668  $non_refundable_flag = ($refund_type === 'NonRefundable') ? TRUE : FALSE;
669  }
670 
671  if (strlen($product_name) > $name_length) {
672  $name_length = strlen(array_get_index($details, 'name', ''));
673  }
674  if ($tmp_taxable) {
675  if (strlen('Excluded Tax ('.$taxation_name.')') > $name_length) {
676  $name_length = strlen('Excluded Tax ('.$taxation_name.')');
677  }
678  }
679 
680  // determine whether all items have price
681  $price = array_get_index($details, 'price', -1);
682  if (!$has_price && ($price != -1)) {
683  $has_price = TRUE;
684  }
685 
686  // check if there are not only donations
687  if (!$has_products && !array_get_index($details, 'is_donation', FALSE) && ($price != -1)) {
688  $has_products = TRUE;
689  }
690  }
691 
692  // calculate the longest item name and calculate the number of flat_charges
693  $flat_charges_total = 0.0;
694  foreach ($flat_charges as $name => $price) {
695  if (strlen($name) > $name_length) {
696  $name_length = strlen($name);
697  }
698  $flat_charges_total += $price;
699  }
700 
701  // standard format for the receipt header and rows
702  $row_format = '%-'.$name_length.'s %10s'.($has_price && $has_products ? ' %10s' : '')."\n";
703 
704  if ($has_price) {
705  if ($has_products) {
706  $header = sprintf($row_format, 'Item', 'Quantity', 'Price');
707  } else { // there are donations only
708  $header = sprintf($row_format, 'Item', 'Amount');
709  }
710  } else {
711  $header = sprintf($row_format, 'Item', 'Quantity');
712  }
713 
714  $break = '';
715  for ($ii = 1; $ii < strlen($header); $ii++) {
716  $break .= '-';
717  }
718  $break .= "\n";
719 
720  echo $header;
721  echo $break;
722 
723  // print each row
724  foreach ($this->cart as $assetid => $details) {
725  $cost = array_get_index($details, 'price', 0);
726  // calculate tax for this item only if we are not removing tax
727  $has_tax = array_get_index($details, 'has_tax', FALSE);
728  $calculate_tax = array_get_index($details, 'taxable', FALSE);
729  if ($calculate_tax) {
730  $item_costs = $this->_calculateItemCosts($cost, $taxation_rate, $has_tax);
731  $cost = $item_costs['tax_adjusted_cost'];
732  $tax_cost = $item_costs['tax_cost'];
733  } else {
734  $tax_cost = 0;
735  }
736 
737  $product_quantity = array_get_index($details, 'quantity', 0);
738 
739  if ($cost < 0) {
740  $cost = '';
741  } else {
742  $cost = sprintf($price_format, $cost * $product_quantity);
743  $tax_cost = sprintf($price_format, $tax_cost * $product_quantity);
744  }
745 
746  if ($has_price) {
747  if ($has_products) {
748  if (array_get_index($details, 'is_donation', FALSE)) {
749  $product_quantity = '';
750  }
751  printf($row_format, array_get_index($details, 'name', ''), $product_quantity, $cost);
752  } else {
753  printf($row_format, array_get_index($details, 'name', ''), $cost);
754  }
755  } else {
756  printf($row_format, array_get_index($details, 'name', ''), $product_quantity);
757  }
758  }//end foreach
759 
760  echo $break;
761 
762  // change the row format for the totals line
763  $row_format = '%'.$name_length.'s %10s'.($has_price && $has_products ? ' %10s' : '')."\n";
764 
765  if ($has_price) {
766  $total_tax = $this->getTotalTax($taxation_rate);
767 
768  $sub_total = $remove_tax ? $this->getTotal($total_tax, $taxation_rate)-$total_tax : $this->getTotal(0, $taxation_rate);
769 
770  if ($has_products) {
771  echo sprintf($row_format, 'Total', $this->getCount(), sprintf($price_format, $sub_total));
772  } else {
773  echo sprintf($row_format, 'Total', sprintf($price_format, $sub_total));
774  }
775 
776  if (($total_tax > 0) || ($flat_charges_total > 0.0)) {
777  if ($total_tax > 0) {
778  $row_title = $remove_tax ? 'Excluded Tax ('.$taxation_name.')' : 'Total ('.$taxation_name.')';
779  echo sprintf($row_format, $row_title, $this->getCount(), sprintf($price_format, $total_tax));
780  }
781  echo $break;
782  foreach ($flat_charges as $name => $price) {
783  echo sprintf($row_format, $name, '', sprintf($price_format, $price));
784  }
785 
786  $grand_total_ex_delivery = $this->getGrandTotalExDelivery($taxation_rate, $remove_tax, $flat_charges);
787 
788  $total_delivery_cost = $this->getTotalDeliveryCosts($grand_total_ex_delivery);
789 
790  if ($total_delivery_cost > 0) {
791  echo sprintf($row_format, 'Delivery Fee', '', sprintf($price_format, $total_delivery_cost));
792  }
793 
794  // figure out whether to print this last line break
795  if (($total_delivery_cost > 0) || ($flat_charges_total > 0)) {
796  echo $break;
797  }
798  echo sprintf($row_format, 'Grand Total', '', sprintf($price_format, $grand_total_ex_delivery + $total_delivery_cost));
799  }
800  if (!empty($refund_info) && $non_refundable_flag) {
801  echo "\n".$refund_info."\n";
802  }
803 
804  } else {
805  echo sprintf($row_format, 'Total', $this->getCount());
806  }
807 
808  return;
809 
810  }//end printReceipt()
811 
812 
829  function printCustomisedReceipt(&$contents_bodycopy, &$format_bodycopy, $taxation_name='GST', $taxation_rate=0, $flat_charges=Array(), $remove_tax=FALSE, $currency_symbol='$', $float_precision=2)
830  {
831  if (empty($this->cart)) return;
832 
833  // ensure the float precision is an int
834  $float_precision = (int) $float_precision;
835  $price_format = $currency_symbol.'%.'.$float_precision.'f';
836 
837  $content_keywords = $contents_bodycopy->getKeywords();
838  $content_replacements = Array();
839 
840  // print each asset in the cart using the receipt type format
841  $format_keywords = $format_bodycopy->getKeywords();
842  ob_start();
843  foreach ($this->cart as $assetid => $details) {
844  $asset =& $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
845  $format_replacements = Array();
846 
847  // replace all asset based keywords
848  foreach ($format_keywords as $keyword) {
849  $format_replacements[$keyword] = $asset->getKeywordReplacement($keyword);
850  }
851 
852  $product_quantity = array_get_index($details, 'quantity', 0);
853 
854  // calculate tax and costs for this item
855  $cost = array_get_index($details, 'price', 0);
856  // calculate tax for this item only if we are not removing tax
857  $has_tax = array_get_index($details, 'has_tax', FALSE);
858  $calculate_tax = array_get_index($details, 'taxable', FALSE);
859  if ($calculate_tax) {
860  $item_costs = $this->_calculateItemCosts($cost, $taxation_rate, $has_tax);
861  $cost = $item_costs['tax_adjusted_cost'];
862  $tax_cost = $item_costs['tax_cost'];
863  } else {
864  $tax_cost = 0;
865  }
866 
867  // replace all ecommerce specific keywords
868  if (array_get_index($details, 'is_donation', FALSE)) {
869  $format_replacements['item_quantity_added'] = '';
870  $format_replacements['item_price_incl_tax'] = sprintf($price_format, $product_quantity);
871  } else {
872  $format_replacements['item_quantity_added'] = $product_quantity;
873  $format_replacements['item_price_incl_tax'] = sprintf($price_format, $cost);
874  }
875 
876  $format_replacements['item_tax'] = sprintf($price_format, $tax_cost);
877 
878  $format_replacements['total_item_price'] = sprintf($price_format, $cost * $product_quantity);
879  $format_replacements['total_item_tax'] = sprintf($price_format, $tax_cost * $product_quantity);
880 
881  $format_bodycopy->setKeywordReplacements($format_replacements);
882  $format_bodycopy->printBody();
883  }
884 
885  // receipt contents bodycopy keyword replacements
886  $content_replacements['receipt_listing'] = ob_get_clean();
887 
888  $total_tax = $this->getTotalTax($taxation_rate);
889  $content_replacements['total_tax'] = $remove_tax ? sprintf($price_format, 0) : sprintf($price_format, $total_tax);
890  $content_replacements['tax_name'] = $taxation_name;
891 
892  ob_start();
893  $flat_charges_total = 0.0;
894  echo '<table>';
895  foreach ($flat_charges as $name => $price) {
896  echo '<tr><td>'.$name.':</td><td>'.sprintf($price_format, $price).'</td></tr>';
897  $flat_charges_total += $price;
898  }
899  echo '</table>';
900  $content_replacements['flat_fees_table'] = ob_get_clean();
901  $content_replacements['flat_fees_total'] = sprintf($price_format, $flat_charges_total);
902 
903  $sub_total = $remove_tax ? $this->getTotal($total_tax, $taxation_rate)-$total_tax : $this->getTotal(0, $taxation_rate);
904  $content_replacements['sub_total'] = sprintf($price_format, $sub_total);
905 
906  $grand_total_ex_delivery = $this->getGrandTotalExDelivery($taxation_rate, $remove_tax, $flat_charges);
907  $total_delivery_cost = $this->getTotalDeliveryCosts($grand_total_ex_delivery);
908  $content_replacements['delivery_total'] = sprintf($price_format, $total_delivery_cost);
909  $content_replacements['grand_total'] = sprintf($price_format, $grand_total_ex_delivery + $total_delivery_cost);
910 
911  $contents_bodycopy->setKeywordReplacements($content_replacements);
912 
913  ob_start();
914  $contents_bodycopy->printBody();
915  $customised_receipt = ob_get_clean();
916 
917  return $customised_receipt;
918 
919  }//end printCustomisedReceipt()
920 
921 
936  function getCartXml($taxation_name='GST', $taxation_rate=0, $flat_charges=Array(), $remove_tax=FALSE, $currency_symbol='$', $float_precision=2)
937  {
938  if (empty($this->cart)) return '';
939 
940  // ensure the float precision is an int
941  $float_precision = (int) $float_precision;
942  $price_format = '%.'.$float_precision.'f';
943 
944  //get Metadata Manager
945  $mm =& $GLOBALS['SQ_SYSTEM']->getMetadataManager();
946 
947  $products_str = '';
948  foreach ($this->cart as $assetid => $details) {
949  $quantity = array_get_index($details, 'quantity', 0);
950 
951  // calculate tax and costs for this item
952  $cost = array_get_index($details, 'price', 0);
953  if (array_get_index($details, 'taxable', FALSE)) {
954  $item_costs = $this->_calculateItemCosts($cost, $taxation_rate, array_get_index($details, 'has_tax', FALSE));
955  $cost = $item_costs['tax_adjusted_cost'];
956  $tax_cost = $item_costs['tax_cost'];
957  } else {
958  $tax_cost = 0;
959  }
960 
961  //information of product is different if it is a donation
962  if (array_get_index($details, 'is_donation', FALSE)) {
963  $donation = 'yes';
964  $cost = $quantity;
965  $quantity = 1;
966  } else {
967  $donation = 'no';
968  }
969 
970  $item_price = sprintf($price_format, $cost);
971  $item_price_incl_tax = sprintf($price_format, $cost + $tax_cost);
972  $total_item_price = sprintf($price_format, $cost * $quantity);
973  $total_item_price_incl_tax = sprintf($price_format, ($cost + $tax_cost) * $quantity);
974  $total_item_tax = sprintf($price_format, $tax_cost * $quantity);
975 
976  //get metadata of the product/donation asset
977  $metadata_values = $mm->getMetadataFieldValues($assetid);
978  $metadata_values_str = '';
979  foreach ($metadata_values as $name => $value) {
980  $metadata_values_str .= <<<HEREDOC
981 
982  <field>
983  <name><![CDATA[$name]]></name>
984  <value><![CDATA[$value]]></value>
985  </field>
986 HEREDOC;
987  }
988 
989  $metadata_str = '';
990  if ($metadata_values_str != '') {
991  $metadata_str = <<<HEREDOC
992 
993  <metadata>$metadata_values_str
994  </metadata>
995 HEREDOC;
996  }
997 
998  $products_str .= <<<HEREDOC
999 
1000  <product>
1001  <id>$assetid</id>
1002  <name><![CDATA[{$details['name']}]]></name>
1003  <is_donation>$donation</is_donation>
1004  <quantity>$quantity</quantity>
1005  <item_price>$item_price</item_price>
1006  <total_item_price>$total_item_price</total_item_price>
1007  <item_price_incl_tax>$item_price_incl_tax</item_price_incl_tax>
1008  <total_item_price_incl_tax>$total_item_price_incl_tax</total_item_price_incl_tax>
1009  <item_tax>$tax_cost</item_tax>
1010  <total_item_tax>$total_item_tax</total_item_tax>$metadata_str
1011  </product>
1012 HEREDOC;
1013  }
1014 
1015  //get total tax
1016  $total_tax = $this->getTotalTax($taxation_rate);
1017  $total_tax = $remove_tax ? sprintf('-'.$price_format, $total_tax) : sprintf($price_format, $total_tax);
1018 
1019  //get flat charges string
1020  $flat_charge_values_str = '';
1021  $flat_charges_total = 0.0;
1022  foreach ($flat_charges as $name => $price) {
1023  $flat_charge_values_str .= <<<HEREDOC
1024 
1025  <flat_charge>
1026  <name><![CDATA[$name]]></name>
1027  <price>$price</price>
1028  </flat_charge>
1029 HEREDOC;
1030  $flat_charges_total += $price;
1031  }
1032 
1033  $flat_charges_str = '';
1034  if ($flat_charge_values_str != ''){
1035  $flat_charges_str = <<<HEREDOC
1036 
1037  <flat_charges>$flat_charge_values_str
1038  </flat_charges>
1039 HEREDOC;
1040 
1041  }
1042 
1043  $flat_fees_total = sprintf($price_format, $flat_charges_total);
1044 
1045  $sub_total = sprintf($price_format, $this->getTotal(0, $taxation_rate));
1046 
1047  $grand_total_ex_delivery = $this->getGrandTotalExDelivery($taxation_rate, $remove_tax, $flat_charges);
1048  $delivery_total = $this->getTotalDeliveryCosts($grand_total_ex_delivery);
1049 
1050  $grand_total = sprintf($price_format, $grand_total_ex_delivery + $delivery_total);
1051  $grand_total_ex_delivery = sprintf($price_format, $grand_total_ex_delivery);
1052  $delivery_total = sprintf($price_format, $delivery_total);
1053 
1054  $cart_xml = <<<HEREDOC
1055  <cart>
1056  <currency_symbol><![CDATA[$currency_symbol]]></currency_symbol>
1057  <sub_total>$sub_total</sub_total>
1058  <taxation_name><![CDATA[$taxation_name]]></taxation_name>
1059  <total_tax>$total_tax</total_tax>
1060  <flat_fees_total>$flat_fees_total</flat_fees_total>
1061  <delivery_total>$delivery_total</delivery_total>
1062  <grand_total_ex_delivery>$grand_total_ex_delivery</grand_total_ex_delivery>
1063  <grand_total>$grand_total</grand_total>
1064  <products>$products_str
1065  </products>$flat_charges_str
1066  </cart>
1067 HEREDOC;
1068 
1069  return $cart_xml;
1070 
1071  }//end getCartXml()
1072 
1073 
1081  function validateCart()
1082  {
1083  if (empty($this->cart)) return;
1084 
1085  $asset_ids = array_keys($this->cart);
1086  $result = $GLOBALS['SQ_SYSTEM']->am->assetExists($asset_ids);
1087 
1088  // the array of existing assetids
1089  if (is_array($result)) {
1090 
1091  foreach ($asset_ids as $id) {
1092  if (!is_int(array_search($id, $result))) {
1093  $this->removeItem($id);
1094  }
1095  }
1096 
1097  // false, one item doesn't exist.
1098  } else if (is_bool($result) && !$result) {
1099  $this->removeItem($id);
1100  }
1101 
1102  }//end validateCart()
1103 
1104 
1105 }//end class
1106 
1107 
1108 ?>