Commit 3066c2ed authored by Reinhold Kainhofer's avatar Reinhold Kainhofer

Merge in changes from VM and WC

parent f9050f24
......@@ -51,11 +51,12 @@ function is_equal($a, $b) {
class RulesShippingFramework {
static $_version = "0.1";
protected $_callbacks = array();
protected $callbacks = array();
// Store the parsed and possibly evaluated rules for each method (method ID is used as key)
protected $rules = array();
protected $match = array();
var $custom_functions = array ();
protected $custom_functions = array ();
protected $available_scopings = array();
function __construct() {
// $this->registerCallback('addCustomCartValues', array($this, 'addCustomCartValues'));
......@@ -79,6 +80,24 @@ class RulesShippingFramework {
$this->callbacks[$callback] = $func;
}
/**
* Register all possible scopings to the framework in the form
* array("skus" => "products" , "products" => "products")
* This registers functions evaluate_for_skus and evaluate_for_products,
* which both filter products (they are identical).
*/
public function registerScopings($scopings) {
$this->available_scopings = $scopings;
}
/**
* Get the list of all scopings the framework implementation claims to have
* implemented.
*/
public function getScopings() {
return $this->available_scopings;
}
public function readableString($string) {
switch ($string) {
case "OTSHIPMENT_RULES_CUSTOMFUNCTIONS_ALREADY_DEFINED":
......@@ -113,6 +132,8 @@ class RulesShippingFramework {
return "Unknown operator '%s' in shipment rule '%s'";
case "OTSHIPMENT_RULES_UNKNOWN_TYPE":
return "Unknown rule type '%s' encountered for rule '%s'";
case "OTSHIPMENT_RULES_SCOPING_UNKNOWN":
return "Unknown scoping function 'evaluate_for_%s' encountered in rule '%s'";
case "OTSHIPMENT_RULES_UNKNOWN_VARIABLE":
return "Unknown variable '%s' in rule '%s'";
default:
......@@ -146,6 +167,10 @@ class RulesShippingFramework {
return array ();
}
function getCustomFunctionDefinitions() {
return $this->custom_functions;
}
/** @tag system-specific
* @function printWarning()
* Print a warning in the system-specific way.
......@@ -182,22 +207,14 @@ class RulesShippingFramework {
*/
public function setup() {
$custfuncdefs = $this->getCustomFunctions();
// Loop through the return values of all plugins:
foreach ($custfuncdefs as $custfuncs) {
if (empty($custfuncs))
continue;
if (!is_array($custfuncs)) {
$this->warning('OTSHIPMENT_RULES_CUSTOMFUNCTIONS_NOARRAY');
}
// Now loop through all custom function definitions of this plugin
// If a function was registered before, print a warning and use the first definition
foreach ($custfuncs as $fname => $func) {
if (isset($this->custom_functions[$fname])) {
$this->warning('OTSHIPMENT_RULES_CUSTOMFUNCTIONS_ALREADY_DEFINED', $fname);
} else {
$this->debug("Defining custom function $fname");
$this->custom_functions[strtolower($fname)] = $func;
}
// 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 ($custfuncdefs as $fname => $func) {
if (isset($this->custom_functions[$fname]) && $this->custom_functions[$fname]!=$custfuncs[$fname]) {
$this->warning('OTSHIPMENT_RULES_CUSTOMFUNCTIONS_ALREADY_DEFINED', $fname);
} else {
$this->debug("Defining custom function $fname");
$this->custom_functions[strtolower($fname)] = $func;
}
}
}
......@@ -266,10 +283,18 @@ class RulesShippingFramework {
return array();
}
protected function getDebugVariables ($cart, $products, $method) {
return array(
'debug_cart'=> print_r($cart,1),
'debug_products' => print_r($products, 1),
);
}
/**
* Extract information about non-numerical zip codes (UK and Canada) from the postal code
*/
protected function getAddressZIP ($zip) {
public function getAddressZIP ($zip) {
$values = array();
// Postal code Check for UK postal codes: Use regexp to determine if ZIP structure matches and also to extract the parts.
......@@ -309,11 +334,14 @@ class RulesShippingFramework {
/** Allow child classes to add additional variables for the rules or modify existing one
*/
protected function addCustomCartValues ($cart, $products, $method, &$values) {
// Pass all args through to the callback, if it exists
if (isset($this->callbacks['addCustomCartValues'])) {
return $this->callbacks['addCustomCartValues']($cart, $products, $method, $values);
return call_user_func_array($this->callbacks['addCustomCartValues'], array($cart, $products, $method, &$values)/*func_get_args()*/);
}
return $values;
}
protected function addPluginCartValues($cart, $products, $method, &$values) {
return $values;
}
public function getCartValues ($cart, $products, $method) {
......@@ -328,7 +356,8 @@ class RulesShippingFramework {
$this->getOrderAddress ($cart, $method),
// Add Total/Min/Max weight and dimension variables:
$this->getOrderWeights ($cart, $products, $method),
$this->getOrderDimensions ($cart, $products, $method)
$this->getOrderDimensions ($cart, $products, $method),
$this->getDebugVariables ($cart, $products, $method)
);
// Let child classes update the $cartvals array, or add new variables
$this->addCustomCartValues($cart, $products, $method, $cartvals);
......@@ -396,7 +425,7 @@ class RulesShippingFramework {
}
}
// None of the rules matched, so return NULL, but keep the evaluated results;
$this->match[$id] = $result;
$this->match[$id] = NULL;
return NULL;
}
......@@ -424,7 +453,7 @@ class RulesShippingFramework {
$this->parseMethodRules($method);
// TODO: This needs to be redone sooner or later!
$match = $this->evaluateMethodRules ($cart, $method);
if ($match && !is_null ($match['rule'])) {
if ($match && isset($match['rule']) && !is_null ($match['rule'])) {
$this->setMethodCosts($method, $match, null);
// If NoShipping is set, this method should NOT offer any shipping at all, so return FALSE, otherwise TRUE
// If the rule has a name, print it as warning (otherwise don't print anything)
......@@ -455,7 +484,7 @@ class RulesShippingFramework {
if (!isset($this->rules[$id]))
$this->parseMethodRules($method);
$match = $this->evaluateMethodRules ($cart, $method);
if ($match) {
if ($match && isset($match['rule']) && !is_null ($match['rule'])) {
if ($this->handleNoShipping($match, $method)) {
return $results;
}
......@@ -509,7 +538,8 @@ class RulesShippingFramework {
protected function createMethodRule ($r, $countries, $ruleinfo) {
if (isset($this->callbacks['initRule'])) {
return $this->callbacks['initRule']($this, $r, $countries, $ruleinfo);
return call_user_func_array($this->callbacks['initRule'],
array($this, $r, $countries, $ruleinfo));
} else {
return new ShippingRule($this, $r, $countries, $ruleinfo);
}
......@@ -528,7 +558,7 @@ class RulesShippingFramework {
$rules1 = preg_split("/(\r\n|\n|\r)/", $rulestring);
foreach ($rules1 as $r) {
// Ignore empty lines
if (empty($r)) continue;
if (empty($r) || trim($r)=='') continue;
$result[] = $this->createMethodRule ($r, $countries, $ruleinfo);
}
return $result;
......@@ -614,7 +644,7 @@ class ShippingRule {
/* 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;
if (!isset($rulepart) || $rulepart==='') return;
// Special-case the name assignment, where we don't want to interpret the value as an arithmetic expression!
......@@ -764,24 +794,31 @@ class ShippingRule {
}
}
protected function normalizeScoping($scoping) {
$scopings = $this->framework->getScopings();
// $this->framework->warning("<pre>normalizing Scoping $scoping. Registered scopings are: ".print_r($scopings,1)."</pre>");
if (isset($scopings[$scoping])) {
return $scopings[$scoping];
} else {
return false;
}
}
/** Evaluate the given expression $expr only for the products that match the filter given by the scoping
* function and the corresponding conditions */
protected function evaluateScoping($expr, $scoping, $conditionvals, $vals, $products, $cartvals_callback) {
if (count($conditionvals)<1)
return $this->evaluateTerm($expr, $vals, $products, $cartvals_callback);
// TODO: Make this more general!
$filterkeys = array(
"evaluate_for_categories" => 'categories',
"evaluate_for_products" => 'products',
"evaluate_for_skus" => 'products',
"evaluate_for_vendors" => 'vendors',
"evaluate_for_manufacturers" => 'manufacturers',
);
$conditions = array();
if (isset($filterkeys[$scoping]))
$conditions[$filterkeys[$scoping]] = $conditionvals;
// $this->framework->warning("<pre>evaluating scoping $scoping of expression ".print_r($expr,1)." with conditions ".print_r($conditionvals,1)."</pre>");
// Normalize aliases (e.g. 'skus' and 'products' usually indicate the same scoping
$normalizedScoping = $this->normalizeScoping($scoping);
if (!$normalizedScoping) {
$this->framework->warning('OTSHIPMENT_RULES_SCOPING_UNKNOWN', $scoping, $this->rulestring);
return false;
} else {
$conditions = array($normalizedScoping => $conditionvals);
}
// Pass the conditions to the parent plugin class to filter the current list of products:
$filteredproducts = $this->framework->filterProducts($products, $conditions);
......@@ -794,9 +831,10 @@ class ShippingRule {
$func = strtolower($function);
// Check if we have a custom function definition and use that if so.
// This is done first to allow plugins to override even built-in functions!
if (isset($this->plugin->custom_functions[$func])) {
$customfunctions = $this->framework->getCustomFunctionDefinitions();
if (isset($customfunctions[$func])) {
$this->framework->debug("Evaluating custom function $function, defined by a plugin");
return call_user_func_array($this->plugin->custom_functions[$func], $args, $this);
return call_user_func($customfunctions[$func], $args, $this);
}
// Functions with no argument:
......@@ -878,8 +916,11 @@ class ShippingRule {
return $varname;
} elseif ($varname=='values') {
return $vals;
} elseif ($varname=='values_debug') {
return print_r($vals,1);
} elseif ($varname=='values_debug' || $varname=='debug_values') {
$tmpvals = $vals;
unset($tmpvals['debug_cart']);
unset($tmpvals['debug_products']);
return print_r($tmpvals,1);
} else {
$this->framework->warning('OTSHIPMENT_RULES_EVALUATE_UNKNOWN_VALUE', $expr, $this->rulestring);
return null;
......@@ -890,8 +931,7 @@ class ShippingRule {
// The scoping functions need to be handled differently, because they first need to adjust the cart variables to the filtered product list
// before evaluating its first argument. So even though parsing the rules handles scoping functions like any other function, their
// evaluation is fundamentally different and is special-cased here:
$scoping_functions = array("evaluate_for_categories", "evaluate_for_products", "evaluate_for_vendors", "evaluate_for_manufacturers");
$is_scoping = is_array($expr) && ($expr[0]=="FUNCTION") && (count($expr)>1) && in_array($expr[1], $scoping_functions);
$is_scoping = is_array($expr) && ($expr[0]=="FUNCTION") && (count($expr)>1) && (substr($expr[1], 0, 13)==="evaluate_for_");
if (is_null($expr)) {
return $expr;
......@@ -906,10 +946,14 @@ class ShippingRule {
}
} elseif ($is_scoping) {
$op = array_shift($expr); // ignore the "FUNCTION"
$func = array_shift($expr); // The scoping function name
$scope = substr(array_shift($expr), 13); // The scoping function name with "evaluate_for_" cut off
$expression = array_shift($expr); // The expression to be evaluated
$conditions = $expr; // the remaining $expr list now contains the conditions
return $this->evaluateScoping ($expression, $func, $conditions, $vals, $products, $cartvals_callback);
// the remaining $expr list now contains the conditions. Evaluate them one by one:
$conditions = array();
foreach ($expr as $e) {
$conditions[] = $this->evaluateTerm($e, $vals, $products, $cartvals_callback);
}
return $this->evaluateScoping ($expression, $scope, $conditions, $vals, $products, $cartvals_callback);
} elseif (is_array($expr)) {
// Operator
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment