Page 1 of 1

[TUTORIALS] Opencart's "cart" - a deeper look

Posted: Thu Dec 20, 2012 5:19 am
by Avvici
Yes that's correct, you know that it works but do you know how? Most don't and to be fair, most don't need to know nor do they want to know. However for those that are learning the basics of how a shopping cart works then this posting is for you. I will reveal to you everything that happens from the point of pressing ADD TO CART, to viewing items that you have added. This passage will be using code taken from the latest Opencart version 1.5.4.1

Phase One (clicking the add to cart button)

For Opencart versions 1.5x, the add to cart button relies on the use of AJAX (Asynchronous JavaScript and XML) where on earlier versions it does not. Either way, both submit an HTTP POST with data to a Control file which in turn contacts a system file. All methods of adding to the cart session use the same function but the data could come from different areas in the View. For example: in catalog/view/theme/your_theme/template/product/category.tpl the add to cart buttons contact a function that is within catalog/view/javascript/common.js, where in catalog/view/theme/your_theme/template/product/product.tpl the function is embedded in the TPL itself. Both contact a Control Function entitled public function add() { which is located in catalog/controller/checkout/cart.php . During the add-to-cart process the Model is never used.

Before continuing on we want to reiterate that by default there are two JavaScript functions used to contact the Control , which in turn contacts the system file "which we will discuss later":

(1) The JavaScript function, function addToCart(product_id, quantity) { is used mostly, and is found in catalog/view/javascript/common.js. This is used by all of the default Opencart modules that offer Add-to-cart buttons, specifically pages that have MORE THAN ONE BUTTON. With the help of AJAX, this allows many communications with the same function by passing what are called "Arguments", instead of having a gazillion functions with different Element ID's on the same page "which would be necessary with many buttons." addToCart(ARGUMENT ONE, ARGUMENT TWO) - product_id & quantity.
(2)The JavaScript function, $('#button-cart').bind('click', function() { is used on the product details page found at the bottom of catalog/view/theme/your_theme/template/product/product.tpl. This function is for the product details page and is for ONE add-to-cart button only.

When we use the word "Control", "Model", and "View" we are talking about the MVC-L (the L standing for language). This article won't be about MVC-L and what it is, but it helps to know the basics of MVC-L considering Open Cart "is" just that. If you want to know more then go here: http://forum.opencart.com/viewtopic.php?f=20&t=4113
Image

Phase Two (contacting stored procedure(s) to add to cart)

We have learned that the Add-To-Cart buttons when clicked make good use of JavaScript(with the help of AJAX) to first contact the control file, who's function communicates with the Core Cart Function. Let's take a closer look, starting with the JavaScript.

In catalog/view/javascript/common.js you will see the following line of code: (commented with //)

Code: Select all

function addToCart(product_id, quantity) {
quantity = typeof(quantity) != 'undefined' ? quantity : 1;
$.ajax({
url: 'index.php?route=checkout/cart/add', //URL TO CONTROL FUNCTION add()
type: 'post',
data: 'product_id=' + product_id + '&quantity=' + quantity,
dataType: 'json',
success: function(json) {
$('.success, .warning, .attention, .information, .error').remove();
if (json['redirect']) {
location = json['redirect'];
}
if (json['success']) {
$('#notification').html('<div style="display: none;">' + json['success'] + '<img src="catalog/view/theme/default/image/close.png" alt="" /></div>');
$('.success').fadeIn('slow');
$('#cart-total').html(json['total']);
$('html, body').animate({ scrollTop: 0 }, 'slow');
}
}
});
}
In that function is the AJAX CALL to the control file cart.php url: 'index.php?route=checkout/cart/add',

Here is a very simplified explanation on how AJAX works with Opencart add-to-cart procedure:
Image

In AJAX, the user sends a request that executes an action (which is a POST or a GET ) and the action's response is shown into a layer, identified by an ID, without reloading the full page. This is why the only thing you see is the green success bar at the top. Next, in catalog/controller/checkout/cart.php you will see a function called public function add(). This is the function being called publicly by url: 'index.php?route=checkout/cart/add' in the Ajax. Within this function you will see this code:

Code: Select all

if (!$json) {
$this->cart->add($this->request->post['product_id'], $quantity, $option);
$json['success'] = sprintf($this->language->get('text_success'), $this->url->link('product/product', 'product_id=' . $this->request->post['product_id']), $product_info['name'], $this->url->link('checkout/cart'));
What is happening here? First, the code checks to see if there are any required fields left empty. If this checks out then it adds the item to the cart array, hence the if (!$json) {. This basically states: IF EMPTY $json , ADD TO CART. }ELSE{ send error(s) to display in the html.

Phase Three (adding to the cart array)

Now that we have pressed the add to cart button, contacted the control by means of using AJAX and passed validation, we must now contact the core system file found in system/library/cart.php Above you saw the call for $this->cart->add($this->request->post['product_id'], $quantity, $option); This is looking for a function that will be explained below.

Before I go on further to explain how this function works we need to discuss what has already happened LONG before you ever pressed the add to cart button. In a nutshell, all items along with their options are stored in a session variable. This session is started when you reach the store for the first time and does not die until you either close your browser (in which case it might still survive depending upon your browser settings) OR if you clean your cache, and lastly if you complete a checkout entirely. If you scroll to the top of system/library/cart.php you will see the following code:

Code: Select all

if (!isset($this->session->data['cart']) || !is_array($this->session->data['cart'])) {
$this->session->data['cart'] = array();
}
This very simply states that, IF NO CART SESSION EXISTS OR IF CART SESSION IS NOT ARRAY, CREATE ONE. The array will remain empty until you start adding items.

Continuing on with the ADD function. The function looks like this:

Code: Select all

public function add($product_id, $qty = 1, $option = array()) {
if (!$option) {
$key = (int)$product_id;
} else {
$key = (int)$product_id . ':' . base64_encode(serialize($option));
}
if ((int)$qty && ((int)$qty > 0)) {
if (!isset($this->session->data['cart'][$key])) {
$this->session->data['cart'][$key] = (int)$qty;
} else {
$this->session->data['cart'][$key] += (int)$qty;
}
}$this->data = array();
}
The arguments being passed to the function by the control are $product_id, $qty = 1, $option = array(). The arguments in the control look like this: $this->cart->add($this->request->post['product_id'], $quantity, $option) Passing arguments is a nifty way to distribute data from one function to the next and in this case we are dealing with the product_id, quantity, and options (if any). Let's look closely at the first part of the function:

Code: Select all

if (!$option) {
$key = (int)$product_id;
} else {
$key = (int)$product_id . ':' . base64_encode(serialize($option));
}
What you are seeing is: IF NO OPTION(s) variable $key = $product_id }ELSE{ variable $key = STRING. This string is very important; it holds the product_id and the entire option array, concatenated together by a ':'. You will notice the $option variable is base64_encode(serialize(). Why do this? This encoding is designed to make binary data survive transport through transport layers that are not 8-bit clean. Likewise, the serialize function is designed to keep array's in tact so you can use them again later.

And now the last part of this function:

Code: Select all

if ((int)$qty && ((int)$qty > 0)) {
if (!isset($this->session->data['cart'][$key])) {
$this->session->data['cart'][$key] = (int)$qty;
} else {
$this->session->data['cart'][$key] += (int)$qty;
}
}$this->data = array();
And here is the function with comments to explain what is happening:

Code: Select all


if ((int)$qty && ((int)$qty > 0)) {

//validation that quantity is greater than 0 and correct form

if (!isset($this->session->data['cart'][$key])) {

//if the product DOESN'T exist in cart with matching properties/options. We are using the variable $key to run the check

$this->session->data['cart'][$key] = (int)$qty;

//add product to cart array (new product)

} else {

$this->session->data['cart'][$key] += (int)$qty;

//this is if product DOES exist in cart with matching properties/options //Add another of the same to change quantity. This is done by (+=)
}
}
In short, we add a new item to the cart if it doesn't already exist OR we increase the quantity of an existing item because the system has detected an identical match with the same options/properties. If you remember that we started with the session array: $this->session->data['cart'] as an empty array. Now we are filling the array with a KEY and a VALUE. The KEY is $key and the VALUE is the quantity, so it is declared here: $this->session->data['cart'][$key] =

The cart session array is now filled. It could be small or it could be VERY LARGE depending upon how many products or options you have added to those products. Displayed below is a print_r() of a cart filled with 4 items. This is so you can see what exactly is in the array. You can do this at anytime by running the following line:

Code: Select all

<?php print_r($this->session->data['cart']);?>:
The above outputs:

Array ( [44] => 1 [47:YToxOntpOjIyNTtzOjEwOiIyMDExLTA0LTIyIjt9] => 1 [40] => 1 [30:YToxOntpOjIyNjtzOjI6IjE1Ijt9] => 1 )

Notice the KEY of each position is the product_id and serialized options array, separated by a ':'

Now we get to the good stuff! Just how does Open Cart use this cart array to show the cart contents to the user on the front end?

Phase Four (displaying the cart contents)

The cart contents are displayed in many places throughout the customers shopping experience. For our in depth glance in how this actually happens we are going to choose the shopping cart page found at index.php?route=checkout/cart

In catalog/controller/checkout/cart.php you will see the following code:

Code: Select all

$products = $this->cart->getProducts();
This little line of code is most important in that it makes a call to the function getProducts which is a core procedure located in system/library/cart.php. The variable $products now has the entire cart array + all possible options and can be "iterated" through. To iterate means to sift through an array, gathering it's contents or to LOOP through. Let's take a look at what is happening in the function getProducts(); In system/library/cart.php find this function towards the top:

Code: Select all

public function getProducts() {
if (!$this->data) {
foreach ($this->session->data['cart'] as $key => $quantity) {
$product = explode(':', $key);
$product_id = $product[0];
$stock = true;// Options
if (isset($product[1])) {
$options = unserialize(base64_decode($product[1]));
} else {
$options = array();
}
//The rest of this function cut off to save posting space
For the sake of time we aren't going to go over the entire function but just give you a general idea of what is going on. Let's just take a look at the first few lines here:
foreach ($this->session->data['cart'] as $key => $quantity) {
//loop through the entire cart array. Everything below this line will repeat itself as many times as necc.

Code: Select all

$product = explode(':', $key);
$product_id = $product[0];
$stock = true;
In foreach ($this->session->data['cart'] as $key => $quantity) { we see a php FOREACH() which allows you to loop through the array. Each time it loops through it executes all procedures between the { and } of the for loop which in this case is a LOT OF CODE. If you remember in our discussion about the cart array above, the $key is a string separated by a ':' so let's take that and EXPLODE it into an array so we can better deal with it.

This is being done here: $product = explode(':', $key); //$product is now an array.

The first index of the array is the product_id so we declare that into a variable like this: $product_id = $product[0];

Now let's deal with the possible options we just exploded into an array $products;

Code: Select all

// Options//If any options at all, execute code unserialize + decode. Else, declare empty array.
if (isset($product[1])) {
$options = unserialize(base64_decode($product[1]));
} else {
$options = array();
}
In general, everything below that line gathers all data concerning the products and options found within the cart and puts them in ONE LARGE ARRAY. During this process the database is communicated with at great length in order to gather certain item/option properties. This new array is dealt with in the control procedure $products = $this->cart->getProducts(); which we already mentioned. If you scroll to the bottom of the function we were just working with you will see the following code:

Code: Select all

$this->data[$key] = array(
'key'             => $key,
'product_id'      => $product_query->row['product_id'],
'name'            => $product_query->row['name'],
'model'           => $product_query->row['model'],
'shipping'        => $product_query->row['shipping'],
'image'           => $product_query->row['image'],
'option'          => $option_data,
'download'        => $download_data,
'quantity'        => $quantity,
'minimum'         => $product_query->row['minimum'],
'subtract'        => $product_query->row['subtract'],
'stock'           => $stock,
'price'           => ($price + $option_price),
'total'           => ($price + $option_price) * $quantity,
'reward'          => $reward * $quantity,
'points'          => ($product_query->row['points'] ? ($product_query->row['points'] + $option_points) * $quantity : 0),
'tax_class_id'    => $product_query->row['tax_class_id'],
'weight'          => ($product_query->row['weight'] + $option_weight) * $quantity,
'weight_class_id' => $product_query->row['weight_class_id'],
'length'          => $product_query->row['length'],
'width'           => $product_query->row['width'],
'height'          => $product_query->row['height'],
'length_class_id' => $product_query->row['length_class_id']
This is the master array that is RETURNED to $this->cart->getProducts();
Now back to catalog/controller/checkout/cart.php find this line of code:

Code: Select all

foreach ($products as $product) {
This code is the foreach() loop set up to go through the array we just set up in system/library/cart.php. It is responsible for displaying all of the content seen in the shopping cart page along with the product options. Everything below foreach ($products as $product) { will repeat itself as many times as necessary until ALL PRODUCTS HAVE BEEN DEALT WITH IN THE ARRAY.

Product Options:

A few lines down you will find this code which allows us to iterate through the option array which was tucked inside the main array:

Code: Select all


$option_data = array();

foreach ($product['option'] as $option) {
Now in catalog/controller/checkout/cart.php look for this code:

Code: Select all

$this->data['products'][] = array(
'key'      => $product['key'],
'thumb'    => $image,
'name'     => $product['name'],
'model'    => $product['model'],
'option'   => $option_data,
'quantity' => $product['quantity'],
'stock'    => $product['stock'] ? true : !(!$this->config->get('config_stock_checkout) || $this->config->get('config_stock_warning')),
'reward'   => ($product['reward'] ? sprintf($this->language->get('text_points'), $product['reward']) : "),
'price'    => $price,
'total'    => $total,
'href'     => $this->url->link('product/product', 'product_id=' . $product['product_id']),
'remove'   => $this->url->link('checkout/cart', 'remove=' . $product['key'])
);
This is the array being set up to be echoed on this page: catalog/view/theme/your_theme/template/product/cart.tpl

Open catalog/view/theme/your_theme/template/product/cart.tpl and find this code:

Code: Select all

<?php
foreach ($products as $product) {
Now we can iterate through $products and display cart contents. $products in this file = $this->data['products'][] in the control.

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Thu Dec 20, 2012 9:49 pm
by straightlight

Code: Select all

<?php print_r($this->session->data['cart']);?>:
for:

Code: Select all

<?php if (!empty($this->session->data['token'])) { ?>
    <?php echo "<pre>\n"; ?>
    <?php print_r($this->session->data['cart']); ?>
    <?php echo "</pre>\n"; ?>
<?php } ?>
if the site is live. Login to your admin panel first and click on the store-front link. Then, go to the target page mentioned on the above instructions so that non-registered or registered customers / affiliates may not see the array on top of the store but only administrators.

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Fri Dec 21, 2012 1:03 am
by Avvici
straightlight wrote:

Code: Select all

<?php print_r($this->session->data['cart']);?>:
for:

Code: Select all

<?php if (!empty($this->session->data['token'])) { ?>
    <?php echo "<pre>\n"; ?>
    <?php print_r($this->session->data['cart']); ?>
    <?php echo "</pre>\n"; ?>
<?php } ?>
if the site is live. Login to your admin panel first and click on the store-front link. Then, go to the target page mentioned on the above instructions so that non-registered or registered customers / affiliates may not see the array on top of the store but only administrators.
This is a good point. Instead of amending the posting I will just keep this here for people to see. Thanks. O0

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Fri Dec 21, 2012 1:04 am
by MrTech
Fantastic share! Vote for adding this to official documentation when and if it ever makes it live.

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Fri Dec 21, 2012 1:15 am
by straightlight
An additional statement would be to indicate from the above that when adding a product to the cart, JSON returns the results not only with success but also when an error is involved with the key name (in this case with: warning, attention, information, error') from PHP when encoded with JSON.

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Fri Dec 21, 2012 1:21 am
by Avvici
straightlight wrote:An additional statement would be to indicate from the above that when adding a product to the cart, JSON returns the results not only with success but also when an error is involved with the key name (in this case with: warning, attention, information, error') from PHP when encoded with JSON.
I did mention this:
What is happening here? First, the code checks to see if there are any required fields left empty. If this checks out then it adds the item to the cart array, hence the if (!$json) {. This basically states: IF EMPTY $json , ADD TO CART. }ELSE{ send error(s) to display in the html.
.
}ELSE{ send error(s) to display in the html.
Read carefully lol O0

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Fri Dec 21, 2012 1:30 am
by straightlight
My previous point was stating that not only errors may occurs but also other events followed with successful response from JSON encoding while your previous statement seem to be about erroneous only while common.js file also demonstrates other events than success and errors.

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Fri Dec 21, 2012 1:32 am
by Avvici
straightlight wrote:My previous point was stating that not only errors may occurs but also other events followed with successful response from JSON encoding while your previous statement seem to be about erroneous only.
Yes, but this tutorial is not about JSON and objects. It's about the cart. I wish I had the room to fit everything in but I didn't. There is a character limit to the phpBB forum posting too.

I will just encourage people to post their ideas and comments here if need be. Good day.

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Fri Dec 21, 2012 1:35 am
by straightlight
There is a character limit to the phpBB forum posting too.
Yes, I understand. Perhaps a topic wouldn't be the proper solution to demonstrates OpenCart functionality and object relations. A CHM, PDF file or developed documentation from Google Docs would be more ideal, I believe.

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Fri Dec 21, 2012 1:42 am
by Avvici
straightlight wrote:
There is a character limit to the phpBB forum posting too.
Yes, I understand. Perhaps a topic wouldn't be the proper solution to demonstrates OpenCart functionality and object relations. A CHM, PDF file or developed documentation from Google Docs would be more ideal, I believe.
Yup ;) We could have written about 10 more pages for this tutorial...but it was condensed to cover the basics.

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Fri Dec 21, 2012 2:33 am
by MrTech
It's too bad you took so much time to write a post only to have it torn apart bit by bit eh? Some people's kids just have no clue how to show appreciation.

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Fri Dec 21, 2012 10:36 am
by gangsar.swapurba
yah, it's a bit weird eh ....

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Tue Dec 25, 2012 1:02 am
by nvedia
Nice tutorial

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Fri Dec 28, 2012 3:31 pm
by straightlight
http://forum.opencart.com/viewtopic.php?f=23&t=91816

@avvici: Now, all users can create their own documentation - including you.

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Wed Feb 13, 2013 12:32 am
by imsir
Great tutorial! Great work from you! Thanks for deep research and explanation. O0

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Wed Mar 04, 2015 11:17 am
by natedang
Very helpful of this <3 :joker: :joker:
Thanks so much

Re: [TUTORIALS] Opencart's "cart" - a deeper look

Posted: Sun Jul 19, 2015 3:48 am
by Rohit0071996
This is good.
But can you provide a small detail also?
I'm making my custom add to cart function. I save the cart items in localstorage.
I'm able to add items without any product options with them. But the products with options, I'm not able to add them to cart.

I just want to know in which format I need to save in localstorage and then POST the options array with the help of original cart.add function and what the array should contain (like product_option_id, etc.) so that the product with options chosen is successfully added to the cart. Please give an example if possible.

Thanks.