Virtuemart
Buy X get Y hack for Virtuemart 1.1.2 PDF Print E-mail

This is a hack. You will have to modify your original Virtuemart files, so first of all: backup your original files and your database before proceed.

Don't try this on a production site! The best thing I can suggest is that you fire up a test site and if you are satisfied with the results you can proceed with the production site.

We will go step by step with the modifications, and you can download the modifyed files on the bottom on this howto. I suggest to read it before you overwrite your files, it's better to know what was changed and why.

However if you don't have other hacks or modifications, you can simply replace the original files.

 

What we need to modify:

 

1. We need to modify the jos_vm_product table. This will store the informations for the amount one have to buy in order to get the free items.

2. We need to modify the admin form of the products in order to have two more fields which we can use to add and update these values.

3. We need to modify the checkout process with the editable and the read only baskets as well as the minicart to display the items correctly.

4. We need to modify the invoice files in order to have listed the free items.

 

These are the steps you have to follow in order to get these works:

 

1. Start your phpMyAdmin (or whatever you are using to modify your database) and select the jos_vm_product table. You need to add 2 more fields at the end of the table.

 

Pay attention to the default values. For the "buy" field you should set 1, for the "get" you can leave on 0. Otherwise you should correct one by one the existing items in your shop.

 

2. Next we need to modify the ps_product file. You can find it in the /administrator/components/com_virtuemart/classes folder.

This is the file which store the class to manage your products. We will modify only 3 functions:the add, the update, and the validate.

Search for the fields array in the add function:

