Post by Avvici » Wed Dec 19, 2012 11:59 pm

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.
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();
}
//EVERYTHING ELSE BELOW THIS WAS CUT TO SAVE SPACE IN THE THREAD
 
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.

User avatar
Expert Member

Posts

Joined
Tue Apr 05, 2011 12:09 pm
Location - Asheville, NC
Who is online

Users browsing this forum: No registered users and 7 guests