diff --git a/lib/evalmath/evalmath.class.php b/lib/evalmath/evalmath.class.php index 54bba6b3c62..8e3f3818b46 100644 --- a/lib/evalmath/evalmath.class.php +++ b/lib/evalmath/evalmath.class.php @@ -210,14 +210,14 @@ class EvalMath { $output = array(); // postfix form of expression, to be passed to pfx() $expr = trim(strtolower($expr)); // MDL-14274: new operators for comparison added. - $ops = array('+', '-', '*', '/', '^', '_', '>', '<', '<=', '>=', '=='); - $ops_r = array('+'=>0,'-'=>0,'*'=>0,'/'=>0,'^'=>1); // right-associative operator? - $ops_p = array('+'=>0,'-'=>0,'*'=>1,'/'=>1,'_'=>1,'^'=>2, '>'=>3, '<'=>3, '<='=>3, '>='=>3, '=='=>3); // operator precedence + $ops = array('+', '-', '*', '/', '^', '_', '>', '<', '<=', '>=', '==', '%'); + $ops_r = array('+'=>0,'-'=>0,'*'=>0,'/'=>0,'^'=>1, '%' => 0); // right-associative operator? + $ops_p = array('+'=>0,'-'=>0,'*'=>1,'/'=>1,'_'=>1,'^'=>2, '>'=>3, '<'=>3, '<='=>3, '>='=>3, '=='=>3, '%'=>1); // operator precedence $expecting_op = false; // we use this in syntax-checking the expression // and determining when a - is a negation - if (preg_match("/[^\w\s+*^\/()\.,-<>=]/", $expr, $matches)) { // make sure the characters are all good + if (preg_match("/[^\%\w\s+*^\/()\.,-<>=]/", $expr, $matches)) { // make sure the characters are all good return $this->trigger(get_string('illegalcharactergeneral', 'mathslib', $matches[0])); } @@ -433,7 +433,7 @@ class EvalMath { $stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!! } // if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on - } elseif (in_array($token, array('+', '-', '*', '/', '^', '>', '<', '==', '<=', '>='), true)) { + } elseif (in_array($token, array('+', '-', '*', '/', '^', '>', '<', '==', '<=', '>=', '%'), true)) { if (is_null($op2 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib')); if (is_null($op1 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib')); switch ($token) { @@ -458,6 +458,8 @@ class EvalMath { $stack->push((int)($op1 <= $op2)); break; case '>=': $stack->push((int)($op1 >= $op2)); break; + case '%': + $stack->push($op1%$op2); break; } // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on } elseif ($token == "_") { diff --git a/lib/evalmath/readme_moodle.txt b/lib/evalmath/readme_moodle.txt index 3d460eb9986..948c9c39a20 100644 --- a/lib/evalmath/readme_moodle.txt +++ b/lib/evalmath/readme_moodle.txt @@ -32,4 +32,21 @@ e.g. if (and(condition_1, condition_2, ... condition_n)) Changes by Raquel Ortega (MDL-76413) * Avoid PHP 8.2: Partially-supported callable deprecations -* eg: call_user_func_array(array('self', 'sum'), $args to call_user_func_array(array(self::class, 'sum'), $args) \ No newline at end of file +* eg: call_user_func_array(array('self', 'sum'), $args to call_user_func_array(array(self::class, 'sum'), $args) + +Changes by Meirza (MDL-75464) +* EvalMath has unit tests in lib/tests/mathslib_test.php, +  since version 1.0.1, there are two additional tests: +  - shouldSupportModuloOperator() +  - shouldConsiderDoubleMinusAsPlus() +  To pass the test, some modifications must be made: +  - Adjust the test code so it can run properly by using \calc_formula. +  Please see the differences between the code in MDL-75464 and the upstream code. +  - In the dataprovider for both tests, add the formula in the first array with "=" at the first character. +  Before: +  ``` +  'a%b', // 9%3 => 0 +  ``` +  After: +  ``` +  '=a%b', // 9%3 => 0 \ No newline at end of file diff --git a/lib/tests/mathslib_test.php b/lib/tests/mathslib_test.php index 8e8368add18..5994e2fffdf 100644 --- a/lib/tests/mathslib_test.php +++ b/lib/tests/mathslib_test.php @@ -317,4 +317,93 @@ class mathslib_test extends \basic_testcase { $result = $formula->evaluate(); $this->assertTrue(is_float($result)); } + + /** + * Tests the modulo operator. + * + * @covers calc_formula::evaluate + * @dataProvider moduloOperatorData + * + * @param string $formula + * @param array $values + * @param int|float $expectedResult + */ + public function shouldSupportModuloOperator($formula, $values, $expectedResult) + { + $formula = new calc_formula($formula); + $formula->set_params($values); + $this->assertEquals($expectedResult, $formula->evaluate()); + } + + /** + * Data provider for shouldSupportModuloOperator + * + * @return array + */ + public function moduloOperatorData() { + return array( + array( + '=a%b', // 9%3 => 0 + array('a' => 9, 'b' => 3), + 0 + ), + array( + '=a%b', // 10%3 => 1 + array('a' => 10, 'b' => 3), + 1 + ), + array( + '=10-a%(b+c*d)', // 10-10%(7-2*2) => 9 + array('a' => '10', 'b' => 7, 'c' => -2, 'd' => 2), + 9 + ) + ); + } + + /** + * Tests the double minus as plus. + * + * @covers calc_formula::evaluate + * @dataProvider doubleMinusData + * + * @param string $formula + * @param array $values + * @param int|float $expectedResult + */ + public function shouldConsiderDoubleMinusAsPlus($formula, $values, $expectedResult) + { + $formula = new calc_formula($formula); + $formula->set_params($values); + $this->assertEquals($expectedResult, $formula->evaluate()); + } + + /** + * Data provider for shouldConsiderDoubleMinusAsPlus + * + * @return array + */ + public function doubleMinusData() { + return array( + array( + '=a+b*c--d', // 1+2*3--4 => 1+6+4 => 11 + array( + 'a' => 1, + 'b' => 2, + 'c' => 3, + 'd' => 4 + ), + 11 + ), + array( + '=a+b*c--d', // 1+2*3---4 => 1+6-4 => 3 + array( + 'a' => 1, + 'b' => 2, + 'c' => 3, + 'd' => -4 + ), + 3 + ) + ); + } } diff --git a/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml index c6aca5f4757..6191e21b044 100644 --- a/lib/thirdpartylibs.xml +++ b/lib/thirdpartylibs.xml @@ -42,7 +42,7 @@ evalmath EvalMath Class to safely evaluate math expressions. - 1.0.0 + 1.0.1 BSD https://github.com/dbojdo/eval-math