diff --git a/Makefile b/Makefile
index cbbb226bd8cdded5a113ecc5183c5fc1305aa81d..37b7df01ce870a207ed359c6f6609425f4125be9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,10 @@
 BASE=rules_shipping
 BASE_ADV=rules_shipping_advanced
 PLUGINTYPE=vmshipment
-VERSION=1.1.0
+VERSION=2.0
 
-PLUGINFILES=$(BASE).php $(BASE).script.php $(BASE).xml index.html
-PLUGINFILES_ADV=$(BASE_ADV).php $(BASE).php $(BASE_ADV).script.php $(BASE_ADV).xml index.html
+PLUGINFILES=$(BASE).php $(BASE)_base.php $(BASE).script.php $(BASE).xml index.html
+PLUGINFILES_ADV=$(BASE_ADV).php $(BASE)_base.php $(BASE_ADV).script.php $(BASE_ADV).xml index.html
 
 TRANSLATIONS=$(call wildcard,language/*/*.plg_$(PLUGINTYPE)_$(BASE).*ini) 
 TRANSLATIONS_ADV=$(subst $(BASE),$(BASE_ADV),$(TRANSLATIONS))
diff --git a/language/en-GB/en-GB.plg_vmshipment_rules_shipping.ini b/language/en-GB/en-GB.plg_vmshipment_rules_shipping.ini
index 61c468515cbceb56f8a25e55541c1e7e138d3c72..6e55886577c071bddbc47ed7176ba29d1f6734f4 100644
--- a/language/en-GB/en-GB.plg_vmshipment_rules_shipping.ini
+++ b/language/en-GB/en-GB.plg_vmshipment_rules_shipping.ini
@@ -60,4 +60,6 @@ VMSHIPMENT_RULES_PARSE_PAREN_NOT_CLOSED="Error during parsing expression '%s': A
 VMSHIPMENT_RULES_EVALUATE_NONNUMERIC="Encountered term '%s' during evaluation, that does not evaluate to a numeric value! (Full rule: '%s')"
 VMSHIPMENT_RULES_EVALUATE_SYNTAXERROR="Syntax error during evaluation, RPN is not well formed! (Full rule: '%s')"
 VMSHIPMENT_RULES_EVALUATE_UNKNOWN_OPERATOR="Unknown operator '%s' encountered during evaluation of rule '%s'."
-VMSHIPMENT_RULES_EVALUATE_UNKNOWN_ERROR="Unknown error occurred during evaluation of rule '%s'."
\ No newline at end of file
+VMSHIPMENT_RULES_EVALUATE_UNKNOWN_ERROR="Unknown error occurred during evaluation of rule '%s'."
+VMSHIPMENT_RULES_EVALUATE_ASSIGNMENT_TOPLEVEL="Assignments are not allows inside expressions (rule given was '%s')"
+VMSHIPMENT_RULES_EVALUATE_UNKNOWN_VALUE="Evaluation yields unknown value while evaluating rule part '%s'."
\ No newline at end of file
diff --git a/releases/plg_vmshipment_rules_shipping_advanced_v2.0.zip b/releases/plg_vmshipment_rules_shipping_advanced_v2.0.zip
new file mode 100644
index 0000000000000000000000000000000000000000..41e4dc7729a16aa1bd6dfb4e9526f4d93c36923a
Binary files /dev/null and b/releases/plg_vmshipment_rules_shipping_advanced_v2.0.zip differ
diff --git a/releases/plg_vmshipment_rules_shipping_v2.0.zip b/releases/plg_vmshipment_rules_shipping_v2.0.zip
new file mode 100644
index 0000000000000000000000000000000000000000..83ec336faa2c9b2e7db00a3b9698b3e5f975ca93
Binary files /dev/null and b/releases/plg_vmshipment_rules_shipping_v2.0.zip differ
diff --git a/rules_shipping.php b/rules_shipping.php
index 6c3265d4092351f58bcbf81f278b3e28b74dd7d8..f042ae12406cd2c9331dc3a418d3b4abcfe5694d 100644
--- a/rules_shipping.php
+++ b/rules_shipping.php
@@ -24,619 +24,20 @@ defined ('_JEXEC') or die('Restricted access');
 if (!class_exists ('vmPSPlugin')) {
 	require(JPATH_VM_PLUGINS . DS . 'vmpsplugin.php');
 }
-if (!class_exists ('plgVmShipmentRules_Shipping')) { 
-// Only declare the class once...
+
+if (!class_exists ('plgVmShipmentRules_Shipping_Base')) {
+	require (dirname(__FILE__).DS.'rules_shipping_base.php');
+}
 
 /** Shipping costs according to general rules.
  *  Supported Variables: Weight, ZIP, Amount, Products (1 for each product, even if multiple ordered), Articles 
  *  Assignable variables: Shipping, Name
  */
-class plgVmShipmentRules_Shipping extends vmPSPlugin {
-
-	/**
-	 * @param object $subject
-	 * @param array  $config
-	 */
+class plgVmShipmentRules_Shipping extends plgVmShipmentRules_Shipping_Base {
 	function __construct (& $subject, $config) {
-
 		parent::__construct ($subject, $config);
-
-		$this->_loggable = TRUE;
-		$this->_tablepkey = 'id';
-		$this->_tableId = 'id';
-		$this->tableFields = array_keys ($this->getTableSQLFields ());
-		$varsToPush = $this->getVarsToPush ();
-		$this->setConfigParameterable ($this->_configTableFieldName, $varsToPush);
-	}
-
-	/**
-	 * Create the table for this plugin if it does not yet exist.
-	 *
-	 * @author Valérie Isaksen
-	 */
-	public function getVmPluginCreateTableSQL () {
-		return $this->createTableSQL ('Shipment Rules Table');
-	}
-
-	/**
-	 * @return array
-	 */
-	function getTableSQLFields () {
-		$SQLfields = array(
-			'id'                           => 'int(1) UNSIGNED NOT NULL AUTO_INCREMENT',
-			'virtuemart_order_id'          => 'int(11) UNSIGNED',
-			'order_number'                 => 'char(32)',
-			'virtuemart_shipmentmethod_id' => 'mediumint(1) UNSIGNED',
-			'shipment_name'                => 'varchar(5000)',
-			'rule_name'                    => 'varchar(500)',
-			'order_weight'                 => 'decimal(10,4)',
-			'order_articles'               => 'int(1)',
-			'order_products'               => 'int(1)',
-			'shipment_weight_unit'         => 'char(3) DEFAULT \'KG\'',
-			'shipment_cost'                => 'decimal(10,2)',
-			'tax_id'                       => 'smallint(1)'
-		);
-		return $SQLfields;
-	}
-
-	/**
-	 * This method is fired when showing the order details in the frontend.
-	 * It displays the shipment-specific data.
-	 *
-	 * @param integer $virtuemart_order_id The order ID
-	 * @param integer $virtuemart_shipmentmethod_id The selected shipment method id
-	 * @param string  $shipment_name Shipment Name
-	 * @return mixed Null for shipments that aren't active, text (HTML) otherwise
-	 * @author Valérie Isaksen
-	 * @author Max Milbers
-	 */
-	public function plgVmOnShowOrderFEShipment ($virtuemart_order_id, $virtuemart_shipmentmethod_id, &$shipment_name) {
-		$this->onShowOrderFE ($virtuemart_order_id, $virtuemart_shipmentmethod_id, $shipment_name);
-	}
-
-	/**
-	 * This event is fired after the order has been stored; it gets the shipment method-
-	 * specific data.
-	 *
-	 * @param int    $order_id The order_id being processed
-	 * @param object $cart  the cart
-	 * @param array  $order The actual order saved in the DB
-	 * @return mixed Null when this method was not selected, otherwise true
-	 * @author Valerie Isaksen
-	 */
-	function plgVmConfirmedOrder (VirtueMartCart $cart, $order) {
-
-		if (!($method = $this->getVmPluginMethod ($order['details']['BT']->virtuemart_shipmentmethod_id))) {
-			return NULL; // Another method was selected, do nothing
-		}
-		if (!$this->selectedThisElement ($method->shipment_element)) {
-			return FALSE;
-		}
-		$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;
-		$values['order_weight'] = $this->getOrderWeight ($cart, $method->weight_unit);
-		$values['order_articles'] = $this->getOrderArticles ($cart);
-		$values['order_products'] = $this->getOrderProducts ($cart);
-		$values['shipment_weight_unit'] = $method->weight_unit;
-		$values['shipment_cost'] = $method->cost;
-		$values['tax_id'] = $method->tax_id;
-		$this->storePSPluginInternalData ($values);
-
-		return TRUE;
-	}
-
-	/**
-	 * This method is fired when showing the order details in the backend.
-	 * It displays the shipment-specific data.
-	 * NOTE, this plugin should NOT be used to display form fields, since it's called outside
-	 * a form! Use plgVmOnUpdateOrderBE() instead!
-	 *
-	 * @param integer $virtuemart_order_id The order ID
-	 * @param integer $virtuemart_shipmentmethod_id The order shipment method ID
-	 * @param object  $_shipInfo Object with the properties 'shipment' and 'name'
-	 * @return mixed Null for shipments that aren't active, text (HTML) otherwise
-	 * @author Valerie Isaksen
-	 */
-	public function plgVmOnShowOrderBEShipment ($virtuemart_order_id, $virtuemart_shipmentmethod_id) {
-		if (!($this->selectedThisByMethodId ($virtuemart_shipmentmethod_id))) {
-			return NULL;
-		}
-		$html = $this->getOrderShipmentHtml ($virtuemart_order_id);
-		return $html;
-	}
-
-	/**
-	 * @param $virtuemart_order_id
-	 * @return string
-	 */
-	function getOrderShipmentHtml ($virtuemart_order_id) {
-
-		$db = JFactory::getDBO ();
-		$q = 'SELECT * FROM `' . $this->_tablename . '` '
-			. 'WHERE `virtuemart_order_id` = ' . $virtuemart_order_id;
-		$db->setQuery ($q);
-		if (!($shipinfo = $db->loadObject ())) {
-			vmWarn (500, $q . " " . $db->getErrorMsg ());
-			return '';
-		}
-
-		if (!class_exists ('CurrencyDisplay')) {
-			require(JPATH_VM_ADMINISTRATOR . DS . 'helpers' . DS . 'currencydisplay.php');
-		}
-
-		$currency = CurrencyDisplay::getInstance ();
-		$tax = ShopFunctions::getTaxByID ($shipinfo->tax_id);
-		$taxDisplay = is_array ($tax) ? $tax['calc_value'] . ' ' . $tax['calc_value_mathop'] : $shipinfo->tax_id;
-		$taxDisplay = ($taxDisplay == -1) ? JText::_ ('COM_VIRTUEMART_PRODUCT_TAX_NONE') : $taxDisplay;
-
-		$html = '<table class="adminlist">' . "\n";
-		$html .= $this->getHtmlHeaderBE ();
-		$html .= $this->getHtmlRowBE ('RULES_SHIPPING_NAME', $shipinfo->shipment_name);
-		$html .= $this->getHtmlRowBE ('RULES_WEIGHT', $shipinfo->order_weight . ' ' . ShopFunctions::renderWeightUnit ($shipinfo->shipment_weight_unit));
-		$html .= $this->getHtmlRowBE ('RULES_ARTICLES', $shipinfo->order_articles . '/' . $shipinfo->order_products);
-		$html .= $this->getHtmlRowBE ('RULES_COST', $currency->priceDisplay ($shipinfo->shipment_cost));
-		$html .= $this->getHtmlRowBE ('RULES_TAX', $taxDisplay);
-		$html .= '</table>' . "\n";
-
-		return $html;
-	}
-	
-	/** Include the rule name in the shipment name */
-	protected function renderPluginName ($plugin) {
-		$return = '';
-		$plugin_name = $this->_psType . '_name';
-		$plugin_desc = $this->_psType . '_desc';
-		$description = '';
-		// 		$params = new JParameter($plugin->$plugin_params);
-		// 		$logo = $params->get($this->_psType . '_logos');
-		$logosFieldName = $this->_psType . '_logos';
-		$logos = $plugin->$logosFieldName;
-		if (!empty($logos)) {
-			$return = $this->displayLogos ($logos) . ' ';
-		}
-		if (!empty($plugin->$plugin_desc)) {
-			$description = '<span class="' . $this->_type . '_description">' . $plugin->$plugin_desc . '</span>';
-		}
-		$rulename='';
-		if (!empty($plugin->rule_name)) {
-			$rulename=" (".$plugin->rule_name.")";
-		}
-		$pluginName = $return . '<span class="' . $this->_type . '_name">' . $plugin->$plugin_name . $rulename.'</span>' . $description;
-		return $pluginName;
-	}
-
-
-
-	/**
-	 * @param VirtueMartCart $cart
-	 * @param                $method
-	 * @param                $cart_prices
-	 * @return int
-	 */
-	function getCosts (VirtueMartCart $cart, $method, $cart_prices) {
-		if (empty($method->rules)) $this->parseMethodRules($method);
-		$cartvals = $this->getCartValues ($cart, $cart_prices);
-		
-		foreach ($method->rules as $r) {
-			if ($r->matches($cartvals)) {
-				$method->tax_id = $r->tax_id;
-				$method->matched_rule = $r;
-				$method->rule_name = $r->name;
-				$method->cost = $r->getShippingCosts($cartvals);
-				$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 (
-	 *
-	 * @author Valérie Isaksen (original), Reinhold Kainhofer (tax calculations from shippingWithTax)
-	 *
-	 * @param $cart_prices: $cart_prices['salesPricePayment'] and $cart_prices['paymentTax'] updated. Displayed in the cart.
-	 * @param $value :   fee
-	 * @param $tax_id :  tax id
-	 */
-
-	function setCartPrices (VirtueMartCart $cart, &$cart_prices, $method) {
-
-		if (!class_exists ('calculationHelper')) {
-			require(JPATH_VM_ADMINISTRATOR . DS . 'helpers' . DS . 'calculationh.php');
-		}
-
-		$calculator = calculationHelper::getInstance ();
-
-		$value = $calculator->roundInternal ($this->getCosts ($cart, $method, $cart_prices), 'salesPrice');
-
-		$_psType = ucfirst ($this->_psType);
-		$cart_prices[$this->_psType . 'Value'] = $value;
-
-		$taxrules = array();
-		if (!empty($method->tax_id)) {
-			$cart_prices[$this->_psType . '_calc_id'] = $method->tax_id;
-
-			$db = JFactory::getDBO ();
-			$q = 'SELECT * FROM #__virtuemart_calcs WHERE `virtuemart_calc_id`="' . $method->tax_id . '" ';
-			$db->setQuery ($q);
-			$taxrules = $db->loadAssocList ();
-		}
-
-		if (count ($taxrules) > 0) {
-			if ($method->includes_tax) {
-				$cart_prices['salesPrice' . $_psType] = $calculator->roundInternal ($cart_prices[$this->_psType . 'Value'], 'salesPrice');
-				// Calculate the tax from the final sales price:
-				$calculator->setRevert (true);
-				$cart_prices[$this->_psType . 'Tax'] = $cart_prices['salesPrice' . $_psType] - $calculator->roundInternal ($calculator->executeCalculation($taxrules, $cart_prices[$this->_psType . 'Value'], true));
-				$calculator->setRevert (false);
-			} else {
-				$cart_prices['salesPrice' . $_psType] = $calculator->roundInternal ($calculator->executeCalculation ($taxrules, $cart_prices[$this->_psType . 'Value']), 'salesPrice');
-				$cart_prices[$this->_psType . 'Tax'] = $calculator->roundInternal (($cart_prices['salesPrice' . $_psType] - $cart_prices[$this->_psType . 'Value']), 'salesPrice');
-			}
-			$cart_prices[$this->_psType . '_calc_id'] = $taxrules[0]['virtuemart_calc_id'];
-		} else {
-			$cart_prices['salesPrice' . $_psType] = $value;
-			$cart_prices[$this->_psType . 'Tax'] = 0;
-			$cart_prices[$this->_psType . '_calc_id'] = 0;
-		}
-	}
-
-	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;
-			if (class_exists('ShippingRule_Advanced')) {
-				$method->rules[]=new ShippingRule_Advanced($r, $countries, $tax);
-			} else {
-				$method->rules[]=new ShippingRule($r, $countries, $tax);
-			}
-		}
-	}
-	
-	protected function parseMethodRules (&$method) {
-		$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);
-	}
-
-	protected function getOrderArticles (VirtueMartCart $cart) {
-		/* Cache the value in a static variable and calculate it only once! */
-		static $articles = 0;
-		if(empty($articles) and count($cart->products)>0){
-			foreach ($cart->products as $product) {
-				$articles += $product->quantity;
-			}
-		}
-		return $articles;
-	}
-
-	protected function getOrderDimensions (VirtueMartCart $cart) {
-		/* Cache the value in a static variable and calculate it only once! */
-		static $calculated = 0;
-		static $dimensions=array(
-			'volume' => 0,
-			'maxvolume' => 0, 'minvolume' => 9999999999,
-			'maxlength' => 0, 'minlength' => 9999999999,
-			'maxwidth'  => 0, 'minwidth' => 9999999999,
-			'maxheight' => 0, 'minheight' => 9999999999,
-			);
-		if ($calculated==0) {
-			$calculated=1;
-			foreach ($cart->products as $product) {
-				$volume = $product->product_length * $product->product_width * $product->product_height;
-				$dimensions['volume'] += $volume * $product->quantity;
-				$dimensions['maxvolume'] = max ($dimensions['maxvolume'], $volume);
-				$dimensions['minvolume'] = min ($dimensions['minvolume'], $volume);
-				$dimensions['maxlength'] = max ($dimensions['maxlength'], $product->product_length);
-				$dimensions['minlength'] = min ($dimensions['minlength'], $product->product_length);
-				$dimensions['maxwidth'] = max ($dimensions['maxwidth'], $product->product_width);
-				$dimensions['minwidth'] = min ($dimensions['minwidth'], $product->product_width);
-				$dimensions['maxheight'] = max ($dimensions['maxheight'], $product->product_height);
-				$dimensions['minheight'] = min ($dimensions['minheight'], $product->product_height);
-				$articles += $product->quantity;
-			}
-		}
-		return $dimensions;
-	}
-
-	protected function getOrderProducts (VirtueMartCart $cart) {
-		/* Cache the value in a static variable and calculate it only once! */
-		static $products = 0;
-		if(empty($products) and count($cart->products)>0){
-			$products = count($cart->products);
-		}
-		return $products;
-	}
-	
-	protected function getCartValues (VirtueMartCart $cart, $cart_prices) {
-		$orderWeight = $this->getOrderWeight ($cart, $method->weight_unit);
-		$dimensions = $this->getOrderDimensions ($cart);
-		$address = (($cart->ST == 0) ? $cart->BT : $cart->ST);
-		
-		$products = 0;
-		$articles = 0;
-		foreach ($cart->products as $product) {
-			$products += 1;
-			$articles += $product->quantity;
-		}
-		$cartvals = array('weight'=>$orderWeight, 
-				  'zip'=>$address['zip'], 
-				  'articles'=>$articles, 
-				  'products'=>$products,
-				  'amount'=>$cart_prices['salesPrice'],
-				  'country'=>$address['virtuemart_country_id'],
-				  'volume' => $dimensions['volume'],
-				  'maxvolume' => $dimensions['maxvolume'], 
-				  'minvolume' => $dimensions['minvolume'],
-				  'maxlength' => $dimensions['maxlength'], 
-				  'minlength' => $dimensions['minlength'],
-				  'maxwidth'  => $dimensions['maxwidth'], 
-				  'minwidth' => $dimensions['minwidth'],
-				  'maxheight' => $dimensions['maxheight'], 
-				  'minheight' => $dimensions['minheight']
-			);
-		return $cartvals;
-	}
-
-
-	/**
-	 * @param \VirtueMartCart $cart
-	 * @param int             $method
-	 * @param array           $cart_prices
-	 * @return bool
-	 */
-	protected function checkConditions ($cart, $method, $cart_prices) {
-		if (empty($method->rules)) $this->parseMethodRules($method);
-
-		$cartvals = $this->getCartValues ($cart, $cart_prices);
-		foreach ($method->rules as $r) {
-			if ($r->matches($cartvals)) {
-				$method->matched_rule = $r;
-				$method->rule_name = $r->name;
-				return TRUE;
-			}
-		}
-		vmdebug('checkConditions '.$method->name.' does not fit');
-		return FALSE;
-	}
-
-	/**
-	 * Create the table for this plugin if it does not yet exist.
-	 * This functions checks if the called plugin is active one.
-	 * When yes it is calling the standard method to create the tables
-	 *
-	 * @author Valérie Isaksen
-	 *
-	 */
-	function plgVmOnStoreInstallShipmentPluginTable ($jplugin_id) {
-		return $this->onStoreInstallPluginTable ($jplugin_id);
-	}
-
-	/**
-	 * @param VirtueMartCart $cart
-	 * @return null
-	 */
-	public function plgVmOnSelectCheckShipment (VirtueMartCart &$cart) {
-		return $this->OnSelectCheck ($cart);
-	}
-
-	/**
-	 * plgVmDisplayListFE
-	 * This event is fired to display the pluginmethods in the cart (edit shipment/payment) for example
-	 *
-	 * @param object  $cart Cart object
-	 * @param integer $selected ID of the method selected
-	 * @return boolean True on success, false on failures, null when this plugin was not selected.
-	 * On errors, JError::raiseWarning (or JError::raiseError) must be used to set a message.
-	 *
-	 * @author Valerie Isaksen
-	 * @author Max Milbers
-	 */
-	public function plgVmDisplayListFEShipment (VirtueMartCart $cart, $selected = 0, &$htmlIn) {
-		return $this->displayListFE ($cart, $selected, $htmlIn);
-	}
-
-	/**
-	 * @param VirtueMartCart $cart
-	 * @param array          $cart_prices
-	 * @param                $cart_prices_name
-	 * @return bool|null
-	 */
-	public function plgVmOnSelectedCalculatePriceShipment (VirtueMartCart $cart, array &$cart_prices, &$cart_prices_name) {
-		return $this->onSelectedCalculatePrice ($cart, $cart_prices, $cart_prices_name);
-	}
-
-	/**
-	 * plgVmOnCheckAutomaticSelected
-	 * Checks how many plugins are available. If only one, the user will not have the choice. Enter edit_xxx page
-	 * The plugin must check first if it is the correct type
-	 *
-	 * @author Valerie Isaksen
-	 * @param VirtueMartCart cart: the cart object
-	 * @return null if no plugin was found, 0 if more then one plugin was found,  virtuemart_xxx_id if only one plugin is found
-	 *
-	 */
-	function plgVmOnCheckAutomaticSelectedShipment (VirtueMartCart $cart, array $cart_prices = array(), &$shipCounter) {
-		if ($shipCounter > 1) {
-			return 0;
-		}
-		return $this->onCheckAutomaticSelected ($cart, $cart_prices, $shipCounter);
-	}
-
-	/**
-	 * This method is fired when showing when priting an Order
-	 * It displays the the payment method-specific data.
-	 *
-	 * @param integer $_virtuemart_order_id The order ID
-	 * @param integer $method_id  method used for this order
-	 * @return mixed Null when for payment methods that were not selected, text (HTML) otherwise
-	 * @author Valerie Isaksen
-	 */
-	function plgVmonShowOrderPrint ($order_number, $method_id) {
-		return $this->onShowOrderPrint ($order_number, $method_id);
-	}
-
-	function plgVmDeclarePluginParamsShipment ($name, $id, &$data) {
-		return $this->declarePluginParams ('shipment', $name, $id, $data);
-	}
-
-
-	/**
-	 * @author Max Milbers
-	 * @param $data
-	 * @param $table
-	 * @return bool
-	 */
-	function plgVmSetOnTablePluginShipment(&$data,&$table){
-
-		$name = $data['shipment_element'];
-		$id = $data['shipment_jplugin_id'];
-
-		if (!empty($this->_psType) and !$this->selectedThis ($this->_psType, $name, $id)) {
-			return FALSE;
-		} else {
-			// Try to parse all rules (and spit out error) to inform the user:
-			$method = new StdClass ();
-			$this->parseMethodRule ($data['rules1'], $data['countries1'], $data['tax_id1'], $method);
-			$this->parseMethodRule ($data['rules2'], $data['countries2'], $data['tax_id2'], $method);
-			$this->parseMethodRule ($data['rules3'], $data['countries3'], $data['tax_id3'], $method);
-			$this->parseMethodRule ($data['rules4'], $data['countries4'], $data['tax_id4'], $method);
-			$this->parseMethodRule ($data['rules5'], $data['countries5'], $data['tax_id5'], $method);
-			$this->parseMethodRule ($data['rules6'], $data['countries6'], $data['tax_id6'], $method);
-			$this->parseMethodRule ($data['rules7'], $data['countries7'], $data['tax_id7'], $method);
-			$this->parseMethodRule ($data['rules8'], $data['countries8'], $data['tax_id8'], $method);
-			return $this->setOnTablePluginParams ($name, $id, $table);
-		}
-	}
-
-
-}
-
-class ShippingRule {
-	var $rulestring = '';
-	var $countries = array();
-	var $tax_id = 0;
-	var $conditions = array();
-	var $shipping = 0;
-	var $includes_tax = 0;
-	var $name = '';
-	
-	function __construct ($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);
-	}
-	
-	function handleAssignment ($variable, $value, $rulepart) {
-		switch ($variable) {
-			case 'shipping': $this->shipping = $this->parseShippingTerm($value); break;
-			case 'shippingwithtax': $this->shipping = $this->parseShippingTerm($value); $this->includes_tax = True; break;
-			case 'name': $this->name = $value; break;
-			default: JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_VARIABLE', $variable, $rulepart), 'error');
-		}
-	}
-	function parseRule($rule) {
-		$ruleparts=explode(';', $rule);
-		$operators = array('<', '<=', '=', '>', '>=', '=>', '=<', '<>', '!=', '==');
-		$op_re='/\s*(<=|=>|>=|=>|<>|!=|==|<|=|>)\s*/';
-		foreach ($ruleparts as $p) {
-			$p = trim($p);
-			if (empty($p)) continue;
-			$atoms = preg_split ($op_re, $p, -1, PREG_SPLIT_DELIM_CAPTURE);
-			if (count($atoms)==1) {
-				$this->shipping = $this->parseShippingTerm($atoms[0]);
-			} elseif ($atoms[1]=='=') {
-				$this->handleAssignment (strtolower($atoms[0]), $atoms[2], $p);
-			} 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], $p), 'error');
-						$atoms = array();
-					}
-				}
-			}
-		}
-	}
-
-	function parseShippingTerm($expr) {
-		/* In the advanced version, shipping cost can be given as a full mathematical expression */
-		return strtolower($expr);
-	}
-	
-	function evaluateTerm ($expr, $vals) {
-		/* In the advanced version, all conditions and costs can be given as a full mathematical expression */
-		$e=$expr;
-		// Strings indicate members of the $vals array (weight, amount, etc.)
-		if (!is_numeric($e) && isset($vals[$e])) $e=$vals[$e];
-		if (!is_numeric($e)) {
-			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_NONNUMERIC', $e, $this->rulestring), 'error');
-			$e = 0;
-		}
-		return $e;
-	}
-
-	function calculateShipping ($vals) {
-		return $this->evaluateTerm($this->shipping, $vals);
-	}
-
-	function matches($vals) {
-		// First, check the country, if any conditions are given:
-		if (count ($this->countries) > 0 && !in_array ($vals['country'], $this->countries)) 
-			return False;
-		
-		foreach ($this->conditions as $c) {
-			$v1=$this->evaluateTerm($c[1], $vals);
-			$v2=$this->evaluateTerm($c[2], $vals);
-			// Unknown strings / unevaluatable terms cause the rule to NOT match:
-			if (!is_numeric($v1) || !is_numeric($v2)) {
-				return false;
-			}
-			$res = false;
-			switch ($c[0]) {
-				case '<':  $res = ($v1<$v2);  break;
-				case '<=': 
-				case '=<': $res = ($v1<=$v2); break;
-				case '==': $res = ($v1==$v2); break;
-				case '!=':
-				case '<>': $res = ($v1!=$v2); break;
-				case '>=':
-				case '=>': $res = ($v1>=$v2); break;
-				case '>':  $res = ($v1>$v2);  break;
-			}
-			// Immediately return, if this rule does not match
-			if (!$res) return false;
-		}
-		// All conditions match, so return true
-		return true;
-	}
-
-	function getShippingCosts($vals) {
-		return $this->calculateShipping($vals);
 	}
 
 }
 
-}
 // No closing tag
diff --git a/rules_shipping.xml b/rules_shipping.xml
index a1805d4165b84d1a1da75fc1ae24db1d575c44bf..3c30b2885c5285a9700d6957f45a1ca92ebc327a 100644
--- a/rules_shipping.xml
+++ b/rules_shipping.xml
@@ -1,15 +1,16 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <install version="1.5" type="plugin" group="vmshipment" method="upgrade">
     <name>VMSHIPMENT_RULES</name>
-    <creationDate>2013-01-12</creationDate>
+    <creationDate>2013-02-08</creationDate>
     <author>Reinhold Kainhofer</author>
     <authorUrl>http://www.kainhofer.com</authorUrl>
     <copyright>Copyright (C) 2013, Reinhold Kainhofer</copyright>
     <license>GPL v3+</license>
-    <version>1.1.0</version>
+    <version>2.0.0</version>
     <description>VMSHIPMENT_RULES_DESC</description>
     <files>
 	<filename plugin="rules_shipping">rules_shipping.php</filename>
+	<filename>rules_shipping_base.php</filename>
 	<folder>language</folder>
 	<folder>elements</folder>
     </files>
diff --git a/rules_shipping_advanced.php b/rules_shipping_advanced.php
index d651d1aadc39efce3e704c82da27e8d5a6d37ad7..92b92bc17e198bbbb0b52e80dd05182e0ff78eb5 100644
--- a/rules_shipping_advanced.php
+++ b/rules_shipping_advanced.php
@@ -25,28 +25,98 @@ defined ('_JEXEC') or die('Restricted access');
 if (!class_exists ('vmPSPlugin')) {
 	require(JPATH_VM_PLUGINS . DS . 'vmpsplugin.php');
 }
-if (!class_exists ('plgVmShipmentRules_Shipping')) {
-	require (dirname(__FILE__).DS.'rules_shipping.php');
+if (!class_exists ('plgVmShipmentRules_Shipping_Base')) {
+	require (dirname(__FILE__).DS.'rules_shipping_base.php');
 }
 
 /** Shipping costs according to general rules.
  *  Derived from the standard plugin, no need to change anything! The standard plugin already uses the advanced rules class defined below, if it can be found
  */
-class plgVmShipmentRules_Shipping_Advanced extends plgVmShipmentRules_Shipping {
-// 	function __construct (& $subject, $config) {
-// 		parent::__construct ($subject, $config);
-// 	}
+class plgVmShipmentRules_Shipping_Advanced extends plgVmShipmentRules_Shipping_Base {
+	function __construct (& $subject, $config) {
+		parent::__construct ($subject, $config);
+	}
+	protected function createMethodRule ($r, $countries, $tax) {
+		return new ShippingRule_Advanced ($r, $countries, $tax);
+	}
+	/** Allow child classes to add additional variables for the rules 
+	 */
+	protected function addCustomCartValues (VirtueMartCart $cart, $cart_prices, &$values) {
+		$values['coupon'] = $cart->couponCode;
+	}
+
+}
+
 
+// is_comparison: if the expression $o is a (possibly chained) comparison consisting of operators in $ops, 
+// return the last term of hte comparison; null otherwise
+function is_comparison ($o, $ops) {
+	if (!is_array($o)) 
+		return null;
+	if (in_array($o[0], $ops))
+		return $o[2];
+	$o2comp = is_comparison($o[2], $ops);
+	if (trim($o[0])=='AND' && !is_null (is_comparison($o[1], $ops)) && !is_null($o2comp))
+		return $o2comp;
+	return null;
 }
- 
+
+
 /** Extend the shipping rules by allowing arbitrary mathematical expressions
  */
 class ShippingRule_Advanced extends ShippingRule {
-	var $operators = array("+"=>10, "-"=>10, "*"=>20, "/"=>20, "%"=>30, "^"=>50, "("=>100, ")"=>100);
+	var $operators = array(
+		"^"  => 70, 
+		"*"  => 60, "/"  => 60, "%"  => 60, 
+		"+"  => 50, "-"  => 50, 
+		"<"  => 40, "<=" => 40, ">"  => 40, ">=" => 40, "=>" => 40, "=<" => 40,
+		"==" => 40, "!=" => 40, "<>" => 40,
+		"&"  => 21,
+		" OR "  => 20,
+		"="  => 10,
+		
+		"("  =>  0, ")"  =>0 );
 
 	function __construct ($rule, $countries, $tax_id) {
 		parent::__construct ($rule, $countries, $tax_id);
 	}
+	
+	function tokenize_expression ($expression) {
+		// First, extract all strings, delimited by ":
+		$str_re = '/("(?:\\"|[^"])*"|\'(?:\\\'|[^\'])*\')/';
+		$strings = preg_split($str_re, $expression, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
+		// Then split all other parts of the expression at the operators
+		$op_re = ':\s*( OR |<=|=>|>=|=>|<>|!=|==|<|=|>|\+|-|\*|/|%|\(|\)|\^)\s*:';
+		$atoms = array();
+		foreach ($strings as $s) {
+			if (preg_match($str_re, $s)) {
+				$atoms[] = $s;
+			} else {
+				$newatoms = preg_split($op_re, $s, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
+				$atoms = array_merge ($atoms, $newatoms);
+			}
+		}
+		// Finally handle the double meaning of -: After an operator and before an operand, it is NOT an operator, but belongs to the operand
+		$prevop = true;
+		$result = array();
+		$leftover = '';
+		foreach ($atoms as $a) {
+			if ($a == "-") {
+				$prev = end($result);
+				if (is_null ($prev) || (preg_match ($op_re, $prev) && $prev != ')')) {
+					$leftover = $a;
+				} else {
+					$result[] = $leftover.$a;
+					$leftover = '';
+				}
+			} else {
+				$result[] = $leftover.$a;
+				$leftover = '';
+			}
+		}
+		return $result;
+	}
+	
 
 	/** parse the mathematical expressions (following Knuth 1962):
 	 *  First parse the string into an array of tokens (operators and operands) by a simple regexp with known operators as separators)
@@ -63,50 +133,74 @@ class ShippingRule_Advanced extends ShippingRule {
 	 *        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
-	 *  Store this RPN list for later evaluation
+	 *
+	 *  Afterwards, convert this RPN list into an expression tree to be evaluate
+	 *
 	 */
-	function parseShippingTerm ($expr) {
-		$op_re = ':\s*(\+|-|\*|/|%|\(|\)|\^)\s*:';
-		$atoms = preg_split($op_re, strtolower($expr), -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
+	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)\s*=\s*"?(.*)"?\s*$/i', $rulepart, $matches)) {
+			$this->handleAssignment ($matches[1], $matches[2], $rulepart);
+			return;
+		}
+
+		// Split at all operators:
+		$atoms = $this->tokenize_expression ($rulepart);
+		
+		// Any of these indicate a comparison and thus a condition:
+		$comparison_ops = array('<', '<=', '=<', '<>', '!=', '==', '>', '>=', '=>');
+		$is_comparison = false;
+		$is_assignment = false;
+		
 		if (count($atoms)==1) return $atoms[0];
-		// Operators,including precedence
 		
 		$stack = array ();  // 1)
-		$result = array ();
+		$rpn = array ();
 		foreach ($atoms as $a) { // 2)
-			if (!isset($this->operators[$a])) { // 3)
-				array_push ($result, $a);
-			} elseif ($a == "(") { // 5)
+			if (!isset($this->operators[$a])) { // 3) Operand
+				array_push ($rpn, $a);
+			} elseif ($a == "(") { // 5) parenthesis
 				array_push ($stack, $a);
-			} elseif ($a == ")") { // 6)
+			} elseif ($a == ")") { // 6) parenthesis
 				do {
 					$op=array_pop($stack); // 6a)
-					if (!empty($op) && $op != "(") {
-						array_push ($result, $op); // 6b)
+					if (!is_null($op) && ($op != "(")) {
+						array_push ($rpn, $op); // 6b)
 					} else {
 						if ($op != "(") {
 							// If no ( can be found, the expression is wrong!
-							JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_PARSE_MISSING_PAREN', $expr), 'error');
+							JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_PARSE_MISSING_PAREN', $rulepart), 'error');
 						}
 						break; // We have found the opening parenthesis
 					}
-				} while (0);
-			} else { // 4
+				} while (true);
+			} else { // 4) operators
 				// 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. 
 				$prec = $this->operators[$a];
+				$is_comparison |= in_array($a, $comparison_ops);
+				$is_assignment |= ($a == "=");
 				
 				while (count($stack)>0) { // 4a)
 					$op = array_pop ($stack);
-					// Ignore the right-associative symbols of equal precedence for now...
+					// The only right-associative operator is =, which we allow at most once!
 					if ($op == "(") {
+						// add it back to the stack!
+						array_push ($stack, $op);
 						break;
 					} elseif ($this->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 ($result, $op);
+						array_push ($rpn, $op);
 					}
 				} while (0);
 				array_push ($stack, $a); // 4b)
@@ -116,27 +210,13 @@ class ShippingRule_Advanced extends ShippingRule {
 		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', $expr), 'error');
+				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_PARSE_PAREN_NOT_CLOSED', $rulepart), 'error');
 			} else {
-				array_push ($result, $op);
+				array_push ($rpn, $op);
 			}
 		}
-		/* In the advanced version, shipping cost can be given as a full mathematical expression */
-		return $result;
-	}
 
-	function evaluateTerm ($expr, $vals) {
-		if (!is_array($expr)) {
-			// Normal expression, no complex mathematical formula in RPN
-			if (!is_numeric($expr) && isset($vals[$expr])) $expr=$vals[$expr];
-			if (!is_numeric($expr)) {
-				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_NONNUMERIC', $expr, $this->rulestring), 'error');
-				return 0;
-			}
-			return $expr;
-		}
-
-		/** Evaluate the RPN, according to Knuth: 
+		/** 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
@@ -148,49 +228,58 @@ class ShippingRule_Advanced extends ShippingRule {
 		 *   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 ($expr as $e) { // 2)
+		foreach ($rpn as $e) { // 2)
 			if (!isset($this->operators[$e])) { // 3)
 				// Operand => push onto stack
-				if (!is_numeric($e) && isset($vals[$e])) $e = $vals[$e];
-				if (!is_numeric($e)) {
-					JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_NONNUMERIC', $e, $this->rulestring), 'error');
-					$e = 0;
-				}
 				array_push ($stack, $e);
 			} else { // 4)
 				// Operator => apply to the last two values on the stack
 				if (count($stack)<2) { // 4d)
-					JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_SYNTAXERROR', $this->rulestring), 'error');
+					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);
-				$res = 0;
-				switch ($e) { // 4b)
-					case "+": $res = $o1+$o2; break;
-					case "-": $res = $o1-$o2; break;
-					case "*": $res = $o1*$o2; break;
-					case "/": $res = $o1/$o2; break;
-					case "%": $res = $o1%$o2; break;
-					case "^": $res = $o1^$o1; break;
-					default: 
-						JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_OPERATOR', $e, $this->rulestring), 'error');
-						$res = 0;
+				// TODO: Special-case chained comparisons: if e is a comparison, and operator(o1) is also a comparison, 
+				//       insert AND(o1, e(arg2(o1), o2)) instead of e(o1, o1)
+				// is_comparison nicely returns the last term of the (possibly chained) comparison, null otherwise
+				$o1comp = is_comparison($o1, $comparison_ops);
+				if (in_array ($e, $comparison_ops) && !is_null($o1comp) ) {
+					$op = array ('AND', $o1, array($e, $o1comp, $o2));
+				} else {
+					$op = array ($e, $o1, $o2); // 4b)
 				}
-				array_push($stack, $res); // 4c)
+				array_push ($stack, $op); // 4c)
 			}
 		}
 		// 5a)
 		if (count($stack) != 1) {
-			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_ERROR', $this->rulestring), 'error');
+			JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_ERROR', $rulepart), 'error');
 			$stack = array (0);
 		}
 		$res = array_pop($stack); // 5)
-		return $res;
+		
+		if ($is_comparison) { // Comparisons are conditions
+			$this->conditions[] = $res;
+		} elseif ($is_assignment) {
+
+			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');
+			}
+		} else {
+			// Terms without comparisons or assignments are shipping cost expressions
+			$this->shipping = $res;
+			$this->includes_tax = False;
+		}
 	}
 
+
 }
 
 // No closing tag
diff --git a/rules_shipping_advanced.xml b/rules_shipping_advanced.xml
index 71fe47561f2019c9736cb971e6cdfd253518688e..278d8edabe6cf4524332cabf8c16cba5d7445eb8 100644
--- a/rules_shipping_advanced.xml
+++ b/rules_shipping_advanced.xml
@@ -1,16 +1,16 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <install version="1.5" type="plugin" group="vmshipment" method="upgrade">
     <name>VMSHIPMENT_RULES_ADV</name>
-    <creationDate>2013-01-16</creationDate>
+    <creationDate>2013-02-08</creationDate>
     <author>Reinhold Kainhofer</author>
     <authorUrl>http://www.kainhofer.com</authorUrl>
     <copyright>Copyright (C) 2013, Reinhold Kainhofer</copyright>
     <license>GPL v3+</license>
-    <version>1.1.0</version>
+    <version>2.0.0</version>
     <description>VMSHIPMENT_RULES_ADV_DESC</description>
     <files>
 	<filename plugin="rules_shipping_advanced">rules_shipping_advanced.php</filename>
-	<filename>rules_shipping.php</filename>
+	<filename>rules_shipping_base.php</filename>
 	<folder>language</folder>
 	<folder>elements</folder>
     </files>
diff --git a/rules_shipping_base.php b/rules_shipping_base.php
new file mode 100644
index 0000000000000000000000000000000000000000..c42290ddc856306500c1981cc12ab8954441eabf
--- /dev/null
+++ b/rules_shipping_base.php
@@ -0,0 +1,694 @@
+<?php
+
+defined ('_JEXEC') or die('Restricted access');
+
+/**
+ * Shipment plugin for general, rules-based shipments, like regular postal services with complex shipping cost structures
+ *
+ * @version $Id$
+ * @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.php
+ * 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.
+ * See /administrator/components/com_virtuemart/COPYRIGHT.php for copyright notices and details.
+ *
+ * http://virtuemart.org
+ * @author Reinhold Kainhofer, based on the weight_countries shipping plugin by Valerie Isaksen
+ *
+ */
+if (!class_exists ('vmPSPlugin')) {
+	require(JPATH_VM_PLUGINS . DS . 'vmpsplugin.php');
+}
+if (!class_exists ('plgVmShipmentRules_Shipping_Base')) { 
+// Only declare the class once...
+
+/** Shipping costs according to general rules.
+ *  Supported Variables: Weight, ZIP, Amount, Products (1 for each product, even if multiple ordered), Articles 
+ *  Assignable variables: Shipping, Name
+ */
+class plgVmShipmentRules_Shipping_Base extends vmPSPlugin {
+
+	/**
+	 * @param object $subject
+	 * @param array  $config
+	 */
+	function __construct (& $subject, $config) {
+		parent::__construct ($subject, $config);
+
+		$this->_loggable = TRUE;
+		$this->_tablepkey = 'id';
+		$this->_tableId = 'id';
+		$this->tableFields = array_keys ($this->getTableSQLFields ());
+		$varsToPush = $this->getVarsToPush ();
+		$this->setConfigParameterable ($this->_configTableFieldName, $varsToPush);
+	}
+
+	/**
+	 * Create the table for this plugin if it does not yet exist.
+	 *
+	 * @author Valérie Isaksen
+	 */
+	public function getVmPluginCreateTableSQL () {
+		return $this->createTableSQL ('Shipment Rules Table');
+	}
+
+	/**
+	 * @return array
+	 */
+	function getTableSQLFields () {
+		$SQLfields = array(
+			'id'                           => 'int(1) UNSIGNED NOT NULL AUTO_INCREMENT',
+			'virtuemart_order_id'          => 'int(11) UNSIGNED',
+			'order_number'                 => 'char(32)',
+			'virtuemart_shipmentmethod_id' => 'mediumint(1) UNSIGNED',
+			'shipment_name'                => 'varchar(5000)',
+			'rule_name'                    => 'varchar(500)',
+			'order_weight'                 => 'decimal(10,4)',
+			'order_articles'               => 'int(1)',
+			'order_products'               => 'int(1)',
+			'shipment_weight_unit'         => 'char(3) DEFAULT \'KG\'',
+			'shipment_cost'                => 'decimal(10,2)',
+			'tax_id'                       => 'smallint(1)'
+		);
+		return $SQLfields;
+	}
+
+	/**
+	 * This method is fired when showing the order details in the frontend.
+	 * It displays the shipment-specific data.
+	 *
+	 * @param integer $virtuemart_order_id The order ID
+	 * @param integer $virtuemart_shipmentmethod_id The selected shipment method id
+	 * @param string  $shipment_name Shipment Name
+	 * @return mixed Null for shipments that aren't active, text (HTML) otherwise
+	 * @author Valérie Isaksen
+	 * @author Max Milbers
+	 */
+	public function plgVmOnShowOrderFEShipment ($virtuemart_order_id, $virtuemart_shipmentmethod_id, &$shipment_name) {
+		$this->onShowOrderFE ($virtuemart_order_id, $virtuemart_shipmentmethod_id, $shipment_name);
+	}
+
+	/**
+	 * This event is fired after the order has been stored; it gets the shipment method-
+	 * specific data.
+	 *
+	 * @param int    $order_id The order_id being processed
+	 * @param object $cart  the cart
+	 * @param array  $order The actual order saved in the DB
+	 * @return mixed Null when this method was not selected, otherwise true
+	 * @author Valerie Isaksen
+	 */
+	function plgVmConfirmedOrder (VirtueMartCart $cart, $order) {
+
+		if (!($method = $this->getVmPluginMethod ($order['details']['BT']->virtuemart_shipmentmethod_id))) {
+			return NULL; // Another method was selected, do nothing
+		}
+		if (!$this->selectedThisElement ($method->shipment_element)) {
+			return FALSE;
+		}
+		$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;
+		$values['order_weight'] = $this->getOrderWeight ($cart, $method->weight_unit);
+		$values['order_articles'] = $this->getOrderArticles ($cart);
+		$values['order_products'] = $this->getOrderProducts ($cart);
+		$values['shipment_weight_unit'] = $method->weight_unit;
+		$values['shipment_cost'] = $method->cost;
+		$values['tax_id'] = $method->tax_id;
+		$this->storePSPluginInternalData ($values);
+
+		return TRUE;
+	}
+
+	/**
+	 * This method is fired when showing the order details in the backend.
+	 * It displays the shipment-specific data.
+	 * NOTE, this plugin should NOT be used to display form fields, since it's called outside
+	 * a form! Use plgVmOnUpdateOrderBE() instead!
+	 *
+	 * @param integer $virtuemart_order_id The order ID
+	 * @param integer $virtuemart_shipmentmethod_id The order shipment method ID
+	 * @param object  $_shipInfo Object with the properties 'shipment' and 'name'
+	 * @return mixed Null for shipments that aren't active, text (HTML) otherwise
+	 * @author Valerie Isaksen
+	 */
+	public function plgVmOnShowOrderBEShipment ($virtuemart_order_id, $virtuemart_shipmentmethod_id) {
+		if (!($this->selectedThisByMethodId ($virtuemart_shipmentmethod_id))) {
+			return NULL;
+		}
+		$html = $this->getOrderShipmentHtml ($virtuemart_order_id);
+		return $html;
+	}
+
+	/**
+	 * @param $virtuemart_order_id
+	 * @return string
+	 */
+	function getOrderShipmentHtml ($virtuemart_order_id) {
+
+		$db = JFactory::getDBO ();
+		$q = 'SELECT * FROM `' . $this->_tablename . '` '
+			. 'WHERE `virtuemart_order_id` = ' . $virtuemart_order_id;
+		$db->setQuery ($q);
+		if (!($shipinfo = $db->loadObject ())) {
+			vmWarn (500, $q . " " . $db->getErrorMsg ());
+			return '';
+		}
+
+		if (!class_exists ('CurrencyDisplay')) {
+			require(JPATH_VM_ADMINISTRATOR . DS . 'helpers' . DS . 'currencydisplay.php');
+		}
+
+		$currency = CurrencyDisplay::getInstance ();
+		$tax = ShopFunctions::getTaxByID ($shipinfo->tax_id);
+		$taxDisplay = is_array ($tax) ? $tax['calc_value'] . ' ' . $tax['calc_value_mathop'] : $shipinfo->tax_id;
+		$taxDisplay = ($taxDisplay == -1) ? JText::_ ('COM_VIRTUEMART_PRODUCT_TAX_NONE') : $taxDisplay;
+
+		$html = '<table class="adminlist">' . "\n";
+		$html .= $this->getHtmlHeaderBE ();
+		$html .= $this->getHtmlRowBE ('RULES_SHIPPING_NAME', $shipinfo->shipment_name);
+		$html .= $this->getHtmlRowBE ('RULES_WEIGHT', $shipinfo->order_weight . ' ' . ShopFunctions::renderWeightUnit ($shipinfo->shipment_weight_unit));
+		$html .= $this->getHtmlRowBE ('RULES_ARTICLES', $shipinfo->order_articles . '/' . $shipinfo->order_products);
+		$html .= $this->getHtmlRowBE ('RULES_COST', $currency->priceDisplay ($shipinfo->shipment_cost));
+		$html .= $this->getHtmlRowBE ('RULES_TAX', $taxDisplay);
+		$html .= '</table>' . "\n";
+
+		return $html;
+	}
+	
+	/** Include the rule name in the shipment name */
+	protected function renderPluginName ($plugin) {
+		$return = '';
+		$plugin_name = $this->_psType . '_name';
+		$plugin_desc = $this->_psType . '_desc';
+		$description = '';
+		// 		$params = new JParameter($plugin->$plugin_params);
+		// 		$logo = $params->get($this->_psType . '_logos');
+		$logosFieldName = $this->_psType . '_logos';
+		$logos = $plugin->$logosFieldName;
+		if (!empty($logos)) {
+			$return = $this->displayLogos ($logos) . ' ';
+		}
+		if (!empty($plugin->$plugin_desc)) {
+			$description = '<span class="' . $this->_type . '_description">' . $plugin->$plugin_desc . '</span>';
+		}
+		$rulename='';
+		if (!empty($plugin->rule_name)) {
+			$rulename=" (".$plugin->rule_name.")";
+		}
+		$pluginName = $return . '<span class="' . $this->_type . '_name">' . $plugin->$plugin_name . $rulename.'</span>' . $description;
+		return $pluginName;
+	}
+
+
+
+	/**
+	 * @param VirtueMartCart $cart
+	 * @param                $method
+	 * @param                $cart_prices
+	 * @return int
+	 */
+	function getCosts (VirtueMartCart $cart, $method, $cart_prices) {
+		if (empty($method->rules)) $this->parseMethodRules($method);
+		$cartvals = $this->getCartValues ($cart, $method, $cart_prices);
+		
+		foreach ($method->rules as $r) {
+			if ($r->matches($cartvals)) {
+				$method->tax_id = $r->tax_id;
+				$method->matched_rule = $r;
+				$method->rule_name = $r->name;
+				$method->cost = $r->getShippingCosts($cartvals);
+				$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 (
+	 *
+	 * @author Valérie Isaksen (original), Reinhold Kainhofer (tax calculations from shippingWithTax)
+	 *
+	 * @param $cart_prices: $cart_prices['salesPricePayment'] and $cart_prices['paymentTax'] updated. Displayed in the cart.
+	 * @param $value :   fee
+	 * @param $tax_id :  tax id
+	 */
+
+	function setCartPrices (VirtueMartCart $cart, &$cart_prices, $method) {
+
+		if (!class_exists ('calculationHelper')) {
+			require(JPATH_VM_ADMINISTRATOR . DS . 'helpers' . DS . 'calculationh.php');
+		}
+
+		$calculator = calculationHelper::getInstance ();
+
+		$value = $calculator->roundInternal ($this->getCosts ($cart, $method, $cart_prices), 'salesPrice');
+
+		$_psType = ucfirst ($this->_psType);
+		$cart_prices[$this->_psType . 'Value'] = $value;
+
+		$taxrules = array();
+		if (!empty($method->tax_id)) {
+			$cart_prices[$this->_psType . '_calc_id'] = $method->tax_id;
+
+			$db = JFactory::getDBO ();
+			$q = 'SELECT * FROM #__virtuemart_calcs WHERE `virtuemart_calc_id`="' . $method->tax_id . '" ';
+			$db->setQuery ($q);
+			$taxrules = $db->loadAssocList ();
+		}
+
+		if (count ($taxrules) > 0) {
+			if ($method->includes_tax) {
+				$cart_prices['salesPrice' . $_psType] = $calculator->roundInternal ($cart_prices[$this->_psType . 'Value'], 'salesPrice');
+				// Calculate the tax from the final sales price:
+				$calculator->setRevert (true);
+				$cart_prices[$this->_psType . 'Tax'] = $cart_prices['salesPrice' . $_psType] - $calculator->roundInternal ($calculator->executeCalculation($taxrules, $cart_prices[$this->_psType . 'Value'], true));
+				$calculator->setRevert (false);
+			} else {
+				$cart_prices['salesPrice' . $_psType] = $calculator->roundInternal ($calculator->executeCalculation ($taxrules, $cart_prices[$this->_psType . 'Value']), 'salesPrice');
+				$cart_prices[$this->_psType . 'Tax'] = $calculator->roundInternal (($cart_prices['salesPrice' . $_psType] - $cart_prices[$this->_psType . 'Value']), 'salesPrice');
+			}
+			$cart_prices[$this->_psType . '_calc_id'] = $taxrules[0]['virtuemart_calc_id'];
+		} else {
+			$cart_prices['salesPrice' . $_psType] = $value;
+			$cart_prices[$this->_psType . 'Tax'] = 0;
+			$cart_prices[$this->_psType . '_calc_id'] = 0;
+		}
+	}
+	protected function createMethodRule ($r, $countries, $tax) {
+		return new ShippingRule($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;
+			$method->rules[] = $this->createMethodRule ($r, $countries, $tax);
+		}
+	}
+	
+	protected function parseMethodRules (&$method) {
+		$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);
+	}
+
+	protected function getOrderArticles (VirtueMartCart $cart) {
+		/* Cache the value in a static variable and calculate it only once! */
+		static $articles = 0;
+		if(empty($articles) and count($cart->products)>0){
+			foreach ($cart->products as $product) {
+				$articles += $product->quantity;
+			}
+		}
+		return $articles;
+	}
+
+	protected function getOrderDimensions (VirtueMartCart $cart) {
+		/* Cache the value in a static variable and calculate it only once! */
+		static $calculated = 0;
+		static $dimensions=array(
+			'volume' => 0,
+			'maxvolume' => 0, 'minvolume' => 9999999999,
+			'maxlength' => 0, 'minlength' => 9999999999,
+			'maxwidth'  => 0, 'minwidth' => 9999999999,
+			'maxheight' => 0, 'minheight' => 9999999999,
+			);
+		if ($calculated==0) {
+			$calculated=1;
+			foreach ($cart->products as $product) {
+				$volume = $product->product_length * $product->product_width * $product->product_height;
+				$dimensions['volume'] += $volume * $product->quantity;
+				$dimensions['maxvolume'] = max ($dimensions['maxvolume'], $volume);
+				$dimensions['minvolume'] = min ($dimensions['minvolume'], $volume);
+				$dimensions['maxlength'] = max ($dimensions['maxlength'], $product->product_length);
+				$dimensions['minlength'] = min ($dimensions['minlength'], $product->product_length);
+				$dimensions['maxwidth'] = max ($dimensions['maxwidth'], $product->product_width);
+				$dimensions['minwidth'] = min ($dimensions['minwidth'], $product->product_width);
+				$dimensions['maxheight'] = max ($dimensions['maxheight'], $product->product_height);
+				$dimensions['minheight'] = min ($dimensions['minheight'], $product->product_height);
+			}
+		}
+		return $dimensions;
+	}
+
+	protected function getOrderProducts (VirtueMartCart $cart) {
+		/* Cache the value in a static variable and calculate it only once! */
+		static $products = 0;
+		if(empty($products) and count($cart->products)>0){
+			$products = count($cart->products);
+		}
+		return $products;
+	}
+	
+	/** Allow child classes to add additional variables for the rules 
+	 */
+	protected function addCustomCartValues (VirtueMartCart $cart, $cart_prices, &$values) {
+	}
+	protected function getCartValues (VirtueMartCart $cart, $method, $cart_prices) {
+		$orderWeight = $this->getOrderWeight ($cart, $method->weight_unit);
+		$address = (($cart->ST == 0) ? $cart->BT : $cart->ST);
+		
+		$products = 0;
+		$articles = 0;
+		foreach ($cart->products as $product) {
+			$products += 1;
+			$articles += $product->quantity;
+		}
+		$cartvals = array('weight'=>$orderWeight, 
+				  'zip'=>$address['zip'], 
+				  'articles'=>$articles, 
+				  'products'=>$products,
+				  'amount'=>$cart_prices['salesPrice'],
+				  'amountWithTax'=>$cart_prices['salesPrice'],
+				  'amountWithoutTax'=>$cart_prices['priceWithoutTax'],
+
+				  'basePrice'=>$cart_prices['basePrice'],
+				  'basePriceWithTax'=>$cart_prices['basePriceWithTax'],
+				  'discountedPriceWithoutTax'=>$cart_prices['discountedPriceWithoutTax'],
+				  'salesPrice'=>$cart_prices['salesPrice'],
+				  'taxAmount'=>$cart_prices['taxAmount'],
+				  'salesPriceWithDiscount'=>$cart_prices['salesPriceWithDiscount'],
+				  'discountAmount'=>$cart_prices['discountAmount'],
+				  'priceWithoutTax'=>$cart_prices['priceWithoutTax'],
+				  'discountBeforeTaxBill'=>$cart_prices['discountBeforeTaxBill'],
+
+				  'country'=>$address['virtuemart_country_id'],
+			);
+		$cartvals = array_merge ($cartvals, $this->getOrderDimensions ($cart));
+		// Let child classes update the $cartvals array, or add new variables
+		$this->addCustomCartValues($cart, $cart_prices, $cartvals);
+		return $cartvals;
+	}
+
+
+	/**
+	 * @param \VirtueMartCart $cart
+	 * @param int             $method
+	 * @param array           $cart_prices
+	 * @return bool
+	 */
+	protected function checkConditions ($cart, $method, $cart_prices) {
+		if (empty($method->rules)) $this->parseMethodRules($method);
+
+		$cartvals = $this->getCartValues ($cart, $method, $cart_prices);
+		foreach ($method->rules as $r) {
+			if ($r->matches($cartvals)) {
+				$method->matched_rule = $r;
+				$method->rule_name = $r->name;
+				return TRUE;
+			}
+		}
+		vmdebug('checkConditions '.$method->name.' does not fit');
+		return FALSE;
+	}
+
+	/**
+	 * Create the table for this plugin if it does not yet exist.
+	 * This functions checks if the called plugin is active one.
+	 * When yes it is calling the standard method to create the tables
+	 *
+	 * @author Valérie Isaksen
+	 *
+	 */
+	function plgVmOnStoreInstallShipmentPluginTable ($jplugin_id) {
+		return $this->onStoreInstallPluginTable ($jplugin_id);
+	}
+
+	/**
+	 * @param VirtueMartCart $cart
+	 * @return null
+	 */
+	public function plgVmOnSelectCheckShipment (VirtueMartCart &$cart) {
+		return $this->OnSelectCheck ($cart);
+	}
+
+	/**
+	 * plgVmDisplayListFE
+	 * This event is fired to display the pluginmethods in the cart (edit shipment/payment) for example
+	 *
+	 * @param object  $cart Cart object
+	 * @param integer $selected ID of the method selected
+	 * @return boolean True on success, false on failures, null when this plugin was not selected.
+	 * On errors, JError::raiseWarning (or JError::raiseError) must be used to set a message.
+	 *
+	 * @author Valerie Isaksen
+	 * @author Max Milbers
+	 */
+	public function plgVmDisplayListFEShipment (VirtueMartCart $cart, $selected = 0, &$htmlIn) {
+		return $this->displayListFE ($cart, $selected, $htmlIn);
+	}
+
+	/**
+	 * @param VirtueMartCart $cart
+	 * @param array          $cart_prices
+	 * @param                $cart_prices_name
+	 * @return bool|null
+	 */
+	public function plgVmOnSelectedCalculatePriceShipment (VirtueMartCart $cart, array &$cart_prices, &$cart_prices_name) {
+		return $this->onSelectedCalculatePrice ($cart, $cart_prices, $cart_prices_name);
+	}
+
+	/**
+	 * plgVmOnCheckAutomaticSelected
+	 * Checks how many plugins are available. If only one, the user will not have the choice. Enter edit_xxx page
+	 * The plugin must check first if it is the correct type
+	 *
+	 * @author Valerie Isaksen
+	 * @param VirtueMartCart cart: the cart object
+	 * @return null if no plugin was found, 0 if more then one plugin was found,  virtuemart_xxx_id if only one plugin is found
+	 *
+	 */
+	function plgVmOnCheckAutomaticSelectedShipment (VirtueMartCart $cart, array $cart_prices = array(), &$shipCounter) {
+		if ($shipCounter > 1) {
+			return 0;
+		}
+		return $this->onCheckAutomaticSelected ($cart, $cart_prices, $shipCounter);
+	}
+
+	/**
+	 * This method is fired when showing when priting an Order
+	 * It displays the the payment method-specific data.
+	 *
+	 * @param integer $_virtuemart_order_id The order ID
+	 * @param integer $method_id  method used for this order
+	 * @return mixed Null when for payment methods that were not selected, text (HTML) otherwise
+	 * @author Valerie Isaksen
+	 */
+	function plgVmonShowOrderPrint ($order_number, $method_id) {
+		return $this->onShowOrderPrint ($order_number, $method_id);
+	}
+
+	function plgVmDeclarePluginParamsShipment ($name, $id, &$data) {
+		return $this->declarePluginParams ('shipment', $name, $id, $data);
+	}
+
+
+	/**
+	 * @author Max Milbers
+	 * @param $data
+	 * @param $table
+	 * @return bool
+	 */
+	function plgVmSetOnTablePluginShipment(&$data,&$table){
+
+		$name = $data['shipment_element'];
+		$id = $data['shipment_jplugin_id'];
+
+		if (!empty($this->_psType) and !$this->selectedThis ($this->_psType, $name, $id)) {
+			return FALSE;
+		} else {
+			// Try to parse all rules (and spit out error) to inform the user:
+			$method = new StdClass ();
+			$this->parseMethodRule ($data['rules1'], $data['countries1'], $data['tax_id1'], $method);
+			$this->parseMethodRule ($data['rules2'], $data['countries2'], $data['tax_id2'], $method);
+			$this->parseMethodRule ($data['rules3'], $data['countries3'], $data['tax_id3'], $method);
+			$this->parseMethodRule ($data['rules4'], $data['countries4'], $data['tax_id4'], $method);
+			$this->parseMethodRule ($data['rules5'], $data['countries5'], $data['tax_id5'], $method);
+			$this->parseMethodRule ($data['rules6'], $data['countries6'], $data['tax_id6'], $method);
+			$this->parseMethodRule ($data['rules7'], $data['countries7'], $data['tax_id7'], $method);
+			$this->parseMethodRule ($data['rules8'], $data['countries8'], $data['tax_id8'], $method);
+			$ret=$this->setOnTablePluginParams ($name, $id, $table);
+			return $ret;
+		}
+	}
+
+
+}
+}
+
+if (!class_exists ('ShippingRule')) { 
+
+class ShippingRule {
+	var $rulestring = '';
+	var $countries = array();
+	var $tax_id = 0;
+	var $conditions = array();
+	var $shipping = 0;
+	var $includes_tax = 0;
+	var $name = '';
+	
+	function __construct ($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);
+	}
+	
+	function parseRule($rule) {
+		$ruleparts=explode(';', $rule);
+		foreach ($ruleparts as $p) {
+			$this->parseRulePart($p);
+		}
+	}
+	
+	function handleAssignment ($var, $value, $rulepart) {
+		switch (strtolower($var)) {
+			case 'shipping': $this->shipping = $value; $this->includes_tax = False; break;
+			case 'shippingwithtax': $this->shipping = $value; $this->includes_tax = True; break;
+			case 'name': $this->name = $value; break;
+			default: JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_VARIABLE', $var, $rulepart), 'error');
+		}
+	}
+	
+	
+	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 */
+		$operators = array('<', '<=', '=', '>', '>=', '=>', '=<', '<>', '!=', '==');
+		$op_re='/\s*(<=|=>|>=|=>|<>|!=|==|<|=|>)\s*/';
+		$rulepart = trim($rulepart);
+		if (empty($rulepart)) return;
+		$atoms = preg_split ($op_re, $rulepart, -1, PREG_SPLIT_DELIM_CAPTURE);
+		if (count($atoms)==1) {
+			$this->shipping = $this->parseShippingTerm($atoms[0]);
+		} 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();
+				}
+			}
+		}
+	}
+
+	function parseShippingTerm($expr) {
+		/* In the advanced version, shipping cost can be given as a full mathematical expression */
+		return strtolower($expr);
+	}
+	
+	function evaluateTerm ($expr, $vals) {
+		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);
+			} elseif (array_key_exists(strtolower($expr), $vals)) {
+				return $vals[strtolower($expr)];
+			} else {
+				JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_VALUE', $expr, $this->rulestring), 'error');
+				return null;
+			}
+		} elseif (is_array($expr)) {
+			// Operator
+			$op = array_shift($expr);
+			$args = array();
+			foreach ($expr as $e) {
+				$term = $this->evaluateTerm($e, $vals);
+				if (is_null($term)) return null;
+				$args[] = $term;
+			}
+			$res = false;
+			switch ($op) {
+				case 'OR':
+				case ' OR ':  foreach ($args as $a) { $res = ($res || $a); }; break;
+				case 'AND':
+				case ' AND ':  $res = true; foreach ($args as $a) { $res = ($res && $a); }; break;
+				case 'in': $needle = array_shift($args); $res = in_array($needle, $args); break;
+				
+				case '<':  $res = ($args[0] < $args[1]);  break;
+				case '<=': 
+				case '=<': $res = ($args[0] <= $args[1]); break;
+				case '==': $res = ($args[0] == $args[1]); break;
+				case '!=':
+				case '<>': $res = ($args[0] != $args[1]); break;
+				case '>=':
+				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 = ($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;
+				
+				default:   $res = false;
+			}
+			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;
+		}
+	}
+
+	function calculateShipping ($vals) {
+		return $this->evaluateTerm($this->shipping, $vals);
+	}
+
+	function matches($vals) {
+		// First, check the country, if any conditions are given:
+		if (count ($this->countries) > 0 && !in_array ($vals['country'], $this->countries)) 
+			return False;
+		
+		foreach ($this->conditions as $c) {
+			// All conditions have to match!
+			$ret = $this->evaluateTerm($c, $vals);
+			if (is_null($ret) || (!$ret)) {
+				return false;
+			}
+		}
+		// All conditions match, so return true
+		return true;
+	}
+
+	function getShippingCosts($vals) {
+		return $this->calculateShipping($vals);
+	}
+
+}
+
+}
+// No closing tag