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

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