Entity view (Content)

Ubercart-Customizing shopping cart for Marketplace Website

By rllabore
Sep. 12, 2011

I am currently working on a e-commerce website which requires me to group items on the cart by seller. The shopping cart should also display the subtotal and a submit button for each seller. Ubercart is designed assuming that your website is the only store. However, it is very flexible that you can support multiple stores/merchants on your site. There are many ways in which you can customize Ubercart. When you do, it is important to follow Ubercart methodologies first, by using Ubercart defined hooks. If no Ubercart hook is available, then use Drupal methodologies. Here are the steps that I took in order to customize the shopping cart page:

1.Implement hook_add_to_cart_data() - When an item is added to the cart, I need to be able to retrieve the seller user id or the store node id that the item is associated with. Since the owner of the item will unlikely change, I might as well put the seller user id or store node in the data array of an item.

2.Implement hook_TYPE_alter(), (TYPE = 'uc_cart') – Ubercart allows developers to alter items loaded from the database (see uc_cart_get_contents()). To make it easier for me to display items grouped by seller, I had to alter how raw data was arranged. On the cart page, I rearranged the items data structure so that they are grouped by seller already before it is processed at the form level.

3.Implement hook_TYPE_alter(), (TYPE = 'cart_pane') – The cart page is designed so that other modules can display their information on the page using panes. I needed to override the “cart_form” pane so that it would display my custom cart form which was derived from what Ubercart is using by default.

4.Implement my custom cart form – The reason why implemented my own custom cart form and not use hook_form_alter is because in order to display the desired output, I had to restructure the form data. The default cart form uses Table API to display the items on the cart. By default, there is only one table API element which holds all items on the cart. Since I had to group items by seller, the same number of table API elements should equal the number sellers.

5.Implement my custom table definition function (see uc_cart_view_table) – I had to use my own cart view table function which is an argument in “tapir_get_table”. “tapir_get_table” function is responsible for building a table API element. I implemented my own table definition function because I had to add the “Price” column.

6.Implement the theme function of my custom cart form – The my theme function is derived from the theme function of default cart form. Since the form structure has been altered dramatically, I had to modify my theme function accordingly.


SAMPLE CODE: STEP 1:


/**
 * Implementation of hook_add_to_cart_data()
 */
function MY_MODULE_add_to_cart_data($form_values) {
  $node = node_load($form_values['nid']);
  return array('uid' => $node->uid);
}

STEP 2:


/**
 * Implementation of hook_TYPE_alter().
 * NOTE: TYPE == 'uc_cart'
 */
function MY_MODULE_uc_cart_alter(&$items) {
   $cart_page  = 'cart';
	
   /* before showing to user, rearrange the items by owner; on cart page, I will
       control how the items data structure will be formatted */
   if($_GET['q'] == $cart_page) {
      $temp_basket = array();
      foreach($items as $i => $item) {
         $temp_basket[$item->data['uid']][] = $item;
      }
      $items  = array();		
      $weight = 0;	
      foreach($temp_basket as $uid => $_items) {
         foreach($_items as $_item) {
	    $_item->weight = $weight++; 
	 }
      }	
      $items = $temp_basket;	
   }
}

STEP 3:

/**
 * Implementation of hook_TYPE_alter().
 * NOTE: TYPE == 'cart_pane'
 */
function MY_MODULE_cart_pane_alter(&$panes, $items) {
   if(!count($panes) || !count($items)) {
      return;
   }
	
   foreach($panes as $k => &$pane) {
      if($pane['id'] == 'cart_form') {
         $cart_form = !is_null($items) ? '
'. drupal_get_form('MY_MODULE_view_cart_form', $items) .'
': '';
			
         $pane['body'] = $cart_form;
      }
   }
}

STEP 4:


/**
 * Custom implementation of cart form.
 * @see uc_cart_view_form()
 */
function MY_MODULE_view_cart_form($form_state, $items = NULL) {
	
   $form = array();
   $i    = 0;
	
   /* items in the cart are grouped by node owner */
   foreach ($items as $owner_uid => $owner_items) {
		
      /* seller link */
      $account = user_load($owner_uid);
      $link    = l($account->name, 'profile/'. $account->uid);
		
      $form[$owner_uid]['seller'] = array(
         '#value' => t('Order from ') . $link,
      );
		
      /* build tapir table element; contains items from each seller */
      $form[$owner_uid]['items'] = array(
         '#type' => 'tapir_table',
	 '#tree' => TRUE,
      );
	
      $context = array(
         'revision' => 'themed-original',
	 'type' => 'amount',
      );
		
      /* loop through each item that belongs to the current owner */
      foreach($owner_items as $item) {
			
         display_item = module_invoke($item->module, 'cart_display', $item);
			
	 if (!empty($display_item)) {
			
	    /* its OK to node_load each product since by the time this gets called,
	       the node has been statically cached */
	    $product_node = node_load($display_item['nid']['#value']);
				
	    /* modify the image size */
	    $image = theme('imagecache', 'MY_MODULE_product_preset', $product_node->field_image_cache[0]['filepath']);
	    $l_path    = 'node/'. $product_node->nid;
	    $l_options = array(
	       'html' => TRUE,
	    );

	    $form[$owner_uid]['items'][$i] = $display_item;
	    $form[$owner_uid]['items'][$i]['image']['#value'] =  l($image, $l_path, $l_options);
	
	    $description = $display_item['title']['#value'] . $display_item['description']['#value'];
	    $form[$owner_uid]['items'][$i]['desc']['#value'] = $description;
	
	    $form[$owner_uid]['items'][$i]['title']['#type'] = 'value';
	    $form[$owner_uid]['items'][$i]['description']['#type'] = 'value';
	
	    if (empty($display_item['qty'])) {
	       $form[$owner_uid]['items'][$i]['qty'] = array(
	          '#value' => '',
	       );
	    }
				
	    $form[$owner_uid]['items'][$i]['price']['#value'] = sprintf('$%01.2f',$item->price);
				
	    $form[$owner_uid]['items'][$i]['total'] = array(
	       '#value' => uc_price($display_item['#total'], $context),
	       '#theme' => 'uc_cart_view_price',
	    );
	    $i++;
         }
      }
		
      /* build the tapir element for the given seller */
      $form[$owner_uid]['items'] = tapir_get_table('MY_MODULE__helpers_view_cart_table', $form[$owner_uid]['items']);
	
      /* submit order button for each seller */
      $form[$owner_uid]['submit_order'] = array(
         '#type'   => 'submit',
	 '#value'  => t('Submit Order'),
	 '#submit' => array('MY_MODULE__forms_cart_order_submit'),
	 '#name'   => $owner_uid
      );
   }

  // If the continue shopping element is enabled...
  if (($cs_type = variable_get('uc_continue_shopping_type', 'link')) !== 'none') {
    // Setup the text used for the element.
    $cs_text = variable_get('uc_continue_shopping_text', '') ? variable_get('uc_continue_shopping_text', '') : t('Continue shopping');

    // Add the element to the form based on the element type.
    if (variable_get('uc_continue_shopping_type', 'link') == 'link') {
      $form['continue_shopping'] = array(
        '#value' => l($cs_text, uc_cart_continue_shopping_url()),
      );
    }
    elseif (variable_get('uc_continue_shopping_type', 'link') == 'button') {
      $form['continue_shopping'] = array(
        '#type' => 'submit',
        '#value' => $cs_text,
        '#submit' => array('uc_cart_view_form_submit'),
      );
      $form['continue_shopping_text'] = array(
        '#type' => 'hidden',
        '#value' => $cs_text,
      );
    }
  }

  // Add the control buttons for updating and proceeding to checkout.
  $form['update'] = array(
    '#type' => 'submit',
    '#value' => t('Update cart'),
    '#submit' => array('uc_cart_view_form_submit'),
  );
  return $form;
}

/**
 * Submit handler for each order in the cart
 */
function MY_MODULE__forms_cart_order_submit($form, &$form_state) {
	
  // Remove the cart order variable if the customer came here during checkout.
  if (isset($_SESSION['cart_order'])) {
    unset($_SESSION['cart_order']);
  }

  // Update the items in the shopping cart based on the form values.
  uc_cart_update_item_object((object)$form_state['values']);
	
  /* NOTE: I need to know which submit button was clicked  */
  foreach($form_state['values'] as $k => $info) {
		
  /* #name was used to hold the seller uid. I will use the seller id to filter
    what needs to be checked out */
    if(is_numeric($k)) {
      $form_state['redirect'] = variable_get('uc_checkout_enabled', TRUE) ? array('cart/checkout','seller='. $k) : 'cart';
    }
  }
}


STEP 5:


/**
 * List the products in the cart in a TAPIr table.
 * @see uc_cart_view_table()
 */
function MY_MODULE__helpers_view_cart_table($table) {
  $table['#attributes'] = array('width' => '100%');

  $table['#columns'] = array(
    'remove' => array(
      'cell' => t('Remove'),
      'weight' => 0,
    ),
    'image' => array(
      'cell' => t('Products'),
      'weight' => 1,
    ),
    'desc' => array(
      'cell' => '',
      'weight' => 2,
    ),
    'qty' => array(
      'cell' => t('Qty.'),
      'weight' => 3,
    ),
		'price' => array(
      'cell' => t('Price'),
      'weight' => 3,
    ),
    'total' => array(
      'cell' => t('Total'),
      'weight' => 4,
    ),
  );

  foreach (element_children($table) as $i) {
    $subtotal += $table[$i]['#total'];

    $table[$i]['remove']['#cell_attributes'] = array('align' => 'center', 'class' => 'remove');
    $table[$i]['image']['#cell_attributes'] = array('class' => 'image');
    $table[$i]['desc']['#cell_attributes'] = array('class' => 'desc');
    $table[$i]['qty']['#cell_attributes'] = array('class' => 'qty');
    $table[$i]['price']['#cell_attributes'] = array('class' => 'qty');
    $table[$i]['total']['#cell_attributes'] = array('class' => 'price');
    $table[$i]['#attributes'] = array('valign' => 'top');
  }

  $context = array(
    'revision' => 'themed-original',
    'type' => 'amount',
  );
	
  /* format subtotal per order */
  $subtotal_text = t('Subtotal');
  $shipping_text = t('shipping not included');
  $price         = uc_price($subtotal, $context);
  $approx_text   = t('approximately');
  $total_text ="
    $subtotal_text ($shipping_text):$price USD
$approx_text
$price usd "; $table[] = array( 'total' => array( '#value' => $total_text, '#cell_attributes' => array( 'colspan' => 'full', 'align' => 'right', 'class' => 'subtotal', ), ), ); return $table; }

STEP 6:

/**
 * @ingroup themeable
 * @see uc_cart_view_form()
 */
function theme_MY_MODULE_view_cart_form($form) {
  drupal_add_css(drupal_get_path('module', 'uc_cart') .'/uc_cart.css');
	
  $output = '';
  foreach($form as $id => $info) {
		
    if(is_numeric($id)) {
			
      $seller  = drupal_render($form[$id]['seller']);
      $items   = drupal_render($form[$id]['items']);
      $submit  = drupal_render($form[$id]['submit_order']);
			
      $seller ="
$seller
";
      $items ="
$items
";
      $submit ="
$submit
";
      $order ="
$seller $items $submit
";
      $output.= $order;
			
      /* make sure that these elements don't get displayed again */
      foreach(element_children($form[$id]['items']) as $i) {
	foreach (array('title', 'options', 'remove', 'image', 'qty', 'price') as $column) {
	  $form[$id]['items'][$i][$column]['#printed'] = TRUE;
	}
	$form[$id]['items'][$i]['#printed'] = TRUE;
      }
    }
  }
	
 
  /* Add the continue shopping element and cart submit buttons. */
  if (($type = variable_get('uc_continue_shopping_type', 'link')) != 'none') {
		
    /* Render the continue shopping element into a variable. */
    $cs_element = drupal_render($form['continue_shopping']);

    /* Add the element with the appropriate markup based on the display type. */
    if ($type == 'link') {
      $rendered_form = drupal_render($form);
			
      $output .="
$rendered_form
";
    }
    elseif ($type == 'button') {
      $rendered_form = drupal_render($form);
			
      $output .="
$rendered_form
$cs_element
";
    }
  }
  else {
     $rendered_form = drupal_render($form);
		
     $output .="
$rendered_form

";
  }

  return $output;
}

Post Tags: