rules_shipping_base.php 56.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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');
}
// Only declare the class once...
Reinhold Kainhofer's avatar
Reinhold Kainhofer committed
28
if (class_exists ('plgVmShipmentRules_Shipping_Base')) {
29
30
	return;
}
31

32

33
34
35
function is_equal($a, $b) {
	if (is_array($a) && is_array($b)) {
		return !array_diff($a, $b) && !array_diff($b, $a);
36
37
	} elseif (is_string($a) && is_string($b)) {
		return strcmp($a,$b) == 0;
38
39
40
41
	} else {
		return $a == $b;
	}
}
42
43


44
/** Shipping costs according to general rules.
Reinhold Kainhofer's avatar
Reinhold Kainhofer committed
45
 *  Supported Variables: Weight, ZIP, Amount, Products (1 for each product, even if multiple ordered), Articles
46
47
48
 *  Assignable variables: Shipping, Name
 */
class plgVmShipmentRules_Shipping_Base extends vmPSPlugin {
49
50
51
52
	// Store the parsed and possibly evaluated rules for each method (method ID is used as key)
	protected $rules = array();
	protected $match = array();
	var $custom_functions = array ();
53
54
55
56
57
58
59
60
61
62
63
64
65
66

	/**
	 * @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);
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
		
		// PLUGIN FUNCTIONALITY:
		// Let other plugins add custom functions! 
		// The onVmShippingRulesRegisterCustomFunctions() trigger is expected to return an array of the form:
		//   array ('functionname1' => 'function-to-be-called',
		//          'functionname2' => array($classobject, 'memberfunc')),
		//          ...);
		JPluginHelper::importPlugin('vmshipmentrules');
		$dispatcher = JDispatcher::getInstance();
		$custfuncdefs = $dispatcher->trigger('onVmShippingRulesRegisterCustomFunctions',array());
		// Loop through the return values of all plugins:
		foreach ($custfuncdefs as $custfuncs) {
			if (empty($custfuncs))
				continue;
			if (!is_array($custfuncs)) {
				$this->printWarning(JText::sprintf('VMSHIPMENT_RULES_CUSTOMFUNCTIONS_NOARRAY', $method->rule_name));
			}
			// Now loop through all custom function definitions of this plugin
			// If a function was registered before, print a warning and use the first definition
			foreach ($custfuncs as $fname => $func) {
				if (isset($this->custom_functions[$fname])) {
					$this->printWarning(JText::sprintf('VMSHIPMENT_RULES_CUSTOMFUNCTIONS_ALREADY_DEFINED', $fname));
				} else {
					vmDebug("Defining custom function $fname");
					$this->custom_functions[strtolower($fname)] = $func;
				}
			}
		}
95
96
97
98
99
100
101
102
103
104
	}

	/**
	 * 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');
	}
105
106
	
	public function printWarning($message) {
107
		// Keep track of warning messages, so we don't print them twice:
108
		global $printed_warnings;
109
110
		if (!isset($printed_warnings))
			$printed_warnings = array();
111
112
113
114
115
		if (!in_array($message, $printed_warnings)) {
			JFactory::getApplication()->enqueueMessage($message, 'error');
			$printed_warnings[] = $message;
		}
	}
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175

	/**
	 * @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;
176
177
178
// 		$values['order_weight'] = $this->getOrderWeight ($cart, $method->weight_unit);
// 		$values['order_articles'] = $this->getOrderArticles ($cart);
// 		$values['order_products'] = $this->getOrderProducts ($cart);
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
		$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)) {
257
			$description = '<span class="' . $this->_type . '_description">' . $plugin->$plugin_desc . '</span>';
258
259
260
		}
		$rulename='';
		if (!empty($plugin->rule_name)) {
261
			$rulename=" (".htmlspecialchars($plugin->rule_name).")";
262
		}
263
		$pluginName = $return . '<span class="' . $this->_type . '_name">' . $plugin->$plugin_name . $rulename.'</span>' . $description;
264
265
266
267
		return $pluginName;
	}


268
269
270
	/** This function evaluates all rules, one after the other until it finds a matching rule that
	 *  defines shipping costs (or uses NoShipping). If a modifier or definition is encountered,
	 *  its effect is stored, but the loop continues */
271
272
	protected function evaluateMethodRules ($cart, $method, $cart_prices) {
		// $method->match will cache the matched rule and the modifiers
273
274
		if (isset($this->match[$method->virtuemart_shipmentmethod_id])) {
			return $this->match[$method->virtuemart_shipmentmethod_id];
275
276
277
278
		} else {
			// Evaluate all rules and find the matching ones (including modifiers and definitions!)
			$result = array("rule"=>Null, "rule_name"=>"", "modifiers_add"=>array(), "modifiers_multiply"=>array());
			$cartvals = $this->getCartValues ($cart, $cart->products, $method, $cart_prices);
279
280
281
282
283
			// Pass a callback function to matches to obtain the cartvals for a subset of the products
			$this_class = $this;
			$cartvals_callback = function ($products) use ($this_class, $cart, $method, $cart_prices) {
				return $this_class->getCartValues ($cart, $products, $method, NULL);
			};
284
			foreach ($this->rules[$method->virtuemart_shipmentmethod_id] as $r) {
285
				if ($r->matches($cartvals, $cart->products, $cartvals_callback)) {
286
287
					$rtype = $r->getType();
					switch ($rtype) {
288
289
290
291
						case 'shipping': 
						case 'shippingwithtax':
						case 'noshipping': 
								$result["rule"] = $r;
292
								$result["rule_name"] = $r->getRuleName();
293
								break;
294
295
296
						case 'modifiers_add':
						case 'modifiers_multiply':
								$result[$rtype][] = $r;
297
								break;
298
299
300
						case 'definition': // A definition updates the $cartvals, but has no other effects
								$cartvals[strtolower($r->getRuleName())] = $r->getShippingCosts();
								// TODO
301
302
								break;
						default:
303
								$this->printWarning(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_TYPE', $r->getType(), $r->rulestring));
304
305
306
307
								break;
					}
				}
				if (!is_null($result["rule"])) {
308
					$this->match[$method->virtuemart_shipmentmethod_id] = $result;
309
310
					return $result; // <- This also breaks out of the foreach loop!
				}
311
312
			}
		}
313
314
		// None of the rules matched, so return NULL, but keep the evaluated results;
		$this->match[$method->virtuemart_shipmentmethod_id] = $result;
315
316
317
318
319
320
321
322
323
324
		return NULL;
	}

	/**
	 * @param \VirtueMartCart $cart
	 * @param int             $method
	 * @param array           $cart_prices
	 * @return bool
	 */
	protected function checkConditions ($cart, $method, $cart_prices) {
325
		if (!isset($this->rules[$method->virtuemart_shipmentmethod_id])) 
326
327
			$this->parseMethodRules($method);
		$match = $this->evaluateMethodRules ($cart, $method, $cart_prices);
328
329
330
		if ($match) {
			$method->rule_name = $match["rule_name"];
			// If NoShipping is set, this method should NOT offer any shipping at all, so return FALSE, otherwise TRUE
331
			// If the rule has a name, print it as warning (otherwise don't print anything)
332
			if ($match['rule']->isNoShipping()) {
333
334
				if (!empty($method->rule_name))
					$this->printWarning(JText::sprintf('VMSHIPMENT_RULES_NOSHIPPING_MESSAGE', $method->rule_name));
335
				vmdebug('checkConditions '.$method->shipment_name.' indicates NoShipping for this method, specified by rule "'.$method->rule_name.'" ('.$match['rule']->rulestring.').');
336
337
338
339
340
				return FALSE;
			} else {
				return TRUE;
			}
		}
341
		vmdebug('checkConditions '.$method->shipment_name.' does not fulfill all conditions, no rule matches');
342
343
344
		return FALSE;
	}

345
346
347
348
349
350
351
	/**
	 * @param VirtueMartCart $cart
	 * @param                $method
	 * @param                $cart_prices
	 * @return int
	 */
	function getCosts (VirtueMartCart $cart, $method, $cart_prices) {
352
353
		if (!isset($this->rules[$method->virtuemart_shipmentmethod_id])) 
			$this->parseMethodRules($method);
354
		$match = $this->evaluateMethodRules ($cart, $method, $cart_prices);
355
356
		if ($match) {
			$r = $match["rule"];
357
			vmdebug('Rule ' . $match["rule_name"] . ' ('.$r->rulestring.') matched.');
358
			$method->tax_id = $r->tax_id;
359
			// TODO: Shall we include the name of the modifiers, too?
360
361
362
363
			$method->rule_name = $match["rule_name"];
			// Final shipping costs are calculated as:
			//   Shipping*ExtraShippingMultiplier + ExtraShippingCharge
			// with possibly multiple modifiers
364
			$method->cost = $r->getShippingCosts();
365
			foreach ($match['modifiers_multiply'] as $modifier) {
366
				$method->cost *= $modifier->getShippingCosts();
367
368
			}
			foreach ($match['modifiers_add'] as $modifier) {
369
				$method->cost += $modifier->getShippingCosts();
370
			}
371
372
			$method->includes_tax = $r->includes_tax;
			return $method->cost;
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
		}
		
		vmdebug('getCosts '.$method->name.' does not return shipping costs');
		return 0;
	}

	/**
	 * update the plugin cart_prices (
	 *
	 * @author Valérie Isaksen (original), Reinhold Kainhofer (tax calculations from shippingWithTax)
	 *
	 * @param $cart_prices: $cart_prices['salesPricePayment'] and $cart_prices['paymentTax'] updated. Displayed in the cart.
	 * @param $value :   fee
	 * @param $tax_id :  tax id
	 */

	function setCartPrices (VirtueMartCart $cart, &$cart_prices, $method) {

391

392
393
394
395
		if (!class_exists ('calculationHelper')) {
			require(JPATH_VM_ADMINISTRATOR . DS . 'helpers' . DS . 'calculationh.php');
		}
		$_psType = ucfirst ($this->_psType);
396
		$calculator = calculationHelper::getInstance ();
397

398
399
400
401
402
403
404
405
		$cart_prices[$this->_psType . 'Value'] = $calculator->roundInternal ($this->getCosts ($cart, $method, $cart_prices), 'salesPrice');

		if($this->_psType=='payment'){
			$cartTotalAmountOrig=$this->getCartAmount($cart_prices);
			$cartTotalAmount=($cartTotalAmountOrig + $method->cost_per_transaction) / (1 -($method->cost_percent_total * 0.01));
			$cart_prices[$this->_psType . 'Value'] = $cartTotalAmount - $cartTotalAmountOrig;
		}

406
407

		$taxrules = array();
408
409
410
		if(isset($method->tax_id) and (int)$method->tax_id === -1){

		} else if (!empty($method->tax_id)) {
411
412
413
414
415
416
			$cart_prices[$this->_psType . '_calc_id'] = $method->tax_id;

			$db = JFactory::getDBO ();
			$q = 'SELECT * FROM #__virtuemart_calcs WHERE `virtuemart_calc_id`="' . $method->tax_id . '" ';
			$db->setQuery ($q);
			$taxrules = $db->loadAssocList ();
417

418
419
420
421
422
423
424
425
426
427
428
429
			if(!empty($taxrules) ){
				foreach($taxrules as &$rule){
					if(!isset($rule['subTotal'])) $rule['subTotal'] = 0;
					if(!isset($rule['taxAmount'])) $rule['taxAmount'] = 0;
					$rule['subTotalOld'] = $rule['subTotal'];
					$rule['taxAmountOld'] = $rule['taxAmount'];
					$rule['taxAmount'] = 0;
					$rule['subTotal'] = $cart_prices[$this->_psType . 'Value'];
				}
			}
		} else {
			$taxrules = array_merge($calculator->_cartData['VatTax'],$calculator->_cartData['taxRulesBill']);
430

431
432
			if(!empty($taxrules) ){
				$denominator = 0.0;
433
				foreach($taxrules as &$rule){
434
					//$rule['numerator'] = $rule['calc_value']/100.0 * $rule['subTotal'];
435
436
					if(!isset($rule['subTotal'])) $rule['subTotal'] = 0;
					if(!isset($rule['taxAmount'])) $rule['taxAmount'] = 0;
437
438
439
440
441
442
443
444
445
					$denominator += ($rule['subTotal']-$rule['taxAmount']);
					$rule['subTotalOld'] = $rule['subTotal'];
					$rule['subTotal'] = 0;
					$rule['taxAmountOld'] = $rule['taxAmount'];
					$rule['taxAmount'] = 0;
					//$rule['subTotal'] = $cart_prices[$this->_psType . 'Value'];
				}
				if(empty($denominator)){
					$denominator = 1;
446
447
448
				}

				foreach($taxrules as &$rule){
449
450
					$frac = ($rule['subTotalOld']-$rule['taxAmountOld'])/$denominator;
					$rule['subTotal'] = $cart_prices[$this->_psType . 'Value'] * $frac;
451
					vmdebug('Part $denominator '.$denominator.' $frac '.$frac,$rule['subTotal']);
452
453
				}
			}
454
455
		}

456

457
458
459
460
461
462
		if(empty($method->cost_per_transaction)) $method->cost_per_transaction = 0.0;
		if(empty($method->cost_percent_total)) $method->cost_percent_total = 0.0;

		if (count ($taxrules) > 0 ) {

			// BEGIN_RK_CHANGES
463
			if ($method->includes_tax) {
464

465
466
467
				$cart_prices['salesPrice' . $_psType] = $calculator->roundInternal ($cart_prices[$this->_psType . 'Value'], 'salesPrice');
				// Calculate the tax from the final sales price:
				$calculator->setRevert (true);
468
469
				$cart_prices[$this->_psType . 'Value'] = $calculator->roundInternal ($calculator->executeCalculation($taxrules, $cart_prices[$this->_psType . 'Value'], true));
				$cart_prices[$this->_psType . 'Tax'] = $cart_prices['salesPrice' . $_psType] - $cart_prices[$this->_psType . 'Value'];
470
471
				$calculator->setRevert (false);
			} else {
472
			// END_RK_CHANGES
473
474
475
			$cart_prices['salesPrice' . $_psType] = $calculator->roundInternal ($calculator->executeCalculation ($taxrules, $cart_prices[$this->_psType . 'Value'],true,false), 'salesPrice');
			//vmdebug('I am in '.get_class($this).' and have this rules now',$taxrules,$cart_prices[$this->_psType . 'Value'],$cart_prices['salesPrice' . $_psType]);
			$cart_prices[$this->_psType . 'Tax'] = $calculator->roundInternal (($cart_prices['salesPrice' . $_psType] -  $cart_prices[$this->_psType . 'Value']), 'salesPrice');
476
			// BEGIN_RK_CHANGES
477
			}
478
			// END_RK_CHANGES
479
480
			reset($taxrules);
			$taxrule =  current($taxrules);
481
			$cart_prices[$this->_psType . '_calc_id'] = $taxrule['virtuemart_calc_id'];
482
483
484
485
486
487

			foreach($taxrules as &$rule){
				if(isset($rule['subTotalOld'])) $rule['subTotal'] += $rule['subTotalOld'];
				if(isset($rule['taxAmountOld'])) $rule['taxAmount'] += $rule['taxAmountOld'];
			}

488
		} else {
489
			$cart_prices['salesPrice' . $_psType] = $cart_prices[$this->_psType . 'Value'];
490
491
492
			$cart_prices[$this->_psType . 'Tax'] = 0;
			$cart_prices[$this->_psType . '_calc_id'] = 0;
		}
493
494


495
		return $cart_prices['salesPrice' . $_psType];
496

497
	}
498

499
	protected function createMethodRule ($r, $countries, $tax) {
500
		return new ShippingRule($this, $r, $countries, $tax);
501
502
503
504
505
506
507
	}

	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;
508
			$this->rules[$method->virtuemart_shipmentmethod_id][] = $this->createMethodRule ($r, $countries, $tax);
509
510
511
512
		}
	}
	
	protected function parseMethodRules (&$method) {
513
514
		if (!isset($this->rules[$method->virtuemart_shipmentmethod_id])) 
			$this->rules[$method->virtuemart_shipmentmethod_id] = array();
515
516
517
518
519
520
521
522
523
524
		$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);
	}

