UPDATE: I no longer support this hack. It was created for an older version of Virtuemart and for Joomla 1.0.x. Someone has carried it further in it's development here: http://forum.virtuemart.net/index.php?topic=39666.0Custom Attribues Extended 2.1 [Hack]For Virtuemart 1.0.12Allows you to turn your shopping cart into an in-depth shopping experience. This hack enables you to allow different types of form elements to be included in each product.
Features of this Hack:-
user defined set, add, or subtract price field. Great for a donation systems. No brackets needed.
- text box and text area field.
- File upload field. Great for customers to include artwork or needed documents w/ order.
- Javascript Date Pop-up Calendar.
- Single or Multiple Checkboxes or Radio buttons.
- Drop-down select or Multiple Selectbox.
- Prices can be included in almost all fields using brackets ex. [+3.25]
- Make certain attribute fields required using -r ex: (textarea-r)
- [ ] brakets are stripped off in frontend and replaced with curreny sign
-
NEW include image thumbnails with checkboxes,radio buttons, and select box.
-
NEW charge extra for text on an per word, per character (w/ or w/out spaces), or all
-
NEW charge for a file upload
-
NEW add help tips to each field as a hovering tool tip, below title, above field, or below field
- Tested on Joomla 1.0.13 on JSAS w/ VirtueMart 1.0.10
Here is how to implement the Hack: (files included at end)
Backup files first.Step 1:download files:
ps_product_custom_attribute.php &
ps_product_custom_type.php (found below in .zip file)
Add them to folder: <site_root>/administrator/components/com_virtuemart/classes/
Step 2:open the file:
ps_product_attribute.phplocation: <site_root>/administrator/components/com_virtuemart/classes/
find the function around line 354 called:
list_custom_attribute()replace the whole function with the following code:
/**
* Creates form fields for customizable products from the custom attribute format
* @author Ted Barnett (ted@contemplatedesign.com)
* @param unknown_type $product_id
* @return string HTML code containing form fields with Labels
*/
function list_custom_attribute($product_id) {
global $mosConfig_secret, $mosConfig_live_site, $mainframe;
require_once(CLASSPATH.'ps_product_custom_attribute.php');
$product_custom_attributes = new ps_product_custom_attribute;
$db = new ps_DB;
$q = "SELECT product_id, custom_attribute from #__{vm}_product WHERE product_id='$product_id'";
$db->query($q);
$db->next_record();
$custom_attr_list=$db->f("custom_attribute");
if ($custom_attr_list) {
$has_custom_attributes=1;
$fields=explode(";",$custom_attr_list);
$html = "";
foreach($fields as $field)
{
$fieldVars = array();
$fieldVars = $product_custom_attributes->customAttributeVars($field);
$html .= $product_custom_attributes->customAttributeHTML($fieldVars['attr_type'], $fieldVars);
}
}
if ($custom_attr_list) {
return $html;
}
}
Step 3:in the same file find the function around line 391 called
getAdvancedAttributes()replace the whole function with the following code:
/**
* This function returns an array with all "advanced" attributes of the product specified by
* $product_id
*
* @param int $product_id
*/
function getAdvancedAttributes( $product_id ) {
global $ps_product;
if( is_null( $ps_product )) {
$ps_product = new ps_product();
}
$attributes_array = array();
$attributes = $ps_product->get_field( $product_id, 'attribute' );
require_once(CLASSPATH.'ps_product_custom_attribute.php');
$product_custom_attributes = new ps_product_custom_attribute;
if($attributes){ $attributes .= ';'; }
$attributes .= $ps_product->get_field( $product_id, 'custom_attribute' );
// Get each of the attributes into an array
$product_attribute_keys = explode( ";", $attributes );
foreach( $product_attribute_keys as $attribute ) {
if(strpos($attribute, "(")){
$fieldVars = array();
$fieldVars = $product_custom_attributes->customAttributeVars($attribute);
$attribute_name = trim($fieldVars['attr_title']);
require_once(CLASSPATH.'ps_product_custom_type.php');
$attr_type_class = 'ps_product_custom_type_'.$fieldVars['attr_type'];
$product_custom_type = new $attr_type_class;
$attribute_values = $product_custom_type->customTypeSecureValue($fieldVars);
} else {
$attribute_name = substr( $attribute, 0, strpos($attribute, ",") );
$attribute_values = substr( $attribute, strpos($attribute, ",")+1 );
}
$attributes_array[$attribute_name]['name'] = trim($attribute_name);
// Read the different attribute values into an array
$attribute_values = explode(',', $attribute_values );
$operand = '';
$my_mod = 0;
foreach( $attribute_values as $value ) {
$operand = '';
$my_mod = 0;
// Get the price modification for this attribute value
$start = strpos($value, "[");
$finish = strpos($value,"]", $start);
$o = substr_count ($value, "[");
$c = substr_count ($value, "]");
// check to see if we have a bracket (means: a price modifier)
for ($x=0;$x<$c;$x++) {
if (True != is_int($finish) ) { continue; }
$length = $finish-$start;
// We found a pair of brackets (price modifier?)
if ($length > 1) {
$my_mod=substr($value, $start+1, $length-1);
if ($o != $c) { // skip the tests if we don't have to process the string
if ($o < $c ) {
$char = "]";
$offset = $start;
}
else {
$char = "[";
$offset = $finish;
}
$s = substr_count($my_mod, $char);
for ($r=1;$r<$s;$r++) {
$pos = strrpos($my_mod, $char);
$my_mod = substr($my_mod, $pos+1);
}
}
$operand=substr($my_mod,0,1);
$my_mod=substr($my_mod,1);
}
}
if( $start > 0 ) {
$value = substr($value, 0, $start);
}
$value = trim( $value );
$attributes_array[$attribute_name]['values'][$value]['name'] = $value;
$attributes_array[$attribute_name]['values'][$value]['operand'] = $operand;
$attributes_array[$attribute_name]['values'][$value]['adjustment'] = $my_mod;
$operand = '';
$my_mod = 0;
}
}
return $attributes_array;
}
Step 4:in the same file find the following function called:
cartGetAttributes()within that function, replace this code on line 503: (which should now be line 528)
// added for custom fields by denie van kleef
$custom_attribute_list=$db->f("custom_attribute");
$custom_attribute_given = false;
if ($custom_attribute_list) {
$fields=explode(";",$custom_attribute_list);
$description=$d["description"];
foreach($fields as $field)
{
$pagevar=str_replace(" ","_",$field);
if (!empty($d[$pagevar])) {
$custom_attribute_given = true;
}
if ($description!='') {
$description.="; ";
}
$description.=$field.":";
$description .= empty($d[$pagevar]) ? '' : $d[$pagevar];
}
rtrim($description);
$d["description"] = $description;
// END add for custom fields by denie van kleef
}
with this code:
// added for custom fields by ted barnett
global $charge;
$charge = array();
$custom_attributes = array();
$custom_attribute_list=$db->f("custom_attribute");
$description=$d["description"];
$custom_attribute_given = false;
if ($custom_attribute_list) {
require_once(CLASSPATH.'ps_product_custom_attribute.php');
$product_custom_attributes = new ps_product_custom_attribute;
$fields=explode(";",$custom_attribute_list);
foreach($fields as $field) {
$fieldVars = array();
$fieldVars = $product_custom_attributes->customAttributeVars($field);
$pagevar = trim($fieldVars['attr_pagevar']);
if ($fieldVars['attr_type'] == 'line') {
continue;
}
if ($fieldVars['attr_req'] && empty($d[$pagevar])) {
$custom_attribute_given = false;
break;
}
if (!empty($d[$pagevar])) {
$custom_attribute_given = true;
}
if ($description!='') {
$description.="; ";
}
$description.=trim($fieldVars['attr_title']).":";
// strip accidental currency symbols
// add aditional as needed
$d[$pagevar]=str_replace("$","",$d[$pagevar]); // Dollar
$d[$pagevar]=str_replace("£","",$d[$pagevar]); // Pounds
$d[$pagevar]=str_replace("¥","",$d[$pagevar]); // Yen
$d[$pagevar]=str_replace("€","",$d[$pagevar]); // Euro
require_once(CLASSPATH.'ps_product_custom_type.php');
$attr_type_class = 'ps_product_custom_type_'.$fieldVars['attr_type'];
$product_custom_type = new $attr_type_class;
$d[$pagevar] = $product_custom_type->customTypeProcess($d[$pagevar],$fieldVars);
$description .= empty($d[$pagevar]) ? '' : $d[$pagevar];
}
}
rtrim($description);
$d["description"] = $description;
// END add for custom fields by ted barnett
SAVE THE FILE!
Step 5:open the file:
ps_product.phplocation: <site_root>/administrator/components/com_virtuemart/classes/
find the function
get_adjusted_attribute_price() and go down to about line #1648.
replace the following line of code:
// if we've been given a description to deal with, get the adjusted price
if ($description != '') { // description is safe to use at this point cause it's set to ''
require_once(CLASSPATH.'ps_product_attribute.php');
$product_attributes = ps_product_attribute::getAdvancedAttributes($product_id);
$attribute_keys = explode( ";", $description );
for($i=0; $i < sizeof($attribute_keys); $i++ ) {
$temp_desc = $attribute_keys[$i];
$temp_desc = trim( $temp_desc );
// Get the key name (e.g. "Color" )
$this_key = substr( $temp_desc, 0, strpos($temp_desc, ":") );
$this_value = substr( $temp_desc, strpos($temp_desc, ":")+1 );
if( in_array( $this_key, $custom_attribute_fields )) {
if( @$custom_attribute_fields_check[$this_key] == md5( $mosConfig_secret.$this_key )) {
// the passed value is valid, don't use it for calculating prices
continue;
}
}
$this_value=str_replace("_"," ",$this_value);
if( isset( $product_attributes[$this_key]['values'][$this_value] )) {
$modifier = $product_attributes[$this_key]['values'][$this_value]['adjustment'];
$operand = $product_attributes[$this_key]['values'][$this_value]['operand'];
// if we have a number, allow the adjustment
if (true == is_numeric($modifier) ) {
// Now add or sub the modifier on
if ($operand=="+") {
$adjustment += $modifier;
}
else if ($operand=="-") {
$adjustment -= $modifier;
}
else if ($operand=='=') {
// NOTE: the +=, so if we have 2 sets they get added
// this could be moded to say, if we have a set_price, then
// calc the diff from the base price and start from there if we encounter
// another set price... just a thought.
$setprice += $modifier;
$set_price = true;
}
}
} else {
continue;
}
}
}
with this:
// if we've been given a description to deal with, get the adjusted price
if ($description != '') { // description is safe to use at this point cause it's set to ''
require_once(CLASSPATH.'ps_product_attribute.php');
$product_attributes = ps_product_attribute::getAdvancedAttributes($product_id);
$attribute_keys = explode( ";", $description );
for($i=0; $i < sizeof($attribute_keys); $i++ ) {
$temp_desc = $attribute_keys[$i];
$temp_desc_keys = explode( ",", $temp_desc );
foreach ($temp_desc_keys as $temp_desc){
$temp_desc = trim( $temp_desc );
// Get the key name (e.g. "Color" )
if(strpos($temp_desc, ":")){
$this_key = substr( $temp_desc, 0, strpos($temp_desc, ":") );
$this_value = substr( $temp_desc, strpos($temp_desc, ":")+1 );
} else {
$this_key = $this_key;
$this_value = $temp_desc;
}
if( in_array( $this_key, $custom_attribute_fields )) {
if( @$custom_attribute_fields_check[$this_key] == md5( $mosConfig_secret.$this_key )) {
// the passed value is valid, don't use it for calculating prices
continue;
}
}
$this_value=str_replace("_"," ",$this_value);
if( !isset( $product_attributes[$this_key]['values'][$this_value] )) {
$this_true_value = $this_value;
$this_value = 'vm_charge';
}
if( isset( $product_attributes[$this_key]['values'][$this_value] )) {
$modifier = $product_attributes[$this_key]['values'][$this_value]['adjustment'];
$operand = $product_attributes[$this_key]['values'][$this_value]['operand'];
if($this_value == 'vm_charge'){
$this_value = this_true_value;
}
// if we have a number, allow the adjustment
if (true == is_numeric($modifier) ) {
// Now add or sub the modifier on
if ($operand=="+") {
$adjustment += $modifier;
}
else if ($operand=="-") {
$adjustment -= $modifier;
}
else if ($operand=='=') {
// NOTE: the +=, so if we have 2 sets they get added
// this could be moded to say, if we have a set_price, then
// calc the diff from the base price and start from there if we encounter
// another set price... just a thought.
$setprice += $modifier;
$set_price = true;
}
}
} else {
continue;
}
}
}
}
Step 6:in the same file find the following function called:
getDescriptionWithTax()within that function, replace this code on line 1789: (which should now be line 1811)
foreach( $attribute_keys as $temp_desc ) {
$finish = strpos($temp_desc,"]");
$temp_desc = trim( $temp_desc );
// Get the key name (e.g. "Color" )
$this_key = substr( $temp_desc, 0, strpos($temp_desc, ":") );
$this_value = substr( $temp_desc, strpos($temp_desc, ":")+1 );
if( in_array( $this_key, $custom_attribute_fields )) {
if( @$custom_attribute_fields_check[$this_key] == md5( $mosConfig_secret.$this_key )) {
// the passed value is valid, don't use it for calculating prices
continue;
}
}
$this_value = str_replace("_"," ",$this_value);
if( isset( $product_attributes[$this_key]['values'][$this_value] )) {
$modifier = $product_attributes[$this_key]['values'][$this_value]['adjustment'];
$operand = $product_attributes[$this_key]['values'][$this_value]['operand'];
$value_notax = $modifier;
if( abs($value_notax) >0 ) {
$value_taxed = $value_notax * ($my_taxrate+1);
$temp_desc_new = str_replace( $operand.$modifier, $operand.' '.$CURRENCY_DISPLAY->getFullValue( $value_taxed ), $temp_desc );
$description = str_replace( $this_key.':'.$this_value,
$this_key.':'.$this_value.' ('.$operand.' '.$CURRENCY_DISPLAY->getFullValue( $value_taxed ).')',
$description);
}
$temp_desc = substr($temp_desc, $finish+1);
}
}
with this:
foreach( $attribute_keys as $temp_desc ) {
$temp_desc_keys = explode( ",", $temp_desc );
foreach ($temp_desc_keys as $temp_desc){
$finish = strpos($temp_desc,"]");
$temp_desc = trim( $temp_desc );
// Get the key name (e.g. "Color" )
if(strpos($temp_desc, ":")){
$this_key = substr( $temp_desc, 0, strpos($temp_desc, ":") );
$this_value = substr( $temp_desc, strpos($temp_desc, ":")+1 );
} else {
//$temp_desc .= $this_key.':'.$temp_desc;
$this_key = $this_key;
$this_value = $temp_desc;
}
if( in_array( $this_key, $custom_attribute_fields )) {
if( @$custom_attribute_fields_check[$this_key] == md5( $mosConfig_secret.$this_key )) {
// the passed value is valid, don't use it for calculating prices
continue;
}
}
$this_value = str_replace("_"," ",$this_value);
if( !isset( $product_attributes[$this_key]['values'][$this_value] )) {
$this_true_value = $this_value;
$this_value = 'vm_charge';
}
if( isset( $product_attributes[$this_key]['values'][$this_value] )) {
$modifier = $product_attributes[$this_key]['values'][$this_value]['adjustment'];
$operand = $product_attributes[$this_key]['values'][$this_value]['operand'];
if($this_value == 'vm_charge'){
$this_value = $this_true_value;
}
$value_notax = $modifier;
if( abs($value_notax) >0 ) {
$value_taxed = $value_notax * ($my_taxrate+1);
$temp_desc_new = str_replace( $operand.$modifier, $operand.' '.$CURRENCY_DISPLAY->getFullValue( $value_taxed ), $temp_desc );
$description = str_replace( $this_value,
$this_value.' ('.$operand.' '.$CURRENCY_DISPLAY->getFullValue( $value_taxed ).')',
$description);
}
$temp_desc = substr($temp_desc, $finish+1);
}
}
}
SAVE THE FILE!
ALL DONE! Enjoy. All new and edited files are found in the .zip file below.
The next post will explain the syntax.
-barnett
--UPDATED -- 9-18-07
-- rehashed how to handle added charges to apply tax
-- Updated to Joomla 1.0.13 & VM 1.0.12
-- UPDATED -- 5-17-07
- fixed files in step 1
- fixed lines in step 5
- fixed multiple select box image thumbnail problem
- fixed blank results for non-charge radio, checkbox, select, & multiple items
- added
NEW attribute type (number)... see syntax
-- UPDATED -- 5-19-07
- added Max file Size & Min File Size options to file upload... see syntax
-- UPDATED -- 5-23-07
- fixed "File Type Error" for file upload
- fixed File Upload always defaulting to required
- fixed Default Checked Radio & Check boxes for Safari (haven't tested)
-- UPDATED -- 5-23-07
- fixed multiple attributes of same type not being calculated in IE bug
-- UPDATED -- 5-23-07
- fixed file upload to play nice w/ multiple file uploads
- print file name to screen when there is an error
-- UPDATED -- 5-24-07
- fixed IE7 bug: image path was appearing in drop-down select
- added "-d" option to text and textarea charge types to show cost break down in cart... see syntax
[attachment cleanup by admin]