From 46207f14ef4605457ecc9ee958a01efc2f51c061 Mon Sep 17 00:00:00 2001
From: Reinhold Kainhofer <reinhold@kainhofer.com>
Date: Sat, 6 Feb 2016 19:40:58 +0100
Subject: [PATCH] Update to latest library version, some new features.

-) Scoping functions are not handled generally
-) Added evaluate_for_subcategories function
-) Added debug_cart and debug_products and debug_values variables
---
 .../de-DE.plg_vmshipment_rules_shipping.ini   |   2 +
 ...plg_vmshipment_rules_shipping_advanced.ini |   2 +
 .../en-GB.plg_vmshipment_rules_shipping.ini   |   1 +
 library/rules_shipping_framework.php          | 122 ++++++++++++------
 rules_shipping_framework_joomla.php           |  26 +++-
 5 files changed, 112 insertions(+), 41 deletions(-)

diff --git a/language/de-DE/de-DE.plg_vmshipment_rules_shipping.ini b/language/de-DE/de-DE.plg_vmshipment_rules_shipping.ini
index efaf611..c8e4c1d 100644
--- a/language/de-DE/de-DE.plg_vmshipment_rules_shipping.ini
+++ b/language/de-DE/de-DE.plg_vmshipment_rules_shipping.ini
@@ -67,3 +67,5 @@ OPENTOOLS_CHECK_CREDENTIALS_ERROR="Konnte Zugangsdaten nicht überprüfen. Bitte
 OPENTOOLS_XMLMANIFEST_ERROR="Konnte die XML-Manifest-Datei der Erweiterung nicht laden (%s)"
 OPENTOOLS_UPDATESCRIPT_ERROR="Konnte die Aktualisierungsinformationen nicht laden (%s)"
 OPENTOOLS_COMMERCIAL_UPDATES_J25="Automatische Aktualisierungen von kommerziellen Erweiterungen sind in Joomla 2.5 leider nicht möglich (erste ab Joomla 3.x). Bitte informieren Sie sich auf der Homepage des Entwicklers über mögliche Aktualisierungen und installieren Sie diese manuell."
+
+OTSHIPMENT_RULES_SCOPING_UNKNOWN="Unbekannte Scoping-Funktion 'evaluate_for_%s' in der Regel '%s'"
diff --git a/language/de-DE/de-DE.plg_vmshipment_rules_shipping_advanced.ini b/language/de-DE/de-DE.plg_vmshipment_rules_shipping_advanced.ini
index dd1ecaf..5aa0e04 100644
--- a/language/de-DE/de-DE.plg_vmshipment_rules_shipping_advanced.ini
+++ b/language/de-DE/de-DE.plg_vmshipment_rules_shipping_advanced.ini
@@ -57,3 +57,5 @@ OPENTOOLS_CHECK_CREDENTIALS_ERROR="Konnte Zugangsdaten nicht überprüfen. Bitte
 OPENTOOLS_XMLMANIFEST_ERROR="Konnte die XML-Manifest-Datei der Erweiterung nicht laden (%s)"
 OPENTOOLS_UPDATESCRIPT_ERROR="Konnte die Aktualisierungsinformationen nicht laden (%s)"
 OPENTOOLS_COMMERCIAL_UPDATES_J25="Automatische Aktualisierungen von kommerziellen Erweiterungen sind in Joomla 2.5 leider nicht möglich (erste ab Joomla 3.x). Bitte informieren Sie sich auf der Homepage des Entwicklers über mögliche Aktualisierungen und installieren Sie diese manuell."
+
+OTSHIPMENT_RULES_SCOPING_UNKNOWN="Unbekannte Scoping-Funktion 'evaluate_for_%s' in der Regel '%s'"
diff --git a/language/en-GB/en-GB.plg_vmshipment_rules_shipping.ini b/language/en-GB/en-GB.plg_vmshipment_rules_shipping.ini
index b5a23ff..ba9aaa2 100755
--- a/language/en-GB/en-GB.plg_vmshipment_rules_shipping.ini
+++ b/language/en-GB/en-GB.plg_vmshipment_rules_shipping.ini
@@ -61,6 +61,7 @@ OTSHIPMENT_RULES_EVALUATE_LISTFUNCTION_UNKNOWN="Unknown list function '%s' encou
 OTSHIPMENT_RULES_NOSHIPPING_MESSAGE="%s"
 
 OTSHIPMENT_RULES_UNKNOWN_TYPE="Unknown rule type '%s' encountered for rule '%s'"
+OTSHIPMENT_RULES_SCOPING_UNKNOWN="Unknown scoping function 'evaluate_for_%s' encountered in rule '%s'"
 
 
 VMSHIPMENT_RULES_CUSTOMFUNCTIONS_NOARRAY="Definition of custom functions (returned by a vmshipmentrules plugin) is not a proper array. Ignoring."
diff --git a/library/rules_shipping_framework.php b/library/rules_shipping_framework.php
index 3c89c16..942edc5 100644
--- a/library/rules_shipping_framework.php
+++ b/library/rules_shipping_framework.php
@@ -55,7 +55,8 @@ class RulesShippingFramework {
 	// 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,44 +80,69 @@ class RulesShippingFramework {
 		$this->callbacks[$callback] = $func;
 	}
 	
-	public function __($string) {
-		$args = func_get_args();
+	/**
+	 * 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":
-					$args[0]=""; break;
+					return "Custom function %s already defined. Ignoring this definition and using previous one.";
 			case "OTSHIPMENT_RULES_CUSTOMFUNCTIONS_NOARRAY":
-					$args[0]=""; break;
+					return "Definition of custom functions (returned by a plugin) is not a proper array. Ignoring.";
 			case "OTSHIPMENT_RULES_EVALUATE_ASSIGNMENT_TOPLEVEL":
-					$args[0]="Assignments are not allowed inside expressions (rule given was '%s')"; break;
+					return "Assignments are not allowed inside expressions (rule given was '%s')";
 			case "OTSHIPMENT_RULES_EVALUATE_LISTFUNCTION_ARGS":
-					$args[0]="List function '%s' requires all arguments to be lists. (Full rule: '%s')"; break;
+					return "List function '%s' requires all arguments to be lists. (Full rule: '%s')";
 			case "OTSHIPMENT_RULES_EVALUATE_LISTFUNCTION_CONTAIN_ARGS":
-					$args[0]="List function '%s' requires the first argument to be lists. (Full rule: '%s')"; break;
+					return "List function '%s' requires the first argument to be lists. (Full rule: '%s')";
 			case "OTSHIPMENT_RULES_EVALUATE_LISTFUNCTION_UNKNOWN":
-					$args[0]="Unknown list function '%s' encountered. (Full rule: '%s')"; break;
+					return "Unknown list function '%s' encountered. (Full rule: '%s')";
 			case "OTSHIPMENT_RULES_EVALUATE_SYNTAXERROR":
-					$args[0]="Syntax error during evaluation, RPN is not well formed! (Full rule: '%s')"; break;
+					return "Syntax error during evaluation, RPN is not well formed! (Full rule: '%s')";
 			case "OTSHIPMENT_RULES_EVALUATE_UNKNOWN_ERROR":
-					$args[0]="Unknown error occurred during evaluation of rule '%s'."; break;
+					return "Unknown error occurred during evaluation of rule '%s'.";
 			case "OTSHIPMENT_RULES_EVALUATE_UNKNOWN_FUNCTION":
-					$args[0]="Unknown function '%s' encountered during evaluation of rule '%s'."; break;
+					return "Unknown function '%s' encountered during evaluation of rule '%s'.";
 			case "OTSHIPMENT_RULES_EVALUATE_UNKNOWN_VALUE":
-					$args[0]="Evaluation yields unknown value while evaluating rule part '%s'."; break;
+					return "Evaluation yields unknown value while evaluating rule part '%s'.";
 			case "OTSHIPMENT_RULES_NOSHIPPING_MESSAGE":
-					$args[0]=""; break;
+					return "%s";
 			case "OTSHIPMENT_RULES_PARSE_FUNCTION_NOT_CLOSED":
-					$args[0]="Error during parsing expression '%s': A function call was not closed properly!"; break;
+					return "Error during parsing expression '%s': A function call was not closed properly!";
 			case "OTSHIPMENT_RULES_PARSE_MISSING_PAREN":
-					$args[0]="Error during parsing expression '%s': Opening parenthesis cannot be found!"; break;
+					return "Error during parsing expression '%s': Opening parenthesis cannot be found!";
 			case "OTSHIPMENT_RULES_PARSE_PAREN_NOT_CLOSED":
-					$args[0]="Error during parsing expression '%s': A parenthesis was not closed properly!"; break;
+					return "Error during parsing expression '%s': A parenthesis was not closed properly!";
 			case "OTSHIPMENT_RULES_UNKNOWN_OPERATOR":
-					$args[0]="Unknown operator '%s' in shipment rule '%s'"; break;
+					return "Unknown operator '%s' in shipment rule '%s'";
 			case "OTSHIPMENT_RULES_UNKNOWN_TYPE":
-					$args[0]=""; break;
+					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":
-					$args[0]="Unknown variable '%s' in rule '%s'"; break;
+					return "Unknown variable '%s' in rule '%s'";
+			default:
+					return $string;
 		}
+	}
+	
+	public function __($string) {
+		$args = func_get_args();
 
 		if (isset($this->callbacks["translate"])) {
 			return call_user_func_array($this->callbacks["translate"], $args);
@@ -261,6 +287,14 @@ 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
 	 */
@@ -323,7 +357,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);
@@ -759,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" =>      'skus',
-			"evaluate_for_skus" =>          'skus',
-			"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);
@@ -873,8 +915,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;
@@ -885,8 +930,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_skus", "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;
@@ -901,14 +945,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
 			// 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, $func, $conditions, $vals, $products, $cartvals_callback);
+			return $this->evaluateScoping ($expression, $scope, $conditions, $vals, $products, $cartvals_callback);
 			
 		} elseif (is_array($expr)) {
 			// Operator
diff --git a/rules_shipping_framework_joomla.php b/rules_shipping_framework_joomla.php
index 6862d3b..11eb008 100644
--- a/rules_shipping_framework_joomla.php
+++ b/rules_shipping_framework_joomla.php
@@ -18,10 +18,11 @@ if (!class_exists( 'RulesShippingFramework' ))
 // $test=new asdfasdsf();
 class RulesShippingFrameworkJoomla extends RulesShippingFramework {
 	/* Constructor: Register the available scopings */
-	function _construct() {
-		parent::_construct();
+	function __construct() {
+		parent::__construct();
 		$this->registerScopings(array(
 			"categories"    => 'categories',
+			"subcategories" => 'subcategories',
 			"products"      => 'products',
 			"skus"          => 'products',
 			"vendors"       => 'vendors',
@@ -344,11 +345,32 @@ class RulesShippingFrameworkJoomla extends RulesShippingFramework {
 	*/
 	public function filterProducts($products, $filter_conditions) {
 		$result = array();
+		
+		// For the subcategories scoping we need all subcategories of the conditions:
+		$subcategories = array();
+		if (isset($filter_conditions['subcategories']) && !empty($filter_conditions['subcategories'])) {
+			$catmodel = VmModel::getModel('category');
+			foreach ($filter_conditions['subcategories'] as $catid) {
+				$subcategories[] = $catid;
+				// Get all child categories from the category model
+				$categories = $catmodel->getCategoryTree($catid,0,/*onlyPublished=*/false);
+				foreach ($categories as $subcat) {
+					$subcategories[] = $subcat->virtuemart_category_id;
+				}
+			}
+			$subcategories = array_unique($subcategories);
+JFactory::getApplication()->enqueueMessage("<pre>Subcategories: ".print_r($subcategories,1)."</pre>", 'error');
+			
+		}
+		
+
 		foreach ($products as $p) {
 			if (!empty($filter_conditions['products']) && !in_array($p->product_sku, $filter_conditions['products']))
 				continue;
 			if (!empty($filter_conditions['categories']) && count(array_intersect($filter_conditions['categories'], $p->categories))==0)
 				continue;
+			if (!empty($filter_conditions['subcategories']) && count(array_intersect($subcategories, $p->categories))==0)
+				continue;
 			if (!empty($filter_conditions['manufacturers']) && count(array_intersect($filter_conditions['manufacturers'], $p->virtuemart_manufacturer_id))==0)
 				continue;
 			if (!empty($filter_conditions['vendors']) && !in_array($p->virtuemart_vendor_id, $filter_conditions['vendors']))
-- 
GitLab