525
526
527
528
529
	/** Functions to calculate all the different variables for the given cart and given (sub)set of products in the cart */
	protected function getOrderArticles (VirtueMartCart $cart, $products) {
		$articles = 0;
		foreach ($products as $product) {
			$articles += $product->quantity;
530
531
532
533
		}
		return $articles;
	}

534
535
	protected function getOrderProducts (VirtueMartCart $cart, $products) {
		return count($products);
536
537
	}

538
	protected function getOrderDimensions (VirtueMartCart $cart, $products, $length_dimension) {
539
		/* Cache the value in a static variable and calculate it only once! */
540
		$dimensions=array(
541
542
			'volume' => 0,
			'maxvolume' => 0, 'minvolume' => 9999999999,
543
544
545
			'maxlength' => 0, 'minlength' => 9999999999, 'totallength' => 0,
			'maxwidth'  => 0, 'minwidth' => 9999999999,  'totalwidth'  => 0,
			'maxheight' => 0, 'minheight' => 9999999999, 'totalheight' => 0,
546
			'maxpackaging' => 0, 'minpackaging' => 9999999999, 'totalpackaging' => 0,
547
		);
548
		foreach ($products as $product) {
549
	
550
551
552
553
554
555
556
557
			$l = ShopFunctions::convertDimensionUnit ($product->product_length, $product->product_lwh_uom, $length_dimension);
			$w = ShopFunctions::convertDimensionUnit ($product->product_width, $product->product_lwh_uom, $length_dimension);
			$h = ShopFunctions::convertDimensionUnit ($product->product_height, $product->product_lwh_uom, $length_dimension);

			$volume = $l * $w * $h;
			$dimensions['volume'] += $volume * $product->quantity;
			$dimensions['maxvolume'] = max ($dimensions['maxvolume'], $volume);
			$dimensions['minvolume'] = min ($dimensions['minvolume'], $volume);
558
				
559
560
561
562
563
564
565
566
567
568
569
570
			$dimensions['totallength'] += $l * $product->quantity;
			$dimensions['maxlength'] = max ($dimensions['maxlength'], $l);
			$dimensions['minlength'] = min ($dimensions['minlength'], $l);
			$dimensions['totalwidth'] += $w * $product->quantity;
			$dimensions['maxwidth'] = max ($dimensions['maxwidth'], $w);
			$dimensions['minwidth'] = min ($dimensions['minwidth'], $w);
			$dimensions['totalheight'] += $h * $product->quantity;
			$dimensions['maxheight'] = max ($dimensions['maxheight'], $h);
			$dimensions['minheight'] = min ($dimensions['minheight'], $h);
			$dimensions['totalpackaging'] += $product->product_packaging * $product->quantity;
			$dimensions['maxpackaging'] = max ($dimensions['maxpackaging'], $product->product_packaging);
			$dimensions['minpackaging'] = min ($dimensions['minpackaging'], $product->product_packaging);
571
		}
572

573
574
		return $dimensions;
	}
