Introduction
This is a Virtuemart plugin that allows the shop owner to determine shipping costs according to arbitrary sets of general rules. Very complex shipping cost structures (depending on the amount, weight, number of products and/or articles and postal code of the order) can be easily implemented as one shipping method by describing each by a simple rule with conditions.
There are two flavors of the plugin: The free "Shipping by Rules" plugin and the paid "Advanced Shipping by Rules" plugin. The main difference is that in the advanced plugin, all terms can be given as mathematical expressions, while in the basic version, only comparisons of the order properties with fixed values are possible.
Shipping costs can depend on:
The name of the rule can also contain variables, which are replaced from the cart values. So e.g. you can have a shipping name "Domestic (Weight 3.25kg)".
The rules are described as a simple line of text with an easy structure (semicolons separate the parts of the rule). For example:
Name=Free Shipping; 100<=Amount; 0
Name=Domestic Small; Articles<5; Amount<100; Shipping=1.50 Name=Domestic Standard; Amount<100; Shipping=3.50
This set of rules describes three shipping costs: Orders of 100€ and more are free, otherwise orders with less than five articles have shipping costs of 1.5€, all others 3.50€.
As another example, consider the following shipping costs of a webshop:
<50€ | <100€ | above 100€ | |
Domestic | 2,50€ for up to 3 articles 2,50€ for weight less than 1kg 5€ otherwise |
6,50€ | FREE |
International | 8,50€ | 8,50€ | FREE |
The standard shipping plugin of Virtuemart requires you to create at least four different shipping methods (domestic <50€, domestic otherwise, international <50€, international otherwise), while the dependence on the article count cannot be implemented at all. Another drawback of using different shipping methods to model this shipping cost structure is that if the customer selects a shipping method and then modifies the quantities in the cart, the new order amount might fall into the new category, and the selected shipping method will be deselected, forcing the user to select a shipping method again.
In contrast, this plugin allows this cost structure to be described by the following rules:
For the domestic country/countries:
Name=Domestic small; Articles<=3 OR Weight<=1; Amount<50; Shipping=2.50 Name=Domestic medium; Amount<50; Shipping=5 Name=Domestic Standard; 50<=Amount<100; Shipping=6.5 Name=Free Shipping above 100€; 100<=Amount; 0
For all other countries (international):
Name=International Shipping; Amount<100; Shipping=8.50
Name=International Free Shipping; Amount>=100; 0
Differences between the standard and the advanced version
"Shipping by Rules" | "Advanced Shipping by Rules" | |
Arbitrary number of rules | ||
Arbitrary number of conditions | ||
Number of different country zones | 8* | 8* |
Comparisons as conditions | ||
Shipping costs given before or after taxes | ||
Shipping costs as fixed amount | ||
NoShipping identifier to prevent shipping for certain conditions; Print warning message for the user if desired |
||
Shipment modifier (ExtraShippingCharge and ExtraShippingModifier): define overall charges that are added to the matching rule |
||
Variable substitutions in the rule name strings | ||
Allow comments in the rules (to document a rule with no effect to the user) | ||
Show error/warning/notice messages | ||
Shipping costs described by mathematical formulas, involving all cart properties |
||
Mathematical expressions as conditions | ||
OR operator and "in" operator (for lists) in conditions | ||
~ operator to compare beginning of variables | ||
Functions like not, round, floor, ceil, max, min, day, weekday, etc. | ||
Custom functions can be provided by vmshipmentrules extensions | ||
Available Variables: | ||
Amount, prices before/after tax/discounts | ||
Weight | ||
Postal code (ZIP) First characters of ZIP: ZIP1, ZIP2, ZIP3, ZIP4, ZIP5, ZIP6 Country and State name variables |
||
Support for alphanumeric postal codes (UK, Canada, Netherlands) | ||
max/min/total volume/height/length/width/volume of the products |
||
Coupon code | ||
Custom variable definitions for arbitrary expressions | ||
Custom variables can be provided by vmshipmentrules extensions |
* Eight country zones are configurable per shipping method. If you need more country zones, simply create a second shipping method, which provides you with eight more country zones.
Installation and Configuration
After installation (like any Joomla plugin, using Joomla's extension manager), you need to create a shipping method of the type "Shipping by Rules" in VirtueMart:
- Go to "Shop" -> "Shipment Methods" and click on "New"
- Choose a name and select the "(Advanced) Shipping by Rules" plugin in the "Shipment Method"
- Click "Save" to populate the "Configuration" tab
- Go to the "Configuration" tab:
- There are eight sections for different country zones that can have different rules.
- Select the countries fo the first zone (if no countries are selected, the rules apply to all countries!)
- Enter your rules, one per line, in the "Rules"input field (see below for the exact format). Each rule consists of conditions, a shipping cost specification and optionally a name. These parts can be given in any order, separated by semicolons.
- When calculating the shipping costs, the plugin looks at each country zone, checks whether the country matches and then checks each of the rules until a rule matches. If no rules matches, the next country zone it investigated.
- The first matching rule will be returned as the shipping cost.
How the shipping cost is determined
When determining a possible shipping rate for a shipping method derived from this plugin, it will walk through all rules in the order they are given and check their conditions. The first rule that matches the country selection as well as all given conditions (with the current cart properties) will be returned and offered to the user by VirtueMart. If the shipping cost of the first matching rule is set to the identifier NoShipping, then no further rules are investigated the method will not offer any shipping.
If the cart properties change (e.g. a new product is added, the quantities are updated or the shipping address is changed), this is repeated.
Knowing this procedure can help you save a lot of time an effort, since once you have checked a condition in the first rule, for all further rules you can safely assume that the conditions of the first rule are not fulfilled.
VirtueMart allows only one shipping cost to be returned per shipping method. If you want to offer e.g. a standard and an express rate, you need to set up two shipping methods with this plugin.
Format of the rules
Each line of the rules field specifies one rule and consists of several rule parts, separated by a semicolon. E.g.:
Name=Domestic Small; Articles<5; 10<=Amount<100; Shipping=3.50
Name=Free shipping with Coupon; Coupon=="FREE_SHIPPING"; Shipping=0
Name=Free Shipping; 100<=Amount; 0
All rule parts with comparison operators (supported operators are: <, <=, =<, ==, !=, <>, >=, =>, >) are understood as conditions, where multiple comparisons can be chained to one longer condition.
Let us now look at the first rule of the example above. It consists of four parts, separated by semicolons:
- Name=Domestic Small
- Name of the rule (will be displayed in the cart and the invoice)
- Articles<5
- Condition: number of articles needs to be less than five
- 10<=Amount<100
- Condition: the total prices of the order needs to be at least 10 and smaller than 100 currency units; This is equivalent to two separate conditions: 10<=Amount; Amount<100
- Shipping=3.50
- Specifies the shipping cost without taxes (in the default currency units). The Shipping= can be left out. This means that any part of the rule that does not contain a comparison operator or an = is understood as the shipping costs without taxes
The second rule shows how to use a string, which is indicated by simply wrapping it with quotes. The name of the rule is an exception and will always be understood as a string, without the need to be wrapped in quotes (although it can be). All identifiers that contain letters and are not wrapped in quotes, are understood as variables, all identifiers containing only of digits (and optionally a decimal point) are understood as numbers. ATTENTION: only a point is understood as a decimal point, not a comma like many European countries use.
The general format of a rule is:
Name=Name of the rule (displayed); [Comment=Comment that won't be shown]; [Condition=]Articles<5; ...; [Shipping=]3.50
None of the parts are required.
The "Condition=" can be left out if the condition contains any comparison operators(like <, >=, ==, etc.). If the condition consists only of a variable or of a function call (advanced version only), the "Condition=" is required to mark this part as a condition and not as shipping costs.
The "Shipping=" can always be left out. Rule parts without any assignment are always interpreted as shipping costs unless the part contains a comparison operator (like <, >=, ==, etc.)
Available Variables
In the conditions, the following properties of the order (case-insensitive) can be used:
Amount, AmountWithTax, salesPrice |
Invoice amount, including taxes |
amountWithoutTax basePrice salesPriceWithDiscount |
Various Cart prices before and after taxes and discounts |
couponAmount, amountAfterCoupon |
coupon amounts (built-in coupon system, NOT AwoCoupon); NOTE: These variables do NOT work with scoping functions, i.e. they cannot be used in evaluate_for_... function! |
Weight |
Total weight of the order Minimal / maximal weights of the all articles in the order |
ZIP ZIPnumeric, ZIPalphanum |
Postal code of the order First 1, 2, 3, 4, 5 and 6 characters of the postal code Only numeric / alphanumeric parts of the postal code (e.g. all - removed for Polish ZIP codes) |
CountryID, Country, Country2, Country3 |
Country/State ID (numeric), name (string), and 2- and 3-letter country codes City name (as a string); CAUTION: Your customer might use different spellings! |
company, title, first_name, middle_name, last_name, address1, address2, city, phone1, phone2, fax, email |
Address fields of the shipping address (can be different from the billing address), as entered by the user. In particular, these might contain dummy data (like "asdfasdfasdf" or typos). |
UserName, UserID | User login name and unique user ID (numeric) |
isGuest, isVendor | Indicates whether the customer is a guest (i.e. not logged in) and whether the customer is a vendor. 0 means NO, 1 means YES. |
ShopperGroups, Groups | VirtueMart shopper groups and Joomla user groups (numeric IDs) of the customer if logged in. Guest users will have the default VM shopper group and an empty groups list. (ADVANCED VERSION only) |
Products | Number of different products in the order |
Articles, Quantity | Total number of articles (each product counted with the according quantity) in the order |
MinQuantity, MaxQuantity | minimum/maximum number of units purchased for all the articles |
Volume MinVolume, MaxVolume |
total/minimal/maximal volume of the products in the order. The volume of each product is calculated as length*width*height in the given length unit |
MinLength, MaxLength |
minimal/maximal extensions of the products in the order |
TotalLength, TotalWidth, TotalHeight, TotalPackaging |
The length/width/height/packaging of all articles summed up (ATTENTION: The parcell will NOT have extensions TotalLength x TotalWidth x TotalHeight! It will rather be considerably smaller!). The TotalPackaging does not use any conversions, so it makes only sense when all products have the same units. |
Coupon | Coupon code entered by the user (Advanced version only!) |
UK_Outward UK_Area UK_District UK_Subdistrict UK_Inward |
UK postal code support (see below) (ADVANCED version only) |
Canada_FSA Canada_Area Canada_Urban Canada_Subarea Canada_LDU |
Canadian postal code support (see below) (ADVANCED version only) |
SKUs | List of all SKUs of the products in the cart. (ADVANCED version only). This can be used to check whether a particular product is in the cart (condition: "your-sku" in SKUs). |
Categories |
List of the category IDs, vendor IDs and manufacturer IDs of all products in the cart. In the advanced version, which can be used to check if e.g. only products from one particular vendor are purchased. |
Year, Year2, Month, Day, Hour, Hour12, AmPm, Minute, Second, Decisecond, Centisecond, Millisecond, Weekday | Date/time when the shipping cost was calculated (which is usually before the order is submitted). Also notice that the reults might be cached and not be re-calculated during each step of the order! Weekday is a value from 1(Monday) to 7 (Sunday). |
Values_Debug, Debug_Values |
DEBUGGING ONLY: A string representation of all available variables. Use e.g. as Name="All values: <pre>{Values_Debug}</pre>" to print all available (built-in) variables |
Debug_Cart, Debug_Products | DEBUGGING ONLY: The internal PHP representation of the whole VirtueMart Cart object and the product list as a string. |
All rule parts of the form [VARIABLE]=VALUE are assignments, with [VARIABLE] being one of
Name | (optional) Name of the shipping cost rule (will be displayed in the cart and in the invoice). This can also be a translatable identifier, which will be translated if a translation is available. |
Shipping | Shipping cost (before taxes) if the rule matches, the tax gross shipping cost will be calculated from the selected tax rule; If set to NoShipping, the shipping method will not offer any shipping. |
ShippingWithTax | Shipping cost including taxes if the rule matches, the tax and the net shipping cost will be calculated from the selected tax rule |
ExtraShippingCharge | This rule defines an extra shipping charge that will be added to the matching rule that gives the actuall shipping costs. |
ExtraShippingMultiplier | This rule defines an extra multiplier for shipping costs that will be multiplied to the shipping costs of the matching rule that gives the actual shipping cost. E.g. to increase shipping by 7% under certain conditions, use ExtraShippingMultiplier=1.07 . |
Comment | A comment that will be completely ignored (i.e. you can use such fields to add comments in the backend that won't be shown to the customers) |
Variable or Definition |
For variable definitions: The name of the variable to be defined |
Value | For variable definitions: The value of the variable to be defined (name of the variable given in Variable=.. or Definition=...) |
Message, Notice, Warning, Error | Display the given message as an error, warning, notice or normal message (displayed typically at the top of the page with red, yellow, blue and green background) |
For the shipping cost, Shipping= can be left out. I.e. if a rule part consists entirely of a numerical value, with no assignment or comparison operator, it is understood as shipping cost before taxes.
Shipping rule name
The (visible) name of the shipping rule is a free form string (no quotes needed, but possible) that further describes the shipping costs. The displayed shipping name will be "Shipping method name (rule name)". The string given as a rule name is translatable through the normal Joomla translation system.
To insert cart values like the total weight into the rule name, you can simply use {variablename} for any of the available variables above. E.g.
Name=Small package: {articles} articles, weight {weight} kg; Articles<3; Weight<5; Shipping=3
will display a name like "Shipping method (Small package: 2 articles, weight 3.2 kg)".
Translating rule names
The rule names are translatable strings and use the standard Joomla translation system (not the VirtueMart multi-language feature, because then the whole ruleset needs to be maintained in multiple languages in parallel, which might be very cumbersome with multiple languages and complex rules).
To translate rule names, it is recommended (but not exactly required) to use a simple identifier in only uppercases as the rule name and then translate it in the Joomla backend under the menu "Extensions -> Language Manager -> Override".
Showing Notices, Warnings and Error Messages
The plugin provides a way to show warnings, notices or error messages if a particular rule is included in the shipping cost calculation. There are the special variables Error, Warning, Notice and Message available that will show their corresponding values as a message if all conditions of the rule match:
Message="Thursday orders have tripple shipping costs"; Weekday==4; ExtraShippingMultiplier=3
Warning="Shipping to Vienna might take longer!"; Error="Please contact us for delivery details!";1000<=ZIP<=1999; Shipping=1
Debug="Fall-Through ZIP condition"; Shipping=15
Shipping Modifiers
Sometimes under certain conditions there should be an extra shipping charge added to all shipping rates. This can be the case for oversized products or products that need special care (e.g. glass products might need extra, expensive packaging). The plugin provides two modifiers: ExtraShippingCharge and ExtraShippingMultiplicator, which are applied to the shipping costs determined by the matching rule.
In the following example if any product of category 1234 is in the order, the shipping costs of 3€ or 5€ will be increased by an extra shipping charge of 5€ to 8€ and 10€ respectively:
Name=Orders with glass products get an extra charge; contains_any(Categories, 1234); ExtraShippingCharge=5
Name=Light package; Weight<50; Shipping=3
Name=Heavy package; Weight>=50; Shipping=5
In the following example, all orders to Alaska will get a 50% (i.e. a multiplier of 1.5) addon to the shipping costs:
Name=Alaska has 50% higher shipping costs; State2=="AK"; ExtraShippingMultiplier=1.5
Name=Light package; Weight<50; Shipping=3
Name=Heavy package; Weight>=50; Shipping=5
The multiplier does not apply to an extra charge, if both an extra charge and an extra multiplier are given. In particular, the calculation of the final shipping cost is: (Shipping * ExtraShippingMultiplier) + ExtraShippingCharge.
If a modifier is given, but none of the rules matches, the method will also not provide shipping costs.
Excluding certain conditions from shipping
Setting the shipping costs to the special identifier NoShipping can be used to disallow shipping when certain conditions are met. For example, you might want to exclude some postal codes from shipping at all (e.g. some islands). An example is:
Name=No shipping of heavy packages to a certain area; Weight>100; 8000<=ZIP<9000; NoShipping
Name=No shipping of more than 100 articles; Articles>100; Shipping=NoShipping
Name=Flat rate otherwise; Shipping=15
Starting with version 4.0 of the plugin, the name of the NoShipping rule will be displayed to the user as a warning message! If the rule does not have any name set, no warning will be printed. You can use Comment=... in the rule to add a description for yourself, which will NOT be displayed to the user:
Comment="No Warning displayed, but disallow shipping to a certain ZIP range"; ZIP>9000; NoShipping
Available Operators (in their order of precendence)
Operator | Description | In basic plugin |
---|---|---|
in | Check whether a certain value is contained in a list | |
^ | Power | |
*, /, % | Multiplication, Division, Modulo | |
+, - | Addition, Subtraction | |
<, <=, =< | less then / less or equal | X |
>, >=, => | larger / larger or equal | X |
== | equal | X |
!=, <> | NOT equal | X |
~ | starts with (the longer term of the left or right side starts with the term on the other side) | |
AND, &, && | logical AND operator to join two comparisons to one condition | |
OR | logical OR operator to join two comparisions to one condition | |
= | Assignment (possible LHS variables are Shippping, ShippingWithTax and Name) | X |
The operator names are case-insensitive.
Like with normal mathematical expressions, parentheses (...) can be used to achieve a different grouping of the terms than the normal precedence rules would imply.
Debugging problems in the Plugin
To debug problems with the plugin, it is often useful to find out the value of a certain variable, or even the full list of all available variables. I recommend to create a new shipping method called "Debugging" without any restrictions that contains just one NoShipping rule:
Name=Here you create some debug output that will be displayed to the user while debugging; NoShipping
The advantage of this approach is that this method will never offer a shipping rate (and does not influence your existing shipping methods), but it will always be considered and(starting with version 4.0 of the plugin) print out its name as a warning.
- If you only want to know the value of one variable, simply include that one variable in the name as {Variablename}.
- If you want to check which variables are available and see the values of all variables, use the varible "Values_Debug", which is a string representation of all pre-defined variables. You can use a rule like the following (note, though, that this does NOT work with user-defined variables, which are available only inside the shipping method they are defined).
Name=All variables: <pre>{Values_Debug}</pre>; NoShipping
This rather long list will show you all variables and their values. Finding the problem is then usually just a matter of thinking your rules through with the displayed values.
Mathematical expression in the "Advanced Shipping by Rules" Plugin
The normal "Shipping by Rules" plugin suffices for most webshops, as it allows arbitrary many shipping cost rules with fixed shipping cost for each rule and supports numeric postal codes.
However, in some cases the shipping costs need to be even more flexible and can not be expressed by fixed shipping amount, but only by arithmetic mathematical expressions. Simple cases are shipping costs of 5% of the order amount, or 10€ per kg, or 2€ shipping per additional article. More advanced rules are employed by cargo companies, where the shipping per kg gets cheaper the more you ship.
For this reason, an advanced version of the plugin is available for sale, which adds support for alphanumeric postal codes and also incorporates arbitrary basic arithmetic expressions (allowed operators are +, -, *, /, %, ^, OR, AND and parentheses) of the above variables in all terms (conditions and shipping costs). An example is the following rule, which applies to all orders of at least 2 articles below 100€ and specifies shipping costs as 5€ fixed plus 3% of the order amount plus 1€ per kg plus 0.5€ per additional article:
Name=Complex shipping function; articles>=2; amount<100; shipping=5+amount*0.03+1*weight+0.5*(articles-2)
For more examples of such advanced shipping cost calculations, see the examples below.
NOTE: When using the OR or AND operator, it is strongly recommended to use a space before and after the operator to prevent parsing errors in certain cirumstances. E.g. "1<3OR3<5" would NOT be parsed correctly, because "3OR3" is understood as one expression!
The operator precedence (which operators are evaluated first, e.g. in 1+3*4, the multiplication has a higher precedence and is first evaluated to get the correct result of 1+12=13) is:
- Function calls
- Power ^
- Multiplication *, Division /, Modulo %
- Addition +, Subtraction -
- Comparisons: <, <=, >, >=, =>, =<, ==, !=, <>
- String startsWith: ~
- AND, &, &&
- OR
- Assignment =
To achieve a different order of evaluation, one can always add parentheses, e.g. (1+3)*4 to evaluate the addition first.
Available functions
Since version 3.0, the mathematical expressions of the advanced version can also contain function calls. The following functions are available (case-insensitive as of version 4.0):
Function | Description | Nr of arguments |
---|---|---|
not(val) |
Logical NOT of the argument (e.g. not("testsku" in SKUs) to check whether a particular product is not in the cart). | 1 (logical value) |
round(val) floor(val) ceil(val) |
Round the argument (less than .5 rounded down, .5 or more rounded up) Round down (decimal part cut off) Round up (always round up to next integer) |
exactly 1 |
round(val, unitval) floor(val, unitval) ceil(val, unitval) |
Round the argument to multiples of unitval Round down to multiples of unitval Round up to multiples of unitval |
exactly 2 |
max(val1, val2, val3, ...) min(val1, val2, val3, ...) |
Maximum/minimum of the arguments (any number of arguments possible) | 1 or more |
year(), month(), yearday(), day(), weekday(), hour(), minute(), second() |
Current date components as integer | NONE |
list(val1, val2, val3, ....) | Create a list from the given arguments. The result can be used with the "in" operator, e.g. to differentiate shipping costs for different states (condition state2 in list("TX", "WS", "MS") ) | 1 or more |
length(list) | Number of elements in the list | 1 (list) |
union(list1, list2, ...), join(list1, list2, ...) | Join two lists to one (union and join behave identical) | 2 or more |
complement(list1, list2, ...) | Return list of all elements of list1 that are NOT in any of the other lists | 2 or more |
intersection(list1, list2, ...) | Return list of all elements that are in all lists | 2 or more |
issubset(childlist, parentlist) |
Returns whether childlist is a subset of parentlist (notice the different order of arguments for issubset and contains) | exactly 2 LISTS |
contains_any(list, elem1, elem2, ...) |
Returns whether the list contains any/all/only/none of the other arguments given. First argument needs to be a list. | 2 or more |
digit(value, n) | Returns the n-th digit (or character) of value | exactly 2 |
substring(string, begin, len) | Returns the substring of string of length len that starts at position begin | exactly 3 |
evaluate_for_categories(EXPRESSION, categoryID, ...) evaluate_for_vendors(EXPRESSION, vendor1, vendor12, ...) evaluate_for_subcategories(EXPRESSION, categoryID, ...) |
Scoping functions (see below for more information): Evaluate EXPRESSION restricted to only products of the given categories/manufacturers/vendors/skus. The evaluate_for_subcategories function evaluates the expression not only for the given categories, but for all of their subcategories, too. |
2 or more |
convertToCurrency(value, "CUR") | Convert the given value (in the shop's default currency) to the given currency (3-letter code form VirtueMart's currency configuration). | exactly 2 |
convertFromCurrency(value, "CUR") | Convert the given value from the given currency (3-letter code from VirtueMart's currency configuration) to the shop's default currency. | exactly 2 |
print_r(value) | DEBUGGING function: returns the value (e.g. a list or a numeric value) as a string | exactly 1 |
Custom Variable Definitions
Since version 4.0, one can also define your own custom variables to hold e.g. complex conditions that are used in multiple rules. The format is similar to rules (i.e. one line per variable definition):
Definition=VariableName; [Value=]ValueOfTheVariable
Instead of "Definition=" one can also use "Variable=", and the "Value=" can be left out (as long as the value does not contain any comparison operators). The VariableName needs to be one string without spaces or any special characters, the value can be any expression or condition that can be used in a rule.
Variable definitions and shipping rules can be given in any order, but they are evaluated sequentially. In particular, a variable definition will only be available in all rules that are given after the definition.
If you want to store conditions in a variable and then use that variable in a rule, you need to mark it as a condition with "Condition=YOURVARIABLE". Otherwise, the plugin would interpret a single variable as the shipping rate.
Here is an example of a variable definition:
Name="Here VAR is not available yet: {VAR}"; Weight>100; 10
Definition=VAR; Value=1000<=ZIP<2000
Name="Here VAR is available: {VAR}"; Condition=VAR; Shipping=50
Variable definitions can overwrite/modify a previous variable definition, and they can also contain conditions:
Definition=myship; Value=0 Definition=myship; 1 in Categories; Value=myship+4 Definition=myship; 2 in Categories; Value=myship+12345 Name="Shipping costs summed up"; Shipping=myship
Obtaining values for only a subset of the cart (e.g. articles or weight of one category only)
Starting with version 5.0 of the plugin, it is possible to access all variables also for a subset of the order, like only for a given category or a given manufacturer. This is implemented using the following functions:
evaluate_for_categories(EXPRESSION, categoryID, ...)
evaluate_for_manufacturers(EXPRESSION, manufacturerID, ...)
evaluate_for_vendors(EXPRESSION, vendorID, ...)
evaluate_for_skus(EXPRESSION, sku1, sku2, ...)
These functions evaluate the given EXPRESSION (can be a complex mathematical expression!) for only those products that match the given categoryID, manufacturerID, vendorID or sku. If multiple IDs are given, all products that math any of the ids are used. To evaluate an expression e.g. for all products in the order that are in TWO given categories, you need to stack two calls to evaluate_for_categories. For example:
Name=Only articles in categories 42 and 45 cost 5€ shipping, all others are free; Shipping=5*evaluate_for_categories(Articles, 42, 45)
Name=Weight of all articles except from manufacturer 3; Weight-evaluate_for_manufacturers(Weight, 3)<50; Shipping=50
Name=Check if product from cat 42 AND manufacturer 5 is included; evaluate_for_manufacturer(evaluate_for_categories(Articles, 42), 5)>0; Shipping=0
Please note this while this gives a lot of potential for checks and calculations, this stil does NOT provide a way to calculate per-product shipping costs, where you can easily assign each product a shipping cost based on their sku or category. As a workaround you can check for each and every SKU or category manually, but this is (1) quite lengthy and cumbersome and (2)whenever you add a new one, you'd have to update the shipping rule, too.
Also note that the evaluate_for_XXX functions do not have any influence on the BillTotal, BillDiscountAmount, BillTaxAmount and BillSubtotal variables, as they are defined as cart-wide values (price modifiers per bill make no sense for subsets of the whole cart, so their value is always for the whole cart!).
Supporting alphanumeric postal codes
Most countries employ numeric postal codes, which allow direct comparisons using the ZIP variable, e.g.
Name=Free shipping to Vienna (Austria); 1000<=ZIP<2000; Shipping=0
However, some countries, most notably the UK, Canada and the Netherlands use alphanumeric postal codes, which also contain letters. Their postal codes all have a certain structure, which allows the postal code to be split up into smaller parts that describe the area further. The advanced version (starting with version 2.4) of the plugin also supports those alphanumeric postal codes by providing several variables that contain the different parts of the postal codes and can be used in the conditions.
UK postal codes
The UK postal codes have the form "A[A]0[0][A] 0AA", where parts in square brackets are options. The first part before the space is called "Outward" part of the postal code and is used to distribute letters to a post office for final distribution. The part after the space is called the "Inward" part and is used by the post office to sort the mail for final delivery. The "Outward" part begins with one or two letters, indicating the postcode area, followed by one or two digits, identifying the district within the area. Some districts in Central London have been further subdivided by an additional letter after the digit.
If the plugin detects that the postal code of the delivery address (or the invoice address if no shipping address is given) matches the form of UK postal codes, it will provide the following variables for use in the conditions:
UK_Outward | The Outward part of the postal code (the two to five characters before the space) |
UK_Area | The postal area (one or two letter) |
UK_District | The postal district within the postal area (one or two digits) |
UK_Subdistrict | The subdivision of some central London districts (a letter, or empty) |
UK_Inward | The Inward part of the postal code (the three characters after the space) |
Here are a few examples of rules for UK postal codes:
Name="Free shipping to Birmingham"; UK_Area=="B"; Shipping=0
Name="Free shipping to parts of Walsall"; UK_Area=="WS" AND 15<=UK_District; Shipping=0
Name=No Shipping to PO boxes in North London; UK_Outward=="N1P"; NoShipping
The British overseas territories also use postal codes the follow the the structure of UK postal codes, except that the outward part consists of four letters and the inward part is always "1ZZ", e.g. "ASCN 1ZZ" for Ascension or "PCRN 1ZZ" for the pitcairn Islands. The only exception is Gibraltar whith a postal code of "GX11 1AA". For those postal codes, only the outward and the inward parts are assigned, while the area, district and subdistrict variables are empty.
Name="Free shipping to Gibraltar"; UK_Outward=="GX11" AND UK_Inward=="1AA"; Shipping=0
Name="No shipping to Falklands"; UK_Outward=="FIQQ"; NoShipping
Canadian postal codes
The Canadian postal codes have the form "A0A 0A0", where the first part is called the "Forward Sortation Area" (or FSA) and the final three characters are the "Local Delivery Unit" (LDU). The initial letter indicates the postal district (province/region), the number at the second position indicates either a particular urban area (if >0) or a rural area (if 0), and the letter at the third position subdivides the urban or rual area further into districts or smaller cities. The LDU then usually simply gives the delivery post office with no particular geographic rules or order.
If the plugin detects that the postal code of the delivery address (or the invoice address if no shipping address is given) matches the form of Canadian postal codes, it will provide the following variables for use in the conditions:
Canada_FSA | The Forward Sortation Area (the three characters before the space) |
Canada_Area | The first letter (indication the Postal District, i.e. typically the province or region) |
Canada_Urban | The digit at the second position (indicating either a rural area if 0, or a particular urban area if larger than 0) |
Canada_Subarea | The letter at the third position (subdividing the urban or rural area even further) |
Canada_LDU | The Local Delivery Unit (the three characters after the space) |
Here are a few examples of rules for Canadian postal codes:
Name=Free Shipping to British Columbia; Canada_Area=="V"; Shipping=0
Name=Chicoutimi (Quebec); Canada_Area=="G" AND Canada_Urban==7 AND "G"<=Canada_Subarea<="K"; Shipping=5
Name=Saint-Joseph-de-Coleraine; Canada_FSA=="G0N" AND Canada_LDU=="1B0"; Shipping=7
Please notice that in the third rule, the separate checks for FSA and LDU are better than a check for ZIP=="G0N 1B0", since it will also work if the user enters multiple spaces between the FSA and the LSU.
In the second rule the area and urban check could also be combined into a check whether the FSA starts with "G7": Canada_FSA~"G7"
Dutch postal codes
The postal code in the Netherlands has the structure "0000 AA" (i.e. four digits and then two letters), where the final two letters only divide the area described by the digits into even smaller parts (at the street/house level). For the calculation of shipping costs, the two final letters are practically never relevant, so by using the variable ZIP4 (the first four characters of the postal code), the Dutch postal codes can be used just like any numeric postal code:
Name=No shipping to Amsterdam; 1011<=ZIP4<=1109; NoShipping
Coupons for Lower Shipping Costs
The advanced version also provides the coupon code as a variable, which allows to make shipping depend on the coupon code given.
To make use of this feature, you need to enable VirtueMart's coupon system (AwoCoupon will not work) and create a coupon, e.g. with code "COUPON_CODE". You can then check for the coupon code in the shipping rule and make the shipping costs depend on the existence of this coupon code:
Name=Free shipping with coupon; Coupon=="COUPON_CODE"; Shipping=0
This allows standard VirtueMart coupons to influence the price as well as the shipping cost. Of course, you can use other conditions to make the coupon only apply if e.g. the order amount reaches a certain threshold, or to set a lower rater per kg, etc.
Working with multiple Currencies
All amounts and shipping costs used in the rules are always understood in the shop's default currency, even if the customer has selected a different currency. If the order confirmation and the invoice is to be created in a different currency, then VM will simply convert from the shop's default currency.
There are, however, cases when one wants to use an explicit particular amount in a foreign currency in the rules. For example, a US-based shop (US$ as default currency) might want to check for an order to the EU whether the total amount is larger than 23€. This is the threshold for paying import duties in Austria, so an order with a US$-amount equivalent to 23€ will incur higher bureaucracy and thus higher shipping costs.
The advanced version of the plugin (starting with Version 6.0) provides two functions: convert_from_currency(value, "CUR") and convert_to_currency(value, "CUR").
convert_to_currency(value, "CUR") | Convert the given value (in the shop's default currency) to the given currency (3-letter code form VirtueMart's currency configuration). |
convert_from_currency(value, "CUR") | Convert the given value from the given currency (3-letter code from VirtueMart's currency configuration) to the shop's default currency. |
The rules for the mentioned US-based company (shop currency USD) would then be:
Name="Above customs duty threshold"; convert_to_currency(Amount, "EUR")>23; Shipping=15
Name="No customs duty"; Shipping=3
The first rule converted the USD-denominated Amount (order total) to EUR and then compared it with the threshold. Equivalently, one could also convert 23 EUR to the equivalent USD-amount and compare it with the order total amount. So, the following rules should have the exact same effect:
Name="Above customs duty threshold"; Amount>convert_from_currency(23, "EUR"); Shipping=15
Name="No customs duty"; Shipping=3
Extending the Plugin with Custom Functions and Variables using "vmshipmentrules" plugins
Sometimes, shipping costs have to depend on properties (e.g. custom field values, awocoupon discounts, etc.) that are not by default provided by this plugin. Starting with version 5.0, the Shipping by Rules plugins can be extended with plugins of type "vmshipmentrules". Such extensions can add new variables, modify existing variables and add new functions (advanced version only).
Find the documentatn and download a plugin template HERE, which you can use as a starting point for such a plugin. You will need to adjust the file names, the .xml file and the class names.
Automatic Updates
Starting with version 5.99, this plugin uses the Joomla 3.x updater to install new versions. The (free) basic plugin will be automatically offer and let the admin install updates.
For the advanced (paid) version of the plugin, you need to enter your update credentials (order number and password of the purchase at open-tools.net) in the plugin configuration page (Joomla Backend: Extensions => Plugins):
The updater will then access the download at open-tools.net with these credentials, which unlock download access. If you enter invalid credentials, then the plugin will work just fine, only updates are not available:
Please note that the automatic update feature for paid plugins (that need some kind of password to unlock the download) is NOT available in Joomla 2.5!
Demo Server
Examples
Frequently Asked Questions (FAQ)
License
This plugin is licenced unter the GNU GPLv3. The "Shipping by Rules Plugin" can be downloaded free of charge, while the "Advanced Shipping by Rules Plugin" (providing additional arithmetic expressions as described above) can only be downloaded after payment. In both cases, you will get all the rights (and duties) that the GPL gives you. You are allowed to use the plugin on as many webshops as you like. We try to give support as our time allows, but we cannot guarantee a certain response time. A payment for the Advanced version gives you access to all future versions of the plugin with no time restriction.
Version History
2017-11-11: Version 6.2.5: Add variables CouponAmount and AmountAfterCoupon (will not work with scoping!)
2017-08-24: Version 6.2.4: Fix PHP warning about array_merge
2017-06-05: Version 6.2.3: Fix custom functions defined in plugins
2017-05-13: Version 6.2.2: Add variables ZIPnumeric and ZIPalphanum (only the digits and only digits and letters); translate rule names after variable substitution again
2016-12-09: Version 6.2.1: Fix variable substitutions in translated user-defined warnings/error messages
2016-12-08: Version 6.2: Add message feature to show errors, warnings, messages and notices to the customer
2016-10-31: Version 6.1.7: Fix BE order view display strings; Remove CustomerNumber again (private property accesses -> error)
2016-08-28: Version 6.1.6: Add variables: Groups, ShopperGroups, UserID, UserName, isGuest, isVendor, CustomerNumber
2016-04-10: Version 6.1.4: Fix multiple tax rules (and ShippingWithTax)
2016-03-13: Version 6.1.3: Fix UK postcodes and callback functions
2016-03-04: Version 6.1.1: Revert 6.1
2016-02-15: Version 6.1: Added Bill* variables for price modifiers and tax per bill
2016-02-07: Version 6.0:
- Automatic updates through the Joomla Updater
- Added convert_from_currency and convert_to_currency functions
- Added evaluate_for_subcategories function
- Added time variables: year, year2, month, day, hour, hour12, ampm, minute, second, decisecond, centisecond, millisecond, weekday
- New debug variables: debug_cart, debug_products
- Factorization of shipping rules logic into a generic library, which is shared by all our shipping plugin (VM, WooCommerce)
2016-01-16: Version 5.99.1 (BETA): Added time variables
2016-01-03: Version 5.99 (BETA): Automatic updates through the Joomla Updater, internal factorization to a generic library
2015-10-25: Version 5.7: Add variables MinQuantity and MaxQuantity
2015-08-02: Version 5.6: Fix free shipping in the free plugin version
2015-07-14: Version 5.5: Fix evaluate_for_skus
2015-04-18: Version 5.4: Fix evaluate_for_categories and evaluate_for_manufacturers
2015-03-15: Version 5.3: Store rule information in the database again (displayed in the backend)
2015-03-11: Version 5.2: Fix some minor bugs and PHP warnings
2015-03-03: Version 5.1: Fix border case with free shipping
2015-02-08: Version 5.0:
- Add scoping functions evaluate_for_categories, evaluate_for_manufacturers, evaluate_for_vendors, evaluate_for_skus
- Implement modifiers: ExtraShippingCharge=..., ExtraShippingMultiplier=...
- New variables: email, address1, address2, city, company, title, first_name, middle_name, last_name, phone1, phone2, fax
- Add plugin API to provide custom variables
- Add plugin API to provide custom functions
- Optimization: Each rule is evaluated only once and then cached
- The 'values' and 'values_debug' variables are evaluated when called (=> custom definitions ARE now included)
2014-10-23: Version 4.3: Add support for Joomla 3 and VirtueMart 3 (at least version 2.9.9f required); Drop support for Joomla 1.5
2014-09-11: Version 4.2: Fix a small, but tricky issue that caused lots of server log warnings about non-object
2014-01-15: Version 4.1: Don't print out a warning if a NoShipping rule has no name
2014-01-14: Version 4.0.1: (Basic plugin only) fix comparisons with strings; allow html and = in rule names
2014-01-11: Version 4.0:
- Add custom variable definitions
- The name of a rule with NoShipping is printed out as a warning
- Add many list handling functions, digit and substring functions
- Function names and operators are now case-insensitive
- Add comments
- Make rule names translatable
- Support carts with multiple different tax rates
- Add print_r function, values_debug variable
2013-09-21: Version 3.3 (Add not operator; add categories, vendors and manufacturers variables; Fix country checks)
2013-08-26: Version 3.2 (Fix some translations due to changed VM internals; Fixed country zone check)
2013-08-17: Version 3.1 (Implement lists and the in operator; add SKUs variable; add country/state name variables)
2013-07-06: Version 3.0.2 (Fix broken UK post code handling)
2013-06-23: Version 3.0.1 (Fix for PHP 5.3)
2013-06-19: Version 3.0.0 (Functions in the rules; packaging variables; Variable substitutions in the name)
2013-06-15: Version 2.4.7 (Allow UK post codes to be entered in lower-case)
2013-05-31: Version 2.4.6 (Fix shippingWithTax)
2013-05-30: Version 2.4.5 (Fix various cart price variables)
2013-05-07: Version 2.4.3 (Allow UK and Canadian postal codes to be entered without the space; ADVANCED version only
2013-05-03: Version 2.4.2 (Added MinWeight and MaxWeight variables)
2013-03-31: Version 2.4 (Added support for UK and Canadian alphanumeric postal codes - ADVANCED version ONLY)
2013-03-30: Version 2.3 (Added NoShipping identifier to exclude shipping; Fixed shipping cost when Shipping= was left out)
2013-03-21: Version 2.2 (Fixed chained conditions; added length unit setting and conversion; added ZIP1, ZIP2, ... variables; added ~ operator to compare beginning of variables; fixed totalLength/Width/Height calculation; )
2013-03-18: Version 2.1.1 (Fixed shipping cost display in shipping selection page)
2013-03-18: Version 2.1 (Fixed bug that parameters were not saved sometimes; totalLength, totalWidth and totalHeight variables; support for Joomla! 1.5)
2013-02-14: Version 2.0 (OR operator, Coupon variable, fixed ZIP when no address given, all Price variables of the cart)
2013-01-22: Version 1.1 (implement Volume and minimal/maximal volume and product extensions )
2013-01-15: Version 1.0 (initial public release)