diff --git a/.gitignore b/.gitignore index b25c15b81fae06e1c55946ac6270bfdb293870e8..dd772ae73a880fcae10fc505c81ec43c867996aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *~ +language/en-GB/en-GB.plg_vmshipment_rules_shipping_advanced.ini +language/en-GB/en-GB.plg_vmshipment_rules_shipping_advanced.sys.ini diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..56fe3dbf1395e241e050cf33ccea6009e30bfc0c --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +BASE=rules_shipping +BASE_ADV=rules_shipping_advanced +PLUGINTYPE=vmshipment +VERSION=1.0.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 + +TRANSLATIONS=$(call wildcard,language/*/*.plg_$(PLUGINTYPE)_$(BASE).*ini) +TRANSLATIONS_ADV=$(subst $(BASE),$(BASE_ADV),$(TRANSLATIONS)) +INDEXFILES=language/index.html $(call wildcard,language/**/index.html) $(call wildcard,elements/*.html) +ELEMENTS=$(call wildcard,elements/*.php) +ZIPFILE=plg_$(PLUGINTYPE)_$(BASE)_v$(VERSION).zip +ZIPFILE_ADV=plg_$(PLUGINTYPE)_$(BASE_ADV)_v$(VERSION).zip + +ADVANCEDFILES=$(call wildcard,$(BASE)_advanced.*) + +all: translations zip + +translations: $(TRANSLATIONS) + @echo $(TRANSLATIONS_ADV) + @cp language/en-GB/en-GB.plg_vmshipment_rules_shipping.ini language/en-GB/en-GB.plg_vmshipment_rules_shipping_advanced.ini + @cp language/en-GB/en-GB.plg_vmshipment_rules_shipping.sys.ini language/en-GB/en-GB.plg_vmshipment_rules_shipping_advanced.sys.ini + +zip: $(PLUGINFILES) $(TRANSLATIONS) $(ADVANCEDFILES) + @echo "Packing all files into distribution file $(ZIPFILE):" + @zip -r $(ZIPFILE) $(PLUGINFILES) $(TRANSLATIONS) $(ELEMENTS) $(INDEXFILES) + @zip -r $(ZIPFILE_ADV) $(PLUGINFILES_ADV) $(TRANSLATIONS_ADV) $(ELEMENTS) $(INDEXFILES) + +clean: + rm -f $(ZIPFILE) $(ZIPFILE_ADV) diff --git a/elements/index.html b/elements/index.html new file mode 100644 index 0000000000000000000000000000000000000000..2efb97f319a35f6bd80f1751134ed71ec11888eb --- /dev/null +++ b/elements/index.html @@ -0,0 +1 @@ +<!DOCTYPE html><title></title> diff --git a/elements/rklabel.php b/elements/rklabel.php new file mode 100644 index 0000000000000000000000000000000000000000..a9725d0fbd1f9743b9e5353178692e9839f98703 --- /dev/null +++ b/elements/rklabel.php @@ -0,0 +1,60 @@ +<?php +// Check to ensure this file is within the rest of the framework +defined('JPATH_BASE') or die(); + +/** + * A label/header element, displayed left-aligned, spanning the whole width. This can be used for section headers as well as for explanatory text + */ + +class JElementRKLabel extends JElement +{ + /** + * Element name + * + * @access protected + * @var string + */ + var $_name = 'rklabel'; + + function fetchElement($name, $value, &$node, $control_name) + { + $class = ( $node->attributes('class') ? 'class="'.$node->attributes('class').'"' : 'class="text_area"' ); + return '<label for="'.$name.'"'.$class.'>'.JText::_($value).'</label>'; + } + + + /** + * Method to render an xml element + * + * @param string &$xmlElement Name of the element + * @param string $value Value of the element + * @param string $control_name Name of the control + * + * @return array Attributes of an element + * + * @deprecated 12.1 + * @since 11.1 + */ + public function render(&$xmlElement, $value, $control_name = 'params') + { + // Deprecation warning. + JLog::add('JElement::render is deprecated.', JLog::WARNING, 'deprecated'); + + $name = $xmlElement->attributes('name'); + $label = $xmlElement->attributes('label'); + $descr = $xmlElement->attributes('description'); + + //make sure we have a valid label + $label = $label ? $label : $name; + $result[0] = NULL; +// $result[0] = $this->fetchTooltip($label, $descr, $xmlElement, $control_name, $name); + $result[1] = $this->fetchElement($name, $value, $xmlElement, $control_name); + $result[2] = $descr; + $result[3] = $label; + $result[4] = $value; + $result[5] = $name; + + return $result; + } + +} \ No newline at end of file diff --git a/images/plg_vmshipping_rules_shipping_advanced_icon.png b/images/plg_vmshipping_rules_shipping_advanced_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..773d0d18c1b765e43e6d4d72465684993d0e8542 Binary files /dev/null and b/images/plg_vmshipping_rules_shipping_advanced_icon.png differ diff --git a/images/plg_vmshipping_rules_shipping_cart.png b/images/plg_vmshipping_rules_shipping_cart.png new file mode 100644 index 0000000000000000000000000000000000000000..90c39ae4f37692d09de6b2f6acdf8c3932043bef Binary files /dev/null and b/images/plg_vmshipping_rules_shipping_cart.png differ diff --git a/images/plg_vmshipping_rules_shipping_cart1.png b/images/plg_vmshipping_rules_shipping_cart1.png new file mode 100644 index 0000000000000000000000000000000000000000..e54a2b4277c1cf0a1e0aedf4e7c8670a657b6cad Binary files /dev/null and b/images/plg_vmshipping_rules_shipping_cart1.png differ diff --git a/images/plg_vmshipping_rules_shipping_cart2.png b/images/plg_vmshipping_rules_shipping_cart2.png new file mode 100644 index 0000000000000000000000000000000000000000..e8c37bd48cb50eaa36eeebbd954d1fbff8e1e926 Binary files /dev/null and b/images/plg_vmshipping_rules_shipping_cart2.png differ diff --git a/images/plg_vmshipping_rules_shipping_cart3.png b/images/plg_vmshipping_rules_shipping_cart3.png new file mode 100644 index 0000000000000000000000000000000000000000..551aeb3de546ef348735daf4ab09d31e441fffd8 Binary files /dev/null and b/images/plg_vmshipping_rules_shipping_cart3.png differ diff --git a/images/plg_vmshipping_rules_shipping_config.png b/images/plg_vmshipping_rules_shipping_config.png new file mode 100644 index 0000000000000000000000000000000000000000..b4d7a23b5fc12bcaa8f066686dafaadba0ee6d6a Binary files /dev/null and b/images/plg_vmshipping_rules_shipping_config.png differ diff --git a/images/plg_vmshipping_rules_shipping_config1.png b/images/plg_vmshipping_rules_shipping_config1.png new file mode 100644 index 0000000000000000000000000000000000000000..94ef7007f50d910a7657c14f5ca286640a3192e8 Binary files /dev/null and b/images/plg_vmshipping_rules_shipping_config1.png differ diff --git a/images/plg_vmshipping_rules_shipping_icon.png b/images/plg_vmshipping_rules_shipping_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..caffeb0775ae3aad57f38c0c93a7d0a46fa2ceb6 Binary files /dev/null and b/images/plg_vmshipping_rules_shipping_icon.png differ diff --git a/images/plg_vmshipping_rules_shipping_icon.xcf b/images/plg_vmshipping_rules_shipping_icon.xcf new file mode 100644 index 0000000000000000000000000000000000000000..bd175057c696dd14ab0de2e28cae1c8e255e4d60 Binary files /dev/null and b/images/plg_vmshipping_rules_shipping_icon.xcf differ diff --git a/images/plg_vmshipping_rules_shipping_installation.png b/images/plg_vmshipping_rules_shipping_installation.png new file mode 100644 index 0000000000000000000000000000000000000000..0a448639cd580ecb3c51962a68d163275e2577a3 Binary files /dev/null and b/images/plg_vmshipping_rules_shipping_installation.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/language/en-GB/en-GB.plg_vmshipment_rules_shipping.ini b/language/en-GB/en-GB.plg_vmshipment_rules_shipping.ini new file mode 100644 index 0000000000000000000000000000000000000000..3b1af168c3e11f5f25dc7db58aaa17a3c006c1b4 --- /dev/null +++ b/language/en-GB/en-GB.plg_vmshipment_rules_shipping.ini @@ -0,0 +1,63 @@ +; VM2 General Shipping plugin +; Copyright (C) 20123Reinhold Kainhofer. All rights reserved. +; License http://www.gnu.org/licenses/gpl.html GNU/GPL +; Note : All ini files need to be saved as UTF-8 - No BOM +VMSHIPMENT_RULES = "VM2 - Shipping by Rules" +VMSHIPMENT_RULES_DESC = "" +VMSHIPMENT_RULES_ADV = "VM2 - Advanced Shipping by Rules" +VMSHIPMENT_RULES_ADV_DESC = "" + +VMSHIPMENT_RULES_PLUGIN="<b>Shipment based on general rules (weight, products, amount) per country-zones</b>" +VMSHIPMENT_RULES_PLUGIN_ADV="<b>Shipment based on general rules (weight, products, amount) per country-zones, including arithmetic expression</b>" + +VMSHIPMENT_RULES_HELP="<div style='background: #DDDDDD; padding:5px; margin:3px;'><h2>Overview of the Rules Syntax</h2> +<ul><li>Each line contains one rule consisting of several <b>conditions and assignments in arbitrary order and separated by semicolons</b>. </li> +<li>The <b>first matching rule is used</b> to calculate the shipping costs.</li> +<li>All rule parts with <b>comparison operators (<, <=, =<, ==, !=, <>, >=, =>, >)</b> are conditions.</li> +<li> Supported <b>variables (case-insensitive)</b>: <b><tt>Amount</tt></b>, <b><tt>Weight</tt></b>, <b><tt>ZIP</tt></b>, <b><tt>Products</tt></b> (number of different products order), <b><tt>Articles</tt></b> (each product counted with its quantity).</li> +<li>A condition can consist of multiple chained comparisons.</li> +<li>All rule parts of the form <b><tt>[VARIABLE]=VALUE</tt> are assignments</b>, with <tt>[VARIABLE]</tt> being one of <b><tt>Name</tt></b> (optional name of the rule, displayed in the cart and invoice), <b><tt>Shipping</tt></b> (shipping cost if the rule matches). The <tt>Shipping=</tt> can be left out. I.e. a rule part consisting only of a numerical value is understood as shipping cost.</li> +</ul> +<p>EXAMPLE: A rule named "Europe" that sets shipping costs of 4.50 for order amounts from 50 to less than 100, and free shipping from 100€ on would be:</p><blockquote><tt>Name=Europe; 50<=Amount<100; Shipping=4.50<br>Name=Free Shipping; 100<= Amount; 0</tt></blockquote> +</div>" +VMSHIPMENT_RULES_HELP_ADV="<div style='background: #DDDDDD; padding:5px; margin:3px;'><h2>Overview of the Rules Syntax</h2> +<ul><li>Each line contains one rule consisting of several <b>conditions and assignments in arbitrary order and separated by semicolons</b>. </li> +<li>The <b>first matching rule is used</b> to calculate the shipping costs.</li> +<li>All rule parts with <b>comparison operators (<, <=, =<, ==, !=, <>, >=, =>, >)</b> are conditions.</li> +<li> Supported <b>variables (case-insensitive)</b>: <b><tt>Amount</tt></b>, <b><tt>Weight</tt></b>, <b><tt>ZIP</tt></b>, <b><tt>Products</tt></b> (number of different products order), <b><tt>Articles</tt></b> (each product counted with its quantity).</li> +<li>A condition can consist of multiple chained comparisons.</li> +<li>All rule parts of the form <b><tt>[VARIABLE]=VALUE</tt> are assignments</b>, with <tt>[VARIABLE]</tt> being one of <b><tt>Name</tt></b> (optional name of the rule, displayed in the cart and invoice), <b><tt>Shipping</tt></b> (shipping cost if the rule matches). The <tt>Shipping=</tt> can be left out. I.e. a rule part consisting only of a numerical value is understood as shipping cost.</li> +<li>In the advanced version, all expressions (conditions and shipping costs) may contain <b>arbitrary basic arithmetic expressions (+, -, *, /, %, ^ and parentheses)</b> of the above variables.</li> +</ul> +<p>EXAMPLE: A rule named "Europe" that sets shipping costs of 5€ plus 1.50€ per article for order amounts from 50 to less than 100, and free shipping from 100€ on would be:</p><blockquote><tt>Name=Europe; 50<=Amount<100; Shipping=5+1.50*Articles<br>Name=Free Shipping; 100<= Amount; 0</blockquote> +</div>" + +VMSHIPMENT_RULES_COUNTRIES1_LABEL="<b>Rules for country set 1</b>" +VMSHIPMENT_RULES_COUNTRIES2_LABEL="<b>Rules for country set 2</b>" +VMSHIPMENT_RULES_COUNTRIES3_LABEL="<b>Rules for country set 3</b>" +VMSHIPMENT_RULES_COUNTRIES4_LABEL="<b>Rules for country set 4</b>" +VMSHIPMENT_RULES_COUNTRIES5_LABEL="<b>Rules for country set 5</b>" +VMSHIPMENT_RULES_COUNTRIES6_LABEL="<b>Rules for country set 6</b>" +VMSHIPMENT_RULES_COUNTRIES7_LABEL="<b>Rules for country set 7</b>" +VMSHIPMENT_RULES_COUNTRIES8_LABEL="<b>Rules for country set 8</b>" +VMSHIPMENT_RULES_COUNTRIES="Countries" +VMSHIPMENT_RULES_COUNTRIES_DESC="Select the countries to which the following rules should be restricted. If left empty, the rules will apply to all countries." +VMSHIPMENT_RULES_TAX="Tax" +VMSHIPMENT_RULES_TAX_DESC="Tax methods applied to the shipping cost if any of the given rules matches." +VMSHIPMENT_RULES_RULES="Rules" +VMSHIPMENT_RULES_RULES_DESC="" + +VMSHIPMENT_RULES_SHIPPING_NAME="Shipment Name" +VMSHIPMENT_RULES_WEIGHT="Order Weight" +VMSHIPMENT_RULES_ARTICLES="Articles/Products in Order" +VMSHIPMENT_RULES_COST="Shipment Cost" + +VMSHIPMENT_RULES_UNKNOWN_VARIABLE="Unknown variable '%s' in rule '%s'" +VMSHIPMENT_RULES_UNKNOWN_OPERATOR="Unknown operator '%s' in shipment rule '%s'" +VMSHIPMENT_RULES_PARSE_MISSING_PAREN="Error during parsing expression '%s': Opening parenthesis cannot be found!" +VMSHIPMENT_RULES_PARSE_PAREN_NOT_CLOSED="Error during parsing expression '%s': A parenthesis was not closed properly!" + +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 diff --git a/language/en-GB/en-GB.plg_vmshipment_rules_shipping.sys.ini b/language/en-GB/en-GB.plg_vmshipment_rules_shipping.sys.ini new file mode 100644 index 0000000000000000000000000000000000000000..211a00a4fca19e2e0f9b14334c2fe55938f1a329 --- /dev/null +++ b/language/en-GB/en-GB.plg_vmshipment_rules_shipping.sys.ini @@ -0,0 +1,8 @@ +; VM2 Rule-based Shipping plugin +; Copyright (C) 20123Reinhold Kainhofer. All rights reserved. +; License http://www.gnu.org/licenses/gpl.html GNU/GPL +; Note : All ini files need to be saved as UTF-8 - No BOM +VMSHIPMENT_RULES = "VM2 - Shipping by Rules" +VMSHIPMENT_RULES_DESC = "" +VMSHIPMENT_RULES_ADV = "VM2 - Advanced Shipping by Rules" +VMSHIPMENT_RULES_ADV_DESC = "" diff --git a/language/en-GB/index.html b/language/en-GB/index.html new file mode 100644 index 0000000000000000000000000000000000000000..2efb97f319a35f6bd80f1751134ed71ec11888eb --- /dev/null +++ b/language/en-GB/index.html @@ -0,0 +1 @@ +<!DOCTYPE html><title></title> diff --git a/language/index.html b/language/index.html new file mode 100644 index 0000000000000000000000000000000000000000..2efb97f319a35f6bd80f1751134ed71ec11888eb --- /dev/null +++ b/language/index.html @@ -0,0 +1 @@ +<!DOCTYPE html><title></title> diff --git a/releases/plg_vmshipment_rules_shipping_v1.0.0.zip b/releases/plg_vmshipment_rules_shipping_v1.0.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..505ffbc8aa191adeb94f8bbbff9b1dc1c7d3b217 Binary files /dev/null and b/releases/plg_vmshipment_rules_shipping_v1.0.0.zip differ diff --git a/rules_shipping.php b/rules_shipping.php new file mode 100644 index 0000000000000000000000000000000000000000..360c56953af62ce1256b079d9eef67528f94d561 --- /dev/null +++ b/rules_shipping.php @@ -0,0 +1,547 @@ +<?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')) { +// 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 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); + + $address = (($cart->ST == 0) ? $cart->BT : $cart->ST); + $cartvals = array('weight' => $this->getOrderWeight ($cart, $method->weight_unit), + 'zip' => $address['zip'], + 'articles' => $this->getOrderArticles ($cart), + 'products' => $this->getOrderProducts ($cart), + 'amount' => $cart_prices['salesPrice'], + 'country' => $address['virtuemart_country_id']); + 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); + return $method->cost; + } + } + + vmdebug('getCosts '.$method->name.' does not return shipping costs'); + return 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 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; + } + + + /** + * @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); + + $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'], + 'country'=>$address['virtuemart_country_id']); + 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 $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); + $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]=='=') { + switch (strtolower($atoms[0])) { + case 'shipping': $this->shipping = $this->parseShippingTerm($atoms[2]); break; + case 'name': $this->name = $atoms[2]; break; + default: JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_VARIABLE', $atoms[0], $p), 'error'); + } + } 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.script.php b/rules_shipping.script.php new file mode 100644 index 0000000000000000000000000000000000000000..7d92ad444e70585b660fdbc7d024147ec26efbf1 --- /dev/null +++ b/rules_shipping.script.php @@ -0,0 +1,79 @@ +<?php +defined('_JEXEC') or die('Restricted access'); + +class plgVmShipmentRules_ShippingInstallerScript +{ + /** + * Constructor + * + * @param JAdapterInstance $adapter The object responsible for running this script + */ +// public function __constructor(JAdapterInstance $adapter); + + /** + * Called before any type of action + * + * @param string $route Which action is happening (install|uninstall|discover_install) + * @param JAdapterInstance $adapter The object responsible for running this script + * + * @return boolean True on success + */ +// public function preflight($route, JAdapterInstance $adapter); + + /** + * Called after any type of action + * + * @param string $route Which action is happening (install|uninstall|discover_install) + * @param JAdapterInstance $adapter The object responsible for running this script + * + * @return boolean True on success + */ +// public function postflight($route, JAdapterInstance $adapter); + + /** + * Called on installation + * + * @param JAdapterInstance $adapter The object responsible for running this script + * + * @return boolean True on success + */ + public function install(JAdapterInstance $adapter) + { + // enabling plugin + $db =& JFactory::getDBO(); + $db->setQuery('update #__extensions set enabled = 1 where type = "plugin" and element = "rules_shipping" and folder = "vmshipment"'); + $db->query(); + + return True; + } + + /** + * Called on update + * + * @param JAdapterInstance $adapter The object responsible for running this script + * + * @return boolean True on success + */ +// public function update(JAdapterInstance $adapter) +// { +// jimport( 'joomla.filesystem.file' ); +// $file = JPATH_ROOT . DS . "administrator" . DS . "language" . DS . "en-GB" . DS . "en-GB.plg_vmshopper_ordernumber.sys.ini"; +// if (JFile::exists($file)) JFile::delete($file); +// $file = JPATH_ROOT . DS . "administrator" . DS . "language" . DS . "de-DE" . DS . "de-DE.plg_vmshopper_ordernumber.sys.ini"; +// if (JFile::exists($file)) JFile::delete($file); +// return true; +// } + + /** + * Called on uninstallation + * + * @param JAdapterInstance $adapter The object responsible for running this script + */ + public function uninstall(JAdapterInstance $adapter) + { + // Remove plugin table + $db =& JFactory::getDBO(); + $db->setQuery('DROP TABLE `#__virtuemart_shipment_plg_rules_shipping`;'); + $db->query(); + } +} \ No newline at end of file diff --git a/rules_shipping.xml b/rules_shipping.xml new file mode 100644 index 0000000000000000000000000000000000000000..35e14344ae3f42f9ebb626186828788a5111afea --- /dev/null +++ b/rules_shipping.xml @@ -0,0 +1,78 @@ +<?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> + <author>Reinhold Kainhofer</author> + <authorUrl>http://www.kainhofer.com</authorUrl> + <copyright>Copyright (C) 2013, Reinhold Kainhofer</copyright> + <license>GPL v3+</license> + <version>1.0.0</version> + <description>VMSHIPMENT_RULES_DESC</description> + <files> + <filename plugin="rules_shipping">rules_shipping.php</filename> + <folder>language</folder> + <folder>elements</folder> + </files> + <languages folder="language/en-GB"> + <language tag="en-GB">en-GB.plg_vmshipment_rules_shipping.ini</language> + <language tag="en-GB">en-GB.plg_vmshipment_rules_shipping.sys.ini</language> + </languages> + <scriptfile>rules_shipping.script.php</scriptfile> + + + <params addpath="/plugins/vmshipment/rules_shipping/elements/" /> + + <params addpath="/administrator/components/com_virtuemart/elements"> + <param type="vmjpluginwarning" /> + <param type="spacer" default='VMSHIPMENT_RULES_PLUGIN' level="level1" /> + <param name="shipment_logos" type="vmfiles" label="VMSHIPMENT_WEIGHT_COUNTRIES_LOGOS" + description="VMSHIPMENT_WEIGHT_COUNTRIES_LOGOS_DESC" + directory="/images/stories/virtuemart/shipment" + default="default" hide_default="1" hide_none="1" + stripext="0" exclude="^_" /> + <param name="weight_unit" type="vmweightunit" size="10" default="" label="VMSHIPMENT_WEIGHT_COUNTRIES_WEIGHT_UNIT" description="VMSHIPMENT_WEIGHT_COUNTRIES_WEIGHT_UNIT_DESC"/> + + <param type="rklabel" default='VMSHIPMENT_RULES_HELP' /> + + <param type="rklabel" default='VMSHIPMENT_RULES_COUNTRIES1_LABEL' /> + <param name="countries1" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id1" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules1" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES2_LABEL" /> + <param name="countries2" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id2" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules2" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES3_LABEL" /> + <param name="countries3" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id3" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules3" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES4_LABEL" /> + <param name="countries4" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id4" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules4" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES5_LABEL" /> + <param name="countries5" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id5" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules5" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES6_LABEL" /> + <param name="countries6" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id6" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules6" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES7_LABEL" /> + <param name="countries7" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id7" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules7" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES8_LABEL" /> + <param name="countries8" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id8" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules8" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + </params> + +</install> diff --git a/rules_shipping_advanced.php b/rules_shipping_advanced.php new file mode 100644 index 0000000000000000000000000000000000000000..d651d1aadc39efce3e704c82da27e8d5a6d37ad7 --- /dev/null +++ b/rules_shipping_advanced.php @@ -0,0 +1,196 @@ +<?php + +defined ('_JEXEC') or die('Restricted access'); + +/** + * Shipment plugin for general, rules-based shipments, like regular postal services with complex shipping cost structures + * Advanced part, implementing general mathematical expression evaluation + * + * @version $Id$ + * @package VirtueMart + * @subpackage Plugins - shipment. + * @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')) { + require (dirname(__FILE__).DS.'rules_shipping.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); +// } + +} + +/** 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); + + function __construct ($rule, $countries, $tax_id) { + parent::__construct ($rule, $countries, $tax_id); + } + + /** 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) + * 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 + * Store this RPN list for later evaluation + */ + function parseShippingTerm ($expr) { + $op_re = ':\s*(\+|-|\*|/|%|\(|\)|\^)\s*:'; + $atoms = preg_split($op_re, strtolower($expr), -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY); + if (count($atoms)==1) return $atoms[0]; + // Operators,including precedence + + $stack = array (); // 1) + $result = array (); + foreach ($atoms as $a) { // 2) + if (!isset($this->operators[$a])) { // 3) + array_push ($result, $a); + } elseif ($a == "(") { // 5) + array_push ($stack, $a); + } elseif ($a == ")") { // 6) + do { + $op=array_pop($stack); // 6a) + if (!empty($op) && $op != "(") { + array_push ($result, $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'); + } + break; // We have found the opening parenthesis + } + } while (0); + } else { // 4 + // 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]; + + while (count($stack)>0) { // 4a) + $op = array_pop ($stack); + // Ignore the right-associative symbols of equal precedence for now... + if ($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); + } + } while (0); + array_push ($stack, $a); // 4b) + } + } + // 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', $expr), 'error'); + } else { + array_push ($result, $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: + * 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 ($expr 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'); + 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; + } + array_push($stack, $res); // 4c) + } + } + // 5a) + if (count($stack) != 1) { + JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_EVALUATE_UNKNOWN_ERROR', $this->rulestring), 'error'); + $stack = array (0); + } + $res = array_pop($stack); // 5) + return $res; + } + +} + +// No closing tag diff --git a/rules_shipping_advanced.script.php b/rules_shipping_advanced.script.php new file mode 100644 index 0000000000000000000000000000000000000000..40021b2e8d2e37cf1d358e9c22a1a9765285efb1 --- /dev/null +++ b/rules_shipping_advanced.script.php @@ -0,0 +1,79 @@ +<?php +defined('_JEXEC') or die('Restricted access'); + +class plgVmShipmentRules_Shipping_AdvancedInstallerScript +{ + /** + * Constructor + * + * @param JAdapterInstance $adapter The object responsible for running this script + */ +// public function __constructor(JAdapterInstance $adapter); + + /** + * Called before any type of action + * + * @param string $route Which action is happening (install|uninstall|discover_install) + * @param JAdapterInstance $adapter The object responsible for running this script + * + * @return boolean True on success + */ +// public function preflight($route, JAdapterInstance $adapter); + + /** + * Called after any type of action + * + * @param string $route Which action is happening (install|uninstall|discover_install) + * @param JAdapterInstance $adapter The object responsible for running this script + * + * @return boolean True on success + */ +// public function postflight($route, JAdapterInstance $adapter); + + /** + * Called on installation + * + * @param JAdapterInstance $adapter The object responsible for running this script + * + * @return boolean True on success + */ + public function install(JAdapterInstance $adapter) + { + // enabling plugin + $db =& JFactory::getDBO(); + $db->setQuery('update #__extensions set enabled = 1 where type = "plugin" and element = "rules_shipping_advanced" and folder = "vmshipment"'); + $db->query(); + + return True; + } + + /** + * Called on update + * + * @param JAdapterInstance $adapter The object responsible for running this script + * + * @return boolean True on success + */ +// public function update(JAdapterInstance $adapter) +// { +// jimport( 'joomla.filesystem.file' ); +// $file = JPATH_ROOT . DS . "administrator" . DS . "language" . DS . "en-GB" . DS . "en-GB.plg_vmshopper_ordernumber.sys.ini"; +// if (JFile::exists($file)) JFile::delete($file); +// $file = JPATH_ROOT . DS . "administrator" . DS . "language" . DS . "de-DE" . DS . "de-DE.plg_vmshopper_ordernumber.sys.ini"; +// if (JFile::exists($file)) JFile::delete($file); +// return true; +// } + + /** + * Called on uninstallation + * + * @param JAdapterInstance $adapter The object responsible for running this script + */ + public function uninstall(JAdapterInstance $adapter) + { + // Remove plugin table + $db =& JFactory::getDBO(); + $db->setQuery('DROP TABLE `#__virtuemart_shipment_plg_rules_shipping_advanced`;'); + $db->query(); + } +} \ No newline at end of file diff --git a/rules_shipping_advanced.xml b/rules_shipping_advanced.xml new file mode 100644 index 0000000000000000000000000000000000000000..3718b56354b752e7667a09c81f7bd5eaa64d3e5c --- /dev/null +++ b/rules_shipping_advanced.xml @@ -0,0 +1,79 @@ +<?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> + <author>Reinhold Kainhofer</author> + <authorUrl>http://www.kainhofer.com</authorUrl> + <copyright>Copyright (C) 2013, Reinhold Kainhofer</copyright> + <license>GPL v3+</license> + <version>1.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> + <folder>language</folder> + <folder>elements</folder> + </files> + <languages folder="language"> + <language tag="en-GB">en-GB/en-GB.plg_vmshipment_rules_shipping_advanced.ini</language> + <language tag="en-GB">en-GB/en-GB.plg_vmshipment_rules_shipping_advanced.sys.ini</language> + </languages> + <scriptfile>rules_shipping_advanced.script.php</scriptfile> + + + <params addpath="/plugins/vmshipment/rules_shipping_advanced/elements/" /> + + <params addpath="/administrator/components/com_virtuemart/elements"> + <param type="vmjpluginwarning" /> + <param type="rklabel" default='VMSHIPMENT_RULES_PLUGIN_ADV' level="level1" /> + <param name="shipment_logos" type="vmfiles" label="VMSHIPMENT_WEIGHT_COUNTRIES_LOGOS" + description="VMSHIPMENT_WEIGHT_COUNTRIES_LOGOS_DESC" + directory="/images/stories/virtuemart/shipment" + default="default" hide_default="1" hide_none="1" + stripext="0" exclude="^_" /> + <param name="weight_unit" type="vmweightunit" size="10" default="" label="VMSHIPMENT_WEIGHT_COUNTRIES_WEIGHT_UNIT" description="VMSHIPMENT_WEIGHT_COUNTRIES_WEIGHT_UNIT_DESC"/> + + <param type="rklabel" default='VMSHIPMENT_RULES_HELP_ADV' /> + + <param type="rklabel" default='VMSHIPMENT_RULES_COUNTRIES1_LABEL' /> + <param name="countries1" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id1" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules1" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES2_LABEL" /> + <param name="countries2" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id2" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules2" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES3_LABEL" /> + <param name="countries3" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id3" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules3" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES4_LABEL" /> + <param name="countries4" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id4" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules4" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES5_LABEL" /> + <param name="countries5" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id5" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules5" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES6_LABEL" /> + <param name="countries6" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id6" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules6" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES7_LABEL" /> + <param name="countries7" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id7" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules7" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + + <param type="rklabel" default="VMSHIPMENT_RULES_COUNTRIES8_LABEL" /> + <param name="countries8" type="vmcountries" scope="com_virtuemart" default="" label="VMSHIPMENT_RULES_COUNTRIES" description="VMSHIPMENT_RULES_COUNTRIES_DESC"/> + <param name="tax_id8" type="vmtaxes" scope="com_virtuemart" label="VMSHIPMENT_RULES_TAX" description="VMSHIPMENT_RULES_TAX_DESC" /> + <param name="rules8" type="textarea" rows="6" cols="80" label="VMSHIPMENT_RULES_RULES" description="VMSHIPMENT_RULES_RULES_DESC" /> + </params> + +</install>