575
	
576
577
	protected function getOrderWeights (VirtueMartCart $cart, $products, $weight_unit) {
		$dimensions=array(
578
579
580
			'weight' => 0,
			'maxweight' => 0, 'minweight' => 9999999999,
		);
581
582
583
584
585
		foreach ($products as $product) {
			$w = ShopFunctions::convertWeigthUnit ($product->product_weight, $product->product_weight_uom, $weight_unit);
			$dimensions['maxweight'] = max ($dimensions['maxweight'], $w);
			$dimensions['minweight'] = min ($dimensions['minweight'], $w);
			$dimensions['weight'] += $w * $product->quantity;
586
		}
587
		return $dimensions;
588
589
	}
	
590
	protected function getOrderListProperties (VirtueMartCart $cart, $products) {
591
592
		$categories = array();
		$vendors = array();
593
		$skus = array();
594
		$manufacturers = array();
595
		foreach ($products as $product) {
596
			$skus[] = $product->product_sku;
597
598
			$categories = array_merge ($categories, $product->categories);
			$vendors[] = $product->virtuemart_vendor_id;
599
600
601
			if ($product->virtuemart_manufacturer_id) {
				$manufacturers[] = $product->virtuemart_manufacturer_id;
			}
602
		}
603
		$skus = array_unique($skus);
604
		$vendors = array_unique($vendors);
605
606
		$categories = array_unique($categories);
		$manufacturers = array_unique($manufacturers);
607
608
609
610
611
		return array ('skus'=>$skus, 
			      'categories'=>$categories,
			      'vendors'=>$vendors,
			      'manufacturers'=>$manufacturers,
		);
612
613
	}
	
