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