dolibarr  18.0.0
price_parser.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2015 Ion Agorria <ion@agorria.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16  */
17 
23 require_once DOL_DOCUMENT_ROOT.'/core/class/evalmath.class.php';
24 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
25 require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_expression.class.php';
26 require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_global_variable.class.php';
27 require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_global_variable_updater.class.php';
28 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
29 
34 {
35  protected $db;
36  // Limit of expressions per price
37  public $limit = 100;
38  // The error that occurred when parsing price
39  public $error_parser;
40  // The expression that caused the error
41  public $error_expr;
42  //The special char
43  public $special_chr = "#";
44  //The separator char
45  public $separator_chr = ";";
46 
52  public function __construct($db)
53  {
54  $this->db = $db;
55  }
56 
62  public function translatedError()
63  {
64  global $langs;
65  $langs->load("errors");
66  /*
67  -No arg
68  9, an unexpected error occured
69  14, division by zero
70  19, expression not found
71  20, empty expression
72 
73  -1 Arg
74  1, cannot assign to constant '%s'
75  2, cannot redefine built-in function '%s'
76  3, undefined variable '%s' in function definition
77  4, illegal character '%s'
78  5, unexpected '%s'
79  8, unexpected operator '%s'
80  10, operator '%s' lacks operand
81  11, expecting '%s'
82  17, undefined variable '%s'
83  21, empty result '%s'
84  22, negative result '%s'
85  24, variable '%s' exists but has no value
86 
87  -2 Args
88  6, wrong number of arguments (%s given, %s expected)
89  23, unknown or non set variable '%s' after %s
90 
91  -internal errors
92  7, internal error
93  12, internal error
94  13, internal error
95  15, internal error
96  16, internal error
97  18, internal error
98  */
99  if (empty($this->error_parser)) {
100  return $langs->trans("ErrorPriceExpressionUnknown", 0); //this is not supposed to happen
101  }
102  list($code, $info) = $this->error_parser;
103  if (in_array($code, array(9, 14, 19, 20))) { //Errors which have 0 arg
104  return $langs->trans("ErrorPriceExpression".$code);
105  } elseif (in_array($code, array(1, 2, 3, 4, 5, 8, 10, 11, 17, 21, 22))) { //Errors which have 1 arg
106  return $langs->trans("ErrorPriceExpression".$code, $info);
107  } elseif (in_array($code, array(6, 23))) { //Errors which have 2 args
108  return $langs->trans("ErrorPriceExpression".$code, $info[0], $info[1]);
109  } elseif (in_array($code, array(7, 12, 13, 15, 16, 18))) { //Internal errors
110  return $langs->trans("ErrorPriceExpressionInternal", $code);
111  } else //Unknown errors
112  {
113  return $langs->trans("ErrorPriceExpressionUnknown", $code);
114  }
115  }
116 
125  public function parseExpression($product, $expression, $values)
126  {
127  global $user, $hookmanager, $extrafields;
128 
129  $action = 'PARSEEXPRESSION';
130  if ($reshook = $hookmanager->executeHooks('doDynamiPrice', array(
131  'expression' => &$expression,
132  'product' => &$product,
133  'values' => &$values
134  ), $this, $action)) {
135  return $hookmanager->resArray['return'];
136  }
137  //Check if empty
138  $expression = trim($expression);
139  if (empty($expression)) {
140  $this->error_parser = array(20, null);
141  return -2;
142  }
143 
144  //Accessible product values by expressions
145  $values = array_merge($values, array(
146  "tva_tx" => $product->tva_tx,
147  "localtax1_tx" => $product->localtax1_tx,
148  "localtax2_tx" => $product->localtax2_tx,
149  "weight" => $product->weight,
150  "length" => $product->length,
151  "surface" => $product->surface,
152  "price_min" => $product->price_min,
153  "cost_price" => $product->cost_price,
154  "pmp" => $product->pmp,
155  ));
156 
157  // Retrieve all extrafields if not already not know (should not happen)
158  if (! is_object($extrafields)) {
159  $extrafields = new ExtraFields($this->db);
160  $extrafields->fetch_name_optionals_label($product->table_element);
161  }
162 
163  $product->fetch_optionals();
164  if (is_array($extrafields->attributes[$product->table_element]['label'])) {
165  foreach ($extrafields->attributes[$product->table_element]['label'] as $key => $label) {
166  $values["extrafield_".$key] = $product->array_options['options_'.$key];
167  }
168  }
169 
170  //Process any pending updaters
171  $price_updaters = new PriceGlobalVariableUpdater($this->db);
172  foreach ($price_updaters->listPendingUpdaters() as $entry) {
173  //Schedule the next update by adding current timestamp (secs) + interval (mins)
174  $entry->update_next_update(dol_now() + ($entry->update_interval * 60), $user);
175  //Do processing
176  $res = $entry->process();
177  //Store any error or clear status if OK
178  $entry->update_status($res < 1 ? $entry->error : '', $user);
179  }
180 
181  //Get all global values
182  $price_globals = new PriceGlobalVariable($this->db);
183  foreach ($price_globals->listGlobalVariables() as $entry) {
184  $values["global_".$entry->code] = $entry->value;
185  }
186 
187  //Remove internal variables
188  unset($values["supplier_id"]);
189 
190  //Prepare the lib, parameters and values
191  $em = new EvalMath();
192  $em->suppress_errors = true; //Don't print errors on page
193  $this->error_expr = null;
194  $last_result = null;
195 
196  //Fill each variable in expression from values
197  $expression = str_replace("\n", $this->separator_chr, $expression);
198  foreach ($values as $key => $value) {
199  if ($value === null && strpos($expression, $key) !== false) {
200  $this->error_parser = array(24, $key);
201  return -7;
202  }
203  $expression = str_replace($this->special_chr.$key.$this->special_chr, strval($value), $expression);
204  }
205 
206  //Check if there is unfilled variable
207  if (strpos($expression, $this->special_chr) !== false) {
208  $data = explode($this->special_chr, $expression);
209  $variable = $this->special_chr.$data[1];
210  if (isset($data[2])) {
211  $variable .= $this->special_chr;
212  }
213  $this->error_parser = array(23, array($variable, $expression));
214  return -6;
215  }
216 
217  //Iterate over each expression splitted by $separator_chr
218  $expressions = explode($this->separator_chr, $expression);
219  $expressions = array_slice($expressions, 0, $this->limit);
220  foreach ($expressions as $expr) {
221  $expr = trim($expr);
222  if (!empty($expr)) {
223  $last_result = $em->evaluate($expr);
224  $this->error_parser = $em->last_error_code;
225  if ($this->error_parser !== null) { //$em->last_error_code is null if no error happened, so just check if error_parser is not null
226  $this->error_expr = $expr;
227  return -3;
228  }
229  }
230  }
231  $vars = $em->vars();
232  if (empty($vars["price"])) {
233  $vars["price"] = $last_result;
234  }
235  if (!isset($vars["price"])) {
236  $this->error_parser = array(21, $expression);
237  return -4;
238  }
239  if ($vars["price"] < 0) {
240  $this->error_parser = array(22, $expression);
241  return -5;
242  }
243  return $vars["price"];
244  }
245 
253  public function parseProduct($product, $extra_values = array())
254  {
255  //Get the expression from db
256  $price_expression = new PriceExpression($this->db);
257  $res = $price_expression->fetch($product->fk_price_expression);
258  if ($res < 1) {
259  $this->error_parser = array(19, null);
260  return -1;
261  }
262 
263  //Get the supplier min price
264  $productFournisseur = new ProductFournisseur($this->db);
265  $res = $productFournisseur->find_min_price_product_fournisseur($product->id, 0, 0);
266  if ($res < 0) {
267  $this->error_parser = array(25, null);
268  return -1;
269  } elseif ($res == 0) {
270  $supplier_min_price = 0;
271  $supplier_min_price_with_discount = 0;
272  } else {
273  $supplier_min_price = $productFournisseur->fourn_unitprice;
274  $supplier_min_price_with_discount = $productFournisseur->fourn_unitprice_with_discount;
275  }
276 
277  //Accessible values by expressions
278  $extra_values = array_merge($extra_values, array(
279  "supplier_min_price" => $supplier_min_price,
280  "supplier_min_price_with_discount" => $supplier_min_price_with_discount,
281  ));
282 
283  //Parse the expression and return the price, if not error occurred check if price is higher than min
284  $result = $this->parseExpression($product, $price_expression->expression, $extra_values);
285  if (empty($this->error_parser)) {
286  if ($result < $product->price_min) {
287  $result = $product->price_min;
288  }
289  }
290  return $result;
291  }
292 
300  public function parseProductSupplier($product_supplier, $extra_values = array())
301  {
302  //Get the expression from db
303  $price_expression = new PriceExpression($this->db);
304  $res = $price_expression->fetch($product_supplier->fk_supplier_price_expression);
305  if ($res < 1) {
306  $this->error_parser = array(19, null);
307  return -1;
308  }
309 
310  //Get the product data (use ignore_expression to avoid possible recursion)
311  $product_supplier->fetch($product_supplier->id, '', '', '', 1);
312 
313  //Accessible values by expressions
314  $extra_values = array_merge($extra_values, array(
315  "supplier_quantity" => $product_supplier->fourn_qty,
316  "supplier_tva_tx" => $product_supplier->fourn_tva_tx,
317  ));
318 
319  //Parse the expression and return the price
320  return $this->parseExpression($product_supplier, $price_expression->expression, $extra_values);
321  }
322 
331  public function testExpression($product_id, $expression, $extra_values = array())
332  {
333  //Get the product data
334  $product = new Product($this->db);
335  $product->fetch($product_id, '', '', 1);
336 
337  //Values for product expressions
338  $extra_values = array_merge($extra_values, array(
339  "supplier_min_price" => 1,
340  "supplier_min_price_with_discount" => 2,
341  ));
342 
343  //Values for supplier product expressions
344  $extra_values = array_merge($extra_values, array(
345  "supplier_quantity" => 3,
346  "supplier_tva_tx" => 4,
347  ));
348  return $this->parseExpression($product, $expression, $extra_values);
349  }
350 }
Class EvalMath.
Class to manage standard extra fields.
Class for accesing price expression table.
Class for accesing price global variables table.
Class for price global variable updaters table.
Class to parse product price expressions.
parseExpression($product, $expression, $values)
Calculates price based on expression.
parseProduct($product, $extra_values=array())
Calculates product price based on product id and associated expression.
parseProductSupplier($product_supplier, $extra_values=array())
Calculates supplier product price based on product supplier price and associated expression.
__construct($db)
Constructor.
testExpression($product_id, $expression, $extra_values=array())
Tests string expression for validity.
translatedError()
Returns translated error.
Class to manage predefined suppliers products.
Class to manage products or services.
dol_now($mode='auto')
Return date for now.