614
	protected function getOrderCountryState (VirtueMartCart $cart, $address) {
615
616
617
618
		$data = array (
			'countryid' => 0, 'country' => '', 'country2' => '', 'country3' => '',
			'stateid'   => 0, 'state'   => '', 'state2'   => '', 'state3'   => '',
		);
619
620
621
		
		$countriesModel = VmModel::getModel('country');
		if (isset($address['virtuemart_country_id'])) {
622
			$data['countryid'] = $address['virtuemart_country_id'];
623
624
625
626
627
			// The following is a workaround to make sure the cache is invalidated
			// because if some other extension meanwhile called $countriesModel->getCountries,
			// the cache will be modified, but the model's id will not be changes, so the
			// getData call will return the wrong cache.
			$countriesModel->setId(0); 
628
			$countriesModel->setId($address['virtuemart_country_id']);
629
			$country = $countriesModel->getData($address['virtuemart_country_id']);
630
			if (!empty($country)) {
631
632
633
634
				$data['country'] = $country->country_name;
				$data['country2'] = $country->country_2_code;
				$data['country3'] = $country->country_3_code;
			}
635
636
637
		}
		
		$statesModel = VmModel::getModel('state');
638
		if (isset($address['virtuemart_state_id'])) {
639
			$data['stateid'] = $address['virtuemart_state_id'];
640
641
642
643
644
			// The following is a workaround to make sure the cache is invalidated
			// because if some other extension meanwhile called $countriesModel->getCountries,
			// the cache will be modified, but the model's id will not be changes, so the
			// getData call will return the wrong cache.
			$statesModel->setId(0); 
645
			$statesModel->setId($address['virtuemart_state_id']);
646
			$state = $statesModel->getData($address['virtuemart_state_id']);
647
			if (!empty($state)) {
648
649
650
651
				$data['state'] = $state->state_name;
				$data['state2'] = $state->state_2_code;
				$data['state3'] = $state->state_3_code;
			}
652
653
654
655
656
657
		}
		
		return $data;

	}
	
658
659
660
661
662
663
664
665
666
667
668
	protected function getOrderAddress (VirtueMartCart $cart, $address) {
		$zip = isset($address['zip'])?trim($address['zip']):'';
		$data = array('zip'=>$zip,
			'zip1'=>substr($zip,0,1),
			'zip2'=>substr($zip,0,2),
			'zip3'=>substr($zip,0,3),
			'zip4'=>substr($zip,0,4),
			'zip5'=>substr($zip,0,5),
			'zip6'=>substr($zip,0,6),
			'city'=>isset($address['city'])?trim($address['city']):'',
		);
669
670
671
672
673
674
675
676
677
678
679
680
		$data['company'] = $address['company'];
		$data['title'] = $address['title'];
		$data['first_name'] = $address['first_name'];
		$data['middle_name'] = $address['middle_name'];
		$data['last_name'] = $address['last_name'];
		$data['address1'] = $address['address_1'];
		$data['address2'] = $address['address_2'];
		$data['city'] = $address['city'];
		$data['phone1'] = $address['phone_1'];
		$data['phone2'] = $address['phone_2'];
		$data['fax'] = $address['fax'];
		$data['email'] = $address['email'];
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
		return $data;
	}
	
	protected function getOrderPrices (VirtueMartCart $cart, $products, $cart_prices) {
		$data = array(
			'amount' => 0, 
			'amountwithtax' => 0, 
			'amountwithouttax' => 0, 
			'baseprice' => 0, 
			'basepricewithtax' => 0, 
			'discountedpricewithouttax' => 0, 
			'salesprice' => 0, 
			'taxamount' => 0, 
			'salespricewithdiscount' => 0, 
			'discountamount' => 0, 
			'pricewithouttax' => 0,
		);
		if (!empty($cart_prices)) {
			// get prices for the whole cart -> simply user the cart_prices
			$data['amount']                 = $cart_prices['salesPrice'];
			$data['amountwithtax']          = $cart_prices['salesPrice'];
			$data['amountwithouttax']       = $cart_prices['priceWithoutTax'];
			$data['baseprice']              = $cart_prices['basePrice'];
			$data['basepricewithtax']       = $cart_prices['basePriceWithTax'];
			$data['discountedpricewithouttax'] = $cart_prices['discountedPriceWithoutTax'];
			$data['salesprice']             = $cart_prices['salesPrice'];
			$data['taxamount']              = $cart_prices['taxAmount'];
			$data['salespricewithdiscount'] = $cart_prices['salesPriceWithDiscount'];
			$data['discountamount']         = $cart_prices['discountAmount'];
			$data['pricewithouttax']        = $cart_prices['priceWithoutTax'];
		} else {
			// TODO: Calculate the prices from the individual products!
			// Possible problems are discounts on the order total
			foreach ($products as $product) {
				$data['amount']                 = $product->allPrices[$product->selectedPrice]['salesPrice'];
				$data['amountwithtax']          = $product->allPrices[$product->selectedPrice]['salesPrice'];
				$data['amountwithouttax']       = $product->allPrices[$product->selectedPrice]['priceWithoutTax'];
				$data['baseprice']              = $product->allPrices[$product->selectedPrice]['basePrice'];
				$data['basepricewithtax']       = $product->allPrices[$product->selectedPrice]['basePriceWithTax'];
				$data['discountedpricewithouttax'] = $product->allPrices[$product->selectedPrice]['discountedPriceWithoutTax'];
				$data['salesprice']             = $product->allPrices[$product->selectedPrice]['salesPrice'];
				$data['taxamount']              = $product->allPrices[$product->selectedPrice]['taxAmount'];
				$data['salespricewithdiscount'] = $product->allPrices[$product->selectedPrice]['salesPriceWithDiscount'];
				$data['discountamount']         = $product->allPrices[$product->selectedPrice]['discountAmount'];
				$data['pricewithouttax']        = $product->allPrices[$product->selectedPrice]['priceWithoutTax'];
			}
		}
		return $data;
	}

	/** Allow child classes to add additional variables for the rules or modify existing one
732
	 */
