PluginProbe
TablePress – Tables in WordPress made easy / 1.12
TablePress – Tables in WordPress made easy v1.12
3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 All 44 releases
tablepress / libraries / evalmath.class.php

evalmath.class.php in TablePress – Tables in WordPress made easy 1.12, at libraries/evalmath.class.php

1,149 lines 33.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * EvalMath - Safely evaluate math expressions
4 *
5 * Based on EvalMath by Miles Kaufmann, with modifications by Petr Skoda.
6 * @link https://github.com/moodle/moodle/blob/4efc3d4096bc1d29e9d77f9af7194b2babfa2821/lib/evalmath/evalmath.class.php
7 *
8 * @package TablePress
9 * @subpackage Formulas
10 * @author Miles Kaufmann, Petr Skoda, Tobias Bäthge
11 * @since 1.0.0
12 */
13
14 // Prohibit direct script loading.
15 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
16
17 /**
18 * Class to safely evaluate math expressions.
19 * @package TablePress
20 * @subpackage Formulas
21 * @since 1.0.0
22 */
23 class EvalMath {
24
25 /**
26 * Pattern used for a valid function or variable name.
27 *
28 * Note, var and func names are case insensitive.
29 *
30 * @since 1.0.0
31 * @var string
32 */
33 protected static $name_pattern = '[a-z][a-z0-9_]*';
34
35 /**
36 * Whether to suppress errors and warnings.
37 *
38 * @since 1.0.0
39 * @var boolean
40 */
41 public $suppress_errors = false;
42
43 /**
44 * The last error message that was raised.
45 *
46 * @since 1.0.0
47 * @var string
48 */
49 public $last_error = '';
50
51 /**
52 * Variables (including constants).
53 *
54 * @since 1.0.0
55 * @var array
56 */
57 public $variables = array();
58
59 /**
60 * User-defined functions.
61 *
62 * @since 1.0.0
63 * @var array
64 */
65 protected $functions = array();
66
67 /**
68 * Constants.
69 *
70 * @since 1.0.0
71 * @var array
72 */
73 protected $constants = array();
74
75 /**
76 * Built-in functions.
77 *
78 * @since 1.0.0
79 * @var array
80 */
81 protected $builtin_functions = array(
82 'sin', 'sinh', 'arcsin', 'asin', 'arcsinh', 'asinh',
83 'cos', 'cosh', 'arccos', 'acos', 'arccosh', 'acosh',
84 'tan', 'tanh', 'arctan', 'atan', 'arctanh', 'atanh',
85 'sqrt', 'abs', 'ln', 'log10', 'exp', 'floor', 'ceil',
86 );
87
88 /**
89 * Emulated functions.
90 *
91 * @since 1.0.0
92 * @var array
93 */
94 protected $calc_functions = array(
95 'average' => array( -1 ),
96 'mean' => array( -1 ),
97 'median' => array( -1 ),
98 'mode' => array( -1 ),
99 'range' => array( -1 ),
100 'max' => array( -1 ),
101 'min' => array( -1 ),
102 'mod' => array( 2 ),
103 'pi' => array( 0 ),
104 'power' => array( 2 ),
105 'log' => array( 1, 2 ),
106 'round' => array( 1, 2 ),
107 'number_format' => array( 1, 2 ),
108 'number_format_eu' => array( 1, 2 ),
109 'sum' => array( -1 ),
110 'counta' => array ( -1 ),
111 'product' => array( -1 ),
112 'rand_int' => array( 2 ),
113 'rand_float' => array( 0 ),
114 'arctan2' => array( 2 ),
115 'atan2' => array( 2 ),
116 'if' => array( 3 ),
117 'not' => array( 1 ),
118 'and' => array( -1 ),
119 'or' => array( -1 ),
120 );
121
122 /**
123 * Class constructor.
124 *
125 * @since 1.0.0
126 */
127 public function __construct() {
128 // Sets default constants.
129 $this->variables['pi'] = pi();
130 $this->variables['e'] = exp( 1 );
131 }
132
133 /**
134 * Evaluate a math expression without checking it for variable or function assignments.
135 *
136 * @since 1.0.0
137 *
138 * @param string $expression The expression that shall be evaluated.
139 * @return string Evaluated expression.
140 */
141 public function evaluate( $expression ) {
142 return $this->pfx( $this->nfx( $expression ) );
143 }
144
145 /**
146 * Evaluate a math expression or formula, and check it for variable an function assignments.
147 *
148 * @since 1.0.0
149 *
150 * @param string $expression The expression that shall be evaluated.
151 * @return string|bool Evaluated expression, true on successful function assignment, or false on error.
152 */
153 public function assign_and_evaluate( $expression ) {
154 $this->last_error = '';
155 $expression = trim( $expression );
156 $expression = rtrim( $expression, ';' );
157
158 // Is the expression a variable assignment?
159 if ( 1 === preg_match( '/^\s*(' . self::$name_pattern . ')\s*=\s*(.+)$/', $expression, $matches ) ) {
160 // Make sure we're not assigning to a constant.
161 if ( in_array( $matches[1], $this->constants, true ) ) {
162 return $this->raise_error( 'cannot_assign_to_constant', $matches[1] );
163 }
164 // Evaluate the assignment.
165 $tmp = $this->pfx( $this->nfx( $matches[2] ) );
166 if ( fales === $tmp ) {
167 return false;
168 }
169 // If it could be evaluated, add it to the variable array,
170 $this->variables[ $matches[1] ] = $tmp;
171 // and return the resulting value.
172 return $tmp;
173
174 // Is the expression a function assignment?
175 } elseif ( 1 === preg_match( '/^\s*(' . self::$name_pattern . ')\s*\(\s*(' . self::$name_pattern . '(?:\s*,\s*' . self::$name_pattern . ')*)\s*\)\s*=\s*(.+)$/', $expression, $matches ) ) {
176 // Get the function name.
177 $function_name = $matches[1];
178 // Make sure it isn't a built-in function -- we can't redefine those.
179 if ( in_array( $matches[1], $this->builtin_functions, true ) ) {
180 return $this->raise_error( 'cannot_redefine_builtin_function', $matches[1] );
181 }
182 // Get the function arguments after removing all whitespace.
183 $matches[2] = str_replace( array( "\n", "\r", "\t", ' ' ), '', $matches[2] );
184 $args = explode( ',', $matches[2] );
185
186 // Convert the function definition to postfix notation.
187 $stack = $this->nfx( $matches[3] );
188 if ( false === $stack ) {
189 return false;
190 }
191 // Freeze the state of the non-argument variables.
192 for ( $i = 0; $i < count( $stack ); $i++ ) {
193 $token = $stack[ $i ];
194 if ( 1 === preg_match( '/^' . self::$name_pattern . '$/', $token ) && ! in_array( $token, $args, true ) ) {
195 if ( array_key_exists( $token, $this->variables ) ) {
196 $stack[ $i ] = $this->variables[ $token ];
197 } else {
198 return $this->raise_error( 'undefined_variable_in_function_definition', $token );
199 }
200 }
201 }
202 $this->functions[ $function_name ] = array( 'args' => $args, 'func' => $stack );
203 return true;
204
205 // No variable or function assignment, so straight-up evaluation.
206 } else {
207 return $this->evaluate( $expression );
208 }
209 }
210
211 /**
212 * Return all user-defined variables and values.
213 *
214 * @since 1.0.0
215 *
216 * @return array User-defined variables and values.
217 */
218 public function variables() {
219 return $this->variables;
220 }
221
222 /**
223 * Return all user-defined functions with their arguments.
224 *
225 * @since 1.0.0
226 *
227 * @return array User-defined functions.
228 */
229 public function functions() {
230 $output = array();
231 foreach ( $this->functions as $name => $data ) {
232 $output[] = $name . '( ' . implode( ', ', $data['args'] ) . ' )';
233 }
234 return $output;
235 }
236
237 /*
238 * Internal methods.
239 */
240
241 /**
242 * Convert infix to postfix notation.
243 *
244 * @since 1.0.0
245 *
246 * @param string $expression Math expression that shall be converted.
247 * @return array|false Converted expression or false on error.
248 */
249 protected function nfx( $expression ) {
250 $index = 0;
251 $stack = new EvalMath_Stack;
252 $output = array(); // postfix form of expression, to be passed to pfx()
253 $expression = trim( strtolower( $expression ) );
254
255 $ops = array( '+', '-', '*', '/', '^', '_', '>', '<', '=' );
256 // Right-associative operator?
257 $ops_r = array( '+' => 0, '-' => 0, '*' => 0, '/' => 0, '^' => 1, '>' => 0, '<' => 0, '=' => 0 );
258 // Operator precedence.
259 $ops_p = array( '+' => 0, '-' => 0, '*' => 1, '/' => 1, '_' => 1, '^' => 2, '>' => 0, '<' => 0, '=' => 0 );
260
261 // We use this in syntax-checking the expression and determining when a - (minus) is a negation.
262 $expecting_operator = false;
263
264 // Make sure the characters are all good.
265 if ( 1 === preg_match( '/[^\w\s+*^\/()\.,-<>=]/', $expression, $matches ) ) {
266 return $this->raise_error( 'illegal_character_general', $matches[0] );
267 }
268
269 // Infinite Loop for the conversion.
270 while ( true ) {
271 // Get the first character at the current index.
272 $op = substr( $expression, $index, 1 );
273 // Find out if we're currently at the beginning of a number/variable/function/parenthesis/operand.
274 $ex = preg_match( '/^(' . self::$name_pattern . '\(?|\d+(?:\.\d*)?(?:(e[+-]?)\d*)?|\.\d+|\()/', substr( $expression, $index ), $match );
275
276 // Is it a negation instead of a minus (in a subtraction)?
277 if ( '-' === $op && ! $expecting_operator ) {
278 // Put a negation on the stack.
279 $stack->push( '_' );
280 $index++;
281 } elseif ( '_' === $op ) {
282 // We have to explicitly deny underscores (as they mean negation), because they are legal on the stack.
283 return $this->raise_error( 'illegal_character_underscore' );
284
285 // Are we putting an operator on the stack?
286 } elseif ( ( in_array( $op, $ops, true ) || $ex ) && $expecting_operator ) {
287 // Are we expecting an operator but have a number/variable/function/opening parethesis?
288 if ( $ex ) {
289 // It's an implicit multiplication.
290 $op = '*';
291 $index--;
292 }
293 // Heart of the algorithm:
294 while ( $stack->count > 0 && ( $o2 = $stack->last() ) && in_array( $o2, $ops, true ) && ( $ops_r[ $op ] ? $ops_p[ $op ] < $ops_p[ $o2 ] : $ops_p[ $op ] <= $ops_p[ $o2 ] ) ) {
295 // Pop stuff off the stack into the output.
296 $output[] = $stack->pop();
297 }
298 // Many thanks: https://en.wikipedia.org/wiki/Reverse_Polish_notation
299 $stack->push( $op ); // finally put OUR operator onto the stack
300 $index++;
301 $expecting_operator = false;
302
303 // Ready to close a parenthesis?
304 } elseif ( ')' === $op && $expecting_operator ) {
305 // Pop off the stack back to the last (.
306 while ( '(' !== ( $o2 = $stack->pop() ) ) {
307 if ( is_null( $o2 ) ) {
308 return $this->raise_error( 'unexpected_closing_bracket' );
309 } else {
310 $output[] = $o2;
311 }
312 }
313
314 // Did we just close a function?
315 if ( 1 === preg_match( '/^(' . self::$name_pattern . ')\($/', $stack->last( 2 ), $matches ) ) {
316 // Get the function name.
317 $function_name = $matches[1];
318 // See how many arguments there were (cleverly stored on the stack, thank you).
319 $arg_count = $stack->pop();
320 $fn = $stack->pop();
321 // Send function to output.
322 $output[] = array( 'function_name' => $function_name, 'arg_count' => $arg_count );
323 // Check the argument count, depending on what type of function we have.
324 if ( in_array( $function_name, $this->builtin_functions, true ) ) {
325 // Built-in functions.
326 if ( $arg_count > 1 ) {
327 $error_data = array( 'expected' => 1, 'given' => $arg_count );
328 return $this->raise_error( 'wrong_number_of_arguments', $error_data );
329 }
330 } elseif ( array_key_exists( $function_name, $this->calc_functions ) ) {
331 // Calc-emulation functions.
332 $counts = $this->calc_functions[ $function_name ];
333 if ( in_array( -1, $counts, true ) && $arg_count > 0 ) {
334 // Everything is fine, we expected an indefinite number arguments and got some.
335 } elseif ( ! in_array( $arg_count, $counts, true ) ) {
336 $error_data = array( 'expected' => implode( '/', $this->calc_functions[ $function_name ] ), 'given' => $arg_count );
337 return $this->raise_error( 'wrong_number_of_arguments', $error_data );
338 }
339 } elseif ( array_key_exists( $function_name, $this->functions ) ) {
340 // User-defined functions.
341 if ( count( $this->functions[ $function_name ]['args'] ) !== $arg_count ) {
342 $error_data = array( 'expected' => count( $this->functions[ $function_name ]['args'] ), 'given' => $arg_count );
343 return $this->raise_error( 'wrong_number_of_arguments', $error_data );
344 }
345 } else {
346 // Did we somehow push a non-function on the stack? This should never happen.
347 return $this->raise_error( 'internal_error' );
348 }
349 }
350 $index++;
351
352 // Did we just finish a function argument?
353 } elseif ( ',' === $op && $expecting_operator ) {
354 while ( '(' !== ( $o2 = $stack->pop() ) ) {
355 if ( is_null( $o2 ) ) {
356 // Oops, never had a (.
357 return $this->raise_error( 'unexpected_comma' );
358 } else {
359 // Pop the argument expression stuff and push onto the output.
360 $output[] = $o2;
361 }
362 }
363 // Make sure there was a function.
364 if ( 0 === preg_match( '/^(' . self::$name_pattern . ')\($/', $stack->last( 2 ), $matches ) ) {
365 return $this->raise_error( 'unexpected_comma' );
366 }
367 // Increment the argument count.
368 $stack->push( $stack->pop() + 1 );
369 // Put the ( back on, we'll need to pop back to it again.
370 $stack->push( '(' );
371 $index++;
372 $expecting_operator = false;
373
374 } elseif ( '(' === $op && ! $expecting_operator ) {
375 $stack->push( '(' ); // That was easy.
376 $index++;
377
378 // Do we now have a function/variable/number?
379 } elseif ( $ex && ! $expecting_operator ) {
380 $expecting_operator = true;
381 $value = $match[1];
382 // May be a function, or variable with implicit multiplication against parentheses...
383 if ( 1 === preg_match( '/^(' . self::$name_pattern . ')\($/', $value, $matches ) ) {
384 // Is it a function?
385 if ( in_array( $matches[1], $this->builtin_functions, true ) || array_key_exists( $matches[1], $this->functions ) || array_key_exists( $matches[1], $this->calc_functions ) ) {
386 $stack->push( $value );
387 $stack->push( 1 );
388 $stack->push( '(' );
389 $expecting_operator = false;
390 // It's a variable with implicit multiplication.
391 } else {
392 $value = $matches[1];
393 $output[] = $value;
394 }
395 } else {
396 // It's a plain old variable or number.
397 $output[] = $value;
398 }
399 $index += strlen( $value );
400
401 } elseif ( ')' === $op ) {
402 // It could be only custom function with no arguments or a general error.
403 if ( '(' !== $stack->last() || 1 !== $stack->last( 2 ) ) {
404 return $this->raise_error( 'unexpected_closing_bracket' );
405 }
406 // Did we just close a function?
407 if ( 1 === preg_match( '/^(' . self::$name_pattern . ')\($/', $stack->last( 3 ), $matches ) ) {
408 $stack->pop(); // (
409 $stack->pop(); // 1
410 $fn = $stack->pop();
411 // Get the function name.
412 $function_name = $matches[1];
413 if ( isset( $this->calc_functions[ $function_name ] ) ) {
414 // Custom calc-emulation function.
415 $counts = $this->calc_functions[ $function_name ];
416 } else {
417 // Default count for built-in functions.
418 $counts = array( 1 );
419 }
420 if ( ! in_array( 0, $counts, true ) ) {
421 $error_data = array( 'expected' => $counts, 'given' => 0 );
422 return $this->raise_error( 'wrong_number_of_arguments', $error_data );
423 }
424 // Send function to output.
425 $output[] = array( 'function_name' => $function_name, 'arg_count' => 0 );
426 $index++;
427 $expecting_operator = true;
428 } else {
429 return $this->raise_error( 'unexpected_closing_bracket' );
430 }
431
432 // Miscellaneous error checking.
433 } elseif ( in_array( $op, $ops, true ) && ! $expecting_operator ) {
434 return $this->raise_error( 'unexpected_operator', $op );
435
436 // I don't even want to know what you did to get here.
437 } else {
438 return $this->raise_error( 'an_unexpected_error_occurred' );
439 }
440
441 if ( strlen( $expression ) === $index ) {
442 // Did we end with an operator? Bad.
443 if ( in_array( $op, $ops, true ) ) {
444 return $this->raise_error( 'operator_lacks_operand', $op );
445 } else {
446 break;
447 }
448 }
449
450 // Step the index past whitespace (pretty much turns whitespace into implicit multiplication if no operator is there).
451 while ( ' ' === substr( $expression, $index, 1 ) ) {
452 $index++;
453 }
454 } // while ( true )
455
456 // Pop everything off the stack and push onto output.
457 while ( ! is_null( $op = $stack->pop() ) ) {
458 if ( '(' === $op ) {
459 // If there are (s on the stack, ()s were unbalanced.
460 return $this->raise_error( 'expecting_a_closing_bracket' );
461 }
462 $output[] = $op;
463 }
464
465 return $output;
466 }
467
468 /**
469 * Evaluate postfix notation.
470 *
471 * @since 1.0.0
472 *
473 * @param array|false $tokens [description]
474 * @param array $variables Optional. [description]
475 * @return mixed [description]
476 */
477 protected function pfx( $tokens, array $variables = array() ) {
478 if ( false === $tokens ) {
479 return false;
480 }
481
482 $stack = new EvalMath_Stack;
483
484 foreach ( $tokens as $token ) {
485 // If the token is a function, pop arguments off the stack, hand them to the function, and push the result back on.
486 if ( is_array( $token ) ) { // it's a function!
487 $function_name = $token['function_name'];
488 $count = $token['arg_count'];
489
490 // Built-in function.
491 if ( in_array( $function_name, $this->builtin_functions, true ) ) {
492 $op1 = $stack->pop();
493 if ( is_null( $op1 ) ) {
494 return $this->raise_error( 'internal_error' );
495 }
496 // For the "arc" trigonometric synonyms.
497 $function_name = preg_replace( '/^arc/', 'a', $function_name );
498 // Rewrite "ln" (only allows one argument) to "log" (natural logarithm).
499 if ( 'ln' === $function_name ) {
500 $function_name = 'log';
501 }
502 // Perfectly safe eval().
503 eval( '$stack->push( ' . $function_name . '( $op1 ) );' );
504
505 // Calc-emulation function.
506 } elseif ( array_key_exists( $function_name, $this->calc_functions ) ) {
507 // Get function arguments.
508 $args = array();
509 for ( $i = $count - 1; $i >= 0; $i-- ) {
510 $arg = $stack->pop();
511 if ( is_null( $arg ) ) {
512 return $this->raise_error( 'internal_error' );
513 } else {
514 $args[] = $arg;
515 }
516 }
517 // Rewrite some functions to their synonyms.
518 if ( 'if' === $function_name ) {
519 $function_name = 'func_if';
520 } elseif ( 'not' === $function_name ) {
521 $function_name = 'func_not';
522 } elseif ( 'and' === $function_name ) {
523 $function_name = 'func_and';
524 } elseif ( 'or' === $function_name ) {
525 $function_name = 'func_or';
526 } elseif ( 'mean' === $function_name ) {
527 $function_name = 'average';
528 } elseif ( 'arctan2' === $function_name ) {
529 $function_name = 'atan2';
530 }
531 $result = EvalMath_Functions::$function_name( ...array_reverse( $args ) );
532 if ( false === $result ) {
533 return $this->raise_error( 'internal_error' );
534 }
535 $stack->push( $result );
536
537 // User-defined function.
538 } elseif ( array_key_exists( $function_name, $this->functions ) ) {
539 // Get function arguments.
540 $args = array();
541 for ( $i = count( $this->functions[ $function_name ]['args'] ) - 1; $i >= 0; $i-- ) {
542 $arg = $stack->pop();
543 if ( is_null( $arg ) ) {
544 return $this->raise_error( 'internal_error' );
545 } else {
546 $args[ $this->functions[ $function_name ]['args'][ $i ] ] = $arg;
547 }
548 }
549 // yay... recursion!
550 $stack->push( $this->pfx( $this->functions[ $function_name ]['func'], $args ) );
551 }
552
553 // If the token is a binary operator, pop two values off the stack, do the operation, and push the result back on.
554 } elseif ( in_array( $token, array( '+', '-', '*', '/', '^', '>', '<', '=' ), true ) ) {
555 $op2 = $stack->pop();
556 if ( is_null( $op2 ) ) {
557 return $this->raise_error( 'internal_error' );
558 }
559 $op1 = $stack->pop();
560 if ( is_null( $op1 ) ) {
561 return $this->raise_error( 'internal_error' );
562 }
563 switch ( $token ) {
564 case '+':
565 $stack->push( $op1 + $op2 );
566 break;
567 case '-':
568 $stack->push( $op1 - $op2 );
569 break;
570 case '*':
571 $stack->push( $op1 * $op2 );
572 break;
573 case '/':
574 if ( 0 === $op2 || '0' === $op2 ) {
575 return $this->raise_error( 'division_by_zero' );
576 }
577 $stack->push( $op1 / $op2 );
578 break;
579 case '^':
580 $stack->push( pow( $op1, $op2 ) );
581 break;
582 case '>':
583 $stack->push( (int) ( $op1 > $op2 ) );
584 break;
585 case '<':
586 $stack->push( (int) ( $op1 < $op2 ) );
587 break;
588 case '=':
589 $stack->push( (int) ( $op1 == $op2 ) ); // Don't use === as the variable type can differ (int/double/bool).
590 break;
591 }
592
593 // If the token is a unary operator, pop one value off the stack, do the operation, and push it back on.
594 } elseif ( '_' === $token ) {
595 $stack->push( -1 * $stack->pop() );
596
597 // If the token is a number or variable, push it on the stack.
598 } else {
599 if ( is_numeric( $token ) ) {
600 $stack->push( $token );
601 } elseif ( array_key_exists( $token, $this->variables ) ) {
602 $stack->push( $this->variables[ $token ] );
603 } elseif ( array_key_exists( $token, $variables ) ) {
604 $stack->push( $variables[ $token ] );
605 } else {
606 return $this->raise_error( 'undefined_variable', $token );
607 }
608 }
609 }
610 // When we're out of tokens, the stack should have a single element, the final result.
611 if ( 1 !== $stack->count ) {
612 return $this->raise_error( 'internal_error' );
613 }
614 return $stack->pop();
615 }
616
617 /**
618 * Raise an error.
619 *
620 * @since 1.0.0
621 *
622 * @param string $message Error message.
623 * @param array|string $error_data Optional. Additional error data.
624 * @return bool False, to stop evaluation.
625 */
626 protected function raise_error( $message, $error_data = null ) {
627 $this->last_error = $this->get_error_string( $message, $error_data );
628 return false;
629 }
630
631
632 /**
633 * Get a translated string for an error message.
634 *
635 * @since 1.0.0
636 *
637 * @link https://github.com/moodle/moodle/blob/13264f35057d2f37374ec3e0e8ad4070f4676bd7/lang/en/mathslib.php
638 * @link https://github.com/moodle/moodle/blob/8e54ce9717c19f768b95f4332f70e3180ffafc46/lib/moodlelib.php#L6323
639 *
640 * @param string $identifier Identifier of the string.
641 * @param array|string $error_data Optional. Additional error data.
642 * @return string Translated string.
643 */
644 protected function get_error_string( $identifier, $error_data = null ) {
645 $strings = array();
646 $strings['an_unexpected_error_occurred'] = 'an unexpected error occurred';
647 $strings['cannot_assign_to_constant'] = 'cannot assign to constant \'{$error_data}\'';
648 $strings['cannot_redefine_builtin_function'] = 'cannot redefine built-in function \'{$error_data}()\'';
649 $strings['division_by_zero'] = 'division by zero';
650 $strings['expecting_a_closing_bracket'] = 'expecting a closing bracket';
651 $strings['illegal_character_general'] = 'illegal character \'{$error_data}\'';
652 $strings['illegal_character_underscore'] = 'illegal character \'_\'';
653 $strings['internal_error'] = 'internal error';
654 $strings['operator_lacks_operand'] = 'operator \'{$error_data}\' lacks operand';
655 $strings['undefined_variable'] = 'undefined variable \'{$error_data}\'';
656 $strings['undefined_variable_in_function_definition'] = 'undefined variable \'{$error_data}\' in function definition';
657 $strings['unexpected_closing_bracket'] = 'unexpected closing bracket';
658 $strings['unexpected_comma'] = 'unexpected comma';
659 $strings['unexpected_operator'] = 'unexpected operator \'{$error_data}\'';
660 $strings['wrong_number_of_arguments'] = 'wrong number of arguments ({$error_data->given} given, {$error_data->expected} expected)';
661
662 $string = $strings[ $identifier ];
663
664 if ( null !== $error_data ) {
665 if ( is_array( $error_data ) ) {
666 $search = array();
667 $replace = array();
668 foreach ( $error_data as $key => $value ) {
669 if ( is_int( $key ) ) {
670 // We do not support numeric keys!
671 continue;
672 }
673 if ( is_object( $value ) || is_array( $value ) ) {
674 $value = (array) $value;
675 if ( count( $value ) > 1 ) {
676 $value = implode( ' or ', $value );
677 } else {
678 $value = (string) $value[0];
679 if ( '-1' === $value ) {
680 $value = 'at least 1';
681 }
682 }
683 }
684 $search[] = '{$error_data->' . $key . '}';
685 $replace[] = (string) $value;
686 }
687 if ( $search ) {
688 $string = str_replace( $search, $replace, $string );
689 }
690 } else {
691 $string = str_replace( '{$error_data}', (string) $error_data, $string );
692 }
693 }
694
695 return $string;
696 }
697
698 } // class EvalMath
699
700 /**
701 * Stack for the postfix/infix conversion of math expressions.
702 * @package TablePress
703 * @subpackage Formulas
704 * @since 1.0.0
705 */
706 class EvalMath_Stack {
707
708 /**
709 * The stack.
710 *
711 * @since 1.0.0
712 * @var array
713 */
714 protected $stack = array();
715
716 /**
717 * Number of items on the stack.
718 *
719 * @since 1.0.0
720 * @var int
721 */
722 public $count = 0;
723
724 /**
725 * Push an item onto the stack.
726 *
727 * @since 1.0.0
728 *
729 * @param mixed $value The item that is pushed onto the stack.
730 */
731 public function push( $value ) {
732 $this->stack[ $this->count ] = $value;
733 $this->count++;
734 }
735
736 /**
737 * Pop an item from the top of the stack.
738 *
739 * @since 1.0.0
740 *
741 * @return mixed The item that is popped from the stack.
742 */
743 public function pop() {
744 if ( $this->count > 0 ) {
745 $this->count--;
746 return $this->stack[ $this->count ];
747 }
748 return null;
749 }
750
751 /**
752 * Pop an item from the end of the stack.
753 *
754 * @since 1.0.0
755 *
756 * @param int $n Count from the end of the stack.
757 * @return mixed The item that is popped from the stack.
758 */
759 public function last( $n = 1 ) {
760 if ( ( $this->count - $n ) >= 0 ) {
761 return $this->stack[ $this->count - $n ];
762 }
763 return null;
764 }
765
766 } // class EvalMath_Stack
767
768 /**
769 * Common math functions, prepared for usage in EvalMath.
770 * @package TablePress
771 * @subpackage EvalMath
772 * @since 1.0.0
773 */
774 class EvalMath_Functions {
775
776 /**
777 * Seed for the generation of random numbers.
778 *
779 * @since 1.0.0
780 * @var string
781 */
782 protected static $random_seed = null;
783
784 /**
785 * Choose from two values based on an if-condition.
786 *
787 * "if" is not a valid function name, which is why it's prefixed with "func_".
788 *
789 * @since 1.0.0
790 *
791 * @param double|int $condition Condition.
792 * @param double|int $then Return value if the condition is true.
793 * @param double|int $else Return value if the condition is false.
794 * @return double|int Result of the if check.
795 */
796 public static function func_if( $condition, $then, $else ) {
797 return ( (bool) $condition ? $then : $else );
798 }
799
800 /**
801 * Return the negation (boolean "not") of a value.
802 *
803 * Similar to "func_if", the function name is prefixed with "func_", although it wouldn't be necessary.
804 *
805 * @since 1.0.0
806 *
807 * @param double|int $value Value to be negated.
808 * @return int Negated value (0 for false, 1 for true).
809 */
810 public static function func_not( $value ) {
811 return (int) ! (bool) $value;
812 }
813
814 /**
815 * Calculate the conjunction (boolean "and") of some values.
816 *
817 * "and" is not a valid function name, which is why it's prefixed with "func_".
818 *
819 * @since 1.0.0
820 *
821 * @param double|int $args Values for which the conjunction shall be calculated.
822 * @return int Conjunction of the passed arguments.
823 */
824 public static function func_and( ...$args ) {
825 foreach ( $args as $value ) {
826 if ( ! $value ) {
827 return 0;
828 }
829 }
830 return 1;
831 }
832
833 /**
834 * Calculate the disjunction (boolean "or") of some values.
835 *
836 * "or" is not a valid function name, which is why it's prefixed with "func_".
837 *
838 * @since 1.0.0
839 *
840 * @param double|int $args Values for which the disjunction shall be calculated.
841 * @return int Disjunction of the passed arguments.
842 */
843 public static function func_or( ...$args ) {
844 foreach ( $args as $value ) {
845 if ( $value ) {
846 return 1;
847 }
848 }
849 return 0;
850 }
851
852 /**
853 * Return the (rounded) value of Pi.
854 *
855 * @since 1.0.0
856 *
857 * @return double Rounded value of Pi.
858 */
859 public static function pi() {
860 return pi();
861 }
862
863 /**
864 * Calculate the sum of the arguments.
865 *
866 * @since 1.0.0
867 *
868 * @param double|int $args Values for which the sum shall be calculated.
869 * @return double|int Sum of the passed arguments.
870 */
871 public static function sum( ...$args ) {
872 return array_sum( $args );
873 }
874
875 /**
876 * Count the number of non-empty arguments.
877 *
878 * @since 1.10.0
879 *
880 * @param double|int $args Values for which the number of non-empty elements shall be counted.
881 * @return double|int Counted number of non-empty elements in the passed values.
882 */
883 public static function counta( ...$args ) {
884 return count( array_filter( $args ) );
885 }
886
887 /**
888 * Calculate the product of the arguments.
889 *
890 * @since 1.0.0
891 *
892 * @param double|int $args Values for which the product shall be calculated.
893 * @return double|int Product of the passed arguments.
894 */
895 public static function product( ...$args ) {
896 return array_product( $args );
897 }
898
899 /**
900 * Calculate the average/mean value of the arguments.
901 *
902 * @since 1.0.0
903 *
904 * @param double|int $args Values for which the average shall be calculated.
905 * @return double|int Average value of the passed arguments.
906 */
907 public static function average( ...$args ) {
908 // Catch division by zero.
909 if ( 0 === count( $args ) ) {
910 return 0;
911 }
912 return array_sum( $args ) / count( $args );
913 }
914
915 /**
916 * Calculate the median of the arguments.
917 *
918 * For even counts of arguments, the upper median is returned.
919 *
920 * @since 1.0.0
921 *
922 * @param double|int $args Values for which the median shall be calculated.
923 * @return double|int Median of the passed arguments.
924 */
925 public static function median( ...$args ) {
926 sort( $args );
927 $middle = floor( count( $args ) / 2 ); // Upper median for even counts.
928 return $args[ $middle ];
929 }
930
931 /**
932 * Calculate the mode of the arguments.
933 *
934 * @since 1.0.0
935 *
936 * @param double|int $args Values for which the mode shall be calculated.
937 * @return double|int Mode of the passed arguments.
938 */
939 public static function mode( ...$args ) {
940 $values = array_count_values( $args );
941 asort( $values );
942 end( $values );
943 return key( $values );
944 }
945
946 /**
947 * Calculate the range of the arguments.
948 *
949 * @since 1.0.0
950 *
951 * @param double|int $args Values for which the range shall be calculated.
952 * @return double|int Range of the passed arguments.
953 */
954 public static function range( ...$args ) {
955 sort( $args );
956 return end( $args ) - reset( $args );
957 }
958
959 /**
960 * Find the maximum value of the arguments.
961 *
962 * @since 1.0.0
963 *
964 * @param double|int $args Values for which the maximum value shall be found.
965 * @return double|int Maximum value of the passed arguments.
966 */
967 public static function max( ...$args ) {
968 return max( $args );
969 }
970
971 /**
972 * Find the minimum value of the arguments.
973 *
974 * @since 1.0.0
975 *
976 * @param double|int $args Values for which the minimum value shall be found.
977 * @return double|int Minimum value of the passed arguments.
978 */
979 public static function min( ...$args ) {
980 return min( $args );
981 }
982
983 /**
984 * Calculate the remainder of a division of two numbers.
985 *
986 * @since 1.0.0
987 *
988 * @param double|int $op1 First number (dividend).
989 * @param double|int $op2 Second number (divisor).
990 * @return double|int Remainer of the division (dividend / divisor).
991 */
992 public static function mod( $op1, $op2 ) {
993 return $op1 % $op2;
994 }
995
996 /**
997 * Calculate the power of a base and an exponent.
998 *
999 * @since 1.0.0
1000 *
1001 * @param double|int $base Base.
1002 * @param double|int $exponent Exponent.
1003 * @return double|int Power base^exponent.
1004 */
1005 public static function power( $base, $exponent ) {
1006 return pow( $base, $exponent );
1007 }
1008
1009 /**
1010 * Calculate the logarithm of a number to a base.
1011 *
1012 * @since 1.0.0
1013 *
1014 * @param double|int $number Number.
1015 * @param double|int $base Optional. Base for the logarithm. Default e (for the natural logarithm).
1016 * @return double Logarithm of the number to the base.
1017 */
1018 public static function log( $number, $base = M_E ) {
1019 return log( $number, $base );
1020 }
1021
1022 /**
1023 * Calculate the arc tangent of two variables.
1024 *
1025 * The signs of the numbers determine the quadrant of the result.
1026 *
1027 * @since 1.0.0
1028 *
1029 * @param double|int $op1 First number.
1030 * @param double|int $op2 Second number.
1031 * @return double Arc tangent of two numbers, similar to arc tangent of $op1/op$ except for the sign.
1032 */
1033 public static function atan2( $op1, $op2 ) {
1034 return atan2( $op1, $op2 );
1035 }
1036
1037 /**
1038 * Round a number to a given precision.
1039 *
1040 * @since 1.0.0
1041 *
1042 * @param double|int $value Number to be rounded.
1043 * @param double|int $decimals Optional. Number of decimals after the comma after the rounding.
1044 * @return double Rounded number.
1045 */
1046 public static function round( $value, $decimals = 0 ) {
1047 return round( $value, $decimals );
1048 }
1049
1050 /**
1051 * Format a number with the . as the decimal separator and the , as the thousand separator, rounded to a precision.
1052 *
1053 * The is the common number format in English-language regions.
1054 *
1055 * @since 1.0.0
1056 *
1057 * @param double|int $value Number to be rounded and formatted.
1058 * @param double|int $decimals Optional. Number of decimals after the decimal separator after the rounding.
1059 * @return double Formatted number.
1060 */
1061 public static function number_format( $value, $decimals = 0 ) {
1062 return number_format( $value, $decimals, '.', ',' );
1063 }
1064
1065 /**
1066 * Format a number with the , as the decimal separator and the space as the thousand separator, rounded to a precision.
1067 *
1068 * The is the common number format in non-English-language regions, mainly in Europe.
1069 *
1070 * @since 1.0.0
1071 *
1072 * @param double|int $value Number to be rounded and formatted.
1073 * @param double|int $decimals Optional. Number of decimals after the decimal separator after the rounding.
1074 * @return double Formatted number.
1075 */
1076 public static function number_format_eu( $value, $decimals = 0 ) {
1077 return number_format( $value, $decimals, ',', ' ' );
1078 }
1079
1080 /**
1081 * Set the seed for the generation of random numbers.
1082 *
1083 * @since 1.0.0
1084 *
1085 * @param string The seed.
1086 */
1087 protected static function _set_random_seed( $random_seed ) {
1088 self::$random_seed = $random_seed;
1089 }
1090
1091 /**
1092 * Get the seed for the generation of random numbers.
1093 *
1094 * @since 1.0.0
1095 *
1096 * @return string The seed.
1097 */
1098 protected static function _get_random_seed() {
1099 if ( is_null( self::$random_seed ) ) {
1100 return microtime();
1101 } else {
1102 return self::$random_seed;
1103 }
1104 }
1105
1106 /**
1107 * Get a random integer from a range.
1108 *
1109 * @since 1.0.0
1110 *
1111 * @param int $min Minimum value for the range.
1112 * @param int $max Maximum value for the range.
1113 * @return int Random integer from the range [$min, $max].
1114 */
1115 public static function rand_int( $min, $max ) {
1116 // Swap min and max value if min is bigger than max.
1117 if ( $min > $max ) {
1118 $tmp = $max;
1119 $max = $min;
1120 $min = $tmp;
1121 unset( $tmp );
1122 }
1123 $number_characters = ceil( log( $max + 1 - $min, '16' ) );
1124 $md5string = md5( self::_get_random_seed() );
1125 $offset = 0;
1126 do {
1127 while ( ( $offset + $number_characters ) > strlen( $md5string ) ) {
1128 $md5string .= md5( $md5string );
1129 }
1130 $randomno = hexdec( substr( $md5string, $offset, $number_characters ) );
1131 $offset += $number_characters;
1132 } while ( ( $min + $randomno ) > $max );
1133 return $min + $randomno;
1134 }
1135
1136 /**
1137 * Get a random double value from a range [0, 1].
1138 *
1139 * @since 1.0.0
1140 *
1141 * @return double Random number from the range [0, 1].
1142 */
1143 public static function rand_float() {
1144 $random_values = unpack( 'v', md5( self::_get_random_seed(), true ) );
1145 return array_shift( $random_values ) / 65536;
1146 }
1147
1148 } // class EvalMath_Functions
1149