Cart.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
  2. /**
  3. * CodeIgniter
  4. *
  5. * An open source application development framework for PHP 5.1.6 or newer
  6. *
  7. * @package CodeIgniter
  8. * @author EllisLab Dev Team
  9. * @copyright Copyright (c) 2006 - 2014, EllisLab, Inc.
  10. * @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/)
  11. * @license http://codeigniter.com/user_guide/license.html
  12. * @link http://codeigniter.com
  13. * @since Version 1.0
  14. * @filesource
  15. */
  16. // ------------------------------------------------------------------------
  17. /**
  18. * Shopping Cart Class
  19. *
  20. * @package CodeIgniter
  21. * @subpackage Libraries
  22. * @category Shopping Cart
  23. * @author EllisLab Dev Team
  24. * @link http://codeigniter.com/user_guide/libraries/cart.html
  25. */
  26. class CI_Cart {
  27. // These are the regular expression rules that we use to validate the product ID and product name
  28. var $product_id_rules = '\.a-z0-9_-'; // alpha-numeric, dashes, underscores, or periods
  29. var $product_name_rules = '\.\:\-_ a-z0-9'; // alpha-numeric, dashes, underscores, colons or periods
  30. // Private variables. Do not change!
  31. var $CI;
  32. var $_cart_contents = array();
  33. /**
  34. * Shopping Class Constructor
  35. *
  36. * The constructor loads the Session class, used to store the shopping cart contents.
  37. */
  38. public function __construct($params = array())
  39. {
  40. // Set the super object to a local variable for use later
  41. $this->CI =& get_instance();
  42. // Are any config settings being passed manually? If so, set them
  43. $config = array();
  44. if (count($params) > 0)
  45. {
  46. foreach ($params as $key => $val)
  47. {
  48. $config[$key] = $val;
  49. }
  50. }
  51. // Load the Sessions class
  52. $this->CI->load->library('session', $config);
  53. // Grab the shopping cart array from the session table, if it exists
  54. if ($this->CI->session->userdata('cart_contents') !== FALSE)
  55. {
  56. $this->_cart_contents = $this->CI->session->userdata('cart_contents');
  57. }
  58. else
  59. {
  60. // No cart exists so we'll set some base values
  61. $this->_cart_contents['cart_total'] = 0;
  62. $this->_cart_contents['total_items'] = 0;
  63. }
  64. log_message('debug', "Cart Class Initialized");
  65. }
  66. // --------------------------------------------------------------------
  67. /**
  68. * Insert items into the cart and save it to the session table
  69. *
  70. * @access public
  71. * @param array
  72. * @return bool
  73. */
  74. function insert($items = array())
  75. {
  76. // Was any cart data passed? No? Bah...
  77. if ( ! is_array($items) OR count($items) == 0)
  78. {
  79. log_message('error', 'The insert method must be passed an array containing data.');
  80. return FALSE;
  81. }
  82. // You can either insert a single product using a one-dimensional array,
  83. // or multiple products using a multi-dimensional one. The way we
  84. // determine the array type is by looking for a required array key named "id"
  85. // at the top level. If it's not found, we will assume it's a multi-dimensional array.
  86. $save_cart = FALSE;
  87. if (isset($items['id']))
  88. {
  89. if (($rowid = $this->_insert($items)))
  90. {
  91. $save_cart = TRUE;
  92. }
  93. }
  94. else
  95. {
  96. foreach ($items as $val)
  97. {
  98. if (is_array($val) AND isset($val['id']))
  99. {
  100. if ($this->_insert($val))
  101. {
  102. $save_cart = TRUE;
  103. }
  104. }
  105. }
  106. }
  107. // Save the cart data if the insert was successful
  108. if ($save_cart == TRUE)
  109. {
  110. $this->_save_cart();
  111. return isset($rowid) ? $rowid : TRUE;
  112. }
  113. return FALSE;
  114. }
  115. // --------------------------------------------------------------------
  116. /**
  117. * Insert
  118. *
  119. * @access private
  120. * @param array
  121. * @return bool
  122. */
  123. function _insert($items = array())
  124. {
  125. // Was any cart data passed? No? Bah...
  126. if ( ! is_array($items) OR count($items) == 0)
  127. {
  128. log_message('error', 'The insert method must be passed an array containing data.');
  129. return FALSE;
  130. }
  131. // --------------------------------------------------------------------
  132. // Does the $items array contain an id, quantity, price, and name? These are required
  133. if ( ! isset($items['id']) OR ! isset($items['qty']) OR ! isset($items['price']) OR ! isset($items['name']))
  134. {
  135. log_message('error', 'The cart array must contain a product ID, quantity, price, and name.');
  136. return FALSE;
  137. }
  138. // --------------------------------------------------------------------
  139. // Prep the quantity. It can only be a number. Duh...
  140. $items['qty'] = trim(preg_replace('/([^0-9])/i', '', $items['qty']));
  141. // Trim any leading zeros
  142. $items['qty'] = trim(preg_replace('/(^[0]+)/i', '', $items['qty']));
  143. // If the quantity is zero or blank there's nothing for us to do
  144. if ( ! is_numeric($items['qty']) OR $items['qty'] == 0)
  145. {
  146. return FALSE;
  147. }
  148. // --------------------------------------------------------------------
  149. // Validate the product ID. It can only be alpha-numeric, dashes, underscores or periods
  150. // Not totally sure we should impose this rule, but it seems prudent to standardize IDs.
  151. // Note: These can be user-specified by setting the $this->product_id_rules variable.
  152. if ( ! preg_match("/^[".$this->product_id_rules."]+$/i", $items['id']))
  153. {
  154. log_message('error', 'Invalid product ID. The product ID can only contain alpha-numeric characters, dashes, and underscores');
  155. return FALSE;
  156. }
  157. // --------------------------------------------------------------------
  158. // Validate the product name. It can only be alpha-numeric, dashes, underscores, colons or periods.
  159. // Note: These can be user-specified by setting the $this->product_name_rules variable.
  160. if ( ! preg_match("/^[".$this->product_name_rules."]+$/i", $items['name']))
  161. {
  162. log_message('error', 'An invalid name was submitted as the product name: '.$items['name'].' The name can only contain alpha-numeric characters, dashes, underscores, colons, and spaces');
  163. return FALSE;
  164. }
  165. // --------------------------------------------------------------------
  166. // Prep the price. Remove anything that isn't a number or decimal point.
  167. $items['price'] = trim(preg_replace('/([^0-9\.])/i', '', $items['price']));
  168. // Trim any leading zeros
  169. $items['price'] = trim(preg_replace('/(^[0]+)/i', '', $items['price']));
  170. // Is the price a valid number?
  171. if ( ! is_numeric($items['price']))
  172. {
  173. log_message('error', 'An invalid price was submitted for product ID: '.$items['id']);
  174. return FALSE;
  175. }
  176. // --------------------------------------------------------------------
  177. // We now need to create a unique identifier for the item being inserted into the cart.
  178. // Every time something is added to the cart it is stored in the master cart array.
  179. // Each row in the cart array, however, must have a unique index that identifies not only
  180. // a particular product, but makes it possible to store identical products with different options.
  181. // For example, what if someone buys two identical t-shirts (same product ID), but in
  182. // different sizes? The product ID (and other attributes, like the name) will be identical for
  183. // both sizes because it's the same shirt. The only difference will be the size.
  184. // Internally, we need to treat identical submissions, but with different options, as a unique product.
  185. // Our solution is to convert the options array to a string and MD5 it along with the product ID.
  186. // This becomes the unique "row ID"
  187. if (isset($items['options']) AND count($items['options']) > 0)
  188. {
  189. $rowid = md5($items['id'].implode('', $items['options']));
  190. }
  191. else
  192. {
  193. // No options were submitted so we simply MD5 the product ID.
  194. // Technically, we don't need to MD5 the ID in this case, but it makes
  195. // sense to standardize the format of array indexes for both conditions
  196. $rowid = md5($items['id']);
  197. }
  198. // --------------------------------------------------------------------
  199. // Now that we have our unique "row ID", we'll add our cart items to the master array
  200. // let's unset this first, just to make sure our index contains only the data from this submission
  201. unset($this->_cart_contents[$rowid]);
  202. // Create a new index with our new row ID
  203. $this->_cart_contents[$rowid]['rowid'] = $rowid;
  204. // And add the new items to the cart array
  205. foreach ($items as $key => $val)
  206. {
  207. $this->_cart_contents[$rowid][$key] = $val;
  208. }
  209. // Woot!
  210. return $rowid;
  211. }
  212. // --------------------------------------------------------------------
  213. /**
  214. * Update the cart
  215. *
  216. * This function permits the quantity of a given item to be changed.
  217. * Typically it is called from the "view cart" page if a user makes
  218. * changes to the quantity before checkout. That array must contain the
  219. * product ID and quantity for each item.
  220. *
  221. * @access public
  222. * @param array
  223. * @param string
  224. * @return bool
  225. */
  226. function update($items = array())
  227. {
  228. // Was any cart data passed?
  229. if ( ! is_array($items) OR count($items) == 0)
  230. {
  231. return FALSE;
  232. }
  233. // You can either update a single product using a one-dimensional array,
  234. // or multiple products using a multi-dimensional one. The way we
  235. // determine the array type is by looking for a required array key named "id".
  236. // If it's not found we assume it's a multi-dimensional array
  237. $save_cart = FALSE;
  238. if (isset($items['rowid']) AND isset($items['qty']))
  239. {
  240. if ($this->_update($items) == TRUE)
  241. {
  242. $save_cart = TRUE;
  243. }
  244. }
  245. else
  246. {
  247. foreach ($items as $val)
  248. {
  249. if (is_array($val) AND isset($val['rowid']) AND isset($val['qty']))
  250. {
  251. if ($this->_update($val) == TRUE)
  252. {
  253. $save_cart = TRUE;
  254. }
  255. }
  256. }
  257. }
  258. // Save the cart data if the insert was successful
  259. if ($save_cart == TRUE)
  260. {
  261. $this->_save_cart();
  262. return TRUE;
  263. }
  264. return FALSE;
  265. }
  266. // --------------------------------------------------------------------
  267. /**
  268. * Update the cart
  269. *
  270. * This function permits the quantity of a given item to be changed.
  271. * Typically it is called from the "view cart" page if a user makes
  272. * changes to the quantity before checkout. That array must contain the
  273. * product ID and quantity for each item.
  274. *
  275. * @access private
  276. * @param array
  277. * @return bool
  278. */
  279. function _update($items = array())
  280. {
  281. // Without these array indexes there is nothing we can do
  282. if ( ! isset($items['qty']) OR ! isset($items['rowid']) OR ! isset($this->_cart_contents[$items['rowid']]))
  283. {
  284. return FALSE;
  285. }
  286. // Prep the quantity
  287. $items['qty'] = preg_replace('/([^0-9])/i', '', $items['qty']);
  288. // Is the quantity a number?
  289. if ( ! is_numeric($items['qty']))
  290. {
  291. return FALSE;
  292. }
  293. // Is the new quantity different than what is already saved in the cart?
  294. // If it's the same there's nothing to do
  295. if ($this->_cart_contents[$items['rowid']]['qty'] == $items['qty'])
  296. {
  297. return FALSE;
  298. }
  299. // Is the quantity zero? If so we will remove the item from the cart.
  300. // If the quantity is greater than zero we are updating
  301. if ($items['qty'] == 0)
  302. {
  303. unset($this->_cart_contents[$items['rowid']]);
  304. }
  305. else
  306. {
  307. $this->_cart_contents[$items['rowid']]['qty'] = $items['qty'];
  308. }
  309. return TRUE;
  310. }
  311. // --------------------------------------------------------------------
  312. /**
  313. * Save the cart array to the session DB
  314. *
  315. * @access private
  316. * @return bool
  317. */
  318. function _save_cart()
  319. {
  320. // Unset these so our total can be calculated correctly below
  321. unset($this->_cart_contents['total_items']);
  322. unset($this->_cart_contents['cart_total']);
  323. // Lets add up the individual prices and set the cart sub-total
  324. $total = 0;
  325. $items = 0;
  326. foreach ($this->_cart_contents as $key => $val)
  327. {
  328. // We make sure the array contains the proper indexes
  329. if ( ! is_array($val) OR ! isset($val['price']) OR ! isset($val['qty']))
  330. {
  331. continue;
  332. }
  333. $total += ($val['price'] * $val['qty']);
  334. $items += $val['qty'];
  335. // Set the subtotal
  336. $this->_cart_contents[$key]['subtotal'] = ($this->_cart_contents[$key]['price'] * $this->_cart_contents[$key]['qty']);
  337. }
  338. // Set the cart total and total items.
  339. $this->_cart_contents['total_items'] = $items;
  340. $this->_cart_contents['cart_total'] = $total;
  341. // Is our cart empty? If so we delete it from the session
  342. if (count($this->_cart_contents) <= 2)
  343. {
  344. $this->CI->session->unset_userdata('cart_contents');
  345. // Nothing more to do... coffee time!
  346. return FALSE;
  347. }
  348. // If we made it this far it means that our cart has data.
  349. // Let's pass it to the Session class so it can be stored
  350. $this->CI->session->set_userdata(array('cart_contents' => $this->_cart_contents));
  351. // Woot!
  352. return TRUE;
  353. }
  354. // --------------------------------------------------------------------
  355. /**
  356. * Cart Total
  357. *
  358. * @access public
  359. * @return integer
  360. */
  361. function total()
  362. {
  363. return $this->_cart_contents['cart_total'];
  364. }
  365. // --------------------------------------------------------------------
  366. /**
  367. * Total Items
  368. *
  369. * Returns the total item count
  370. *
  371. * @access public
  372. * @return integer
  373. */
  374. function total_items()
  375. {
  376. return $this->_cart_contents['total_items'];
  377. }
  378. // --------------------------------------------------------------------
  379. /**
  380. * Cart Contents
  381. *
  382. * Returns the entire cart array
  383. *
  384. * @access public
  385. * @return array
  386. */
  387. function contents()
  388. {
  389. $cart = $this->_cart_contents;
  390. // Remove these so they don't create a problem when showing the cart table
  391. unset($cart['total_items']);
  392. unset($cart['cart_total']);
  393. return $cart;
  394. }
  395. // --------------------------------------------------------------------
  396. /**
  397. * Has options
  398. *
  399. * Returns TRUE if the rowid passed to this function correlates to an item
  400. * that has options associated with it.
  401. *
  402. * @access public
  403. * @return array
  404. */
  405. function has_options($rowid = '')
  406. {
  407. if ( ! isset($this->_cart_contents[$rowid]['options']) OR count($this->_cart_contents[$rowid]['options']) === 0)
  408. {
  409. return FALSE;
  410. }
  411. return TRUE;
  412. }
  413. // --------------------------------------------------------------------
  414. /**
  415. * Product options
  416. *
  417. * Returns the an array of options, for a particular product row ID
  418. *
  419. * @access public
  420. * @return array
  421. */
  422. function product_options($rowid = '')
  423. {
  424. if ( ! isset($this->_cart_contents[$rowid]['options']))
  425. {
  426. return array();
  427. }
  428. return $this->_cart_contents[$rowid]['options'];
  429. }
  430. // --------------------------------------------------------------------
  431. /**
  432. * Format Number
  433. *
  434. * Returns the supplied number with commas and a decimal point.
  435. *
  436. * @access public
  437. * @return integer
  438. */
  439. function format_number($n = '')
  440. {
  441. if ($n == '')
  442. {
  443. return '';
  444. }
  445. // Remove anything that isn't a number or decimal point.
  446. $n = trim(preg_replace('/([^0-9\.])/i', '', $n));
  447. return number_format($n, 2, '.', ',');
  448. }
  449. // --------------------------------------------------------------------
  450. /**
  451. * Destroy the cart
  452. *
  453. * Empties the cart and kills the session
  454. *
  455. * @access public
  456. * @return null
  457. */
  458. function destroy()
  459. {
  460. unset($this->_cart_contents);
  461. $this->_cart_contents['cart_total'] = 0;
  462. $this->_cart_contents['total_items'] = 0;
  463. $this->CI->session->unset_userdata('cart_contents');
  464. }
  465. }
  466. /* End of file Cart.php */
  467. /* Location: ./system/libraries/Cart.php */