diff --git a/documents/ShippingByRules_Framework_API.txt b/documents/ShippingByRules_Framework_API.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3d4c999ff28e5d0787b773ebd71481c48f8e2d40
--- /dev/null
+++ b/documents/ShippingByRules_Framework_API.txt
@@ -0,0 +1,43 @@
+RulesShippingFramework:
+=======================
+
+
+API functions:
+~~~~~~~~~~~~~~
+  -) setup()                                                  // Used in the plugin's constructor
+  -) registerCallback($callback, $func)
+
+  -) parseRuleSyntax($rulestring, $countries, $tax)
+  -) checkConditions ($cart, $method, $cart_prices)
+  -) getCosts($cart, $method, $cart_prices, $variables=null)  // The central function to calculate the shipping cost
+  -) getRuleName($methodid)
+  -) getRuleVariables($methodid)
+  
+  -) filterProducts($products, $conditions)                   // Used in the Rule's evaluateScoping
+
+
+E-commerce specific functions (to be overridden in child classes!):
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  -) printWarning($message)
+  -) __($string)
+  -) getCustomFunctions()
+  -) addPluginCartValues($cart, $products, $method, $cart_prices, &$values)
+
+  -) getOrderArticles($cart, $products)
+  -) getOrderProducts ($cart, $products)
+  -) getOrderDimensions ($cart, $products, $length_dimension)
+  -) getOrderWeights ($cart, $products, $weight_unit)
+  -) getOrderListProperties ($cart, $products)
+  -) getOrderAddress ($cart)
+  -) getOrderPrices ($cart, $products, $cart_prices)
+
+  -) filterProducts($products, $conditions)                   // Used in the Rule's evaluateScoping
+
+
+
+Callback functions:
+~~~~~~~~~~~~~~~~~~~
+  -) translate($message)
+  -) initRule($framework, $rulestring, $countries, $tax)
+  -) addCustomCartValues ($cart, $products, $cart_prices, &$values)
+
diff --git a/library/rules_shipping_framework.php b/library/rules_shipping_framework.php
new file mode 100644
index 0000000000000000000000000000000000000000..271664f075ffef9e80f2ecfa4f09a48b2e655522
--- /dev/null
+++ b/library/rules_shipping_framework.php
@@ -0,0 +1,1203 @@
+<?php
+
+defined ('_JEXEC') or die('Restricted access');
+
+/**
+ * Shipment plugin for general, rules-based shipments, like regular postal services with complex shipping cost structures
+ *
+ * @package VirtueMart
+ * @subpackage Plugins - shipment
+ * @copyright Copyright (C) 2004-2012 VirtueMart Team - All rights reserved.
+ * @copyright Copyright (C) 2013 Reinhold Kainhofer, reinhold@kainhofer.com
+ * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL, see LICENSE.txt
+ * VirtueMart is free software. This version may have been modified pursuant
+ * to the GNU General Public License, and as distributed it includes or
+ * is derivative of works licensed under the GNU General Public License or
+ * other free or open source software licenses.
+ *
+ * @author Reinhold Kainhofer, based on the weight_countries shipping plugin by Valerie Isaksen
+ *
+ */
+// Only declare the class once...
+if (class_exists ('RulesShippingFramework')) {
+	return;
+}
+
+
+function print_array($obj) {
+	$res = "";
+	if (is_array($obj)) {
+		$res .= "array(";
+		$sep = "";
+		foreach ($obj as $e) {
+			$res .= $sep . print_array($e);
+			$sep = ", ";
+		}
+		$res .= ")";
+	} elseif (is_string($obj)) {
+		$res .= "\"$obj\"";
+	} else {
+		$res .= (string)$obj;
+	}
+	return $res;
+}
+
+function is_equal($a, $b) {
+	if (is_array($a) && is_array($b)) {
+		return !array_diff($a, $b) && !array_diff($b, $a);
+	} elseif (is_string($a) && is_string($b)) {
+		return strcmp($a,$b) == 0;
+	} else {
+		return $a == $b;
+	}
+}
+
+class RulesShippingFramework {
+	static $_version = "0.1";
+	protected $_callbacks = array();
+	// Store the parsed and possibly evaluated rules for each method (method ID is used as key)
+	protected $rules = array();
+	protected $match = array();
+	protected $last_cartvals = array();
+	protected $last_rulename = '';
+	var $custom_functions = array ();
+	
+	function __construct() {
+// 		$this->registerCallback('initRule',				array($this, 'createMethodRule'));
+// 		$this->registerCallback('addCustomCartValues',	array($this, 'addCustomCartValues'));
+	}
+	
+	
+	
+	/* Callback handling */
+	
+	/**
+	 * Register a callback for one of the known callback hooks. 
+	 * Valid callbacks are (together with their arguments):
+	 *   - translate($string)
+	 *  @param string $callback 
+	 *     The name of the callback hook (string)
+	 *  @param function $func 
+	 *     The function (usually a member of the plugin object) for the callback
+	 *  @return none
+	 */
+	public function registerCallback($callback, $func) {
+		$this->callbacks[$callback] = $func;
+	}
+	
+	public function __($string) {
+		if (isset($this->callbacks["translate"])) {
+			return $this->callbacks["translate"]($string);
+		} else {
+			return $string;
+		}
+	}
+
+	function getCustomFunctions() {
+		// Let other plugins add custom functions! 
+		// This function is expected to return an array of the form:
+		//   array ('functionname1' => 'function-to-be-called',
+		//          'functionname2' => array($classobject, 'memberfunc')),
+		//          ...);
+		return array ();
+	}
+	
+	/**
+	 * Function printWarning: This needs to be overwritten in derived classes!
+	 */
+	public function printWarning($message) {
+		// TODO!
+	}
+	
+	function setup() {
+		$custfuncdefs = $this->getCustomFunctions();
+		// Loop through the return values of all plugins:
+		foreach ($custfuncdefs as $custfuncs) {
+			if (empty($custfuncs))
+				continue;
+			if (!is_array($custfuncs)) {
+				$this->printWarning(JText::sprintf('VMSHIPMENT_RULES_CUSTOMFUNCTIONS_NOARRAY'));
+			}
+			// Now loop through all custom function definitions of this plugin
+			// If a function was registered before, print a warning and use the first definition
+			foreach ($custfuncs as $fname => $func) {
+				if (isset($this->custom_functions[$fname])) {
+					$this->printWarning(JText::sprintf('VMSHIPMENT_RULES_CUSTOMFUNCTIONS_ALREADY_DEFINED', $fname));
+				} else {
+					vmDebug("Defining custom function $fname");
+					$this->custom_functions[strtolower($fname)] = $func;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Functions to calculate the cart variables:
+	 *   - getOrderArticles($cart, $products)
+	 *   - getOrderProducts
+	 *   - getOrderDimensions
+	 */
+	/** Functions to calculate all the different variables for the given cart and given (sub)set of products in the cart */
+	protected function getOrderArticles ($cart, $products) {
+		return 0;
+	}
+
+	protected function getOrderProducts ($cart, $products) {
+		return count($products);
+	}
+
+	protected function getOrderDimensions ($cart, $products, $length_dimension) {
+		return array();
+	}
+	
+	protected function getOrderWeights ($cart, $products, $weight_unit) {
+		return array();
+	}
+	
+	protected function getOrderListProperties ($cart, $products) {
+		return array();
+	}
+	
+	protected function getOrderAddress ($cart) {
+		return array();
+	}
+	
+	protected function getOrderPrices ($cart, $products, $cart_prices) {
+		return array();
+	}
+
+	/** Allow child classes to add additional variables for the rules or modify existing one
+	 */
+	protected function addCustomCartValues ($cart, $products, $cart_prices, &$values) {
+		if (isset($this->callbacks['addCustomCartValues'])) {
+			return $this->callbacks['addCustomCartValues']($cart, $products, $cart_prices, $values);
+		}
+	}
+	protected function addPluginCartValues($cart, $products, $method, $cart_prices, &$values) {
+	}
+	
+	public function getCartValues ($cart, $products, $method, $cart_prices) {
+		$cartvals = array_merge (
+			array(
+				'articles'=>$this->getOrderArticles($cart, $products),
+				'products'=>$this->getOrderProducts($cart, $products),
+			),
+			// Add the prices, optionally calculated from the products subset of the cart
+			$this->getOrderPrices ($cart, $products, $cart_prices),
+			// Add 'skus', 'categories', 'vendors' variables:
+			$this->getOrderListProperties ($cart, $products),
+			// Add country / state variables:
+			$this->getOrderAddress ($cart),
+			// Add Total/Min/Max weight and dimension variables:
+			$this->getOrderWeights ($cart, $products, $method->weight_unit),
+			$this->getOrderDimensions ($cart, $products, $method->length_unit)
+		);
+		// Let child classes update the $cartvals array, or add new variables
+		$this->addCustomCartValues($cart, $products, $cart_prices, $cartvals);
+		// Let custom plugins update the $cartvals array or add new variables
+		$this->addPluginCartValues($cart, $products, $method, $cart_prices, $cartvals);
+
+		return $cartvals;
+	}
+
+	/** This function evaluates all rules, one after the other until it finds a matching rule that
+	 *  defines shipping costs (or uses NoShipping). If a modifier or definition is encountered,
+	 *  its effect is stored, but the loop continues */
+	protected function evaluateMethodRules ($cart, $method, $cart_prices) {
+		$id = $method->virtuemart_shipmentmethod_id; // TODO: Generalize to other ecommerce systems!
+		// $method->match will cache the matched rule and the modifiers
+		if (isset($this->match[$id])) {
+			return $this->match[$id];
+		} else {
+			// Evaluate all rules and find the matching ones (including modifiers and definitions!)
+			$cartvals = $this->getCartValues ($cart, $cart->products, $method, $cart_prices);
+			$result = array("rule"=>Null, "rule_name"=>"", "modifiers_add"=>array(), "modifiers_multiply"=>array(), "cartvals"=>$cartvals);
+			// Pass a callback function to the rules to obtain the cartvals for a subset of the products
+			$this_class = $this;
+			$cartvals_callback = function ($products) use ($this_class, $cart, $method, $cart_prices) {
+				return $this_class->getCartValues ($cart, $products, $method, NULL);
+			};
+			foreach ($this->rules[$id] as $r) {
+				if ($r->matches($cartvals, $cart->products, $cartvals_callback)) {
+					$rtype = $r->getType();
+					switch ($rtype) {
+						case 'shipping': 
+						case 'shippingwithtax':
+						case 'noshipping': 
+								$result["rule"] = $r;
+								$result["rule_name"] = $r->getRuleName();
+								break;
+						case 'modifiers_add':
+						case 'modifiers_multiply':
+								$result[$rtype][] = $r;
+								break;
+						case 'definition': // A definition updates the $cartvals, but has no other effects
+								$cartvals[strtolower($r->getRuleName())] = $r->getValue();
+								break;
+						default:
+								$this->helper->printWarning(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_TYPE', $r->getType(), $r->rulestring));
+								break;
+					}
+				}
+				if (!is_null($result["rule"])) {
+					$this->match[$id] = $result;
+					return $result; // <- This also breaks out of the foreach loop!
+				}
+			}
+		}
+		// None of the rules matched, so return NULL, but keep the evaluated results;
+		$this->match[$id] = $result;
+		return NULL;
+	}
+
+	/**
+	 * @param \VirtueMartCart $cart
+	 * @param int             $method
+	 * @param array           $cart_prices
+	 * @return bool
+	 */
+	public function checkConditions ($cart, $method, $cart_prices) {
+		if (!isset($this->rules[$method->virtuemart_shipmentmethod_id])) 
+			$this->parseMethodRules($method);
+		$match = $this->evaluateMethodRules ($cart, $method, $cart_prices);
+		if ($match && !is_null ($match['rule'])) {
+			$method->rule_name = $match["rule_name"];
+			// If NoShipping is set, this method should NOT offer any shipping at all, so return FALSE, otherwise TRUE
+			// If the rule has a name, print it as warning (otherwise don't print anything)
+			if ($match['rule']->isNoShipping()) {
+				if (!empty($method->rule_name))
+					$this->helper->pringWarning(JText::sprintf('VMSHIPMENT_RULES_NOSHIPPING_MESSAGE', $method->rule_name));
+				vmdebug('checkConditions '.$method->shipment_name.' indicates NoShipping for this method, specified by rule "'.$method->rule_name.'" ('.$match['rule']->rulestring.').');
+				return FALSE;
+			} else {
+				return TRUE;
+			}
+		}
+		vmdebug('checkConditions '.$method->shipment_name.' does not fulfill all conditions, no rule matches');
+		return FALSE;
+	}
+
+	/**
+	 * @param VirtueMartCart $cart
+	 * @param                $method
+	 * @param                $cart_prices
+	 * @return int
+	 */
+	function getCosts (VirtueMartCart $cart, $method, $cart_prices) {
+		if (!isset($this->rules[$method->virtuemart_shipmentmethod_id])) 
+			$this->parseMethodRules($method);
+		$match = $this->evaluateMethodRules ($cart, $method, $cart_prices);
+		if ($match) {
+			$r = $match["rule"];
+			vmdebug('Rule ' . $match["rule_name"] . ' ('.$r->rulestring.') matched.');
+			$method->tax_id = $r->tax_id;
+			// TODO: Shall we include the name of the modifiers, too?
+			$method->rule_name = $match["rule_name"];
+			// Final shipping costs are calculated as:
+			//   Shipping*ExtraShippingMultiplier + ExtraShippingCharge
+			// with possibly multiple modifiers
+			$method->cost = $r->getShippingCosts();
+			foreach ($match['modifiers_multiply'] as $modifier) {
+				$method->cost *= $modifier->getValue();
+			}
+			foreach ($match['modifiers_add'] as $modifier) {
+				$method->cost += $modifier->getValue();
+			}
+			$method->includes_tax = $r->includes_tax;
+			return $method->cost;
+		}
+		
+		vmdebug('getCosts '.$method->name.' does not return shipping costs');
+		return 0;
+	}
+	
+	public function getRuleName($methodid) {
+		if (isset($this->match[$methodid])) {
+			return $this->match[$methodid]["rule_name"];
+		} else {
+			return '';
+		}
+	}
+
+	public function getRuleVariables($methodid) {
+		if (isset($this->match[$methodid])) {
+			return $this->match[$methodid]["cartvals"];
+		} else {
+			return array();
+		}
+	}
+
+	protected function createMethodRule ($r, $countries, $tax) {
+		if (isset($this->callbacks['initRule'])) {
+			return $this->callbacks['initRule']($this, $r, $countries, $tax);
+		} else {
+			return new ShippingRule($this, $r, $countries, $tax);
+		}
+	}
+
+	// Parse the rule and append all rules to the rule set of the current shipment method (country/tax are already included in the rule itself!)
+	private function parseMethodRule ($rulestring, $countries, $tax, &$method) {
+		foreach ($this->parseRuleSyntax($rulestring, $countries, $tax) as $r) {
+			$this->rules[$method->virtuemart_shipmentmethod_id][] = $r;
+		}
+	}
+	
+	public function parseRuleSyntax($rulestring, $countries, $tax) {
+		$result = array();
+		$rules1 = preg_split("/(\r\n|\n|\r)/", $rulestring);
+		foreach ($rules1 as $r) {
+			// Ignore empty lines
+			if (empty($r)) continue;
+			$result[] = $this->createMethodRule ($r, $countries, $tax);
+		}
+		return $result;
+	}
+	
+	protected function parseMethodRules (&$method) {
+		if (!isset($this->rules[$method->virtuemart_shipmentmethod_id])) 
+			$this->rules[$method->virtuemart_shipmentmethod_id] = array();
+		$this->parseMethodRule ($method->rules1, $method->countries1, $method->tax_id1, $method);
+		$this->parseMethodRule ($method->rules2, $method->countries2, $method->tax_id2, $method);
+		$this->parseMethodRule ($method->rules3, $method->countries3, $method->tax_id3, $method);
+		$this->parseMethodRule ($method->rules4, $method->countries4, $method->tax_id4, $method);
+		$this->parseMethodRule ($method->rules5, $method->countries5, $method->tax_id5, $method);
+		$this->parseMethodRule ($method->rules6, $method->countries6, $method->tax_id6, $method);
+		$this->parseMethodRule ($method->rules7, $method->countries7, $method->tax_id7, $method);
+		$this->parseMethodRule ($method->rules8, $method->countries8, $method->tax_id8, $method);
+	}
+
+	/** Filter the given array of products and return only those that belong to the categories, manufacturers, 
+	 *  vendors or products given in the $filter_conditions. The $filter_conditions is an array of the form:
+	 *     array( 'skus'=>array(....), 'categories'=>array(1,2,3,42), 'manufacturers'=>array(77,78,83), 'vendors'=>array(1,2))
+	 *  Notice that giving an empty array for any of the keys means "no restriction" and is exactly the same 
+	 *  as leaving out the enty altogether
+	 */
+	public function filterProducts($products, $filter_conditions) {
+		return array();
+	}
+}
+
+class ShippingRule {
+	var $framework = Null;
+	var $rulestring = '';
+	var $name = '';
+	var $ruletype = '';
+	var $evaluated = False;
+	var $match = False;
+	var $value = Null;
+	
+	var $shipping = 0;
+	var $conditions = array();
+	var $countries = array();
+	var $tax_id = 0;
+	var $includes_tax = 0;
+	
+	function __construct ($framework, $rule, $countries, $tax_id) {
+		if (is_array($countries)) {
+			$this->countries = $countries;
+		} elseif (!empty($countries)) {
+			$this->countries[0] = $countries;
+		}
+		$this->tax_id = $tax_id;
+		$this->rulestring = $rule;
+		$this->parseRule($rule);
+		$this->framework=$framework;
+	}
+	
+	protected function parseRule($rule) {
+		$ruleparts=explode(';', $rule);
+		foreach ($ruleparts as $p) {
+			$this->parseRulePart($p);
+		}
+	}
+	
+	protected function handleAssignment ($var, $value, $rulepart) {
+		switch (strtolower($var)) {
+			case 'name':            $this->name = $value; break;
+			case 'shipping':        $this->shipping = $value; $this->includes_tax = False; $this->ruletype='shipping'; break;
+			case 'shippingwithtax': $this->shipping = $value; $this->includes_tax = True; $this->ruletype='shipping'; break;
+			case 'variable':        // Variable=... is the same as Definition=...
+			case 'definition':      $this->name = strtolower($value); $this->ruletype = 'definition'; break;
+			case 'value':           $this->shipping = $value; $this->ruletype = 'definition'; break; // definition values are also stored in the shipping member!
+			case 'extrashippingcharge': $this->shipping = $value; $this->ruletype = 'modifiers_add'; break; // modifiers are also stored in the shipping member!
+			case 'extrashippingmultiplier': $this->shipping = $value; $this->ruletype = 'modifiers_multiply'; break; // modifiers are also stored in the shipping member!
+			case 'comment':         break; // Completely ignore all comments!
+			case 'condition':       $this->conditions[] = $value; break;
+			default:                JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_VARIABLE', $var, $rulepart), 'error');
+		}
+	}
+	
+	protected function tokenize_expression ($expression) {
+		// First, extract all strings, delimited by quotes, then all text operators 
+		// (OR, AND, in; but make sure we don't capture parts of words, so we need to 
+		// use lookbehind/lookahead patterns to exclude OR following another letter 
+		// or followed by another letter) and then all arithmetic operators
+		$re = '/\s*("[^"]*"|\'[^\']*\'|<=|=>|>=|=<|<>|!=|==|<|=|>)\s*/i';
+		$atoms = preg_split($re, $expression, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
+		// JFactory::getApplication()->enqueueMessage("TOKENIZING '$expression' returns: <pre>".print_r($atoms,1)."</pre>", 'error');
+		return $atoms;
+	}
+	
+	protected function parseRulePart($rulepart) {
+		/* In the basic version, we only split at the comparison operators and assume each term on the LHS and RHS is one variable or constant */
+		/* In the advanced version, all conditions and costs can be given as a full mathematical expression */
+		/* Both versions create an expression tree, which can be easily evaluated in evaluateTerm */
+		$rulepart = trim($rulepart);
+		if (empty($rulepart)) return;
+
+		
+		// Special-case the name assignment, where we don't want to interpret the value as an arithmetic expression!
+		if (preg_match('/^\s*(name|variable|definition)\s*=\s*(["\']?)(.*)\2\s*$/i', $rulepart, $matches)) {
+			$this->handleAssignment ($matches[1], $matches[3], $rulepart);
+			return;
+		}
+
+		// Split at all operators:
+		$atoms = $this->tokenize_expression ($rulepart);
+		
+		/* Starting from here, the advanced plugin is different! */
+		$operators = array('<', '<=', '=', '>', '>=', '=>', '=<', '<>', '!=', '==');
+		if (count($atoms)==1) {
+			$this->shipping = $this->parseShippingTerm($atoms[0]);
+			$this->ruletype = 'shipping';
+		} elseif ($atoms[1]=='=') {
+			$this->handleAssignment ($atoms[0], $atoms[2], $rulepart);
+		} else {
+			// Conditions, need at least three atoms!
+			while (count($atoms)>1) {
+				if (in_array ($atoms[1], $operators)) {
+					$this->conditions[] = array($atoms[1], $this->parseShippingTerm($atoms[0]), $this->parseShippingTerm($atoms[2]));
+					array_shift($atoms);
+					array_shift($atoms);
+				} else {
+					JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_OPERATOR', $atoms[1], $rulepart), 'error');
+					$atoms = array();
+				}
+			}
+		}
+	}
+
+	protected function parseShippingTerm($expr) {
+		/* In the advanced version, shipping cost can be given as a full mathematical expression */
+		// If the shipping term starts with a double quote, it is a string, so don't turn it into lowercase.
+		// All other expressions need to be turned into lowercase, because variable names are case-insensitive!
+		if (substr($expr, 0, 1) === '"') {
+			return $expr;
+		} else {
+			return strtolower($expr);
+		}
+	}
+	
+	protected function evaluateComparison ($terms, $vals) {
+		while (count($terms)>2) {
+			$res = false;
+			switch ($terms[1]) {
+				case '<':  $res = ($terms[0] < $terms[2]);  break;
+				case '<=':
+				case '=<': $res = ($terms[0] <= $terms[2]); break;
+				case '==': $res = is_equal($terms[0], $terms[2]); break;
+				case '!=':
+				case '<>': $res = ($terms[0] != $terms[2]); break;
+				case '>=':
+				case '=>': $res = ($terms[0] >= $terms[2]); break;
+				case '>':  $res = ($terms[0] >  $terms[2]);  break;
+				case '~':
+					$l=min(strlen($terms[0]), strlen($terms[2]));
+					$res = (strncmp ($terms[0], $terms[2], $l) == 0);
+					break;
+				default:
+					JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_OPERATOR', $terms[1], $this->rulestring), 'error');
+					$res = false;
+			}
+
+			if ($res==false) return false;
+			// Remove the first operand and the operator from the comparison:
+			array_shift($terms);
+			array_shift($terms);
+		}
+		if (count($terms)>1) {
+			// We do not have the correct number of terms for chained comparisons, i.e. two terms leftover instead of one!
+			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_ERROR', $this->rulestring), 'error');
+			return false;
+		}
+		// All conditions were fulfilled, so we can return true
+		return true;
+	}
+	
+	protected function evaluateListFunction ($function, $args) {
+		# First make sure that all arguments are actually lists:
+		$allarrays = True;
+		foreach ($args as $a) {
+			$allarrays = $allarrays && is_array($a);
+		}
+		if (!$allarrays) {
+			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_LISTFUNCTION_ARGS', $function, $this->rulestring), 'error');
+			return false;
+			
+		}
+		switch ($function) {
+			case "length":		return count($args[0]); break;
+			case "union": 
+			case "join":		return call_user_func_array( "array_merge" , $args); break;
+			case "complement":	return call_user_func_array( "array_diff" , $args); break;
+			case "intersection":	return call_user_func_array( "array_intersect" , $args); break;
+			case "issubset":	# Remove all of superset's elements to see if anything else is left: 
+						return !array_diff($args[0], $args[1]); break;
+			case "contains":	# Remove all of superset's elements to see if anything else is left: 
+						# Notice the different argument order compared to issubset!
+						return !array_diff($args[1], $args[0]); break;
+			case "list_equal":	return array_unique($args[0])==array_unique($args[1]); break;
+			default: 
+				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_LISTFUNCTION_UNKNOWN', $function, $this->rulestring), 'error');
+				return false;
+		}
+	}
+	
+	protected function evaluateListContainmentFunction ($function, $args) {
+		# First make sure that the first argument is a list:
+		if (!is_array($args[0])) {
+			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_LISTFUNCTION_CONTAIN_ARGS', $function, $this->rulestring), 'error');
+			return false;
+		}
+		// Extract the array from the args, the $args varialbe will now only contain the elements to be checked:
+		$array = array_shift($args);
+		switch ($function) {
+			case "contains_any": // return true if one of the $args is in the $array
+					foreach ($args as $a) { 
+						if (in_array($a, $array)) 
+							return true; 
+					}
+					return false;
+			
+			case "contains_all": // return false if one of the $args is NOT in the $array
+					foreach ($args as $a) { 
+						if (!in_array($a, $array)) 
+							return false; 
+					}
+					return true;
+			case "contains_only": // return false if one of the $array elements is NOT in $args
+					foreach ($array as $a) {
+						if (!in_array($a, $args))
+							return false;
+					}
+					return true;
+			case "contains_none": // return false if one of the $args IS in the $array
+					foreach ($args as $a) {
+						if (in_array($a, $array))
+							return false;
+					}
+					return true;
+			default: 
+				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_LISTFUNCTION_UNKNOWN', $function, $this->rulestring), 'error');
+				return false;
+		}
+	}
+	
+	/** Evaluate the given expression $expr only for the products that match the filter given by the scoping 
+	 * function and the corresponding conditions */
+	protected function evaluateScoping($expr, $scoping, $conditionvals, $vals, $products, $cartvals_callback) {
+		if (count($conditionvals)<1)
+			return $this->evaluateTerm($expr, $vals, $products, $cartvals_callback);
+
+		$filterkeys = array( 
+			"evaluate_for_categories" =>    'categories',
+			"evaluate_for_products" =>      'products',
+			"evaluate_for_vendors" =>       'vendors',
+			"evaluate_for_manufacturers" => 'manufacturers'
+		);
+		
+		$conditions = array();
+		if (isset($filterkeys[$scoping])) 
+			$conditions[$filterkeys[$scoping]] = $conditionvals;
+
+		// Pass the conditions to the parent plugin class to filter the current list of products:
+		$filteredproducts = $this->framework->filterProducts($products, $conditions);
+		// We have been handed a callback function to calculate the cartvals for the filtered list of products, so use it:
+		$filteredvals = $cartvals_callback($filteredproducts);
+		return $this->evaluateTerm ($expr, $filteredvals, $filteredproducts, $cartvals_callback);
+	}
+
+	protected function evaluateFunction ($function, $args) {
+		$func = strtolower($function);
+		// Check if we have a custom function definition and use that if so.
+		// This is done first to allow plugins to override even built-in functions!
+		if (isset($this->plugin->custom_functions[$func])) {
+			vmDebug("Evaluating custom function $function, defined by a plugin");
+			return call_user_func($this->plugin->custom_functions[$func], $args, $this);
+		}
+
+		// Functions with no argument:
+		if (count($args) == 0) {
+			$dt = getdate();
+			switch ($func) {
+				case "second": return $dt['seconds']; break;
+				case "minute": return $dt['minutes']; break;
+				case "hour":   return $dt['hours']; break;
+				case "day":    return $dt['mday']; break;
+				case "weekday":return $dt['wday']; break;
+				case "month":  return $dt['mon']; break;
+				case "year":   return $dt['year']; break;
+				case "yearday":return $dt['yday']; break;
+			}
+		}
+		// Functions with exactly one argument:
+		if (count($args) == 1) {
+			switch ($func) {
+				case "round": return round($args[0]); break;
+				case "ceil":  return ceil ($args[0]); break;
+				case "floor": return floor($args[0]); break;
+				case "abs":   return abs($args[0]); break;
+				case "not":   return !$args[0]; break;
+				case "print_r": return print_r($args[0],1); break; 
+			}
+		}
+		if (count($args) == 2) {
+			switch ($func) {
+				case "digit": return substr($args[0], $args[1]-1, 1); break;
+				case "round": return round($args[0]/$args[1])*$args[1]; break;
+				case "ceil":  return ceil($args[0]/$args[1])*$args[1]; break;
+				case "floor": return floor($args[0]/$args[1])*$args[1]; break;
+			}
+		}
+		if (count($args) == 3) {
+			switch ($func) {
+				case "substring": return substr($args[0], $args[1]-1, $args[2]); break;
+			}
+		}
+		// Functions with variable number of args
+		switch ($func) {
+			case "max": 
+					return max($args);
+			case "min": 
+					return min($args);
+			case "list": 
+			case "array": 
+					return $args;
+			// List functions:
+		    case "length":
+		    case "complement":
+		    case "issubset":
+		    case "contains":
+		    case "union":
+		    case "join":
+		    case "intersection":
+		    case "list_equal":
+					return $this->evaluateListFunction ($func, $args);
+			case "contains_any": 
+			case "contains_all":
+			case "contains_only":
+			case "contains_none":
+					return $this->evaluateListContainmentFunction($func, $args);
+			
+		}
+		
+		// None of the built-in function 
+		// No known function matches => print an error, return 0
+		JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_FUNCTION', $function, $this->rulestring), 'error');
+		return 0;
+	}
+
+	protected function evaluateVariable ($expr, $vals) {
+		$varname = strtolower($expr);
+		if (array_key_exists(strtolower($expr), $vals)) {
+			return $vals[strtolower($expr)];
+		} elseif ($varname=='noshipping') {
+			return $varname;
+		} elseif ($varname=='values') {
+			return $vals;
+		} elseif ($varname=='values_debug') {
+			return print_r($vals,1);
+		} else {
+			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_VALUE', $expr, $this->rulestring), 'error');
+			return null;
+		}
+	}
+
+	protected function evaluateTerm ($expr, $vals, $products, $cartvals_callback) {
+		// The scoping functions need to be handled differently, because they first need to adjust the cart variables to the filtered product list
+		// before evaluating its first argument. So even though parsing the rules handles scoping functions like any other function, their 
+		// evaluation is fundamentally different and is special-cased here:
+		$scoping_functions = array("evaluate_for_categories", "evaluate_for_products", "evaluate_for_vendors", "evaluate_for_manufacturers");
+		$is_scoping = is_array($expr) && ($expr[0]=="FUNCTION") && (count($expr)>1) && in_array($expr[1], $scoping_functions);
+
+		if (is_null($expr)) {
+			return $expr;
+		} elseif (is_numeric ($expr)) {
+			return $expr;
+		} elseif (is_string ($expr)) {
+			// Explicit strings are delimited by '...' or "..."
+			if (($expr[0]=='\'' || $expr[0]=='"') && ($expr[0]==substr($expr,-1)) ) {
+				return substr($expr,1,-1);
+			} else {
+				return $this->evaluateVariable($expr, $vals);
+			}
+		} elseif ($is_scoping) {
+			$op = array_shift($expr); // ignore the "FUNCTION"
+			$func = array_shift($expr); // The scoping function name
+			$expression = array_shift($expr); // The expression to be evaluated
+			$conditions = $expr; // the remaining $expr list now contains the conditions
+			return $this->evaluateScoping ($expression, $func, $conditions, $vals, $products, $cartvals_callback);
+			
+		} elseif (is_array($expr)) {
+			// Operator
+			$op = array_shift($expr);
+			$args = array();
+			// First evaluate all operands and only after that apply the function / operator to the already evaluated arguments
+			$evaluate = true;
+			if ($op == "FUNCTION") {
+				$evaluate = false;
+			}
+			foreach ($expr as $e) {
+				$term = $evaluate ? ($this->evaluateTerm($e, $vals, $products, $cartvals_callback)) : $e;
+				if ($op == 'COMPARISON') {
+					// For comparisons, we only evaluate every other term (the operators are NOT evaluated!)
+					// The data format for comparisons is: array('COMPARISON', $operand1, '<', $operand2, '<=', ....)
+					$evaluate = !$evaluate;
+				}
+				if ($op == "FUNCTION") {
+					$evaluate = true;
+				}
+				if (is_null($term)) return null;
+				$args[] = $term;
+			}
+			$res = false;
+			// Finally apply the operaton to the evaluated argument values:
+			switch ($op) {
+				// Logical operators:
+				case 'OR':  foreach ($args as $a) { $res = ($res || $a); }; break;
+				case '&&':
+				case 'AND':  $res = true; foreach ($args as $a) { $res = ($res && $a); }; break;
+				case 'IN': $res = in_array($args[0], $args[1]);  break;
+				
+				// Comparisons:
+				case '<':
+				case '<=':
+				case '=<':
+				case '==':
+				case '!=':
+				case '<>':
+				case '>=':
+				case '=>':
+				case '>':
+				case '~':
+					$res = $this->evaluateComparison(array($args[0], $op, $args[1]), $vals); break;
+				case 'COMPARISON':
+					$res = $this->evaluateComparison($args, $vals); break;
+				
+				// Unary operators:
+				case '.-': $res = -$args[0]; break;
+				case '.+': $res = $args[0]; break;
+				
+				// Binary operators
+				case "+":  $res = ($args[0] +  $args[1]); break;
+				case "-":  $res = ($args[0] -  $args[1]); break;
+				case "*":  $res = ($args[0] *  $args[1]); break;
+				case "/":  $res = ($args[0] /  $args[1]); break;
+				case "%":  $res = (fmod($args[0],  $args[1])); break;
+				case "^":  $res = ($args[0] ^  $args[1]); break;
+				
+				// Functions:
+				case "FUNCTION": $func = array_shift($args); $res = $this->evaluateFunction($func, $args); break;
+				
+				default:   $res = false;
+			}
+			
+// 			JFactory::getApplication()->enqueueMessage("<pre>Result of ".print_r($expr,1)." is $res.</pre>", 'error');
+			return $res;
+		} else {
+			// Neither string nor numeric, nor operator...
+			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_VALUE', $expr, $this->rulestring), 'error');
+			return null;
+		}
+	}
+
+	protected function calculateShipping ($vals, $products, $cartvals_callback) {
+		return $this->evaluateTerm($this->shipping, $vals, $products, $cartvals_callback);
+	}
+
+	protected function evaluateRule (&$vals, $products, $cartvals_callback) {
+		if ($this->evaluated) 
+			return; // Already evaluated
+
+		$this->evaluated = True;
+		$this->match = False; // Default, set it to True below if all conditions match...
+		// First, check the country, if any conditions are given:
+		if (count ($this->countries) > 0 && !in_array ($vals['countryid'], $this->countries)) {
+// 			vmdebug('Rule::matches: Country check failed: countryid='.print_r($vals['countryid'],1).', countries are: '.print_r($this->countries,1).'...');
+			return;
+		}
+
+		foreach ($this->conditions as $c) {
+			// All conditions have to match!
+			$ret = $this->evaluateTerm($c, $vals, $products, $cartvals_callback);
+
+			if (is_null($ret) || (!$ret)) {
+				return;
+			}
+		}
+		// All conditions match
+		$this->match = True;
+		// Calculate the value (i.e. shipping cost or modifier)
+		$this->value = $this->calculateShipping($vals, $products, $cartvals_callback);
+		// Evaluate the rule name as a translatable string with variables inserted:
+		// Replace all {variable} tags in the name by the variables from $vals
+		$matches=array();
+		$name=JText::_($this->name);
+		preg_match_all('/{([A-Za-z0-9_]+)}/', $name, $matches);
+		
+		foreach ($matches[1] as $m) {
+			$val = $this->evaluateVariable($m, $vals);
+			if ($val !== null) {
+				$name = str_replace("{".$m."}", $val, $name);
+			}
+		}
+		$this->rulename = $name;
+	}
+
+	function matches(&$vals, $products, $cartvals_callback) {
+		$this->evaluateRule($vals, $products, $cartvals_callback);
+		return $this->match;
+	}
+
+	function getType() {
+		return $this->ruletype;
+	}
+
+	function getRuleName() {
+		if (!$this->evaluated)
+			vmDebug('WARNING: getRuleName called without prior evaluation of the rule, e.g. by calling rule->matches(...)');
+		return $this->rulename;
+	}
+	
+	function getValue() {
+		if (!$this->evaluated)
+			vmDebug('WARNING: getValue called without prior evaluation of the rule, e.g. by calling rule->matches(...)');
+		return $this->value;
+	}
+	function getShippingCosts() {
+		return $this->getValue();
+	}
+	
+	function isNoShipping() {
+		// NoShipping is set, so if the rule matches, this method should not offer any shipping at all
+		return (is_string($this->shipping) && (strtolower($this->shipping)=="noshipping"));
+	}
+
+}
+
+/** Extend the shipping rules by allowing arbitrary mathematical expressions
+ */
+class ShippingRule_Advanced extends ShippingRule {
+	function __construct ($method, $rule, $countries, $tax_id) {
+		parent::__construct ($method, $rule, $countries, $tax_id);
+	}
+	
+	function tokenize_expression ($expression) {
+		// First, extract all strings, delimited by quotes, then all text operators 
+		// (OR, AND, in; but make sure we don't capture parts of words, so we need to 
+		// use lookbehind/lookahead patterns to exclude OR following another letter 
+		// or followed by another letter) and then all arithmetic operators
+		$re = '/\s*("[^"]*"|\'[^\']*\'|(?<![A-Za-z0-9])(?:OR|AND|IN)(?![A-Za-z0-9])|&&|<=|=>|>=|=<|<>|!=|==|<|=|>|~|\+|-|\*|\/|%|\(|\)|\^|,)\s*/i';
+		$atoms = preg_split($re, $expression, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
+		// JFactory::getApplication()->enqueueMessage("TOKENIZING '$expression' returns: <pre>".print_r($atoms,1)."</pre>", 'error');
+		return $atoms;
+	}
+	
+
+	/** parse the mathematical expressions using the Shunting Yard Algorithm by Dijkstra (with some extensions to allow arbitrary functions):
+	 *  First parse the string into an array of tokens (operators and operands) by a simple regexp with known operators as separators)
+	 * TODO: Update this description to include unary operators and general function calls
+	 *  Then convert the infix notation into postfix (RPN), taking care of operator precedence
+	 *    1) Initialize empty stack and empty result variable
+	 *    2) Read infix expression from left to right, one atom at a time
+	 *    3) If operand => Append to result
+	 *    4) If operator:
+	 *        4a) Pop operators from stack until opening parenthesis, operator of 
+	 *            lower precedence or right-associative symbol of equal precedence.
+	 *        4b) Push operator onto stack
+	 *    5) If opening parenthesis => push onto stack
+	 *    6) If closing parenthesis:
+	 *        6a) Pop operators from stack until opening parenthesis is found
+	 *        6b) push them to the result (not the opening parenthesis, of course)
+	 *    7) At the end of the input, pop all operators from the stack and onto the result
+	 *
+	 *  Afterwards, convert this RPN list into an expression tree to be evaluated
+	 * 
+	 *  For the full algorithm, including function parsing, see Wikipedia:
+	 *    http://en.wikipedia.org/wiki/Shunting_yard_algorithm
+	 *
+	 */
+	function parseRulePart($rulepart) {
+		/* In the basic version, we only split at the comparison operators and assume each term on the LHS and RHS is one variable or constant */
+		/* In the advanced version, all conditions and costs can be given as a full mathematical expression */
+		/* Both versions create an expression tree, which can be easily evaluated in evaluateTerm */
+		$rulepart = trim($rulepart);
+		if (!isset($rulepart) || $rulepart==='') return;
+
+		
+		// Special-case the name assignment, where we don't want to interpret the value as an arithmetic expression!
+		if (preg_match('/^\s*(name|variable|definition)\s*=\s*(["\']?)(.*)\2\s*$/i', $rulepart, $matches)) {
+			$this->handleAssignment ($matches[1], $matches[3], $rulepart);
+			return;
+		}
+
+		// Split at all operators:
+		$atoms = $this->tokenize_expression ($rulepart);
+		
+		$operators = array(
+			".-" => 100, ".+" => 100,
+			"IN" => 80, 
+			"^"  => 70, 
+			"*"  => 60, "/"  => 60, "%"  => 60, 
+			"+"  => 50, "-"  => 50, 
+			"<"  => 40, "<=" => 40, ">"  => 40, ">=" => 40, "=>" => 40, "=<" => 40,
+			"==" => 40, "!=" => 40, "<>" => 40, "~" => 40,
+			"&&"  => 21, "AND" => 21,
+			"OR"  => 20,
+			"="  => 10,
+			"("  =>  0, ")"  =>0 
+		);
+		$unary_ops = array("-" => ".-", "+" => ".+");
+
+		// Any of these indicate a comparison and thus a condition:
+		$condition_ops = array('<', '<=', '=<', '<>', '!=', '==', '>', '>=', '=>', '~', 'OR', 'AND', '&&', 'IN');
+		$comparison_ops = array('<', '<=', '=<', '<>', '!=', '==', '>', '>=', '=>', '~');
+		$is_condition = false;
+		$is_assignment = false;
+		
+		$stack = array ();  // 1)/
+		$prev_token_operator = true;
+		$function_args = array();
+		$out_stack = array();
+		foreach ($atoms as $a) { // 2)
+			$aupper = strtoupper($a); # All operators are converted to uppercase!
+		
+			if ($a == ",") { // A function argument separator
+				// pop-and-apply all operators back to the left function paren
+				while (count($stack)>0) { // 4a)
+					$op = array_pop ($stack);
+					if ($op != "FUNCTION(") {
+						array_push ($out_stack, $op);
+					} else {
+						// No unary operator -> add it back to stack, exit loop
+						array_push ($stack, $op);
+						break;
+					}
+				} while (0);
+				$this_func = array_pop($function_args);
+				// Add current output stack as argument, reset temporary output stack
+				if (!empty($out_stack)) $this_func[] = $out_stack;
+				$function_args[] = $this_func;
+				$out_stack = array();
+				$prev_token_operator = true;
+				
+			} elseif ($a == "(" and !$prev_token_operator) { // 5) parenthesis after operand -> FUNCTION CALL
+				array_push ($stack, "FUNCTION(");
+				// retrieve function name from RPN list (remove last entry from operand stack!)
+				$function = strtolower(array_pop ($out_stack));
+				$new_stack = array();
+				// Set up function call data structure on function_args stack:
+				$function_args[] = array(/* old operand stack: */$out_stack, $function);
+				// Use a the temporary operand stack until the closing paren restores the previous operand stack again
+				$out_stack = array();
+				$prev_token_operator = true;
+
+			} elseif ($a == "(" and $prev_token_operator) { // 5) real parenthesis 
+				$stack[] = $a;
+				$prev_token_operator = true;
+				
+			} elseif ($a == ")") { // 6) parenthesis
+				do {
+					$op=array_pop($stack); // 6a)
+					if ($op == "(") {
+						break; // We have found the opening parenthesis
+					} elseif ($op =="FUNCTION(") { // Function call
+						// Remove function info from the functions stack; Format is array(PREVIOUS_OPERAND_STACK, FUNCTION, ARGS...)
+						$this_func = array_pop ($function_args);
+						// Append last argument (if not empty)
+						if (!empty($out_stack)) $this_func[] = $out_stack;
+						// restore old output/operand stack
+						$out_stack = array_shift($this_func);
+						// Function name is the next entry
+						$function = array_shift($this_func);
+						// All other entries are function arguments, so append them to the current operand stack
+						foreach ($this_func as $a) {
+							foreach ($a as $aa) {
+								$out_stack[] = $aa;
+							}
+						}
+						$out_stack[] = array("FUNCTION", $function, count($this_func));
+						break; // We have found the opening parenthesis
+					} elseif (!is_null($op)) {
+						$out_stack[]=$op; // 6b) "normal" operators
+					} else {
+						// no ( and no operator, so the expression is wrong!
+						JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_PARSE_MISSING_PAREN', $rulepart), 'error');
+						break;
+					}
+				} while (true);
+				$prev_token_operator = false;
+				
+			} elseif (isset($unary_ops[$aupper]) && $prev_token_operator) { // 4) UNARY operators
+				// Unary and binary operators need to be handled differently: 
+				// Unary operators must only pop other unary operators, never any binary operator
+				$unary_op = $unary_ops[$aupper];
+				// For unary operators, pop other unary operators from the stack until you reach an opening parenthesis, 
+				// an operator of lower precedence, or a right associative symbol of equal precedence. 
+				while (count($stack)>0) { // 4a)
+					$op = array_pop ($stack);
+					// Remove all other unary operators:
+					if (in_array ($op, $unary_ops)) {
+						array_push ($out_stack, $op);
+					} else {
+						// No unary operator -> add it back to stack, exit loop
+						array_push ($stack, $op);
+						break;
+					}
+				} while (0);
+				array_push ($stack, $unary_op); // 4b)
+				$prev_token_operator = true;
+				
+			} elseif (isset($operators[$aupper])) { // 4) BINARY operators
+				$prec = $operators[$aupper];
+				$is_condition |= in_array($aupper, $condition_ops);
+				$is_assignment |= ($aupper == "=");
+				
+				// For operators, pop operators from the stack until you reach an opening parenthesis, 
+				// an operator of lower precedence, or a right associative symbol of equal precedence. 
+				while (count($stack)>0) { // 4a)
+					$op = array_pop ($stack);
+					// The only right-associative operator is =, which we allow at most once!
+					if ($op == "(" || $op == "FUNCTION(") {
+						// add it back to the stack!
+						array_push ($stack, $op);
+						break;
+					} elseif ($operators[$op]<$prec) {
+						// We found an operator with lower precedence, add it back to the stack!
+						array_push ($stack, $op); // 4b)
+						break;
+					} else {
+						array_push ($out_stack, $op);
+					}
+				} while (0);
+				array_push ($stack, $aupper); // 4b)
+				$prev_token_operator = true;
+				
+			} else { // 3) Everything else is an Operand
+				$out_stack[] = $a;
+				$prev_token_operator = false;
+			}
+		}
+		// Finally, pop all operators from the stack and append them to the result
+		while ($op=array_pop($stack)) {
+			// Opening parentheses should not be found on the stack any more. That would mean a closing paren is missing!
+			if ($op == "(") {
+				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_PARSE_PAREN_NOT_CLOSED', $rulepart), 'error');
+			} else {
+				array_push ($out_stack, $op);
+			}
+		}
+		if (!empty($function_args)) {
+				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_PARSE_FUNCTION_NOT_CLOSED', $rulepart), 'error');
+		}
+
+
+		/** Now, turn the RPN into an expression tree (i.e. "evaluate" it into a tree structure), according to Knuth:
+		 *   1) Initialize an empty stack
+		 *   2) Read the RPN from left to right
+		 *   3) If operand, push it onto the stack
+		 *   4) If operator:
+		 *       4a) pop two operands
+		 *       4b) perform operation
+		 *       4c) push result onto stack
+		 *       4d) (If less than two operands => ERROR, invalid syntax)
+		 *   5) At the end of the RPN, pop the result from the stack. 
+		 *       5a) The stack should now be empty (otherwise, ERROR, invalid syntax)
+		 */
+
+		$stack=array(); // 1)
+		foreach ($out_stack as $e) { // 2)
+			if (is_array($e) && $e[0]=="FUNCTION") { // A function call (#args is saved as $e[2], so remove that number of operands from the stack)
+				$function = $e[1];
+				$argc = $e[2];
+				$args = array();
+				for ($i = $argc; $i > 0; $i--) {
+					$a = array_pop($stack);
+					array_unshift($args, $a);
+				}
+				array_unshift($args, $function);
+				array_unshift($args, "FUNCTION"); 
+				$stack[] = $args;
+			} elseif (in_array($e, $unary_ops)) { // 4) unary operators
+				// Operator => apply to the last value on the stack
+				if (count($stack)<1) { // 4d)
+					JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_SYNTAXERROR', $rulepart), 'error');
+					array_push($stack, 0);
+					continue;
+				}
+				$o1 = array_pop($stack);
+				// Special-case chained comparisons: if e is a comparison, and operator(o1) is also a comparison, 
+				// insert the arguments to the existing comparison instead of creating a new one
+				$op = array ($e, $o1); // 4b)
+				array_push ($stack, $op); // 4c)
+			} elseif (isset($operators[$e])) { // 4) binary operators
+				// Operator => apply to the last two values on the stack
+				if (count($stack)<2) { // 4d)
+					JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_SYNTAXERROR', $rulepart), 'error');
+					array_push($stack, 0);
+					continue;
+				}
+				$o2 = array_pop($stack); // 4a)
+				$o1 = array_pop($stack);
+				// Special-case chained comparisons, e.g. 1<=Amount<100:
+				// if e is a comparison, and operator(o1) is also a comparison, 
+				// insert the arguments to the existing comparison instead of creating a new one
+				if (in_array ($e, $comparison_ops)) {
+					if ($o1[0]=='COMPARISON') {
+						$op = $o1;
+						// Append the new comparison to the existing one
+						array_push($op, $e, $o2);
+					} else {
+						$op = array ('COMPARISON', $o1, $e, $o2);
+					}
+				} else {
+					$op = array ($e, $o1, $o2); // 4b)
+				}
+				array_push ($stack, $op); // 4c)
+			} else { // 3)
+				// Operand => push onto stack
+				array_push ($stack, $e);
+			}
+			
+		}
+		// 5a)
+		if (count($stack) != 1) {
+			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_ERROR', $rulepart), 'error');
+			JFactory::getApplication()->enqueueMessage(JText::sprintf('Outstack: <pre>%s</pre>', print_r($out_stack,1)), 'error');
+			
+			$stack = array (0);
+		}
+		$res = array_pop($stack); // 5)
+		
+		if ($is_assignment) { // Assignments are handled first, so conditions can be assigned to variables
+			if ($res[0]=='=') {
+				$this->handleAssignment ($res[1], $res[2], $rulepart);
+			} else {
+				// Assignment has to be top-level!
+				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_ASSIGNMENT_TOPLEVEL', $rulepart), 'error');
+			}
+		} elseif ($is_condition) { // Comparisons are conditions
+			$this->conditions[] = $res;
+		} else {
+			// Terms without comparisons or assignments are shipping cost expressions
+			$this->shipping = $res;
+			$this->ruletype = 'shipping';
+			$this->includes_tax = False;
+		}
+// 		JFactory::getApplication()->enqueueMessage("<pre>Rule part '$rulepart' (type $this->ruletype) parsed into (condition=".print_r($is_condition,1).", assignment=".print_r($is_assignment,1)."): ".print_r($res,1)."</pre>", 'error');
+	}
+
+
+}
+
+// No closing tag
diff --git a/rules_shipping_advanced.php b/rules_shipping_advanced.php
index cfa5efb7ca3f10b908aff3f2e7035c6367b52d6c..b0e0653d17d92b4ecd38d4604c8ffc327347086e 100644
--- a/rules_shipping_advanced.php
+++ b/rules_shipping_advanced.php
@@ -27,23 +27,6 @@ if (!class_exists ('plgVmShipmentRules_Shipping_Base')) {
 }
 
 
-function print_array($obj) {
-	$res = "";
-	if (is_array($obj)) {
-		$res .= "array(";
-		$sep = "";
-		foreach ($obj as $e) {
-			$res .= $sep . print_array($e);
-			$sep = ", ";
-		}
-		$res .= ")";
-	} elseif (is_string($obj)) {
-		$res .= "\"$obj\"";
-	} else {
-		$res .= (string)$obj;
-	}
-	return $res;
-}
 
 
 /** Shipping costs according to general rules.
@@ -52,8 +35,10 @@ function print_array($obj) {
 class plgVmShipmentRules_Shipping_Advanced extends plgVmShipmentRules_Shipping_Base {
 	function __construct (& $subject, $config) {
 		parent::__construct ($subject, $config);
+		$this->helper->registerCallback('initRule',				array($this, 'initRule'));
+		$this->helper->registerCallback('addCustomCartValues',	array($this, 'addCustomCartValues'));
 	}
-	protected function createMethodRule ($r, $countries, $tax) {
+	protected function initRule ($r, $countries, $tax) {
 		return new ShippingRule_Advanced ($this, $r, $countries, $tax);
 	}
 	/** Allow child classes to add additional variables for the rules 
@@ -97,338 +82,3 @@ class plgVmShipmentRules_Shipping_Advanced extends plgVmShipmentRules_Shipping_B
 	}
 
 }
-
-
-/** Extend the shipping rules by allowing arbitrary mathematical expressions
- */
-class ShippingRule_Advanced extends ShippingRule {
-	function __construct ($method, $rule, $countries, $tax_id) {
-		parent::__construct ($method, $rule, $countries, $tax_id);
-	}
-	
-	function tokenize_expression ($expression) {
-		// First, extract all strings, delimited by quotes, then all text operators 
-		// (OR, AND, in; but make sure we don't capture parts of words, so we need to 
-		// use lookbehind/lookahead patterns to exclude OR following another letter 
-		// or followed by another letter) and then all arithmetic operators
-		$re = '/\s*("[^"]*"|\'[^\']*\'|(?<![A-Za-z0-9])(?:OR|AND|IN)(?![A-Za-z0-9])|&&|<=|=>|>=|=<|<>|!=|==|<|=|>|~|\+|-|\*|\/|%|\(|\)|\^|,)\s*/i';
-		$atoms = preg_split($re, $expression, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
-		// JFactory::getApplication()->enqueueMessage("TOKENIZING '$expression' returns: <pre>".print_r($atoms,1)."</pre>", 'error');
-		return $atoms;
-	}
-	
-
-	/** parse the mathematical expressions using the Shunting Yard Algorithm by Dijkstra (with some extensions to allow arbitrary functions):
-	 *  First parse the string into an array of tokens (operators and operands) by a simple regexp with known operators as separators)
-	 * TODO: Update this description to include unary operators and general function calls
-	 *  Then convert the infix notation into postfix (RPN), taking care of operator precedence
-	 *    1) Initialize empty stack and empty result variable
-	 *    2) Read infix expression from left to right, one atom at a time
-	 *    3) If operand => Append to result
-	 *    4) If operator:
-	 *        4a) Pop operators from stack until opening parenthesis, operator of 
-	 *            lower precedence or right-associative symbol of equal precedence.
-	 *        4b) Push operator onto stack
-	 *    5) If opening parenthesis => push onto stack
-	 *    6) If closing parenthesis:
-	 *        6a) Pop operators from stack until opening parenthesis is found
-	 *        6b) push them to the result (not the opening parenthesis, of course)
-	 *    7) At the end of the input, pop all operators from the stack and onto the result
-	 *
-	 *  Afterwards, convert this RPN list into an expression tree to be evaluated
-	 * 
-	 *  For the full algorithm, including function parsing, see Wikipedia:
-	 *    http://en.wikipedia.org/wiki/Shunting_yard_algorithm
-	 *
-	 */
-	function parseRulePart($rulepart) {
-		/* In the basic version, we only split at the comparison operators and assume each term on the LHS and RHS is one variable or constant */
-		/* In the advanced version, all conditions and costs can be given as a full mathematical expression */
-		/* Both versions create an expression tree, which can be easily evaluated in evaluateTerm */
-		$rulepart = trim($rulepart);
-		if (!isset($rulepart) || $rulepart==='') return;
-
-		
-		// Special-case the name assignment, where we don't want to interpret the value as an arithmetic expression!
-		if (preg_match('/^\s*(name|variable|definition)\s*=\s*(["\']?)(.*)\2\s*$/i', $rulepart, $matches)) {
-			$this->handleAssignment ($matches[1], $matches[3], $rulepart);
-			return;
-		}
-
-		// Split at all operators:
-		$atoms = $this->tokenize_expression ($rulepart);
-		
-		$operators = array(
-			".-" => 100, ".+" => 100,
-			"IN" => 80, 
-			"^"  => 70, 
-			"*"  => 60, "/"  => 60, "%"  => 60, 
-			"+"  => 50, "-"  => 50, 
-			"<"  => 40, "<=" => 40, ">"  => 40, ">=" => 40, "=>" => 40, "=<" => 40,
-			"==" => 40, "!=" => 40, "<>" => 40, "~" => 40,
-			"&&"  => 21, "AND" => 21,
-			"OR"  => 20,
-			"="  => 10,
-			"("  =>  0, ")"  =>0 
-		);
-		$unary_ops = array("-" => ".-", "+" => ".+");
-
-		// Any of these indicate a comparison and thus a condition:
-		$condition_ops = array('<', '<=', '=<', '<>', '!=', '==', '>', '>=', '=>', '~', 'OR', 'AND', '&&', 'IN');
-		$comparison_ops = array('<', '<=', '=<', '<>', '!=', '==', '>', '>=', '=>', '~');
-		$is_condition = false;
-		$is_assignment = false;
-		
-		$stack = array ();  // 1)/
-		$prev_token_operator = true;
-		$function_args = array();
-		$out_stack = array();
-		foreach ($atoms as $a) { // 2)
-			$aupper = strtoupper($a); # All operators are converted to uppercase!
-		
-			if ($a == ",") { // A function argument separator
-				// pop-and-apply all operators back to the left function paren
-				while (count($stack)>0) { // 4a)
-					$op = array_pop ($stack);
-					if ($op != "FUNCTION(") {
-						array_push ($out_stack, $op);
-					} else {
-						// No unary operator -> add it back to stack, exit loop
-						array_push ($stack, $op);
-						break;
-					}
-				} while (0);
-				$this_func = array_pop($function_args);
-				// Add current output stack as argument, reset temporary output stack
-				if (!empty($out_stack)) $this_func[] = $out_stack;
-				$function_args[] = $this_func;
-				$out_stack = array();
-				$prev_token_operator = true;
-				
-			} elseif ($a == "(" and !$prev_token_operator) { // 5) parenthesis after operand -> FUNCTION CALL
-				array_push ($stack, "FUNCTION(");
-				// retrieve function name from RPN list (remove last entry from operand stack!)
-				$function = strtolower(array_pop ($out_stack));
-				$new_stack = array();
-				// Set up function call data structure on function_args stack:
-				$function_args[] = array(/* old operand stack: */$out_stack, $function);
-				// Use a the temporary operand stack until the closing paren restores the previous operand stack again
-				$out_stack = array();
-				$prev_token_operator = true;
-
-			} elseif ($a == "(" and $prev_token_operator) { // 5) real parenthesis 
-				$stack[] = $a;
-				$prev_token_operator = true;
-				
-			} elseif ($a == ")") { // 6) parenthesis
-				do {
-					$op=array_pop($stack); // 6a)
-					if ($op == "(") {
-						break; // We have found the opening parenthesis
-					} elseif ($op =="FUNCTION(") { // Function call
-						// Remove function info from the functions stack; Format is array(PREVIOUS_OPERAND_STACK, FUNCTION, ARGS...)
-						$this_func = array_pop ($function_args);
-						// Append last argument (if not empty)
-						if (!empty($out_stack)) $this_func[] = $out_stack;
-						// restore old output/operand stack
-						$out_stack = array_shift($this_func);
-						// Function name is the next entry
-						$function = array_shift($this_func);
-						// All other entries are function arguments, so append them to the current operand stack
-						foreach ($this_func as $a) {
-							foreach ($a as $aa) {
-								$out_stack[] = $aa;
-							}
-						}
-						$out_stack[] = array("FUNCTION", $function, count($this_func));
-						break; // We have found the opening parenthesis
-					} elseif (!is_null($op)) {
-						$out_stack[]=$op; // 6b) "normal" operators
-					} else {
-						// no ( and no operator, so the expression is wrong!
-						JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_PARSE_MISSING_PAREN', $rulepart), 'error');
-						break;
-					}
-				} while (true);
-				$prev_token_operator = false;
-				
-			} elseif (isset($unary_ops[$aupper]) && $prev_token_operator) { // 4) UNARY operators
-				// Unary and binary operators need to be handled differently: 
-				// Unary operators must only pop other unary operators, never any binary operator
-				$unary_op = $unary_ops[$aupper];
-				// For unary operators, pop other unary operators from the stack until you reach an opening parenthesis, 
-				// an operator of lower precedence, or a right associative symbol of equal precedence. 
-				while (count($stack)>0) { // 4a)
-					$op = array_pop ($stack);
-					// Remove all other unary operators:
-					if (in_array ($op, $unary_ops)) {
-						array_push ($out_stack, $op);
-					} else {
-						// No unary operator -> add it back to stack, exit loop
-						array_push ($stack, $op);
-						break;
-					}
-				} while (0);
-				array_push ($stack, $unary_op); // 4b)
-				$prev_token_operator = true;
-				
-			} elseif (isset($operators[$aupper])) { // 4) BINARY operators
-				$prec = $operators[$aupper];
-				$is_condition |= in_array($aupper, $condition_ops);
-				$is_assignment |= ($aupper == "=");
-				
-				// For operators, pop operators from the stack until you reach an opening parenthesis, 
-				// an operator of lower precedence, or a right associative symbol of equal precedence. 
-				while (count($stack)>0) { // 4a)
-					$op = array_pop ($stack);
-					// The only right-associative operator is =, which we allow at most once!
-					if ($op == "(" || $op == "FUNCTION(") {
-						// add it back to the stack!
-						array_push ($stack, $op);
-						break;
-					} elseif ($operators[$op]<$prec) {
-						// We found an operator with lower precedence, add it back to the stack!
-						array_push ($stack, $op); // 4b)
-						break;
-					} else {
-						array_push ($out_stack, $op);
-					}
-				} while (0);
-				array_push ($stack, $aupper); // 4b)
-				$prev_token_operator = true;
-				
-			} else { // 3) Everything else is an Operand
-				$out_stack[] = $a;
-				$prev_token_operator = false;
-			}
-		}
-		// Finally, pop all operators from the stack and append them to the result
-		while ($op=array_pop($stack)) {
-			// Opening parentheses should not be found on the stack any more. That would mean a closing paren is missing!
-			if ($op == "(") {
-				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_PARSE_PAREN_NOT_CLOSED', $rulepart), 'error');
-			} else {
-				array_push ($out_stack, $op);
-			}
-		}
-		if (!empty($function_args)) {
-				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_PARSE_FUNCTION_NOT_CLOSED', $rulepart), 'error');
-		}
-
-
-		/** Now, turn the RPN into an expression tree (i.e. "evaluate" it into a tree structure), according to Knuth:
-		 *   1) Initialize an empty stack
-		 *   2) Read the RPN from left to right
-		 *   3) If operand, push it onto the stack
-		 *   4) If operator:
-		 *       4a) pop two operands
-		 *       4b) perform operation
-		 *       4c) push result onto stack
-		 *       4d) (If less than two operands => ERROR, invalid syntax)
-		 *   5) At the end of the RPN, pop the result from the stack. 
-		 *       5a) The stack should now be empty (otherwise, ERROR, invalid syntax)
-		 */
-
-		$stack=array(); // 1)
-		foreach ($out_stack as $e) { // 2)
-			if (is_array($e) && $e[0]=="FUNCTION") { // A function call (#args is saved as $e[2], so remove that number of operands from the stack)
-				$function = $e[1];
-				$argc = $e[2];
-				$args = array();
-				for ($i = $argc; $i > 0; $i--) {
-					$a = array_pop($stack);
-					array_unshift($args, $a);
-				}
-				array_unshift($args, $function);
-				array_unshift($args, "FUNCTION"); 
-				$stack[] = $args;
-			} elseif (in_array($e, $unary_ops)) { // 4) unary operators
-				// Operator => apply to the last value on the stack
-				if (count($stack)<1) { // 4d)
-					JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_SYNTAXERROR', $rulepart), 'error');
-					array_push($stack, 0);
-					continue;
-				}
-				$o1 = array_pop($stack);
-				// Special-case chained comparisons: if e is a comparison, and operator(o1) is also a comparison, 
-				// insert the arguments to the existing comparison instead of creating a new one
-				$op = array ($e, $o1); // 4b)
-				array_push ($stack, $op); // 4c)
-			} elseif (isset($operators[$e])) { // 4) binary operators
-				// Operator => apply to the last two values on the stack
-				if (count($stack)<2) { // 4d)
-					JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_SYNTAXERROR', $rulepart), 'error');
-					array_push($stack, 0);
-					continue;
-				}
-				$o2 = array_pop($stack); // 4a)
-				$o1 = array_pop($stack);
-				// Special-case chained comparisons, e.g. 1<=Amount<100:
-				// if e is a comparison, and operator(o1) is also a comparison, 
-				// insert the arguments to the existing comparison instead of creating a new one
-				if (in_array ($e, $comparison_ops)) {
-					if ($o1[0]=='COMPARISON') {
-						$op = $o1;
-						// Append the new comparison to the existing one
-						array_push($op, $e, $o2);
-					} else {
-						$op = array ('COMPARISON', $o1, $e, $o2);
-					}
-				} else {
-					$op = array ($e, $o1, $o2); // 4b)
-				}
-				array_push ($stack, $op); // 4c)
-			} else { // 3)
-				// Operand => push onto stack
-				array_push ($stack, $e);
-			}
-			
-		}
-		// 5a)
-		if (count($stack) != 1) {
-			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_ERROR', $rulepart), 'error');
-			JFactory::getApplication()->enqueueMessage(JText::sprintf('Outstack: <pre>%s</pre>', print_r($out_stack,1)), 'error');
-			
-			$stack = array (0);
-		}
-		$res = array_pop($stack); // 5)
-		
-		if ($is_assignment) { // Assignments are handled first, so conditions can be assigned to variables
-			if ($res[0]=='=') {
-				$this->handleAssignment ($res[1], $res[2], $rulepart);
-			} else {
-				// Assignment has to be top-level!
-				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_ASSIGNMENT_TOPLEVEL', $rulepart), 'error');
-			}
-		} elseif ($is_condition) { // Comparisons are conditions
-			$this->conditions[] = $res;
-		} else {
-			// Terms without comparisons or assignments are shipping cost expressions
-			$this->shipping = $res;
-			$this->ruletype = 'shipping';
-			$this->includes_tax = False;
-		}
-// 		JFactory::getApplication()->enqueueMessage("<pre>Rule part '$rulepart' (type $this->ruletype) parsed into (condition=".print_r($is_condition,1).", assignment=".print_r($is_assignment,1)."): ".print_r($res,1)."</pre>", 'error');
-	}
-
-
-}
-
-// No closing tag
-/*$rule = new ShippingRule_Advanced("", array(), 0);
-$rp="a+b+(-1+d)";
-$rp="1+maxx(a,b)";
-$rp="f(1,2,+)";
-$rp="1+year()";
-$rp = "1+max( 1,2,3,4,5) + min(9,10,101)";
-$rp = "abs(0-1.9999)";
-$rp="max(1,min(0,5,7), 9)";
-// $rp="f()";
-print_r($rule->tokenize_expression($rp)); 
-
-$rule->parseRulePart($rp);
-print_r($rule->conditions);
-print_r($rule->shipping);
-
-print_r($rule->evaluateTerm($rule->shipping, array()));
-*/
\ No newline at end of file
diff --git a/rules_shipping_base.php b/rules_shipping_base.php
index 143ae09d73d51c5ef47d7e153548762e33abeec1..635eecc38d4c781de4775063722267f9c4c8e0b1 100644
--- a/rules_shipping_base.php
+++ b/rules_shipping_base.php
@@ -22,20 +22,11 @@ if (!class_exists ('vmPSPlugin')) {
 	require(JPATH_VM_PLUGINS . DS . 'vmpsplugin.php');
 }
 // Only declare the class once...
-if (class_exists ('plgVmShipmentRules_Shipping_Base')) {
-	return;
-}
-
-
-function is_equal($a, $b) {
-	if (is_array($a) && is_array($b)) {
-		return !array_diff($a, $b) && !array_diff($b, $a);
-	} elseif (is_string($a) && is_string($b)) {
-		return strcmp($a,$b) == 0;
-	} else {
-		return $a == $b;
-	}
-}
+// if (class_exists ('plgVmShipmentRules_Shipping_Base')) {
+// 	return;
+// }
+if (!class_exists('RulesShippingFrameworkJoomla'))
+	require_once (dirname(__FILE__) . DS . 'rules_shipping_framework_joomla.php');
 
 
 /** Shipping costs according to general rules.
@@ -43,10 +34,7 @@ function is_equal($a, $b) {
  *  Assignable variables: Shipping, Name
  */
 class plgVmShipmentRules_Shipping_Base extends vmPSPlugin {
-	// Store the parsed and possibly evaluated rules for each method (method ID is used as key)
-	protected $rules = array();
-	protected $match = array();
-	var $custom_functions = array ();
+	protected $helper = null;
 
 	/**
 	 * @param object $subject
@@ -61,34 +49,9 @@ class plgVmShipmentRules_Shipping_Base extends vmPSPlugin {
 		$this->tableFields = array_keys ($this->getTableSQLFields ());
 		$varsToPush = $this->getVarsToPush ();
 		$this->setConfigParameterable ($this->_configTableFieldName, $varsToPush);
-		
-		// PLUGIN FUNCTIONALITY:
-		// Let other plugins add custom functions! 
-		// The onVmShippingRulesRegisterCustomFunctions() trigger is expected to return an array of the form:
-		//   array ('functionname1' => 'function-to-be-called',
-		//          'functionname2' => array($classobject, 'memberfunc')),
-		//          ...);
-		JPluginHelper::importPlugin('vmshipmentrules');
-		$dispatcher = JDispatcher::getInstance();
-		$custfuncdefs = $dispatcher->trigger('onVmShippingRulesRegisterCustomFunctions',array());
-		// Loop through the return values of all plugins:
-		foreach ($custfuncdefs as $custfuncs) {
-			if (empty($custfuncs))
-				continue;
-			if (!is_array($custfuncs)) {
-				$this->printWarning(JText::sprintf('VMSHIPMENT_RULES_CUSTOMFUNCTIONS_NOARRAY', $method->rule_name));
-			}
-			// Now loop through all custom function definitions of this plugin
-			// If a function was registered before, print a warning and use the first definition
-			foreach ($custfuncs as $fname => $func) {
-				if (isset($this->custom_functions[$fname])) {
-					$this->printWarning(JText::sprintf('VMSHIPMENT_RULES_CUSTOMFUNCTIONS_ALREADY_DEFINED', $fname));
-				} else {
-					vmDebug("Defining custom function $fname");
-					$this->custom_functions[strtolower($fname)] = $func;
-				}
-			}
-		}
+
+		$this->helper = new RulesShippingFrameworkJoomla();
+		$this->helper->setup();
 	}
 
 	/**
@@ -100,17 +63,6 @@ class plgVmShipmentRules_Shipping_Base extends vmPSPlugin {
 		return $this->createTableSQL ('Shipment Rules Table');
 	}
 	
-	public function printWarning($message) {
-		// Keep track of warning messages, so we don't print them twice:
-		global $printed_warnings;
-		if (!isset($printed_warnings))
-			$printed_warnings = array();
-		if (!in_array($message, $printed_warnings)) {
-			JFactory::getApplication()->enqueueMessage($message, 'error');
-			$printed_warnings[] = $message;
-		}
-	}
-
 	/**
 	 * @return array
 	 */
@@ -167,16 +119,17 @@ class plgVmShipmentRules_Shipping_Base extends vmPSPlugin {
 		}
 		// We need to call getCosts, because in J3 $method->rule_name and $method->cost as set in getCosts is no longer preserved.
 		// Instead, we simply call getCosts again, which as a side-effect sets all those members of $method.
-		$costs = $this->getCosts($cart,$method,$cart->cartPrices);
+		$costs = $this->helper->getCosts($cart,$method,$cart->cartPrices);
+		$rulename = $this->helper->getRuleName($method->virtuemart_shipmentmethod_id);
+		$variables = $this->helper->getRuleVariables($method->virtuemart_shipmentmethod_id);
 		$values['virtuemart_order_id'] = $order['details']['BT']->virtuemart_order_id;
 		$values['order_number'] = $order['details']['BT']->order_number;
 		$values['virtuemart_shipmentmethod_id'] = $order['details']['BT']->virtuemart_shipmentmethod_id;
 		$values['shipment_name'] = $this->renderPluginName ($method);
-		$values['rule_name'] = $method->rule_name;
-		$weights = $this->getOrderWeights ($cart, $cart->products, $method->weight_unit);
-		$values['order_weight'] = $weights['weight'];
-		$values['order_articles'] = $this->getOrderArticles ($cart, $cart->products);
-		$values['order_products'] = $this->getOrderProducts ($cart, $cart->products);
+		$values['rule_name'] = $rulename; 
+		$values['order_weight'] = $variables['weight'];
+		$values['order_articles'] = $variables['articles'];
+		$values['order_products'] = $variables['products'];
 		$values['shipment_weight_unit'] = $method->weight_unit;
 		$values['shipment_cost'] = $method->cost;
 		$values['tax_id'] = $method->tax_id;
@@ -184,7 +137,12 @@ class plgVmShipmentRules_Shipping_Base extends vmPSPlugin {
 
 		return TRUE;
 	}
-
+	function getCosts (VirtueMartCart $cart, $method, $cart_prices) {
+		return $this->helper->getCosts($cart, $method, $cart_prices);
+	}
+	protected function checkConditions ($cart, $method, $cart_prices) {
+		return $this->helper->checkConditions($cart, $method, $cart_prices);
+	}
 	/**
 	 * This method is fired when showing the order details in the backend.
 	 * It displays the shipment-specific data.
@@ -265,118 +223,6 @@ class plgVmShipmentRules_Shipping_Base extends vmPSPlugin {
 		return $pluginName;
 	}
 
-
-	/** This function evaluates all rules, one after the other until it finds a matching rule that
-	 *  defines shipping costs (or uses NoShipping). If a modifier or definition is encountered,
-	 *  its effect is stored, but the loop continues */
-	protected function evaluateMethodRules ($cart, $method, $cart_prices) {
-		// $method->match will cache the matched rule and the modifiers
-		if (isset($this->match[$method->virtuemart_shipmentmethod_id])) {
-			return $this->match[$method->virtuemart_shipmentmethod_id];
-		} else {
-			// Evaluate all rules and find the matching ones (including modifiers and definitions!)
-			$result = array("rule"=>Null, "rule_name"=>"", "modifiers_add"=>array(), "modifiers_multiply"=>array());
-			$cartvals = $this->getCartValues ($cart, $cart->products, $method, $cart_prices);
-			// Pass a callback function to the rules to obtain the cartvals for a subset of the products
-			$this_class = $this;
-			$cartvals_callback = function ($products) use ($this_class, $cart, $method, $cart_prices) {
-				return $this_class->getCartValues ($cart, $products, $method, NULL);
-			};
-			foreach ($this->rules[$method->virtuemart_shipmentmethod_id] as $r) {
-				if ($r->matches($cartvals, $cart->products, $cartvals_callback)) {
-					$rtype = $r->getType();
-					switch ($rtype) {
-						case 'shipping': 
-						case 'shippingwithtax':
-						case 'noshipping': 
-								$result["rule"] = $r;
-								$result["rule_name"] = $r->getRuleName();
-								break;
-						case 'modifiers_add':
-						case 'modifiers_multiply':
-								$result[$rtype][] = $r;
-								break;
-						case 'definition': // A definition updates the $cartvals, but has no other effects
-								$cartvals[strtolower($r->getRuleName())] = $r->getValue();
-								break;
-						default:
-								$this->printWarning(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_TYPE', $r->getType(), $r->rulestring));
-								break;
-					}
-				}
-				if (!is_null($result["rule"])) {
-					$this->match[$method->virtuemart_shipmentmethod_id] = $result;
-					return $result; // <- This also breaks out of the foreach loop!
-				}
-			}
-		}
-		// None of the rules matched, so return NULL, but keep the evaluated results;
-		$this->match[$method->virtuemart_shipmentmethod_id] = $result;
-		return NULL;
-	}
-
-	/**
-	 * @param \VirtueMartCart $cart
-	 * @param int             $method
-	 * @param array           $cart_prices
-	 * @return bool
-	 */
-	protected function checkConditions ($cart, $method, $cart_prices) {
-		if (!isset($this->rules[$method->virtuemart_shipmentmethod_id])) 
-			$this->parseMethodRules($method);
-		$match = $this->evaluateMethodRules ($cart, $method, $cart_prices);
-		if ($match && !is_null ($match['rule'])) {
-			$method->rule_name = $match["rule_name"];
-			// If NoShipping is set, this method should NOT offer any shipping at all, so return FALSE, otherwise TRUE
-			// If the rule has a name, print it as warning (otherwise don't print anything)
-			if ($match['rule']->isNoShipping()) {
-				if (!empty($method->rule_name))
-					$this->printWarning(JText::sprintf('VMSHIPMENT_RULES_NOSHIPPING_MESSAGE', $method->rule_name));
-				vmdebug('checkConditions '.$method->shipment_name.' indicates NoShipping for this method, specified by rule "'.$method->rule_name.'" ('.$match['rule']->rulestring.').');
-				return FALSE;
-			} else {
-				return TRUE;
-			}
-		}
-		vmdebug('checkConditions '.$method->shipment_name.' does not fulfill all conditions, no rule matches');
-		return FALSE;
-	}
-
-	/**
-	 * @param VirtueMartCart $cart
-	 * @param                $method
-	 * @param                $cart_prices
-	 * @return int
-	 */
-	function getCosts (VirtueMartCart $cart, $method, $cart_prices) {
-		if (!isset($this->rules[$method->virtuemart_shipmentmethod_id])) 
-			$this->parseMethodRules($method);
-		$match = $this->evaluateMethodRules ($cart, $method, $cart_prices);
-		if ($match) {
-			$r = $match["rule"];
-			vmdebug('Rule ' . $match["rule_name"] . ' ('.$r->rulestring.') matched.');
-			$method->tax_id = $r->tax_id;
-			// TODO: Shall we include the name of the modifiers, too?
-			$method->rule_name = $match["rule_name"];
-			// Final shipping costs are calculated as:
-			//   Shipping*ExtraShippingMultiplier + ExtraShippingCharge
-			// with possibly multiple modifiers
-			$method->cost = $r->getShippingCosts();
-			foreach ($match['modifiers_multiply'] as $modifier) {
-				$method->cost *= $modifier->getValue();
-			}
-			foreach ($match['modifiers_add'] as $modifier) {
-				$method->cost += $modifier->getValue();
-			}
-			$method->includes_tax = $r->includes_tax;
-			return $method->cost;
-		}
-		
-		vmdebug('getCosts '.$method->name.' does not return shipping costs');
-		return 0;
-	}
-
-
 	/**
 	 * update the plugin cart_prices
 	 *
@@ -469,7 +315,7 @@ class plgVmShipmentRules_Shipping_Base extends vmPSPlugin {
 			}
 		} else {
 			// BEGIN_RK_CHANGES: VM change in VM3!
-			if (is_array($calculator->_cartData)) { // VM2:
+			if (isset($calculator->_cartData) && is_array($calculator->_cartData)) { // VM2:
 				$taxrules = array_merge($calculator->_cartData['VatTax'],$calculator->_cartData['taxRulesBill']);
 			} else { // VM3:
 				$taxrules = array_merge($cart->cartData['VatTax'],$cart->cartData['taxRulesBill']);
@@ -566,273 +412,6 @@ class plgVmShipmentRules_Shipping_Base extends vmPSPlugin {
 
 	}
 
-	protected function createMethodRule ($r, $countries, $tax) {
-		return new ShippingRule($this, $r, $countries, $tax);
-	}
-
-	private function parseMethodRule ($rulestring, $countries, $tax, &$method) {
-		$rules1 = preg_split("/(\r\n|\n|\r)/", $rulestring);
-		foreach ($rules1 as $r) {
-			// Ignore empty lines
-			if (empty($r)) continue;
-			$this->rules[$method->virtuemart_shipmentmethod_id][] = $this->createMethodRule ($r, $countries, $tax);
-		}
-	}
-	
-	protected function parseMethodRules (&$method) {
-		if (!isset($this->rules[$method->virtuemart_shipmentmethod_id])) 
-			$this->rules[$method->virtuemart_shipmentmethod_id] = array();
-		$this->parseMethodRule ($method->rules1, $method->countries1, $method->tax_id1, $method);
-		$this->parseMethodRule ($method->rules2, $method->countries2, $method->tax_id2, $method);
-		$this->parseMethodRule ($method->rules3, $method->countries3, $method->tax_id3, $method);
-		$this->parseMethodRule ($method->rules4, $method->countries4, $method->tax_id4, $method);
-		$this->parseMethodRule ($method->rules5, $method->countries5, $method->tax_id5, $method);
-		$this->parseMethodRule ($method->rules6, $method->countries6, $method->tax_id6, $method);
-		$this->parseMethodRule ($method->rules7, $method->countries7, $method->tax_id7, $method);
-		$this->parseMethodRule ($method->rules8, $method->countries8, $method->tax_id8, $method);
-	}
-
-	/** Functions to calculate all the different variables for the given cart and given (sub)set of products in the cart */
-	protected function getOrderArticles (VirtueMartCart $cart, $products) {
-		$articles = 0;
-		foreach ($products as $product) {
-			$articles += $product->quantity;
-		}
-		return $articles;
-	}
-
-	protected function getOrderProducts (VirtueMartCart $cart, $products) {
-		return count($products);
-	}
-
-	protected function getOrderDimensions (VirtueMartCart $cart, $products, $length_dimension) {
-		/* Cache the value in a static variable and calculate it only once! */
-		$dimensions=array(
-			'volume' => 0,
-			'maxvolume' => 0, 'minvolume' => 9999999999,
-			'maxlength' => 0, 'minlength' => 9999999999, 'totallength' => 0,
-			'maxwidth'  => 0, 'minwidth' => 9999999999,  'totalwidth'  => 0,
-			'maxheight' => 0, 'minheight' => 9999999999, 'totalheight' => 0,
-			'maxpackaging' => 0, 'minpackaging' => 9999999999, 'totalpackaging' => 0,
-		);
-		foreach ($products as $product) {
-	
-			$l = ShopFunctions::convertDimensionUnit ($product->product_length, $product->product_lwh_uom, $length_dimension);
-			$w = ShopFunctions::convertDimensionUnit ($product->product_width, $product->product_lwh_uom, $length_dimension);
-			$h = ShopFunctions::convertDimensionUnit ($product->product_height, $product->product_lwh_uom, $length_dimension);
-
-			$volume = $l * $w * $h;
-			$dimensions['volume'] += $volume * $product->quantity;
-			$dimensions['maxvolume'] = max ($dimensions['maxvolume'], $volume);
-			$dimensions['minvolume'] = min ($dimensions['minvolume'], $volume);
-				
-			$dimensions['totallength'] += $l * $product->quantity;
-			$dimensions['maxlength'] = max ($dimensions['maxlength'], $l);
-			$dimensions['minlength'] = min ($dimensions['minlength'], $l);
-			$dimensions['totalwidth'] += $w * $product->quantity;
-			$dimensions['maxwidth'] = max ($dimensions['maxwidth'], $w);
-			$dimensions['minwidth'] = min ($dimensions['minwidth'], $w);
-			$dimensions['totalheight'] += $h * $product->quantity;
-			$dimensions['maxheight'] = max ($dimensions['maxheight'], $h);
-			$dimensions['minheight'] = min ($dimensions['minheight'], $h);
-			$dimensions['totalpackaging'] += $product->product_packaging * $product->quantity;
-			$dimensions['maxpackaging'] = max ($dimensions['maxpackaging'], $product->product_packaging);
-			$dimensions['minpackaging'] = min ($dimensions['minpackaging'], $product->product_packaging);
-		}
-
-		return $dimensions;
-	}
-	
-	protected function getOrderWeights (VirtueMartCart $cart, $products, $weight_unit) {
-		$dimensions=array(
-			'weight' => 0,
-			'maxweight' => 0, 'minweight' => 9999999999,
-		);
-		foreach ($products as $product) {
-			$w = ShopFunctions::convertWeigthUnit ($product->product_weight, $product->product_weight_uom, $weight_unit);
-			$dimensions['maxweight'] = max ($dimensions['maxweight'], $w);
-			$dimensions['minweight'] = min ($dimensions['minweight'], $w);
-			$dimensions['weight'] += $w * $product->quantity;
-		}
-		return $dimensions;
-	}
-	
-	protected function getOrderListProperties (VirtueMartCart $cart, $products) {
-		$categories = array();
-		$vendors = array();
-		$skus = array();
-		$manufacturers = array();
-		foreach ($products as $product) {
-			$skus[] = $product->product_sku;
-			$categories = array_merge ($categories, $product->categories);
-			$vendors[] = $product->virtuemart_vendor_id;
-			if (is_array($product->virtuemart_manufacturer_id)) {
-				$manufacturers = array_merge($manufacturers, $product->virtuemart_manufacturer_id);
-			} elseif ($product->virtuemart_manufacturer_id) {
-				$manufacturers[] = $product->virtuemart_manufacturer_id;
-			}
-		}
-		$skus = array_unique($skus);
-		$vendors = array_unique($vendors);
-		$categories = array_unique($categories);
-		$manufacturers = array_unique($manufacturers);
-		return array ('skus'=>$skus, 
-			      'categories'=>$categories,
-			      'vendors'=>$vendors,
-			      'manufacturers'=>$manufacturers,
-		);
-	}
-	
-	protected function getOrderCountryState (VirtueMartCart $cart, $address) {
-		$data = array (
-			'countryid' => 0, 'country' => '', 'country2' => '', 'country3' => '',
-			'stateid'   => 0, 'state'   => '', 'state2'   => '', 'state3'   => '',
-		);
-		
-		$countriesModel = VmModel::getModel('country');
-		if (isset($address['virtuemart_country_id'])) {
-			$data['countryid'] = $address['virtuemart_country_id'];
-			// The following is a workaround to make sure the cache is invalidated
-			// because if some other extension meanwhile called $countriesModel->getCountries,
-			// the cache will be modified, but the model's id will not be changes, so the
-			// getData call will return the wrong cache.
-			$countriesModel->setId(0); 
-			$countriesModel->setId($address['virtuemart_country_id']);
-			$country = $countriesModel->getData($address['virtuemart_country_id']);
-			if (!empty($country)) {
-				$data['country'] = $country->country_name;
-				$data['country2'] = $country->country_2_code;
-				$data['country3'] = $country->country_3_code;
-			}
-		}
-		
-		$statesModel = VmModel::getModel('state');
-		if (isset($address['virtuemart_state_id'])) {
-			$data['stateid'] = $address['virtuemart_state_id'];
-			// The following is a workaround to make sure the cache is invalidated
-			// because if some other extension meanwhile called $countriesModel->getCountries,
-			// the cache will be modified, but the model's id will not be changes, so the
-			// getData call will return the wrong cache.
-			$statesModel->setId(0); 
-			$statesModel->setId($address['virtuemart_state_id']);
-			$state = $statesModel->getData($address['virtuemart_state_id']);
-			if (!empty($state)) {
-				$data['state'] = $state->state_name;
-				$data['state2'] = $state->state_2_code;
-				$data['state3'] = $state->state_3_code;
-			}
-		}
-		
-		return $data;
-
-	}
-	
-	protected function getOrderAddress (VirtueMartCart $cart, $address) {
-		$zip = isset($address['zip'])?trim($address['zip']):'';
-		$data = array('zip'=>$zip,
-			'zip1'=>substr($zip,0,1),
-			'zip2'=>substr($zip,0,2),
-			'zip3'=>substr($zip,0,3),
-			'zip4'=>substr($zip,0,4),
-			'zip5'=>substr($zip,0,5),
-			'zip6'=>substr($zip,0,6),
-			'city'=>isset($address['city'])?trim($address['city']):'',
-		);
-		$data['company'] = isset($address['company'])?$address['company']:'';
-		$data['title'] = isset($address['title'])?$address['title']:'';
-		$data['first_name'] = isset($address['title'])?$address['title']:'';
-		$data['middle_name'] = isset($address['middle_name'])?$address['middle_name']:'';
-		$data['last_name'] = isset($address['last_name'])?$address['last_name']:'';
-		$data['address1'] = isset($address['address_1'])?$address['address_1']:'';
-		$data['address2'] = isset($address['address_2'])?$address['address_2']:'';
-		$data['city'] = isset($address['city'])?$address['city']:'';
-		$data['phone1'] = isset($address['phone_1'])?$address['phone_1']:'';
-		$data['phone2'] = isset($address['phone_2'])?$address['phone_2']:'';
-		$data['fax'] = isset($address['fax'])?$address['fax']:'';
-		$data['email'] = isset($address['email'])?$address['email']:'';
-		return $data;
-	}
-	
-	protected function getOrderPrices (VirtueMartCart $cart, $products, $cart_prices) {
-		$data = array(
-			'amount' => 0, 
-			'amountwithtax' => 0, 
-			'amountwithouttax' => 0, 
-			'baseprice' => 0, 
-			'basepricewithtax' => 0, 
-			'discountedpricewithouttax' => 0, 
-			'salesprice' => 0, 
-			'taxamount' => 0, 
-			'salespricewithdiscount' => 0, 
-			'discountamount' => 0, 
-			'pricewithouttax' => 0,
-		);
-		if (!empty($cart_prices)) {
-			// get prices for the whole cart -> simply user the cart_prices
-			$data['amount']                 = $cart_prices['salesPrice'];
-			$data['amountwithtax']          = $cart_prices['salesPrice'];
-			$data['amountwithouttax']       = $cart_prices['priceWithoutTax'];
-			$data['baseprice']              = $cart_prices['basePrice'];
-			$data['basepricewithtax']       = $cart_prices['basePriceWithTax'];
-			$data['discountedpricewithouttax'] = $cart_prices['discountedPriceWithoutTax'];
-			$data['salesprice']             = $cart_prices['salesPrice'];
-			$data['taxamount']              = $cart_prices['taxAmount'];
-			$data['salespricewithdiscount'] = $cart_prices['salesPriceWithDiscount'];
-			$data['discountamount']         = $cart_prices['discountAmount'];
-			$data['pricewithouttax']        = $cart_prices['priceWithoutTax'];
-		} else {
-			// Calculate the prices from the individual products!
-			// Possible problems are discounts on the order total
-			foreach ($products as $product) {
-				$data['amount']                    += $product->quantity*$product->allPrices[$product->selectedPrice]['salesPrice'];
-				$data['amountwithtax']             += $product->quantity*$product->allPrices[$product->selectedPrice]['salesPrice'];
-				$data['amountwithouttax']          += $product->quantity*$product->allPrices[$product->selectedPrice]['priceWithoutTax'];
-				$data['baseprice']                 += $product->quantity*$product->allPrices[$product->selectedPrice]['basePrice'];
-				$data['basepricewithtax']          += $product->quantity*$product->allPrices[$product->selectedPrice]['basePriceWithTax'];
-				$data['discountedpricewithouttax'] += $product->quantity*$product->allPrices[$product->selectedPrice]['discountedPriceWithoutTax'];
-				$data['salesprice']                += $product->quantity*$product->allPrices[$product->selectedPrice]['salesPrice'];
-				$data['taxamount']                 += $product->quantity*$product->allPrices[$product->selectedPrice]['taxAmount'];
-				$data['salespricewithdiscount']    += $product->quantity*$product->allPrices[$product->selectedPrice]['salesPriceWithDiscount'];
-				$data['discountamount']            += $product->quantity*$product->allPrices[$product->selectedPrice]['discountAmount'];
-				$data['pricewithouttax']           += $product->quantity*$product->allPrices[$product->selectedPrice]['priceWithoutTax'];
-			}
-		}
-		return $data;
-	}
-
-	/** Allow child classes to add additional variables for the rules or modify existing one
-	 */
-	protected function addCustomCartValues (VirtueMartCart $cart, $products, $cart_prices, &$values) {
-	}
-
-	public function getCartValues (VirtueMartCart $cart, $products, $method, $cart_prices) {
-		$address = (($cart->ST == 0 || $cart->STsameAsBT == 1) ? $cart->BT : $cart->ST);
-		$cartvals = array_merge (
-			array(
-				'articles'=>$this->getOrderArticles($cart, $products),
-				'products'=>$this->getOrderProducts($cart, $products),
-			),
-			// Add the prices, optionally calculated from the products subset of the cart
-			$this->getOrderPrices ($cart, $products, $cart_prices),
-			// Add 'skus', 'categories', 'vendors' variables:
-			$this->getOrderListProperties ($cart, $products),
-			// Add country / state variables:
-			$this->getOrderAddress ($cart, $address),
-			$this->getOrderCountryState ($cart, $address),
-			// Add Total/Min/Max weight and dimension variables:
-			$this->getOrderWeights ($cart, $products, $method->weight_unit),
-			$this->getOrderDimensions ($cart, $products, $method->length_unit)
-		);
-		// Let child classes update the $cartvals array, or add new variables
-		$this->addCustomCartValues($cart, $products, $cart_prices, $cartvals);
-
-		// Finally, call the triger of vmshipmentrules plugins to let them add/modify variables
-		JPluginHelper::importPlugin('vmshipmentrules');
-		JDispatcher::getInstance()->trigger('onVmShippingRulesGetCartValues',array(&$cartvals, $cart, $products, $method, $cart_prices));
-
-		return $cartvals;
-	}
-
 	/**
 	 * Create the table for this plugin if it does not yet exist.
 	 * This functions checks if the called plugin is active one.
@@ -934,16 +513,14 @@ class plgVmShipmentRules_Shipping_Base extends vmPSPlugin {
 		if (isset($data['rules1'])) {
 			// Try to parse all rules (and spit out error) to inform the user. There is no other 
 			// reason to parse the rules here, it's really only to trigger warnings/errors in case of a syntax error.
-			$method = new StdClass ();
-			$method->virtuemart_shipmentmethod_id = $data['virtuemart_shipmentmethod_id'];
-			$this->parseMethodRule ($data['rules1'], isset($data['countries1'])?$data['countries1']:array(), $data['tax_id1'], $method);
-			$this->parseMethodRule ($data['rules2'], isset($data['countries2'])?$data['countries2']:array(), $data['tax_id2'], $method);
-			$this->parseMethodRule ($data['rules3'], isset($data['countries3'])?$data['countries3']:array(), $data['tax_id3'], $method);
-			$this->parseMethodRule ($data['rules4'], isset($data['countries4'])?$data['countries4']:array(), $data['tax_id4'], $method);
-			$this->parseMethodRule ($data['rules5'], isset($data['countries5'])?$data['countries5']:array(), $data['tax_id5'], $method);
-			$this->parseMethodRule ($data['rules6'], isset($data['countries6'])?$data['countries6']:array(), $data['tax_id6'], $method);
-			$this->parseMethodRule ($data['rules7'], isset($data['countries7'])?$data['countries7']:array(), $data['tax_id7'], $method);
-			$this->parseMethodRule ($data['rules8'], isset($data['countries8'])?$data['countries8']:array(), $data['tax_id8'], $method);
+			$this->helper->parseRuleSyntax ($data['rules1'], isset($data['countries1'])?$data['countries1']:array(), $data['tax_id1']);
+			$this->helper->parseRuleSyntax ($data['rules2'], isset($data['countries2'])?$data['countries2']:array(), $data['tax_id2']);
+			$this->helper->parseRuleSyntax ($data['rules3'], isset($data['countries3'])?$data['countries3']:array(), $data['tax_id3']);
+			$this->helper->parseRuleSyntax ($data['rules4'], isset($data['countries4'])?$data['countries4']:array(), $data['tax_id4']);
+			$this->helper->parseRuleSyntax ($data['rules5'], isset($data['countries5'])?$data['countries5']:array(), $data['tax_id5']);
+			$this->helper->parseRuleSyntax ($data['rules6'], isset($data['countries6'])?$data['countries6']:array(), $data['tax_id6']);
+			$this->helper->parseRuleSyntax ($data['rules7'], isset($data['countries7'])?$data['countries7']:array(), $data['tax_id7']);
+			$this->helper->parseRuleSyntax ($data['rules8'], isset($data['countries8'])?$data['countries8']:array(), $data['tax_id8']);
 		}
 		$ret=$this->setOnTablePluginParams ($name, $id, $table);
 		return $ret;
@@ -951,541 +528,4 @@ class plgVmShipmentRules_Shipping_Base extends vmPSPlugin {
 
 }
 
-if (class_exists ('ShippingRule')) {
-	return;
-}
-
-/** Filter the given array of products and return only those that belong to the categories, manufacturers, 
- *  vendors or products given in the $filter_conditions. The $filter_conditions is an array of the form:
- *     array( 'skus'=>array(....), 'categories'=>array(1,2,3,42), 'manufacturers'=>array(77,78,83), 'vendors'=>array(1,2))
- *  Notice that giving an empty array for any of the keys means "no restriction" and is exactly the same 
- *  as leaving out the enty altogether
- */
-function filterProducts($products, $filter_conditions) {
-	$result = array();
-	foreach ($products as $p) {
-// JFactory::getApplication()->enqueueMessage("<pre>Product: ".print_r($p,1)."</pre>", 'error');
-		if (!empty($filter_conditions['skus']) && !in_array($p->product_sku, $filter_conditions['skus']))
-			continue;
-		if (!empty($filter_conditions['categories']) && count(array_intersect($filter_conditions['categories'], $p->categories))==0)
-			continue;
-		if (!empty($filter_conditions['manufacturers']) && count(array_intersect($filter_conditions['manufacturers'], $p->virtuemart_manufacturer_id))==0)
-			continue;
-		if (!empty($filter_conditions['vendors']) && !in_array($p->virtuemart_vendor_id, $filter_conditions['vendors']))
-			continue;
-		$result[] = $p;
-	}
-	return $result;
-}
-	
-
-class ShippingRule {
-	var $plugin = Null;
-	var $rulestring = '';
-	var $name = '';
-	var $ruletype = '';
-	var $evaluated = False;
-	var $match = False;
-	var $value = Null;
-	
-	var $shipping = 0;
-	var $conditions = array();
-	var $countries = array();
-	var $tax_id = 0;
-	var $includes_tax = 0;
-	
-	function __construct ($plugin, $rule, $countries, $tax_id) {
-		if (is_array($countries)) {
-			$this->countries = $countries;
-		} elseif (!empty($countries)) {
-			$this->countries[0] = $countries;
-		}
-		$this->tax_id = $tax_id;
-		$this->rulestring = $rule;
-		$this->parseRule($rule);
-		$this->plugin=$plugin;
-	}
-	
-	protected function parseRule($rule) {
-		$ruleparts=explode(';', $rule);
-		foreach ($ruleparts as $p) {
-			$this->parseRulePart($p);
-		}
-	}
-	
-	protected function handleAssignment ($var, $value, $rulepart) {
-		switch (strtolower($var)) {
-			case 'name':            $this->name = $value; break;
-			case 'shipping':        $this->shipping = $value; $this->includes_tax = False; $this->ruletype='shipping'; break;
-			case 'shippingwithtax': $this->shipping = $value; $this->includes_tax = True; $this->ruletype='shipping'; break;
-			case 'variable':        // Variable=... is the same as Definition=...
-			case 'definition':      $this->name = strtolower($value); $this->ruletype = 'definition'; break;
-			case 'value':           $this->shipping = $value; $this->ruletype = 'definition'; break; // definition values are also stored in the shipping member!
-			case 'extrashippingcharge': $this->shipping = $value; $this->ruletype = 'modifiers_add'; break; // modifiers are also stored in the shipping member!
-			case 'extrashippingmultiplier': $this->shipping = $value; $this->ruletype = 'modifiers_multiply'; break; // modifiers are also stored in the shipping member!
-			case 'comment':         break; // Completely ignore all comments!
-			case 'condition':       $this->conditions[] = $value; break;
-			default:                JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_VARIABLE', $var, $rulepart), 'error');
-		}
-	}
-	
-	protected function tokenize_expression ($expression) {
-		// First, extract all strings, delimited by quotes, then all text operators 
-		// (OR, AND, in; but make sure we don't capture parts of words, so we need to 
-		// use lookbehind/lookahead patterns to exclude OR following another letter 
-		// or followed by another letter) and then all arithmetic operators
-		$re = '/\s*("[^"]*"|\'[^\']*\'|<=|=>|>=|=<|<>|!=|==|<|=|>)\s*/i';
-		$atoms = preg_split($re, $expression, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
-		// JFactory::getApplication()->enqueueMessage("TOKENIZING '$expression' returns: <pre>".print_r($atoms,1)."</pre>", 'error');
-		return $atoms;
-	}
-	
-	protected function parseRulePart($rulepart) {
-		/* In the basic version, we only split at the comparison operators and assume each term on the LHS and RHS is one variable or constant */
-		/* In the advanced version, all conditions and costs can be given as a full mathematical expression */
-		/* Both versions create an expression tree, which can be easily evaluated in evaluateTerm */
-		$rulepart = trim($rulepart);
-		if (empty($rulepart)) return;
-
-		
-		// Special-case the name assignment, where we don't want to interpret the value as an arithmetic expression!
-		if (preg_match('/^\s*(name|variable|definition)\s*=\s*(["\']?)(.*)\2\s*$/i', $rulepart, $matches)) {
-			$this->handleAssignment ($matches[1], $matches[3], $rulepart);
-			return;
-		}
-
-		// Split at all operators:
-		$atoms = $this->tokenize_expression ($rulepart);
-		
-		/* TODO: Starting from here, the advanced plugin is different! */
-		$operators = array('<', '<=', '=', '>', '>=', '=>', '=<', '<>', '!=', '==');
-		if (count($atoms)==1) {
-			$this->shipping = $this->parseShippingTerm($atoms[0]);
-			$this->ruletype = 'shipping';
-		} elseif ($atoms[1]=='=') {
-			$this->handleAssignment ($atoms[0], $atoms[2], $rulepart);
-		} else {
-			// Conditions, need at least three atoms!
-			while (count($atoms)>1) {
-				if (in_array ($atoms[1], $operators)) {
-					$this->conditions[] = array($atoms[1], $this->parseShippingTerm($atoms[0]), $this->parseShippingTerm($atoms[2]));
-					array_shift($atoms);
-					array_shift($atoms);
-				} else {
-					JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_OPERATOR', $atoms[1], $rulepart), 'error');
-					$atoms = array();
-				}
-			}
-		}
-	}
-
-	protected function parseShippingTerm($expr) {
-		/* In the advanced version, shipping cost can be given as a full mathematical expression */
-		// If the shipping term starts with a double quote, it is a string, so don't turn it into lowercase.
-		// All other expressions need to be turned into lowercase, because variable names are case-insensitive!
-		if (substr($expr, 0, 1) === '"') {
-			return $expr;
-		} else {
-			return strtolower($expr);
-		}
-	}
-	
-	protected function evaluateComparison ($terms, $vals) {
-		while (count($terms)>2) {
-			$res = false;
-			switch ($terms[1]) {
-				case '<':  $res = ($terms[0] < $terms[2]);  break;
-				case '<=':
-				case '=<': $res = ($terms[0] <= $terms[2]); break;
-				case '==': $res = is_equal($terms[0], $terms[2]); break;
-				case '!=':
-				case '<>': $res = ($terms[0] != $terms[2]); break;
-				case '>=':
-				case '=>': $res = ($terms[0] >= $terms[2]); break;
-				case '>':  $res = ($terms[0] >  $terms[2]);  break;
-				case '~':
-					$l=min(strlen($terms[0]), strlen($terms[2]));
-					$res = (strncmp ($terms[0], $terms[2], $l) == 0);
-					break;
-				default:
-					JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_OPERATOR', $terms[1], $this->rulestring), 'error');
-					$res = false;
-			}
-
-			if ($res==false) return false;
-			// Remove the first operand and the operator from the comparison:
-			array_shift($terms);
-			array_shift($terms);
-		}
-		if (count($terms)>1) {
-			// We do not have the correct number of terms for chained comparisons, i.e. two terms leftover instead of one!
-			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_ERROR', $this->rulestring), 'error');
-			return false;
-		}
-		// All conditions were fulfilled, so we can return true
-		return true;
-	}
-	
-	protected function evaluateListFunction ($function, $args) {
-		# First make sure that all arguments are actually lists:
-		$allarrays = True;
-		foreach ($args as $a) {
-			$allarrays = $allarrays && is_array($a);
-		}
-		if (!$allarrays) {
-			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_LISTFUNCTION_ARGS', $function, $this->rulestring), 'error');
-			return false;
-			
-		}
-		switch ($function) {
-			case "length":		return count($args[0]); break;
-			case "union": 
-			case "join":		return call_user_func_array( "array_merge" , $args); break;
-			case "complement":	return call_user_func_array( "array_diff" , $args); break;
-			case "intersection":	return call_user_func_array( "array_intersect" , $args); break;
-			case "issubset":	# Remove all of superset's elements to see if anything else is left: 
-						return !array_diff($args[0], $args[1]); break;
-			case "contains":	# Remove all of superset's elements to see if anything else is left: 
-						# Notice the different argument order compared to issubset!
-						return !array_diff($args[1], $args[0]); break;
-			case "list_equal":	return array_unique($args[0])==array_unique($args[1]); break;
-			default: 
-				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_LISTFUNCTION_UNKNOWN', $function, $this->rulestring), 'error');
-				return false;
-		}
-	}
-	
-	protected function evaluateListContainmentFunction ($function, $args) {
-		# First make sure that the first argument is a list:
-		if (!is_array($args[0])) {
-			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_LISTFUNCTION_CONTAIN_ARGS', $function, $this->rulestring), 'error');
-			return false;
-		}
-		// Extract the array from the args, the $args varialbe will now only contain the elements to be checked:
-		$array = array_shift($args);
-		switch ($function) {
-			case "contains_any": // return true if one of the $args is in the $array
-					foreach ($args as $a) { 
-						if (in_array($a, $array)) 
-							return true; 
-					}
-					return false;
-			
-			case "contains_all": // return false if one of the $args is NOT in the $array
-					foreach ($args as $a) { 
-						if (!in_array($a, $array)) 
-							return false; 
-					}
-					return true;
-			case "contains_only": // return false if one of the $array elements is NOT in $args
-					foreach ($array as $a) {
-						if (!in_array($a, $args))
-							return false;
-					}
-					return true;
-			case "contains_none": // return false if one of the $args IS in the $array
-					foreach ($args as $a) {
-						if (in_array($a, $array))
-							return false;
-					}
-					return true;
-			default: 
-				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_LISTFUNCTION_UNKNOWN', $function, $this->rulestring), 'error');
-				return false;
-		}
-	}
-	
-	/** Evaluate the given expression $expr only for the products that match the filter given by the scoping 
-	 * function and the corresponding conditions */
-	protected function evaluateScoping($expr, $scoping, $conditionvals, $vals, $products, $cartvals_callback) {
-// JFactory::getApplication()->enqueueMessage("<pre>Scoping, begin, scoping=$scoping, expression=".print_r($expr,1).", conditionvals=".print_r($conditionvals, 1)."</pre>", 'error');
-		if (count($conditionvals)<1)
-			return $this->evaluateTerm($expr, $vals, $products, $cartvals_callback);
-
-		$filterkeys = array( 
-			"evaluate_for_categories" =>    'categories',
-			"evaluate_for_products" =>      'products',
-			"evaluate_for_vendors" =>       'vendors',
-			"evaluate_for_manufacturers" => 'manufacturers'
-		);
-		
-		$conditions = array();
-		if (isset($filterkeys[$scoping])) 
-			$conditions[$filterkeys[$scoping]] = $conditionvals;
-
-		// Pass the conditions to the parent plugin class to filter the current list of products:
-		$filteredproducts = filterProducts($products, $conditions);
-		// We have been handed a callback function to calculate the cartvals for the filtered list of products, so use it:
-		$filteredvals = $cartvals_callback($filteredproducts);
-		return $this->evaluateTerm ($expr, $filteredvals, $filteredproducts, $cartvals_callback);
-	}
-
-	protected function evaluateFunction ($function, $args) {
-		$func = strtolower($function);
-		// Check if we have a custom function definition and use that if so.
-		// This is done first to allow plugins to override even built-in functions!
-		if (isset($this->plugin->custom_functions[$func])) {
-			vmDebug("Evaluating custom function $function, defined by a plugin");
-			return call_user_func($this->plugin->custom_functions[$func], $args, $this);
-		}
-
-		// Functions with no argument:
-		if (count($args) == 0) {
-			$dt = getdate();
-			switch ($func) {
-				case "second": return $dt['seconds']; break;
-				case "minute": return $dt['minutes']; break;
-				case "hour":   return $dt['hours']; break;
-				case "day":    return $dt['mday']; break;
-				case "weekday":return $dt['wday']; break;
-				case "month":  return $dt['mon']; break;
-				case "year":   return $dt['year']; break;
-				case "yearday":return $dt['yday']; break;
-			}
-		}
-		// Functions with exactly one argument:
-		if (count($args) == 1) {
-			switch ($func) {
-				case "round": return round($args[0]); break;
-				case "ceil":  return ceil ($args[0]); break;
-				case "floor": return floor($args[0]); break;
-				case "abs":   return abs($args[0]); break;
-				case "not":   return !$args[0]; break;
-				case "print_r": return print_r($args[0],1); break; 
-			}
-		}
-		if (count($args) == 2) {
-			switch ($func) {
-				case "digit": return substr($args[0], $args[1]-1, 1); break;
-				case "round": return round($args[0]/$args[1])*$args[1]; break;
-				case "ceil":  return ceil($args[0]/$args[1])*$args[1]; break;
-				case "floor": return floor($args[0]/$args[1])*$args[1]; break;
-			}
-		}
-		if (count($args) == 3) {
-			switch ($func) {
-				case "substring": return substr($args[0], $args[1]-1, $args[2]); break;
-			}
-		}
-		// Functions with variable number of args
-		switch ($func) {
-			case "max": 
-					return max($args);
-			case "min": 
-					return min($args);
-			case "list": 
-			case "array": 
-					return $args;
-			// List functions:
-		    case "length":
-		    case "complement":
-		    case "issubset":
-		    case "contains":
-		    case "union":
-		    case "join":
-		    case "intersection":
-		    case "list_equal":
-					return $this->evaluateListFunction ($func, $args);
-			case "contains_any": 
-			case "contains_all":
-			case "contains_only":
-			case "contains_none":
-					return $this->evaluateListContainmentFunction($func, $args);
-			
-		}
-		
-		// None of the built-in function 
-		// No known function matches => print an error, return 0
-		JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_FUNCTION', $function, $this->rulestring), 'error');
-		return 0;
-	}
-
-	protected function evaluateVariable ($expr, $vals) {
-		$varname = strtolower($expr);
-		if (array_key_exists(strtolower($expr), $vals)) {
-			return $vals[strtolower($expr)];
-		} elseif ($varname=='noshipping') {
-			return $varname;
-		} elseif ($varname=='values') {
-			return $vals;
-		} elseif ($varname=='values_debug') {
-			return print_r($vals,1);
-		} else {
-			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_VALUE', $expr, $this->rulestring), 'error');
-			return null;
-		}
-	}
-
-	protected function evaluateTerm ($expr, $vals, $products, $cartvals_callback) {
-		// The scoping functions need to be handled differently, because they first need to adjust the cart variables to the filtered product list
-		// before evaluating its first argument. So even though parsing the rules handles scoping functions like any other function, their 
-		// evaluation is fundamentally different and is special-cased here:
-		$scoping_functions = array("evaluate_for_categories", "evaluate_for_products", "evaluate_for_vendors", "evaluate_for_manufacturers");
-		$is_scoping = is_array($expr) && ($expr[0]=="FUNCTION") && (count($expr)>1) && in_array($expr[1], $scoping_functions);
-
-		if (is_null($expr)) {
-			return $expr;
-		} elseif (is_numeric ($expr)) {
-			return $expr;
-		} elseif (is_string ($expr)) {
-			// Explicit strings are delimited by '...' or "..."
-			if (($expr[0]=='\'' || $expr[0]=='"') && ($expr[0]==substr($expr,-1)) ) {
-				return substr($expr,1,-1);
-			} else {
-				return $this->evaluateVariable($expr, $vals);
-			}
-		} elseif ($is_scoping) {
-			$op = array_shift($expr); // ignore the "FUNCTION"
-			$func = array_shift($expr); // The scoping function name
-			$expression = array_shift($expr); // The expression to be evaluated
-			$conditions = $expr; // the remaining $expr list now contains the conditions
-			return $this->evaluateScoping ($expression, $func, $conditions, $vals, $products, $cartvals_callback);
-			
-		} elseif (is_array($expr)) {
-			// Operator
-			$op = array_shift($expr);
-			$args = array();
-			// First evaluate all operands and only after that apply the function / operator to the already evaluated arguments
-			$evaluate = true;
-			if ($op == "FUNCTION") {
-				$evaluate = false;
-			}
-			foreach ($expr as $e) {
-				$term = $evaluate ? ($this->evaluateTerm($e, $vals, $products, $cartvals_callback)) : $e;
-				if ($op == 'COMPARISON') {
-					// For comparisons, we only evaluate every other term (the operators are NOT evaluated!)
-					// The data format for comparisons is: array('COMPARISON', $operand1, '<', $operand2, '<=', ....)
-					$evaluate = !$evaluate;
-				}
-				if ($op == "FUNCTION") {
-					$evaluate = true;
-				}
-				if (is_null($term)) return null;
-				$args[] = $term;
-			}
-			$res = false;
-			// Finally apply the operaton to the evaluated argument values:
-			switch ($op) {
-				// Logical operators:
-				case 'OR':  foreach ($args as $a) { $res = ($res || $a); }; break;
-				case '&&':
-				case 'AND':  $res = true; foreach ($args as $a) { $res = ($res && $a); }; break;
-				case 'IN': $res = in_array($args[0], $args[1]);  break;
-				
-				// Comparisons:
-				case '<':
-				case '<=':
-				case '=<':
-				case '==':
-				case '!=':
-				case '<>':
-				case '>=':
-				case '=>':
-				case '>':
-				case '~':
-					$res = $this->evaluateComparison(array($args[0], $op, $args[1]), $vals); break;
-				case 'COMPARISON':
-					$res = $this->evaluateComparison($args, $vals); break;
-				
-				// Unary operators:
-				case '.-': $res = -$args[0]; break;
-				case '.+': $res = $args[0]; break;
-				
-				// Binary operators
-				case "+":  $res = ($args[0] +  $args[1]); break;
-				case "-":  $res = ($args[0] -  $args[1]); break;
-				case "*":  $res = ($args[0] *  $args[1]); break;
-				case "/":  $res = ($args[0] /  $args[1]); break;
-				case "%":  $res = (fmod($args[0],  $args[1])); break;
-				case "^":  $res = ($args[0] ^  $args[1]); break;
-				
-				// Functions:
-				case "FUNCTION": $func = array_shift($args); $res = $this->evaluateFunction($func, $args); break;
-				
-				default:   $res = false;
-			}
-			
-// 			JFactory::getApplication()->enqueueMessage("<pre>Result of ".print_r($expr,1)." is $res.</pre>", 'error');
-			return $res;
-		} else {
-			// Neither string nor numeric, nor operator...
-			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_VALUE', $expr, $this->rulestring), 'error');
-			return null;
-		}
-	}
-
-	protected function calculateShipping ($vals, $products, $cartvals_callback) {
-		return $this->evaluateTerm($this->shipping, $vals, $products, $cartvals_callback);
-	}
-
-	protected function evaluateRule (&$vals, $products, $cartvals_callback) {
-		if ($this->evaluated) 
-			return; // Already evaluated
-
-		$this->evaluated = True;
-		$this->match = False; // Default, set it to True below if all conditions match...
-		// First, check the country, if any conditions are given:
-		if (count ($this->countries) > 0 && !in_array ($vals['countryid'], $this->countries)) {
-// 			vmdebug('Rule::matches: Country check failed: countryid='.print_r($vals['countryid'],1).', countries are: '.print_r($this->countries,1).'...');
-			return;
-		}
-
-		foreach ($this->conditions as $c) {
-			// All conditions have to match!
-			$ret = $this->evaluateTerm($c, $vals, $products, $cartvals_callback);
-
-			if (is_null($ret) || (!$ret)) {
-				return;
-			}
-		}
-		// All conditions match
-		$this->match = True;
-		// Calculate the value (i.e. shipping cost or modifier)
-		$this->value = $this->calculateShipping($vals, $products, $cartvals_callback);
-		// Evaluate the rule name as a translatable string with variables inserted:
-		// Replace all {variable} tags in the name by the variables from $vals
-		$matches=array();
-		$name=JText::_($this->name);
-		preg_match_all('/{([A-Za-z0-9_]+)}/', $name, $matches);
-		
-		foreach ($matches[1] as $m) {
-			$val = $this->evaluateVariable($m, $vals);
-			if ($val !== null) {
-				$name = str_replace("{".$m."}", $val, $name);
-			}
-		}
-		$this->rulename = $name;
-	}
-
-	function matches(&$vals, $products, $cartvals_callback) {
-		$this->evaluateRule($vals, $products, $cartvals_callback);
-		return $this->match;
-	}
-
-	function getType() {
-		return $this->ruletype;
-	}
-
-	function getRuleName() {
-		if (!$this->evaluated)
-			vmDebug('WARNING: getRuleName called without prior evaluation of the rule, e.g. by calling rule->matches(...)');
-		return $this->rulename;
-	}
-	
-	function getValue() {
-		if (!$this->evaluated)
-			vmDebug('WARNING: getValue called without prior evaluation of the rule, e.g. by calling rule->matches(...)');
-		return $this->value;
-	}
-	function getShippingCosts() {
-		return $this->getValue();
-	}
-	
-	function isNoShipping() {
-		// NoShipping is set, so if the rule matches, this method should not offer any shipping at all
-		return (is_string($this->shipping) && (strtolower($this->shipping)=="noshipping"));
-	}
-
-}
-
 // No closing tag
diff --git a/rules_shipping_framework_joomla.php b/rules_shipping_framework_joomla.php
new file mode 100644
index 0000000000000000000000000000000000000000..74053e8b5da7abadd17b7eb74b220668cd613b36
--- /dev/null
+++ b/rules_shipping_framework_joomla.php
@@ -0,0 +1,289 @@
+<?php
+/**
+ * Shipping by Rules generic helper class (Joomla/VM-specific)
+ * Reinhold Kainhofer, Open Tools, office@open-tools.net
+ * @copyright (C) 2012-2015 - Reinhold Kainhofer
+ * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
+**/
+
+// defined('_JEXEC') or	 die( 'Direct Access to ' . basename( __FILE__ ) . ' is not allowed.' ) ;
+
+// if (!class_exists( 'VmConfig' )) 
+//     require(JPATH_ROOT.DS.'administrator'.DS.'components'.DS.'com_virtuemart'.DS.'helpers'.DS.'config.php');
+// VmConfig::loadConfig();
+
+if (!class_exists( 'RulesShippingFramework' )) 
+	require_once (dirname(__FILE__) . DS . 'library' . DS . 'rules_shipping_framework.php');
+
+// $test=new asdfasdsf();
+class RulesShippingFrameworkJoomla extends RulesShippingFramework {
+	function getCustomFunctions() {
+		// Let other plugins add custom functions! 
+		// The onVmShippingRulesRegisterCustomFunctions() trigger is expected to return an array of the form:
+		//   array ('functionname1' => 'function-to-be-called',
+		//          'functionname2' => array($classobject, 'memberfunc')),
+		//          ...);
+		JPluginHelper::importPlugin('vmshipmentrules');
+		$dispatcher = JDispatcher::getInstance();
+		$custfuncdefs = $dispatcher->trigger('onVmShippingRulesRegisterCustomFunctions',array());
+		
+		return array ();
+	}
+	
+	public function printWarning($message) {
+		// Keep track of warning messages, so we don't print them twice:
+		global $printed_warnings;
+		if (!isset($printed_warnings))
+			$printed_warnings = array();
+		if (!in_array($message, $printed_warnings)) {
+			JFactory::getApplication()->enqueueMessage($message, 'error');
+			$printed_warnings[] = $message;
+		}
+	}
+
+	/**
+	 * Functions to calculate the cart variables:
+	 *   - getOrderArticles($cart, $products)
+	 *   - getOrderProducts
+	 *   - getOrderDimensions
+	 */
+	/** Functions to calculate all the different variables for the given cart and given (sub)set of products in the cart */
+	protected function getOrderArticles ($cart, $products) {
+		$articles = 0;
+		foreach ($products as $product) {
+			$articles += $product->quantity;
+		}
+		return $articles;
+	}
+
+	protected function getOrderProducts ($cart, $products) {
+		return count($products);
+	}
+
+	protected function getOrderDimensions ($cart, $products, $length_dimension) {
+		/* Cache the value in a static variable and calculate it only once! */
+		$dimensions=array(
+			'volume' => 0,
+			'maxvolume' => 0, 'minvolume' => 9999999999,
+			'maxlength' => 0, 'minlength' => 9999999999, 'totallength' => 0,
+			'maxwidth'  => 0, 'minwidth' => 9999999999,  'totalwidth'  => 0,
+			'maxheight' => 0, 'minheight' => 9999999999, 'totalheight' => 0,
+			'maxpackaging' => 0, 'minpackaging' => 9999999999, 'totalpackaging' => 0,
+		);
+		foreach ($products as $product) {
+	
+			$l = ShopFunctions::convertDimensionUnit ($product->product_length, $product->product_lwh_uom, $length_dimension);
+			$w = ShopFunctions::convertDimensionUnit ($product->product_width, $product->product_lwh_uom, $length_dimension);
+			$h = ShopFunctions::convertDimensionUnit ($product->product_height, $product->product_lwh_uom, $length_dimension);
+
+			$volume = $l * $w * $h;
+			$dimensions['volume'] += $volume * $product->quantity;
+			$dimensions['maxvolume'] = max ($dimensions['maxvolume'], $volume);
+			$dimensions['minvolume'] = min ($dimensions['minvolume'], $volume);
+				
+			$dimensions['totallength'] += $l * $product->quantity;
+			$dimensions['maxlength'] = max ($dimensions['maxlength'], $l);
+			$dimensions['minlength'] = min ($dimensions['minlength'], $l);
+			$dimensions['totalwidth'] += $w * $product->quantity;
+			$dimensions['maxwidth'] = max ($dimensions['maxwidth'], $w);
+			$dimensions['minwidth'] = min ($dimensions['minwidth'], $w);
+			$dimensions['totalheight'] += $h * $product->quantity;
+			$dimensions['maxheight'] = max ($dimensions['maxheight'], $h);
+			$dimensions['minheight'] = min ($dimensions['minheight'], $h);
+			$dimensions['totalpackaging'] += $product->product_packaging * $product->quantity;
+			$dimensions['maxpackaging'] = max ($dimensions['maxpackaging'], $product->product_packaging);
+			$dimensions['minpackaging'] = min ($dimensions['minpackaging'], $product->product_packaging);
+		}
+
+		return $dimensions;
+	}
+	
+	protected function getOrderWeights ($cart, $products, $weight_unit) {
+		$dimensions=array(
+			'weight' => 0,
+			'maxweight' => 0, 'minweight' => 9999999999,
+		);
+		foreach ($products as $product) {
+			$w = ShopFunctions::convertWeigthUnit ($product->product_weight, $product->product_weight_uom, $weight_unit);
+			$dimensions['maxweight'] = max ($dimensions['maxweight'], $w);
+			$dimensions['minweight'] = min ($dimensions['minweight'], $w);
+			$dimensions['weight'] += $w * $product->quantity;
+		}
+		return $dimensions;
+	}
+	
+	protected function getOrderListProperties ($cart, $products) {
+		$categories = array();
+		$vendors = array();
+		$skus = array();
+		$manufacturers = array();
+		foreach ($products as $product) {
+			$skus[] = $product->product_sku;
+			$categories = array_merge ($categories, $product->categories);
+			$vendors[] = $product->virtuemart_vendor_id;
+			if (is_array($product->virtuemart_manufacturer_id)) {
+				$manufacturers = array_merge($manufacturers, $product->virtuemart_manufacturer_id);
+			} elseif ($product->virtuemart_manufacturer_id) {
+				$manufacturers[] = $product->virtuemart_manufacturer_id;
+			}
+		}
+		$skus = array_unique($skus);
+		$vendors = array_unique($vendors);
+		$categories = array_unique($categories);
+		$manufacturers = array_unique($manufacturers);
+		return array ('skus'=>$skus, 
+			      'categories'=>$categories,
+			      'vendors'=>$vendors,
+			      'manufacturers'=>$manufacturers,
+		);
+	}
+	
+	protected function getOrderCountryState ($cart, $address) {
+
+	}
+	
+	protected function getOrderAddress ($cart) {
+		$address = (($cart->ST == 0 || $cart->STsameAsBT == 1) ? $cart->BT : $cart->ST);
+		$zip = isset($address['zip'])?trim($address['zip']):'';
+		$data = array('zip'=>$zip,
+			'zip1'=>substr($zip,0,1),
+			'zip2'=>substr($zip,0,2),
+			'zip3'=>substr($zip,0,3),
+			'zip4'=>substr($zip,0,4),
+			'zip5'=>substr($zip,0,5),
+			'zip6'=>substr($zip,0,6),
+			'city'=>isset($address['city'])?trim($address['city']):'',
+			'countryid' => 0, 'country' => '', 'country2' => '', 'country3' => '',
+			'stateid'   => 0, 'state'   => '', 'state2'   => '', 'state3'   => '',
+		);
+		$data['company'] = isset($address['company'])?$address['company']:'';
+		$data['title'] = isset($address['title'])?$address['title']:'';
+		$data['first_name'] = isset($address['title'])?$address['title']:'';
+		$data['middle_name'] = isset($address['middle_name'])?$address['middle_name']:'';
+		$data['last_name'] = isset($address['last_name'])?$address['last_name']:'';
+		$data['address1'] = isset($address['address_1'])?$address['address_1']:'';
+		$data['address2'] = isset($address['address_2'])?$address['address_2']:'';
+		$data['city'] = isset($address['city'])?$address['city']:'';
+		$data['phone1'] = isset($address['phone_1'])?$address['phone_1']:'';
+		$data['phone2'] = isset($address['phone_2'])?$address['phone_2']:'';
+		$data['fax'] = isset($address['fax'])?$address['fax']:'';
+		$data['email'] = isset($address['email'])?$address['email']:'';
+		
+		// Country and State variables:
+		$countriesModel = VmModel::getModel('country');
+		if (isset($address['virtuemart_country_id'])) {
+			$data['countryid'] = $address['virtuemart_country_id'];
+			// The following is a workaround to make sure the cache is invalidated
+			// because if some other extension meanwhile called $countriesModel->getCountries,
+			// the cache will be modified, but the model's id will not be changes, so the
+			// getData call will return the wrong cache.
+			$countriesModel->setId(0); 
+			$countriesModel->setId($address['virtuemart_country_id']);
+			$country = $countriesModel->getData($address['virtuemart_country_id']);
+			if (!empty($country)) {
+				$data['country'] = $country->country_name;
+				$data['country2'] = $country->country_2_code;
+				$data['country3'] = $country->country_3_code;
+			}
+		}
+		
+		$statesModel = VmModel::getModel('state');
+		if (isset($address['virtuemart_state_id'])) {
+			$data['stateid'] = $address['virtuemart_state_id'];
+			// The following is a workaround to make sure the cache is invalidated
+			// because if some other extension meanwhile called $countriesModel->getCountries,
+			// the cache will be modified, but the model's id will not be changes, so the
+			// getData call will return the wrong cache.
+			$statesModel->setId(0); 
+			$statesModel->setId($address['virtuemart_state_id']);
+			$state = $statesModel->getData($address['virtuemart_state_id']);
+			if (!empty($state)) {
+				$data['state'] = $state->state_name;
+				$data['state2'] = $state->state_2_code;
+				$data['state3'] = $state->state_3_code;
+			}
+		}
+		
+		return $data;
+	}
+	
+	protected function getOrderPrices ($cart, $products, $cart_prices) {
+		$data = array(
+			'amount' => 0, 
+			'amountwithtax' => 0, 
+			'amountwithouttax' => 0, 
+			'baseprice' => 0, 
+			'basepricewithtax' => 0, 
+			'discountedpricewithouttax' => 0, 
+			'salesprice' => 0, 
+			'taxamount' => 0, 
+			'salespricewithdiscount' => 0, 
+			'discountamount' => 0, 
+			'pricewithouttax' => 0,
+		);
+		if (!empty($cart_prices)) {
+			// get prices for the whole cart -> simply user the cart_prices
+			$data['amount']                 = $cart_prices['salesPrice'];
+			$data['amountwithtax']          = $cart_prices['salesPrice'];
+			$data['amountwithouttax']       = $cart_prices['priceWithoutTax'];
+			$data['baseprice']              = $cart_prices['basePrice'];
+			$data['basepricewithtax']       = $cart_prices['basePriceWithTax'];
+			$data['discountedpricewithouttax'] = $cart_prices['discountedPriceWithoutTax'];
+			$data['salesprice']             = $cart_prices['salesPrice'];
+			$data['taxamount']              = $cart_prices['taxAmount'];
+			$data['salespricewithdiscount'] = $cart_prices['salesPriceWithDiscount'];
+			$data['discountamount']         = $cart_prices['discountAmount'];
+			$data['pricewithouttax']        = $cart_prices['priceWithoutTax'];
+		} else {
+			// Calculate the prices from the individual products!
+			// Possible problems are discounts on the order total
+			foreach ($products as $product) {
+				$data['amount']                    += $product->quantity*$product->allPrices[$product->selectedPrice]['salesPrice'];
+				$data['amountwithtax']             += $product->quantity*$product->allPrices[$product->selectedPrice]['salesPrice'];
+				$data['amountwithouttax']          += $product->quantity*$product->allPrices[$product->selectedPrice]['priceWithoutTax'];
+				$data['baseprice']                 += $product->quantity*$product->allPrices[$product->selectedPrice]['basePrice'];
+				$data['basepricewithtax']          += $product->quantity*$product->allPrices[$product->selectedPrice]['basePriceWithTax'];
+				$data['discountedpricewithouttax'] += $product->quantity*$product->allPrices[$product->selectedPrice]['discountedPriceWithoutTax'];
+				$data['salesprice']                += $product->quantity*$product->allPrices[$product->selectedPrice]['salesPrice'];
+				$data['taxamount']                 += $product->quantity*$product->allPrices[$product->selectedPrice]['taxAmount'];
+				$data['salespricewithdiscount']    += $product->quantity*$product->allPrices[$product->selectedPrice]['salesPriceWithDiscount'];
+				$data['discountamount']            += $product->quantity*$product->allPrices[$product->selectedPrice]['discountAmount'];
+				$data['pricewithouttax']           += $product->quantity*$product->allPrices[$product->selectedPrice]['priceWithoutTax'];
+			}
+		}
+		return $data;
+	}
+
+	/** Allow child classes to add additional variables for the rules or modify existing one
+	 */
+	protected function addCustomCartValues ($cart, $products, $cart_prices, &$values) {
+	}
+	protected function addPluginCartValues($cart, $products, $method, $cart_prices, &$values) {
+		// Finally, call the triger of vmshipmentrules plugins to let them add/modify variables
+		JPluginHelper::importPlugin('vmshipmentrules');
+		JDispatcher::getInstance()->trigger('onVmShippingRulesGetCartValues',array(&$cartvals, $cart, $products, $method, $cart_prices));
+	}
+
+	/** Filter the given array of products and return only those that belong to the categories, manufacturers, 
+	*  vendors or products given in the $filter_conditions. The $filter_conditions is an array of the form:
+	*     array( 'skus'=>array(....), 'categories'=>array(1,2,3,42), 'manufacturers'=>array(77,78,83), 'vendors'=>array(1,2))
+	*  Notice that giving an empty array for any of the keys means "no restriction" and is exactly the same 
+	*  as leaving out the entry altogether
+	*/
+	public function filterProducts($products, $filter_conditions) {
+		$result = array();
+		foreach ($products as $p) {
+			if (!empty($filter_conditions['skus']) && !in_array($p->product_sku, $filter_conditions['skus']))
+				continue;
+			if (!empty($filter_conditions['categories']) && count(array_intersect($filter_conditions['categories'], $p->categories))==0)
+				continue;
+			if (!empty($filter_conditions['manufacturers']) && count(array_intersect($filter_conditions['manufacturers'], $p->virtuemart_manufacturer_id))==0)
+				continue;
+			if (!empty($filter_conditions['vendors']) && !in_array($p->virtuemart_vendor_id, $filter_conditions['vendors']))
+				continue;
+			$result[] = $p;
+		}
+		return $result;
+	}
+	
+}