dolibarr  x.y.z
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;
128  global $hookmanager;
129  $action = 'PARSEEXPRESSION';
130  if ($result = $hookmanager->executeHooks('doDynamiPrice', array(
131  'expression' =>$expression,
132  'product' => $product,
133  'values' => $values
134  ), $this, $action)) {
135  return $result;
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 extrafield for product and add it to values
158  $extrafields = new ExtraFields($this->db);
159  $product->fetch_optionals();
160  if (is_array($extrafields->attributes[$product->table_element]['label'])) {
161  foreach ($extrafields->attributes[$product->table_element]['label'] as $key => $label) {
162  $values["extrafield_".$key] = $product->array_options['options_'.$key];
163  }
164  }
165 
166  //Process any pending updaters
167  $price_updaters = new PriceGlobalVariableUpdater($this->db);
168  foreach ($price_updaters->listPendingUpdaters() as $entry) {
169  //Schedule the next update by adding current timestamp (secs) + interval (mins)
170  $entry->update_next_update(dol_now() + ($entry->update_interval * 60), $user);
171  //Do processing
172  $res = $entry->process();
173  //Store any error or clear status if OK
174  $entry->update_status($res < 1 ? $entry->error : '', $user);
175  }
176 
177  //Get all global values
178  $price_globals = new PriceGlobalVariable($this->db);
179  foreach ($price_globals->listGlobalVariables() as $entry) {
180  $values["global_".$entry->code] = $entry->value;
181  }
182 
183  //Remove internal variables
184  unset($values["supplier_id"]);
185 
186  //Prepare the lib, parameters and values
187  $em = new EvalMath();
188  $em->suppress_errors = true; //Don't print errors on page
189  $this->error_expr = null;
190  $last_result = null;
191 
192  //Fill each variable in expression from values
193  $expression = str_replace("\n", $this->separator_chr, $expression);
194  foreach ($values as $key => $value) {
195  if ($value === null && strpos($expression, $key) !== false) {
196  $this->error_parser = array(24, $key);
197  return -7;
198  }
199  $expression = str_replace($this->special_chr.$key.$this->special_chr, strval($value), $expression);
200  }
201 
202  //Check if there is unfilled variable
203  if (strpos($expression, $this->special_chr) !== false) {
204  $data = explode($this->special_chr, $expression);
205  $variable = $this->special_chr.$data[1];
206  if (isset($data[2])) {
207  $variable .= $this->special_chr;
208  }
209  $this->error_parser = array(23, array($variable, $expression));
210  return -6;
211  }
212 
213  //Iterate over each expression splitted by $separator_chr
214  $expressions = explode($this->separator_chr, $expression);
215  $expressions = array_slice($expressions, 0, $this->limit);
216  foreach ($expressions as $expr) {
217  $expr = trim($expr);
218  if (!empty($expr)) {
219  $last_result = $em->evaluate($expr);
220  $this->error_parser = $em->last_error_code;
221  if ($this->error_parser !== null) { //$em->last_error_code is null if no error happened, so just check if error_parser is not null
222  $this->error_expr = $expr;
223  return -3;
224  }
225  }
226  }
227  $vars = $em->vars();
228  if (empty($vars["price"])) {
229  $vars["price"] = $last_result;
230  }
231  if (!isset($vars["price"])) {
232  $this->error_parser = array(21, $expression);
233  return -4;
234  }
235  if ($vars["price"] < 0) {
236  $this->error_parser = array(22, $expression);
237  return -5;
238  }
239  return $vars["price"];
240  }
241 
249  public function parseProduct($product, $extra_values = array())
250  {
251  //Get the expression from db
252  $price_expression = new PriceExpression($this->db);
253  $res = $price_expression->fetch($product->fk_price_expression);
254  if ($res < 1) {
255  $this->error_parser = array(19, null);
256  return -1;
257  }
258 
259  //Get the supplier min price
260  $productFournisseur = new ProductFournisseur($this->db);
261  $res = $productFournisseur->find_min_price_product_fournisseur($product->id, 0, 0);
262  if ($res < 0) {
263  $this->error_parser = array(25, null);
264  return -1;
265  } elseif ($res == 0) {
266  $supplier_min_price = 0;
267  $supplier_min_price_with_discount = 0;
268  } else {
269  $supplier_min_price = $productFournisseur->fourn_unitprice;
270  $supplier_min_price_with_discount = $productFournisseur->fourn_unitprice_with_discount;
271  }
272 
273  //Accessible values by expressions
274  $extra_values = array_merge($extra_values, array(
275  "supplier_min_price" => $supplier_min_price,
276  "supplier_min_price_with_discount" => $supplier_min_price_with_discount,
277  ));
278 
279  //Parse the expression and return the price, if not error occurred check if price is higher than min
280  $result = $this->parseExpression($product, $price_expression->expression, $extra_values);
281  if (empty($this->error_parser)) {
282  if ($result < $product->price_min) {
283  $result = $product->price_min;
284  }
285  }
286  return $result;
287  }
288 
296  public function parseProductSupplier($product_supplier, $extra_values = array())
297  {
298  //Get the expression from db
299  $price_expression = new PriceExpression($this->db);
300  $res = $price_expression->fetch($product_supplier->fk_supplier_price_expression);
301  if ($res < 1) {
302  $this->error_parser = array(19, null);
303  return -1;
304  }
305 
306  //Get the product data (use ignore_expression to avoid possible recursion)
307  $product_supplier->fetch($product_supplier->id, '', '', '', 1);
308 
309  //Accessible values by expressions
310  $extra_values = array_merge($extra_values, array(
311  "supplier_quantity" => $product_supplier->fourn_qty,
312  "supplier_tva_tx" => $product_supplier->fourn_tva_tx,
313  ));
314 
315  //Parse the expression and return the price
316  return $this->parseExpression($product_supplier, $price_expression->expression, $extra_values);
317  }
318 
327  public function testExpression($product_id, $expression, $extra_values = array())
328  {
329  //Get the product data
330  $product = new Product($this->db);
331  $product->fetch($product_id, '', '', 1);
332 
333  //Values for product expressions
334  $extra_values = array_merge($extra_values, array(
335  "supplier_min_price" => 1,
336  "supplier_min_price_with_discount" => 2,
337  ));
338 
339  //Values for supplier product expressions
340  $extra_values = array_merge($extra_values, array(
341  "supplier_quantity" => 3,
342  "supplier_tva_tx" => 4,
343  ));
344  return $this->parseExpression($product, $expression, $extra_values);
345  }
346 }
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.
$conf db
API class for accounts.
Definition: inc.php:41