733
	protected function addCustomCartValues (VirtueMartCart $cart, $products, $cart_prices, &$values) {
734
	}
735
736

	protected function getCartValues (VirtueMartCart $cart, $products, $method, $cart_prices) {
737
		$address = (($cart->ST == 0 || $cart->STSameAsBT == 1) ? $cart->BT : $cart->ST);
738
739
740
741
742
743
744
745
746
747
		$cartvals = array_merge (
			array(
				'articles'=>$this->getOrderArticles($cart, $products),
				'products'=>$this->getOrderProducts($cart, $products),
			),
			// Add the prices, optionally calculated from the products subset of the cart
			$this->getOrderPrices ($cart, $products, $cart_prices),
			// Add 'skus', 'categories', 'vendors' variables:
			$this->getOrderListProperties ($cart, $products),
			// Add country / state variables:
748
			$this->getOrderAddress ($cart, $address),
749
750
751
752
753
			$this->getOrderCountryState ($cart, $address),
			// Add Total/Min/Max weight and dimension variables:
			$this->getOrderWeights ($cart, $products, $method->weight_unit),
			$this->getOrderDimensions ($cart, $products, $method->length_unit)
		);
754
		// Let child classes update the $cartvals array, or add new variables
755
		$this->addCustomCartValues($cart, $products, $cart_prices, $cartvals);
756

757
758
759
760
		// Finally, call the triger of vmshipmentrules plugins to let them add/modify variables
		JPluginHelper::importPlugin('vmshipmentrules');
		$dispatcher = JDispatcher::getInstance();
		$dispatcher->trigger('onVmShippingRulesGetCartValues',array(&$cartvals, $cart, $products, $method, $cart_prices));
761

762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
		return $cartvals;
	}

	/**
	 * 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);
	}

845
846
847
848
849
	/* This function is needed in VM 2.0.14 etc. because otherwise the params are not saved */
	function plgVmSetOnTablePluginParamsShipment ($name, $id, &$table) {

		return $this->setOnTablePluginParams ($name, $id, $table);
	}
850

851
852
853
854
	function plgVmDeclarePluginParamsShipmentVM3 (&$data) {
		return $this->declarePluginParams ('shipment', $data);
	}

855
856
857
858
859
860
861
	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;
862
863
		}
		if (isset($data['rules1'])) {
864
865
			// Try to parse all rules (and spit out error) to inform the user. There is no other 
			// reason to parse the rules here, it's really only to trigger warnings/errors in case of a syntax error.
866
			$method = new StdClass ();
867
868
869
870
871
872
873
874
			$this->parseMethodRule ($data['rules1'], isset($data['countries1'])?$data['countries1']:array(), $data['tax_id1'], $method);
			$this->parseMethodRule ($data['rules2'], isset($data['countries2'])?$data['countries2']:array(), $data['tax_id2'], $method);
			$this->parseMethodRule ($data['rules3'], isset($data['countries3'])?$data['countries3']:array(), $data['tax_id3'], $method);
			$this->parseMethodRule ($data['rules4'], isset($data['countries4'])?$data['countries4']:array(), $data['tax_id4'], $method);
			$this->parseMethodRule ($data['rules5'], isset($data['countries5'])?$data['countries5']:array(), $data['tax_id5'], $method);
			$this->parseMethodRule ($data['rules6'], isset($data['countries6'])?$data['countries6']:array(), $data['tax_id6'], $method);
			$this->parseMethodRule ($data['rules7'], isset($data['countries7'])?$data['countries7']:array(), $data['tax_id7'], $method);
			$this->parseMethodRule ($data['rules8'], isset($data['countries8'])?$data['countries8']:array(), $data['tax_id8'], $method);
875
		}
876
877
		$ret=$this->setOnTablePluginParams ($name, $id, $table);
		return $ret;
878
879
880
881
	}

}

Reinhold Kainhofer's avatar
Reinhold Kainhofer committed
882
if (class_exists ('ShippingRule')) {
883
884
	return;
}
885

886
887
888
889
890
891
892
893
894
895
896
/** Filter the given array of products and return only those that belong to the categories, manufacturers, 
 *  vendors or products given in the $filter_conditions. The $filter_conditions is an array of the form:
 *     array( 'skus'=>array(....), 'categories'=>array(1,2,3,42), 'manufacturers'=>array(77,78,83), 'vendors'=>array(1,2))
 *  Notice that giving an empty array for any of the keys means "no restriction" and is exactly the same 
 *  as leaving out the enty altogether
 */
function filterProducts($products, $filter_conditions) {
	$result = array();
	foreach ($products as $p) {
		if (!empty($filter_conditions['skus']) && !in_array($p->product_sku, $filter_conditions['skus']))
			continue;
897
		if (!empty($filter_conditions['categories']) && count(array_intersect($filter_conditions['categories'], $p->categories))==0)
898
899
900
901
902
903
904
905
906
907
908
			continue;
		if (!empty($filter_conditions['manufacturers']) && count(array_intersect($filter_conditions['manufacturers'], $p->product_manufacturers))==0)
			continue;
		if (!empty($filter_conditions['vendors']) && count(array_intersect($filter_conditions['vendors'], $p->product_vendors))==0)
			continue;
		$result[] = $p;
	}
	return $result;
}
	