$fields = array ( 'vendor_id' => $vendor_id,

You will have only two results, the first in the add function, the second in the update function.

Replace the array with this:

$fields = array ( 'vendor_id' => $vendor_id,
  'product_sku' => vmGet($d,'product_sku'),
  'product_name' => vmGet($d,'product_name'),
  'product_desc' => vmRequest::getVar('product_desc', '', 'default', '', VMREQUEST_ALLOWHTML),
  'product_s_desc' => vmRequest::getVar('product_s_desc', '', 'default', '', VMREQUEST_ALLOWHTML),
  'product_thumb_image' => vmGet($d,'product_thumb_image'),
  'product_full_image' => vmGet($d,'product_full_image'),
  'product_publish' => $d['product_publish'],
  'product_weight' => vmRequest::getFloat('product_weight'),
  'product_weight_uom' => vmGet($d,'product_weight_uom'),
  'product_length' => vmRequest::getFloat('product_length'),
  'product_width' => vmRequest::getFloat('product_width'),
  'product_height' => vmRequest::getFloat('product_height'),
  'product_lwh_uom' => vmGet($d,'product_lwh_uom'),
  'product_unit' => vmGet($d,'product_unit'),
  'product_packaging' => (($d["product_box"] << 16) | ($d["product_packaging"]&0xFFFF)),
  'product_url' => vmGet($d,'product_url'),
  'product_in_stock' => vmRequest::getInt('product_in_stock'),
  'attribute' => ps_product_attribute::formatAttributeX(),
  'custom_attribute' => vmGet($d,'product_custom_attribute'),
  'product_available_date' => $d['product_available_date_timestamp'],
  'product_availability' => vmGet($d,'product_availability'),
  'product_special' => $d['product_special'],
  'child_options' => $d['child_options'],
  'quantity_options' => $d['quantity_options'],
  'product_discount_id' => vmRequest::getInt('product_discount_id'),
  'mdate' => $timestamp,
  'product_tax_id' => vmRequest::getInt('product_tax_id'),
  'child_option_ids' => vmGet($d,'included_product_id'),
  'product_order_levels' => $d['order_levels'],
  //hack by otx
  'buy' => $d['buy'],
  'get' => $d['get']);
  //end hack by otx

 

Or you can add only the last two lines between the //hack by otx and //end hack by otx. Pay attention to the "," and ";" on the end of the lines. Don't forget to add the ")" to the last line!

 

Replace the array in the update function, too. It's the same array so you can use the same code.

 

Now we need a validation for the "buy" item. After this modification when you will create a new product in your shop, even if you forget to set the correct values for the "buy" and "get" fields, which are 1 and 0, you will have this set after you save the product form.

Look for the following expression:

return $valid;

This is on the end of the validate function.

Insert this before the above line:

 

// hack by otx
       
 if ($d['buy'] ==0){
     $d['buy'] = 1;
 }
       
 // end hack by otx

So it will look like this:

// hack by otx
       
 if ($d['buy'] ==0){
     $d['buy'] = 1;
}
       
// end hack by otx
       
return $valid;

 

So far we have a working solution to store and update these values. Now we need to modify the admin form of the products in order to have fields which we can use to add and update these values.

 

3. Open the product.product_form.php. You can find it in the /administrator/components/com_virtuemart/html folder.

 

Search for the following lines (around 343):

        <?php echo vmToolTip( $VM_LANG->_('PHPSHOP_PRODUCT_FORM_DISCOUNTED_PRICE_TIP') ) ?>
        </td>
    </tr>

 Add after:

<!-- ******************** hack by otx *************************** -->
<!-- buy-get -->
   
   
     <tr class="row1">
      <td width="20%" ><div style="text-align:right;font-weight:bold;">
        <?php echo "Buy"; ?>:</div>
      </td>
      <td width="20%" >
<input type="text" class="inputbox"  name="buy" value="<?php $db->sp("buy"); ?>" 
size="10" maxlength="10"     />
      </td>
     </tr>
     <tr>
      <td width="20%" ><div style="text-align:right;font-weight:bold;">
        <?php echo "Get"; ?>:</div>
      </td>
      <td width="20%" >
<input type="text" class="inputbox"  name="get" value="<?php $db->sp("get"); ?>" 
size="10" maxlength="10"     /><span style="text-align:left;font-weight:bold;">
        <?php echo "for free"; ?></span>
      </td>
    </tr>
   
    <!-- end  buy-get -->
   
    <!-- ******************** end hack by otx *************************** -->

After this, if you edit some product in your admin area you will get this:

 

 

Notice the last two lines. You can use whatever combination you like it.

 

In order to use this feature we have to modify the checkout forms and the baskets.

 

4.Open the /administrator/components/com_virtuemart/html/basket.php

Search for the

$product_price = round( $product_price, 2 );

Should be around line 111. Add after:

// hack by otx 
		
$amount = $cart[$i]["quantity"];
$buy = $ps_product->get_field($cart[$i]["product_id"],"buy");
$get = $ps_product->get_field($cart[$i]["product_id"],"get");
$free = $amount - $cart[$i]["quantity"];
if ($get >= 0){
		$get = $ps_product->get_field($cart[$i]["product_id"],"get");
		$total_quantity = $buy + $get;
		$amount = (int)(($cart[$i]["quantity"] * ($total_quantity)) / $buy);
		$free = $amount - $cart[$i]["quantity"];
}

// end hack by otx

 Should look like this:

$product_price = round( $product_price, 2 );
			
// hack by otx 
		
$amount = $cart[$i]["quantity"];
$buy = $ps_product->get_field($cart[$i]["product_id"],"buy");
$get = $ps_product->get_field($cart[$i]["product_id"],"get");
$free = $amount - $cart[$i]["quantity"];
if ($get >= 0){
		$get = $ps_product->get_field($cart[$i]["product_id"],"get");
		$total_quantity = $buy + $get;
		$amount = (int)(($cart[$i]["quantity"] * ($total_quantity)) / $buy);
		$free = $amount - $cart[$i]["quantity"];
}

// end hack by otx
		
$product_rows[$i]['product_price'] = $GLOBALS['CURRENCY_DISPLAY']->getFullValue($product_price);

/* SUBTOTAL CALCULATION */

This will update the amount in the basket including the free items (and sets the variable for the amount of free items, which we will use later).

Next, still in the basket.php, look for the

$action_url = $mm_action_url.basename($_SERVER['PHP_SELF']);

line.

Replace the form below this line with:

 

//hack by otx
       
        if ($free >0) {
        $product_rows[$i]['update_form'] = '<form action="'. $action_url .'" 
method="post" style="display: inline;">
        <input type="hidden" name="option" value="com_virtuemart"     />
        <input type="text" title="'. $VM_LANG->_('PHPSHOP_CART_UPDATE') .'" 
class="inputbox" size="1" maxlength="4" name="quantity" 
value="'.$cart[$i]["quantity"].'"     />'.' + '.$free.$VM_LANG->_('PHPSHOP_CART_FREE').'
        <input type="hidden" name="page" value="'. $page .'"     />
    <input type="hidden" name="func" value="cartUpdate"     />
    <input type="hidden" name="product_id" value="'. $_SESSION['cart'][$i]["product_id"] .'"     />
    <input type="hidden" name="prod_id" value="'. $_SESSION['cart'][$i]["product_id"] .'"     />
    <input type="hidden" name="Itemid" value="'. $sess->getShopItemid() .'"     />
    <input type="hidden" name="description" value="'. stripslashes($cart[$i]["description"]).'"     />
    <input type="image" name="update" title="'. $VM_LANG->_('PHPSHOP_CART_UPDATE') .'" 
src="'. VM_THEMEURL .'images/update_quantity_cart.png" border="0"  
alt="'. $VM_LANG->_('PHPSHOP_UPDATE') .'" align="absmiddle"     />
  </form>';
        $product_rows[$i]['delete_form'] = '<form action="'.$action_url.'" method="post" 
name="delete" style="display: inline;">
    <input type="hidden" name="option" value="com_virtuemart"     />
    <input type="hidden" name="page" value="'. $page .'"     />
    <input type="hidden" name="Itemid" value="'. $sess->getShopItemid() .'"     />
    <input type="hidden" name="func" value="cartDelete"     />
    <input type="hidden" name="product_id" value="'. $_SESSION['cart'][$i]["product_id"] .'"     />
    <input type="hidden" name="description" value="'. $cart[$i]["description"].'"     />
      <input type="image" name="delete" title="'. $VM_LANG->_('PHPSHOP_CART_DELETE') .'" 
src="'. VM_THEMEURL .'images/remove_from_cart.png" border="0" 
alt="'. $VM_LANG->_('PHPSHOP_CART_DELETE') .'" align="absmiddle"     />
  </form>';}
        else{
    $product_rows[$i]['update_form'] = '<form action="'. $action_url .'" 
method="post" style="display: inline;">
        <input type="hidden" name="option" value="com_virtuemart"     />
        <input type="text" title="'. $VM_LANG->_('PHPSHOP_CART_UPDATE') .'" 
class="inputbox" size="3" maxlength="4" name="quantity" value="'.$cart[$i]["quantity"].'"     />
        <input type="hidden" name="page" value="'. $page .'"     />
    <input type="hidden" name="func" value="cartUpdate"     />
    <input type="hidden" name="product_id" value="'. $_SESSION['cart'][$i]["product_id"] .'"     />
    <input type="hidden" name="prod_id" value="'. $_SESSION['cart'][$i]["product_id"] .'"     />
    <input type="hidden" name="Itemid" value="'. $sess->getShopItemid() .'"     />
    <input type="hidden" name="description" value="'. stripslashes($cart[$i]["description"]).'"     />
    <input type="image" name="update" title="'. $VM_LANG->_('PHPSHOP_CART_UPDATE') .'" 
src="'. VM_THEMEURL .'images/update_quantity_cart.png" border="0"  
alt="'. $VM_LANG->_('PHPSHOP_UPDATE') .'" align="absmiddle"     />
  </form>';
        $product_rows[$i]['delete_form'] = '<form action="'.$action_url.'" 
method="post" name="delete" style="display: inline;">
    <input type="hidden" name="option" value="com_virtuemart"     />
    <input type="hidden" name="page" value="'. $page .'"     />
    <input type="hidden" name="Itemid" value="'. $sess->getShopItemid() .'"     />
    <input type="hidden" name="func" value="cartDelete"     />
    <input type="hidden" name="product_id" value="'. $_SESSION['cart'][$i]["product_id"] .'"     />
    <input type="hidden" name="description" value="'. $cart[$i]["description"].'"     />
      <input type="image" name="delete" title="'. $VM_LANG->_('PHPSHOP_CART_DELETE') .'" 
src="'. VM_THEMEURL .'images/remove_from_cart.png" border="0" 
alt="'. $VM_LANG->_('PHPSHOP_CART_DELETE') .'" align="absmiddle"     />
  </form>';}
 
    //end hack by otx

You have to get this:

 

        /* UPDATE CART / DELETE FROM CART */
		$action_url = $mm_action_url.basename($_SERVER['PHP_SELF']);
		
		//hack by otx
       
        if ($free >0) {
        $product_rows[$i]['update_form'] = '<form action="'. $action_url .'" 
method="post" style="display: inline;">
<input type="hidden" name="option" value="com_virtuemart"     />
<input type="text" title="'. $VM_LANG->_('PHPSHOP_CART_UPDATE') .'" 
class="inputbox" size="1" maxlength="4" name="quantity" 
value="'.$cart[$i]["quantity"].'"     />'.' + '.$free.$VM_LANG->_('PHPSHOP_CART_FREE').'
<input type="hidden" name="page" value="'. $page .'"     />
<input type="hidden" name="func" value="cartUpdate"     />
<input type="hidden" name="product_id" value="'. $_SESSION['cart'][$i]["product_id"] .'"     />
<input type="hidden" name="prod_id" value="'. $_SESSION['cart'][$i]["product_id"] .'"     />
<input type="hidden" name="Itemid" value="'. $sess->getShopItemid() .'"     />
<input type="hidden" name="description" value="'. stripslashes($cart[$i]["description"]).'"     />
<input type="image" name="update" title="'. $VM_LANG->_('PHPSHOP_CART_UPDATE') .'" 
src="'. VM_THEMEURL .'images/update_quantity_cart.png" border="0"  
alt="'. $VM_LANG->_('PHPSHOP_UPDATE') .'" align="absmiddle"     />
</form>';
        $product_rows[$i]['delete_form'] = '<form action="'.$action_url.'" method="post" 
name="delete" style="display: inline;">
<input type="hidden" name="option" value="com_virtuemart"     />
<input type="hidden" name="page" value="'. $page .'"     />
<input type="hidden" name="Itemid" value="'. $sess->getShopItemid() .'"     />
<input type="hidden" name="func" value="cartDelete"     />
<input type="hidden" name="product_id" value="'. $_SESSION['cart'][$i]["product_id"] .'"     />
<input type="hidden" name="description" value="'. $cart[$i]["description"].'"     />
<input type="image" name="delete" title="'. $VM_LANG->_('PHPSHOP_CART_DELETE') .'" 
src="'. VM_THEMEURL .'images/remove_from_cart.png" border="0" 
alt="'. $VM_LANG->_('PHPSHOP_CART_DELETE') .'" align="absmiddle"     />
</form>';}
        else{
    $product_rows[$i]['update_form'] = '<form action="'. $action_url .'" 
method="post" style="display: inline;">
<input type="hidden" name="option" value="com_virtuemart"     />
<input type="text" title="'. $VM_LANG->_('PHPSHOP_CART_UPDATE') .'" 
class="inputbox" size="3" maxlength="4" name="quantity" value="'.$cart[$i]["quantity"].'"     />
<input type="hidden" name="page" value="'. $page .'"     />
<input type="hidden" name="func" value="cartUpdate"     />
<input type="hidden" name="product_id" value="'. $_SESSION['cart'][$i]["product_id"] .'"     />
<input type="hidden" name="prod_id" value="'. $_SESSION['cart'][$i]["product_id"] .'"     />
<input type="hidden" name="Itemid" value="'. $sess->getShopItemid() .'"     />
<input type="hidden" name="description" value="'. stripslashes($cart[$i]["description"]).'"     />
<input type="image" name="update" title="'. $VM_LANG->_('PHPSHOP_CART_UPDATE') .'" 
src="'. VM_THEMEURL .'images/update_quantity_cart.png" border="0"  
alt="'. $VM_LANG->_('PHPSHOP_UPDATE') .'" align="absmiddle"     />
</form>';
        $product_rows[$i]['delete_form'] = '<form action="'.$action_url.'" 
method="post" name="delete" style="display: inline;">
<input type="hidden" name="option" value="com_virtuemart"     />
<input type="hidden" name="page" value="'. $page .'"     />
<input type="hidden" name="Itemid" value="'. $sess->getShopItemid() .'"     />
<input type="hidden" name="func" value="cartDelete"     />
<input type="hidden" name="product_id" value="'. $_SESSION['cart'][$i]["product_id"] .'"     />
<input type="hidden" name="description" value="'. $cart[$i]["description"].'"     />
<input type="image" name="delete" title="'. $VM_LANG->_('PHPSHOP_CART_DELETE') .'" 
src="'. VM_THEMEURL .'images/remove_from_cart.png" border="0" 
alt="'. $VM_LANG->_('PHPSHOP_CART_DELETE') .'" align="absmiddle"     />
</form>';}
 
    //end hack by otx

  
	} // End of for loop through the Cart

This is a little bit ugly, since I had to put some extra linebreaks in order to get this displayed on the site. In the original file the formatting is fine.

 

After this step, if somebody buy something in your shop, he will see the exact amount of purchased items as well as the free items he will get (hopefully :))

Something like this:

 

In the last line we have a product which is a promotional one.

Insert the following string in your language file in order to get displayed the "free" expression after the free items. The above screenshot is taken from a romanian site, and we have "gratis" in this case.

 

'PHPSHOP_CART_FREE' => 'free'

Again, pay attention to the "," at the end of the lines, otherwise your site will go blank. The last one doesn't have to contain this, all the others should.

 

 5. We need to modify the read only basket, too. Open the /administrator/components/com_virtuemart/html/basket.php file.

Search for the line

/* Quantity Box */

around line100, and make like this:

/* Quantity Box */
       
// hack by otx
               
$amount = $cart[$i]["quantity"];
$buy = $ps_product->get_field($cart[$i]["product_id"],"buy");
if ($amount >= $buy){
      $get = $ps_product->get_field($cart[$i]["product_id"],"get");
      $total_quantity = $buy + $get;
      $amount = (int)(($cart[$i]["quantity"] * ($total_quantity)) / $buy);
      $free = $amount - $cart[$i]["quantity"];
}
       
if ($free>0){
       $product_rows[$i]['quantity'] = $cart[$i]["quantity"].' + '. $free.$VM_LANG->_('PHPSHOP_CART_FREE');
}else{
       $product_rows[$i]['quantity'] = $cart[$i]["quantity"] + $free;
}

// end hack by otx
       
/* WEIGHT CALCULATION */

After this modification the customers will have the final confirmation something like this:

6. We need to display the free items in the minicart, too. Open the /administrator/components/com_virtuemart/html/shop.basket_short.php file.

Search for the

$amount += $cart[$i]["quantity"];

line.

Replace width:

// hack by otx
       
$buy = $ps_product->get_field($cart[$i]["product_id"],"buy");
$get = $ps_product->get_field($cart[$i]["product_id"],"get");
$total_quantity = $buy + $get;
if ($get>0) {
     $amount += (int)(($cart[$i]["quantity"] * ($total_quantity)) / $buy);
}
else {
     $amount += $cart[$i]["quantity"];
}
       
// end hack by otx

And the result:

As you can see (on the top right side) the minicart displays the amount of purchased items and the free items.

My client use the minicart only to display the total amount of purchased items and the final price, which seems logical given the fact that usualy there are a lot of items in the cart and we don't have the space to display all of them. 

 

You can download the modified files below, remember that even if you skip the tutorial and simply overwrite your original files, you still need to modify the jos_vm_product table in order to have this work for you.

 

 

To doo:

- make the necessary modifications for the invoice files in order to display the free items (soon)

- modify the flypage and the browse templates to display automatically the amount of free items

- a module to display the promotional items

- modify the minicart to display the free items one by one (like x + y free), not only the final amount

 

This will be done in the next few days, so check back if you are interested.

 

I hope that this will be useful for somebody who works with an implementation of the above hack, and maybe in the future we will have a modul or something like this, in order to not to loose the modifications after an upgrade.

If you have further questions, feel free to ask for help in the forum, I will help as much as I can.

 

 

 

Attachments:
FileDescriptionFile sizeDownloadsLast Modified
Download this file (vm_buyxgety_01.zip)vm_buyxgety_01.zipHack for Vrtuemart 1.1.2. Allow to sell items in "buy x get y" promotions.45 Kb17809/13/08 14:46