PluginProbe
TablePress – Tables in WordPress made easy / 1.9.2
TablePress – Tables in WordPress made easy v1.9.2
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.9.2, at libraries/evalmath.class.php

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