909
class ShippingRule {
910
	var $plugin = Null;
911
	var $rulestring = '';
912
913
914
915
916
917
918
919
	var $name = '';
	var $ruletype = '';
	var $evaluated = False;
	var $match = False;
	var $value = Null;
	
	var $shipping = 0;
	var $conditions = array();
920
921
922
923
	var $countries = array();
	var $tax_id = 0;
	var $includes_tax = 0;
	
924
	function __construct ($plugin, $rule, $countries, $tax_id) {
925
926
927
928
929
930
931
932
		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);
933
		$this->plugin=$plugin;
934
935
	}
	
936
	protected function parseRule($rule) {
937
938
939
940
941
942
		$ruleparts=explode(';', $rule);
		foreach ($ruleparts as $p) {
			$this->parseRulePart($p);
		}
	}
	
943
	protected function handleAssignment ($var, $value, $rulepart) {
944
		switch (strtolower($var)) {
945
946
947
948
949
950
951
952
953
954
955
			case 'name':            $this->name = $value; break;
			case 'shipping':        $this->shipping = $value; $this->includes_tax = False; $this->ruletype='shipping'; break;
			case 'shippingwithtax': $this->shipping = $value; $this->includes_tax = True; $this->ruletype='shipping'; break;
			case 'variable':        // Variable=... is the same as Definition=...
			case 'definition':      $this->name = strtolower($value); $this->ruletype = 'definition'; break;
			case 'value':           $this->shipping = $value; $this->ruletype = 'definition'; break; // definition values are also stored in the shipping member!
			case 'extrashippingcharge': $this->shipping = $value; $this->ruletype = 'modifiers_add'; break; // modifiers are also stored in the shipping member!
			case 'extrashippingmultiplier': $this->shipping = $value; $this->ruletype = 'modifiers_multiply'; break; // modifiers are also stored in the shipping member!
			case 'comment':         break; // Completely ignore all comments!
			case 'condition':       $this->conditions[] = $value; break;
			default:                JFactory::getApplication()->enqueueMessage(JText::sprintf('VMSHIPMENT_RULES_UNKNOWN_VARIABLE', $var, $rulepart), 'error');
956
957
958
		}
	}
	
959
	protected function tokenize_expression ($expression) {
960
961
962
963
964
965
966
967
968
969
		// First, extract all strings, delimited by quotes, then all text operators 
		// (OR, AND, in; but make sure we don't capture parts of words, so we need to 
		// use lookbehind/lookahead patterns to exclude OR following another letter 
		// or followed by another letter) and then all arithmetic operators
		$re = '/\s*("[^"]*"|\'[^\']*\'|<=|=>|>=|=<|<>|!=|==|<|=|>)\s*/i';
		$atoms = preg_split($re, $expression, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
		// JFactory::getApplication()->enqueueMessage("TOKENIZING '$expression' returns: <pre>".print_r($atoms,1)."</pre>", 'error');
		return $atoms;
	}
	
970
	protected function parseRulePart($rulepart) {
971
972
973
974
975
		/* In the basic version, we only split at the comparison operators and assume each term on the LHS and RHS is one variable or constant */
		/* In the advanced version, all conditions and costs can be given as a full mathematical expression */
		/* Both versions create an expression tree, which can be easily evaluated in evaluateTerm */
		$rulepart = trim($rulepart);
		if (empty($rulepart)) return;
976
977
978
979
980
981
982
983
984
985
986
987
988

		
		// Special-case the name assignment, where we don't want to interpret the value as an arithmetic expression!
		if (preg_match('/^\s*(name|variable|definition)\s*=\s*(["\']?)(.*)\2\s*$/i', $rulepart, $matches)) {
			$this->handleAssignment ($matches[1], $matches[3], $rulepart);
			return;
		}

		// Split at all operators:
		$atoms = $this->tokenize_expression ($rulepart);
		
		/* TODO: Starting from here, the advanced plugin is different! */
		$operators = array('<', '<=', '=', '>', '>=', '=>', '=<', '<>', '!=', '==');
989
990
		if (count($atoms)==1) {
			$this->shipping = $this->parseShippingTerm($atoms[0]);
991
			$this->ruletype = 'shipping';
992
993
994
995
996
997
998
999
1000
		} elseif ($atoms[1]=='=') {
			$this->handleAssignment ($atoms[0], $atoms[2], $rulepart);
		} else {
			// Conditions, need at least three atoms!
			while (count($atoms)>1) {
				if (in_array ($atoms[1], $operators)) {
					$this->conditions[] = array($atoms[1], $this->parseShippingTerm($atoms[0]), $this->parseShippingTerm($atoms[2]));
					array_shift($atoms);
					array_shift($atoms);
For faster browsing, not all history is shown. View entire blame