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
Branches
No related tags found
No related merge requests found
...@@ -51,11 +51,12 @@ function is_equal($a, $b) { ...@@ -51,11 +51,12 @@ function is_equal($a, $b) {
class RulesShippingFramework { class RulesShippingFramework {
static $_version = "0.1"; 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) // Store the parsed and possibly evaluated rules for each method (method ID is used as key)
protected $rules = array(); protected $rules = array();
protected $match = array(); protected $match = array();
var $custom_functions = array (); protected $custom_functions = array ();
protected $available_scopings = array();
function __construct() { function __construct() {
// $this->registerCallback('addCustomCartValues', array($this, 'addCustomCartValues')); // $this->registerCallback('addCustomCartValues', array($this, 'addCustomCartValues'));
...@@ -79,6 +80,24 @@ class RulesShippingFramework { ...@@ -79,6 +80,24 @@ class RulesShippingFramework {
$this->callbacks[$callback] = $func; $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) { public function readableString($string) {
switch ($string) { switch ($string) {
case "OTSHIPMENT_RULES_CUSTOMFUNCTIONS_ALREADY_DEFINED": case "OTSHIPMENT_RULES_CUSTOMFUNCTIONS_ALREADY_DEFINED":
...@@ -113,6 +132,8 @@ class RulesShippingFramework { ...@@ -113,6 +132,8 @@ class RulesShippingFramework {
return "Unknown operator '%s' in shipment rule '%s'"; return "Unknown operator '%s' in shipment rule '%s'";
case "OTSHIPMENT_RULES_UNKNOWN_TYPE": case "OTSHIPMENT_RULES_UNKNOWN_TYPE":
return "Unknown rule type '%s' encountered for rule '%s'"; 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": case "OTSHIPMENT_RULES_UNKNOWN_VARIABLE":
return "Unknown variable '%s' in rule '%s'"; return "Unknown variable '%s' in rule '%s'";
default: default:
...@@ -146,6 +167,10 @@ class RulesShippingFramework { ...@@ -146,6 +167,10 @@ class RulesShippingFramework {
return array (); return array ();
} }
function getCustomFunctionDefinitions() {
return $this->custom_functions;
}
/** @tag system-specific /** @tag system-specific
* @function printWarning() * @function printWarning()
* Print a warning in the system-specific way. * Print a warning in the system-specific way.
...@@ -182,22 +207,14 @@ class RulesShippingFramework { ...@@ -182,22 +207,14 @@ class RulesShippingFramework {
*/ */
public function setup() { public function setup() {
$custfuncdefs = $this->getCustomFunctions(); $custfuncdefs = $this->getCustomFunctions();
// Loop through the return values of all plugins: // Now loop through all custom function definitions of this plugin
foreach ($custfuncdefs as $custfuncs) { // If a function was registered before, print a warning and use the first definition
if (empty($custfuncs)) foreach ($custfuncdefs as $fname => $func) {
continue; if (isset($this->custom_functions[$fname]) && $this->custom_functions[$fname]!=$custfuncs[$fname]) {
if (!is_array($custfuncs)) { $this->warning('OTSHIPMENT_RULES_CUSTOMFUNCTIONS_ALREADY_DEFINED', $fname);
$this->warning('OTSHIPMENT_RULES_CUSTOMFUNCTIONS_NOARRAY'); } else {
} $this->debug("Defining custom function $fname");
// Now loop through all custom function definitions of this plugin $this->custom_functions[strtolower($fname)] = $func;
// 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;
}
} }
} }
} }
...@@ -266,10 +283,18 @@ class RulesShippingFramework { ...@@ -266,10 +283,18 @@ class RulesShippingFramework {
return array(); 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 * Extract information about non-numerical zip codes (UK and Canada) from the postal code
*/ */
protected function getAddressZIP ($zip) { public function getAddressZIP ($zip) {
$values = array(); $values = array();
// Postal code Check for UK postal codes: Use regexp to determine if ZIP structure matches and also to extract the parts. // 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 { ...@@ -309,11 +334,14 @@ class RulesShippingFramework {
/** Allow child classes to add additional variables for the rules or modify existing one /** Allow child classes to add additional variables for the rules or modify existing one
*/ */
protected function addCustomCartValues ($cart, $products, $method, &$values) { protected function addCustomCartValues ($cart, $products, $method, &$values) {
// Pass all args through to the callback, if it exists
if (isset($this->callbacks['addCustomCartValues'])) { 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) { protected function addPluginCartValues($cart, $products, $method, &$values) {
return $values;
} }
public function getCartValues ($cart, $products, $method) { public function getCartValues ($cart, $products, $method) {
...@@ -328,7 +356,8 @@ class RulesShippingFramework { ...@@ -328,7 +356,8 @@ class RulesShippingFramework {
$this->getOrderAddress ($cart, $method), $this->getOrderAddress ($cart, $method),
// Add Total/Min/Max weight and dimension variables: // Add Total/Min/Max weight and dimension variables:
$this->getOrderWeights ($cart, $products, $method), $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 // Let child classes update the $cartvals array, or add new variables
$this->addCustomCartValues($cart, $products, $method, $cartvals); $this->addCustomCartValues($cart, $products, $method, $cartvals);
...@@ -396,7 +425,7 @@ class RulesShippingFramework { ...@@ -396,7 +425,7 @@ class RulesShippingFramework {
} }
} }
// None of the rules matched, so return NULL, but keep the evaluated results; // None of the rules matched, so return NULL, but keep the evaluated results;
$this->match[$id] = $result; $this->match[$id] = NULL;
return NULL; return NULL;
} }
...@@ -424,7 +453,7 @@ class RulesShippingFramework { ...@@ -424,7 +453,7 @@ class RulesShippingFramework {
$this->parseMethodRules($method); $this->parseMethodRules($method);
// TODO: This needs to be redone sooner or later! // TODO: This needs to be redone sooner or later!
$match = $this->evaluateMethodRules ($cart, $method); $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); $this->setMethodCosts($method, $match, null);
// If NoShipping is set, this method should NOT offer any shipping at all, so return FALSE, otherwise TRUE // 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) // If the rule has a name, print it as warning (otherwise don't print anything)
...@@ -455,7 +484,7 @@ class RulesShippingFramework { ...@@ -455,7 +484,7 @@ class RulesShippingFramework {
if (!isset($this->rules[$id])) if (!isset($this->rules[$id]))
$this->parseMethodRules($method); $this->parseMethodRules($method);
$match = $this->evaluateMethodRules ($cart, $method); $match = $this->evaluateMethodRules ($cart, $method);
if ($match) { if ($match && isset($match['rule']) && !is_null ($match['rule'])) {
if ($this->handleNoShipping($match, $method)) { if ($this->handleNoShipping($match, $method)) {
return $results; return $results;
} }
...@@ -509,7 +538,8 @@ class RulesShippingFramework { ...@@ -509,7 +538,8 @@ class RulesShippingFramework {
protected function createMethodRule ($r, $countries, $ruleinfo) { protected function createMethodRule ($r, $countries, $ruleinfo) {
if (isset($this->callbacks['initRule'])) { 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 { } else {
return new ShippingRule($this, $r, $countries, $ruleinfo); return new ShippingRule($this, $r, $countries, $ruleinfo);
} }
...@@ -528,7 +558,7 @@ class RulesShippingFramework { ...@@ -528,7 +558,7 @@ class RulesShippingFramework {
$rules1 = preg_split("/(\r\n|\n|\r)/", $rulestring); $rules1 = preg_split("/(\r\n|\n|\r)/", $rulestring);
foreach ($rules1 as $r) { foreach ($rules1 as $r) {
// Ignore empty lines // Ignore empty lines
if (empty($r)) continue; if (empty($r) || trim($r)=='') continue;
$result[] = $this->createMethodRule ($r, $countries, $ruleinfo); $result[] = $this->createMethodRule ($r, $countries, $ruleinfo);
} }
return $result; return $result;
...@@ -614,7 +644,7 @@ class ShippingRule { ...@@ -614,7 +644,7 @@ class ShippingRule {
/* In the advanced version, all conditions and costs can be given as a full mathematical expression */ /* 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 */ /* Both versions create an expression tree, which can be easily evaluated in evaluateTerm */
$rulepart = trim($rulepart); $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! // Special-case the name assignment, where we don't want to interpret the value as an arithmetic expression!
...@@ -764,24 +794,31 @@ class ShippingRule { ...@@ -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 /** Evaluate the given expression $expr only for the products that match the filter given by the scoping
* function and the corresponding conditions */ * function and the corresponding conditions */
protected function evaluateScoping($expr, $scoping, $conditionvals, $vals, $products, $cartvals_callback) { protected function evaluateScoping($expr, $scoping, $conditionvals, $vals, $products, $cartvals_callback) {
if (count($conditionvals)<1) if (count($conditionvals)<1)
return $this->evaluateTerm($expr, $vals, $products, $cartvals_callback); 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(); // $this->framework->warning("<pre>evaluating scoping $scoping of expression ".print_r($expr,1)." with conditions ".print_r($conditionvals,1)."</pre>");
if (isset($filterkeys[$scoping])) // Normalize aliases (e.g. 'skus' and 'products' usually indicate the same scoping
$conditions[$filterkeys[$scoping]] = $conditionvals; $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: // Pass the conditions to the parent plugin class to filter the current list of products:
$filteredproducts = $this->framework->filterProducts($products, $conditions); $filteredproducts = $this->framework->filterProducts($products, $conditions);
...@@ -794,9 +831,10 @@ class ShippingRule { ...@@ -794,9 +831,10 @@ class ShippingRule {
$func = strtolower($function); $func = strtolower($function);
// Check if we have a custom function definition and use that if so. // 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! // 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"); $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: // Functions with no argument:
...@@ -878,8 +916,11 @@ class ShippingRule { ...@@ -878,8 +916,11 @@ class ShippingRule {
return $varname; return $varname;
} elseif ($varname=='values') { } elseif ($varname=='values') {
return $vals; return $vals;
} elseif ($varname=='values_debug') { } elseif ($varname=='values_debug' || $varname=='debug_values') {
return print_r($vals,1); $tmpvals = $vals;
unset($tmpvals['debug_cart']);
unset($tmpvals['debug_products']);
return print_r($tmpvals,1);
} else { } else {
$this->framework->warning('OTSHIPMENT_RULES_EVALUATE_UNKNOWN_VALUE', $expr, $this->rulestring); $this->framework->warning('OTSHIPMENT_RULES_EVALUATE_UNKNOWN_VALUE', $expr, $this->rulestring);
return null; return null;
...@@ -890,8 +931,7 @@ class ShippingRule { ...@@ -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 // 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 // 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: // 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) && (substr($expr[1], 0, 13)==="evaluate_for_");
$is_scoping = is_array($expr) && ($expr[0]=="FUNCTION") && (count($expr)>1) && in_array($expr[1], $scoping_functions);
if (is_null($expr)) { if (is_null($expr)) {
return $expr; return $expr;
...@@ -906,10 +946,14 @@ class ShippingRule { ...@@ -906,10 +946,14 @@ class ShippingRule {
} }
} elseif ($is_scoping) { } elseif ($is_scoping) {
$op = array_shift($expr); // ignore the "FUNCTION" $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 $expression = array_shift($expr); // The expression to be evaluated
$conditions = $expr; // the remaining $expr list now contains the conditions // the remaining $expr list now contains the conditions. Evaluate them one by one:
return $this->evaluateScoping ($expression, $func, $conditions, $vals, $products, $cartvals_callback); $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)) { } elseif (is_array($expr)) {
// Operator // Operator
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment