Skip to content
Snippets Groups Projects
Commit 3066c2ed authored by Reinhold Kainhofer's avatar Reinhold Kainhofer
Browse files

Merge in changes from VM and WC

parent f9050f24
No related branches found
No related tags found
No related merge requests found
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment