All Data Structures Functions Variables Pages
CalculatedNumberUtil.php
1 <?php
2  /*********************************************************************************
3  * Zurmo is a customer relationship management program developed by
4  * Zurmo, Inc. Copyright (C) 2017 Zurmo Inc.
5  *
6  * Zurmo is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Affero General Public License version 3 as published by the
8  * Free Software Foundation with the addition of the following permission added
9  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
10  * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
11  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12  *
13  * Zurmo is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Affero General Public License along with
19  * this program; if not, see http://www.gnu.org/licenses or write to the Free
20  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21  * 02110-1301 USA.
22  *
23  * You can contact Zurmo, Inc. with a mailing address at 27 North Wacker Drive
24  * Suite 370 Chicago, IL 60606. or at email address contact@zurmo.com.
25  *
26  * The interactive user interfaces in original and modified versions
27  * of this program must display Appropriate Legal Notices, as required under
28  * Section 5 of the GNU Affero General Public License version 3.
29  *
30  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
31  * these Appropriate Legal Notices must retain the display of the Zurmo
32  * logo and Zurmo copyright notice. If the display of the logo is not reasonably
33  * feasible for technical reasons, the Appropriate Legal Notices must display the words
34  * "Copyright Zurmo Inc. 2017. All rights reserved".
35  ********************************************************************************/
36 
41  {
42  const FORMAT_TYPE_INTEGER = 1;
43 
44  const FORMAT_TYPE_DECIMAL = 2;
45 
46  const FORMAT_TYPE_CURRENCY_VALUE = 3;
47 
55  public static function calculateByFormulaAndModelAndResolveFormat($formula, RedBeanModel $model)
56  {
57  $formatType = self::FORMAT_TYPE_INTEGER;
58  $currencyCode = null;
59  $value = static::calculateByFormulaAndModel($formula, $model, $formatType, $currencyCode);
60  if ($formatType == self::FORMAT_TYPE_INTEGER)
61  {
62  return Yii::app()->numberFormatter->formatDecimal((int)$value);
63  }
64  elseif ($formatType == self::FORMAT_TYPE_DECIMAL)
65  {
66  return Yii::app()->format->formatDecimal($value);
67  }
68  elseif ($formatType == self::FORMAT_TYPE_CURRENCY_VALUE && $currencyCode != null)
69  {
70  return Yii::app()->numberFormatter->formatCurrency((float)$value, $currencyCode);
71  }
72  elseif ($formatType == self::FORMAT_TYPE_CURRENCY_VALUE)
73  {
74  return Yii::app()->numberFormatter->formatDecimal((float)$value);
75  }
76  else
77  {
78  return strval($value);
79  }
80  }
81 
91  public static function calculateByFormulaAndModel($formula, RedBeanModel $model, & $formatType, & $currencyCode)
92  {
93  assert('is_string($formula)');
94  assert('is_int($formatType)');
95  assert('is_string($currencyCode) || $currencyCode === null');
96  $ifStatementParts = static::getIfStatementParts($formula);
97  if ($ifStatementParts)
98  {
99  $conditionValue = false;
100  $conditionParts = static::getConditionParts($ifStatementParts['condition']);
101  if ($conditionParts)
102  {
103  $leftFormat = null;
104  $leftCurrencyCode = null;
105  $left = static::calculateByExpressionAndModel($conditionParts['left'],
106  $model,
107  $leftFormat,
108  $leftCurrencyCode);
109  $rightFormat = null;
110  $rightCurrencyCode = null;
111  $right = static::calculateByExpressionAndModel($conditionParts['right'],
112  $model,
113  $rightFormat,
114  $rightCurrencyCode);
115  $operator = $conditionParts['operator'];
116  if (is_string($left))
117  {
118  $left = "'" . $left . "'";
119  }
120  if (is_string($right))
121  {
122  $right = "'" . $right . "'";
123  }
124  @eval("\$conditionValue = " . $left . $operator . $right . ";");
125  }
126  if ((bool) $conditionValue)
127  {
128  return static::calculateByExpressionAndModel($ifStatementParts['true'],
129  $model,
130  $formatType,
131  $currencyCode);
132  }
133  return static::calculateByExpressionAndModel($ifStatementParts['false'],
134  $model,
135  $formatType,
136  $currencyCode);
137  }
138  return static::calculateByExpressionAndModel($formula, $model, $formatType, $currencyCode);
139  }
140 
141  protected static function calculateByExpressionAndModel($expression, RedBeanModel $model, & $formatType, & $currencyCode)
142  {
143  $expression = trim($expression);
144  if (static::isAttribute($expression, get_class($model)))
145  {
146  if (!($model->{$expression} instanceof CurrencyValue))
147  {
148  $formatType = ModelAttributeToMixedTypeUtil::getType($model, $expression);
149  return strval($model->{$expression});
150  }
151  }
152  if (static::isString($expression))
153  {
154  $formatType = 'Text';
155  return trim($expression, '\"\'');
156  }
157  $adapter = new ModelNumberOrCurrencyAttributesAdapter($model);
158  foreach ($adapter->getAttributes() as $attribute => $data)
159  {
160  if (($model->{$attribute} instanceof CurrencyValue && $model->{$attribute}->value == null) ||
161  $model->{$attribute} == null)
162  {
163  $replacementValue = 0;
164  }
165  else
166  {
167  if ($model->{$attribute} instanceof CurrencyValue)
168  {
169  $replacementValue = $model->{$attribute}->value;
170  }
171  else
172  {
173  $replacementValue = $model->{$attribute};
174  }
175  }
176  $oldExpression = $expression;
177  $pattern = '/\b' . $attribute . '\b/';
178  $expression = preg_replace($pattern, $replacementValue, $expression);
179  if ($expression !== $oldExpression)
180  {
181  self::resolveFormatTypeAndCurrencyCode($formatType, $currencyCode, $model, $attribute);
182  }
183  }
184  $extraZerosForDecimalPart = '';
185  if (strpos($expression, '.') !== false && is_numeric($expression))
186  {
187  if ($formatType == self::FORMAT_TYPE_INTEGER)
188  {
189  $formatType = self::FORMAT_TYPE_DECIMAL;
190  }
191  $extraZerosForDecimalPart = str_replace((float)$expression, '', $expression);
192  }
193  $result = static::mathEval($expression);
194  if ($result === false)
195  {
196  return Zurmo::t('Core', 'Invalid');
197  }
198  return $result . $extraZerosForDecimalPart;
199  }
200 
208  public static function isFormulaValid($formula, $modelClassName)
209  {
210  assert('is_string($formula)');
211  $ifStatementParts = static::getIfStatementParts($formula);
212  if ($ifStatementParts)
213  {
214  return static::isIfStatementValid(
215  $ifStatementParts['condition'],
216  $ifStatementParts['true'],
217  $ifStatementParts['false'],
218  $modelClassName);
219  }
220  return static::isExpressionValid($formula, $modelClassName);
221  }
222 
223  protected static function getIfStatementParts($formula)
224  {
225  $matches = array();
226  preg_match('/IF\((.*);(.*);(.*)\)/', $formula, $matches);
227  if (!empty($matches))
228  {
229  return array(
230  'condition' => $matches[1],
231  'true' => $matches[2],
232  'false' => $matches[3]
233  );
234  }
235  return false;
236  }
237 
238  protected static function getConditionParts($condition)
239  {
240  $matches = array();
241  preg_match('/(.*)(<=|>=|!=|==|>|<)(.*)/', $condition, $matches); // Not Coding Standard
242  if (!empty($matches))
243  {
244  return array('left' => $matches[1],
245  'right' => $matches[3],
246  'operator' => $matches[2]);
247  }
248  return false;
249  }
250 
251  protected static function isExpressionValid($expression, $modelClassName)
252  {
253  assert('is_string($expression)');
254  if (strpos($expression, '"') !== false)
255  {
256  return false;
257  }
258  $expression = trim($expression);
259  if (static::isString($expression))
260  {
261  return true;
262  }
263  if (static::isAttribute($expression, $modelClassName))
264  {
265  return true;
266  }
267  $model = new $modelClassName(false);
268  $adapter = new ModelNumberOrCurrencyAttributesAdapter($model);
269  $patterns = array();
270  foreach ($adapter->getAttributes() as $attribute => $data)
271  {
272  $patterns[] = '/\b' . $attribute . '\b/';
273  }
274  $expression = preg_replace($patterns, '1', $expression);
275  if ($expression != strtoupper($expression) || $expression != strtolower($expression))
276  {
277  return false;
278  }
279  if (static::mathEval($expression) === false)
280  {
281  return false;
282  }
283  return true;
284  }
285 
286  protected static function isString($expression)
287  {
288  $length = strlen($expression);
289  $trimedExpression = trim($expression, '\"\'');
290  if (strlen($trimedExpression) == $length - 2)
291  {
292  return true;
293  }
294  return false;
295  }
296 
297  protected static function isAttribute($expression, $modelClassName)
298  {
299  $model = new $modelClassName(false);
300  $adapter = new ModelAttributesAdapter($model);
301  $attributeNames = array_keys($adapter->getAttributes());
302  if (in_array($expression, $attributeNames))
303  {
304  return true;
305  }
306  return false;
307  }
308 
309  protected static function isConditionValid($condition, $modelClassName)
310  {
311  $conditionParts = static::getConditionParts($condition);
312  if ($conditionParts)
313  {
314  $left = trim($conditionParts['left']);
315  $right = trim($conditionParts['right']);
316  if (static::isExpressionValid($left, $modelClassName) &&
317  static::isExpressionValid($right, $modelClassName))
318  {
319  return true;
320  }
321  }
322  return false;
323  }
324 
325  protected static function isIfStatementValid($condition, $trueExpression, $falseExpression, $modelClassName)
326  {
327  $isConditionValid = static::isConditionValid($condition, $modelClassName);
328  $isTrueExpressionValid = static::isExpressionValid($trueExpression, $modelClassName);
329  $isFlaseExpressionValid = static::isExpressionValid($falseExpression, $modelClassName);
330  if ($isConditionValid && $isFlaseExpressionValid && $isTrueExpressionValid)
331  {
332  return true;
333  }
334  return false;
335  }
336 
337  protected static function mathEval($equation)
338  {
339  $equation = preg_replace("/([+-])([0-9]{1})(%)/","*(1\$1.0\$2)",$equation); // Not Coding Standard
340  $equation = preg_replace("/([+-])([0-9]+)(%)/","*(1\$1.\$2)",$equation); // Not Coding Standard
341  $equation = preg_replace("/([0-9]+)(%)/",".\$1",$equation); // Not Coding Standard
342  if ($equation == "")
343  {
344  $return = 0;
345  }
346  else
347  {
348  try
349  {
350  $success = @eval("\$return=" . $equation . ";" ); // Not Coding Standard
351  }
352  catch (ParseError $e)
353  {
354  return false;
355  }
356  // php5 backward compatible
357  if ($success === false)
358  {
359  return false;
360  }
361  }
362  return $return;
363  }
364 
365  protected static function resolveFormatTypeAndCurrencyCode(& $formatType, & $currencyCode, $model, $attribute)
366  {
367  assert('is_int($formatType)');
368  assert('is_string($currencyCode) || $currencyCode === null');
369  $attributeType = ModelAttributeToMixedTypeUtil::getType($model, $attribute);
370  if ($attributeType == 'Decimal' && $formatType == self::FORMAT_TYPE_INTEGER)
371  {
372  $formatType = self::FORMAT_TYPE_DECIMAL;
373  }
374  if ($attributeType == 'CurrencyValue' &&
375  ($formatType == self::FORMAT_TYPE_INTEGER || $formatType == self::FORMAT_TYPE_DECIMAL))
376  {
377  $formatType = self::FORMAT_TYPE_CURRENCY_VALUE;
378  }
379  if ($attributeType == 'CurrencyValue' && $currencyCode === null)
380  {
381  $currencyCode = $model->{$attribute}->currency->code;
382  }
383  elseif ($attributeType == 'CurrencyValue' && $currencyCode != null &&
384  $model->{$attribute}->currency->code != $currencyCode)
385  {
386  //An empty value, not null, indicates there is mixed currencies
387  $currencyCode = '';
388  }
389  }
390  }
391 ?>
static calculateByFormulaAndModelAndResolveFormat($formula, RedBeanModel $model)
static calculateByFormulaAndModel($formula, RedBeanModel $model, &$formatType, &$currencyCode)
static getType($model, $attributeName)
static isFormulaValid($formula, $modelClassName)
Generated on Thu May 28 2020 07:10:36