Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
/*
4
================================================================================
5
 
6
EvalMath - PHP Class to safely evaluate math expressions
7
Copyright (C) 2005 Miles Kaufmann <http://www.twmagic.com/>
8
 
9
================================================================================
10
 
11
NAME
12
    EvalMath - safely evaluate math expressions
13
 
14
SYNOPSIS
15
    <?
16
      include('evalmath.class.php');
17
      $m = new EvalMath;
18
      // basic evaluation:
19
      $result = $m->evaluate('2+2');
20
      // supports: order of operation; parentheses; negation; built-in functions
21
      $result = $m->evaluate('-8(5/2)^2*(1-sqrt(4))-8');
22
      // create your own variables
23
      $m->evaluate('a = e^(ln(pi))');
24
      // or functions
25
      $m->evaluate('f(x,y) = x^2 + y^2 - 2x*y + 1');
26
      // and then use them
27
      $result = $m->evaluate('3*f(42,a)');
28
    ?>
29
 
30
DESCRIPTION
31
    Use the EvalMath class when you want to evaluate mathematical expressions
32
    from untrusted sources.  You can define your own variables and functions,
33
    which are stored in the object.  Try it, it's fun!
34
 
35
METHODS
36
    $m->evalute($expr)
37
        Evaluates the expression and returns the result.  If an error occurs,
38
        prints a warning and returns false.  If $expr is a function assignment,
39
        returns true on success.
40
 
41
    $m->e($expr)
42
        A synonym for $m->evaluate().
43
 
44
    $m->vars()
45
        Returns an associative array of all user-defined variables and values.
46
 
47
    $m->funcs()
48
        Returns an array of all user-defined functions.
49
 
50
PARAMETERS
51
    $m->suppress_errors
52
        Set to true to turn off warnings when evaluating expressions
53
 
54
    $m->last_error
55
        If the last evaluation failed, contains a string describing the error.
56
        (Useful when suppress_errors is on).
57
 
58
AUTHOR INFORMATION
59
    Copyright 2005, Miles Kaufmann.
60
 
61
LICENSE
62
    Redistribution and use in source and binary forms, with or without
63
    modification, are permitted provided that the following conditions are
64
    met:
65
 
66
    1   Redistributions of source code must retain the above copyright
67
        notice, this list of conditions and the following disclaimer.
68
    2.  Redistributions in binary form must reproduce the above copyright
69
        notice, this list of conditions and the following disclaimer in the
70
        documentation and/or other materials provided with the distribution.
71
    3.  The name of the author may not be used to endorse or promote
72
        products derived from this software without specific prior written
73
        permission.
74
 
75
    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
76
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
77
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
78
    DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
79
    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
80
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
81
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
82
    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
83
    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
84
    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
85
    POSSIBILITY OF SUCH DAMAGE.
86
 
87
*/
88
 
89
/**
90
 * This class was heavily modified in order to get usefull spreadsheet emulation ;-)
91
 * skodak
92
 * This class was modified to allow comparison operators (<, <=, ==, >=, >)
93
 * and synonyms functions (for the 'if' function). See MDL-14274 for more details.
94
 */
95
 
96
class EvalMath {
97
 
98
    /** @var string Pattern used for a valid function or variable name. Note, var and func names are case insensitive.*/
99
    private static $namepat = '[a-z][a-z0-9_]*';
100
 
101
    var $suppress_errors = false;
102
    var $last_error = null;
103
 
104
    var $v = array(); // variables (and constants)
105
    var $f = array(); // user-defined functions
106
    var $vb = array(); // constants
107
    var $fb = array(  // built-in functions
108
        'sin','sinh','arcsin','asin','arcsinh','asinh',
109
        'cos','cosh','arccos','acos','arccosh','acosh',
110
        'tan','tanh','arctan','atan','arctanh','atanh',
111
        'sqrt','abs','ln','log','exp','floor','ceil');
112
 
113
    var $fc = array( // calc functions emulation
114
        'average'=>array(-1), 'max'=>array(-1),  'min'=>array(-1),
115
        'mod'=>array(2),      'pi'=>array(0),    'power'=>array(2),
116
        'round'=>array(1, 2), 'sum'=>array(-1), 'rand_int'=>array(2),
117
        'rand_float'=>array(0), 'ifthenelse'=>array(3), 'cond_and'=>array(-1), 'cond_or'=>array(-1));
118
    var $fcsynonyms = array('if' => 'ifthenelse', 'and' => 'cond_and', 'or' => 'cond_or');
119
 
120
    var $allowimplicitmultiplication;
121
 
122
    public function __construct($allowconstants = false, $allowimplicitmultiplication = false) {
123
        if ($allowconstants){
124
            $this->v['pi'] = pi();
125
            $this->v['e'] = exp(1);
126
        }
127
        $this->allowimplicitmultiplication = $allowimplicitmultiplication;
128
    }
129
 
130
    /**
131
     * Old syntax of class constructor. Deprecated in PHP7.
132
     *
133
     * @deprecated since Moodle 3.1
134
     */
135
    public function EvalMath($allowconstants = false, $allowimplicitmultiplication = false) {
136
        debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
137
        self::__construct($allowconstants, $allowimplicitmultiplication);
138
    }
139
 
140
    function e($expr) {
141
        return $this->evaluate($expr);
142
    }
143
 
144
    function evaluate($expr) {
145
        $this->last_error = null;
146
        $expr = trim($expr);
147
        if (substr($expr, -1, 1) == ';') $expr = substr($expr, 0, strlen($expr)-1); // strip semicolons at the end
148
        //===============
149
        // is it a variable assignment?
150
        if (preg_match('/^\s*('.self::$namepat.')\s*=\s*(.+)$/', $expr, $matches)) {
151
            if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant
152
                return $this->trigger(get_string('cannotassigntoconstant', 'mathslib', $matches[1]));
153
            }
154
            if (($tmp = $this->pfx($this->nfx($matches[2]))) === false) return false; // get the result and make sure it's good
155
            $this->v[$matches[1]] = $tmp; // if so, stick it in the variable array
156
            return $this->v[$matches[1]]; // and return the resulting value
157
        //===============
158
        // is it a function assignment?
159
        } elseif (preg_match('/^\s*('.self::$namepat.')\s*\(\s*('.self::$namepat.'(?:\s*,\s*'.self::$namepat.')*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
160
            $fnn = $matches[1]; // get the function name
161
            if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
162
                return $this->trigger(get_string('cannotredefinebuiltinfunction', 'mathslib', $matches[1]));
163
            }
164
            $args = explode(",", preg_replace("/\s+/", "", $matches[2])); // get the arguments
165
            if (($stack = $this->nfx($matches[3])) === false) return false; // see if it can be converted to postfix
166
            for ($i = 0; $i<count($stack); $i++) { // freeze the state of the non-argument variables
167
                $token = $stack[$i];
168
                if (preg_match('/^'.self::$namepat.'$/', $token) and !in_array($token, $args)) {
169
                    if (array_key_exists($token, $this->v)) {
170
                        $stack[$i] = $this->v[$token];
171
                    } else {
172
                        return $this->trigger(get_string('undefinedvariableinfunctiondefinition', 'mathslib', $token));
173
                    }
174
                }
175
            }
176
            $this->f[$fnn] = array('args'=>$args, 'func'=>$stack);
177
            return true;
178
        //===============
179
        } else {
180
            return $this->pfx($this->nfx($expr)); // straight up evaluation, woo
181
        }
182
    }
183
 
184
    function vars() {
185
        return $this->v;
186
    }
187
 
188
    function funcs() {
189
        $output = array();
190
        foreach ($this->f as $fnn=>$dat)
191
            $output[] = $fnn . '(' . implode(',', $dat['args']) . ')';
192
        return $output;
193
    }
194
 
195
    /**
196
     * @param string $name
197
     * @return boolean Is this a valid var or function name?
198
     */
199
    public static function is_valid_var_or_func_name($name){
200
        return preg_match('/'.self::$namepat.'$/iA', $name);
201
    }
202
 
203
    //===================== HERE BE INTERNAL METHODS ====================\\
204
 
205
    // Convert infix to postfix notation
206
    function nfx($expr) {
207
 
208
        $index = 0;
209
        $stack = new EvalMathStack;
210
        $output = array(); // postfix form of expression, to be passed to pfx()
211
        $expr = trim(strtolower($expr));
212
        // MDL-14274: new operators for comparison added.
213
        $ops   = array('+', '-', '*', '/', '^', '_', '>', '<', '<=', '>=', '==', '%');
214
        $ops_r = array('+'=>0,'-'=>0,'*'=>0,'/'=>0,'^'=>1, '%' => 0); // right-associative operator?
215
        $ops_p = array('+'=>0,'-'=>0,'*'=>1,'/'=>1,'_'=>1,'^'=>2, '>'=>3, '<'=>3, '<='=>3, '>='=>3, '=='=>3, '%'=>1); // operator precedence
216
 
217
        $expecting_op = false; // we use this in syntax-checking the expression
218
                               // and determining when a - is a negation
219
 
220
        if (preg_match("/[^\%\w\s+*^\/()\.,-<>=]/", $expr, $matches)) { // make sure the characters are all good
221
            return $this->trigger(get_string('illegalcharactergeneral', 'mathslib', $matches[0]));
222
        }
223
 
224
        while(1) { // 1 Infinite Loop ;)
225
            // MDL-14274 Test two character operators.
226
            $op = substr($expr, $index, 2);
227
            if (!in_array($op, $ops)) {
228
                // MDL-14274 Get one character operator.
229
                $op = substr($expr, $index, 1); // get the first character at the current index
230
            }
231
            // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand
232
            $ex = preg_match('/^('.self::$namepat.'\(?|\d+(?:\.\d*)?(?:(e[+-]?)\d*)?|\.\d+|\()/', substr($expr, $index), $match);
233
            //===============
234
            if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus?
235
                $stack->push('_'); // put a negation on the stack
236
                $index++;
237
            } elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack
238
                return $this->trigger(get_string('illegalcharacterunderscore', 'mathslib')); // but not in the input expression
239
            //===============
240
            } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack?
241
                if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?
242
                    if (!$this->allowimplicitmultiplication){
243
                        return $this->trigger(get_string('implicitmultiplicationnotallowed', 'mathslib'));
244
                    } else {// it's an implicit multiplication
245
                        $op = '*';
246
                        $index--;
247
                    }
248
                }
249
                // heart of the algorithm:
250
                while($stack->count > 0 and ($o2 = $stack->last()) and in_array($o2, $ops) and ($ops_r[$op] ? $ops_p[$op] < $ops_p[$o2] : $ops_p[$op] <= $ops_p[$o2])) {
251
                    $output[] = $stack->pop(); // pop stuff off the stack into the output
252
                }
253
                // many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
254
                $stack->push($op); // finally put OUR operator onto the stack
255
                $index += strlen($op);
256
                $expecting_op = false;
257
            //===============
258
            } elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis?
259
                while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last (
260
                    if (is_null($o2)) return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
261
                    else $output[] = $o2;
262
                }
263
                if (preg_match('/^('.self::$namepat.')\($/', $stack->last(2) ?? '', $matches)) { // did we just close a function?
264
                    $fnn = $matches[1]; // get the function name
265
                    $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)
266
                    $fn = $stack->pop();
267
                    $output[] = array('fn'=>$fn, 'fnn'=>$fnn, 'argcount'=>$arg_count); // send function to output
268
                    if (in_array($fnn, $this->fb)) { // check the argument count
269
                        if($arg_count > 1) {
270
                            $a= new stdClass();
271
                            $a->expected = 1;
272
                            $a->given = $arg_count;
273
                            return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
274
                        }
275
                    } elseif ($this->get_native_function_name($fnn)) {
276
                        $fnn = $this->get_native_function_name($fnn); // Resolve synonyms.
277
 
278
                        $counts = $this->fc[$fnn];
279
                        if (in_array(-1, $counts) and $arg_count > 0) {}
280
                        elseif (!in_array($arg_count, $counts)) {
281
                            $a= new stdClass();
282
                            $a->expected = implode('/',$this->fc[$fnn]);
283
                            $a->given = $arg_count;
284
                            return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
285
                        }
286
                    } elseif (array_key_exists($fnn, $this->f)) {
287
                        if ($arg_count != count($this->f[$fnn]['args'])) {
288
                            $a= new stdClass();
289
                            $a->expected = count($this->f[$fnn]['args']);
290
                            $a->given = $arg_count;
291
                            return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
292
                        }
293
                    } else { // did we somehow push a non-function on the stack? this should never happen
294
                        return $this->trigger(get_string('internalerror', 'mathslib'));
295
                    }
296
                }
297
                $index++;
298
            //===============
299
            } elseif ($op == ',' and $expecting_op) { // did we just finish a function argument?
300
                while (($o2 = $stack->pop()) != '(') {
301
                    if (is_null($o2)) return $this->trigger(get_string('unexpectedcomma', 'mathslib')); // oops, never had a (
302
                    else $output[] = $o2; // pop the argument expression stuff and push onto the output
303
                }
304
                // make sure there was a function
305
                if (!preg_match('/^('.self::$namepat.')\($/', $stack->last(2), $matches))
306
                    return $this->trigger(get_string('unexpectedcomma', 'mathslib'));
307
                $stack->push($stack->pop()+1); // increment the argument count
308
                $stack->push('('); // put the ( back on, we'll need to pop back to it again
309
                $index++;
310
                $expecting_op = false;
311
            //===============
312
            } elseif ($op == '(' and !$expecting_op) {
313
                $stack->push('('); // that was easy
314
                $index++;
315
                $allow_neg = true;
316
            //===============
317
            } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?
318
                $expecting_op = true;
319
                $val = $match[1];
320
                if (preg_match('/^('.self::$namepat.')\($/', $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
321
                    if (in_array($matches[1], $this->fb) or
322
                                array_key_exists($matches[1], $this->f) or
323
                                $this->get_native_function_name($matches[1])){ // it's a func
324
                        $stack->push($val);
325
                        $stack->push(1);
326
                        $stack->push('(');
327
                        $expecting_op = false;
328
                    } else { // it's a var w/ implicit multiplication
329
                        $val = $matches[1];
330
                        $output[] = $val;
331
                    }
332
                } else { // it's a plain old var or num
333
                    $output[] = $val;
334
                }
335
                $index += strlen($val);
336
            //===============
337
            } elseif ($op == ')') {
338
                //it could be only custom function with no params or general error
339
                if ($stack->last() != '(' or $stack->last(2) != 1) return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
340
                if (preg_match('/^('.self::$namepat.')\($/', $stack->last(3), $matches)) { // did we just close a function?
341
                    $stack->pop();// (
342
                    $stack->pop();// 1
343
                    $fn = $stack->pop();
344
                    $fnn = $matches[1]; // get the function name
345
                    $fnn = $this->get_native_function_name($fnn); // Resolve synonyms.
346
                    $counts = $this->fc[$fnn];
347
                    if (!in_array(0, $counts)){
348
                        $a= new stdClass();
349
                        $a->expected = $this->fc[$fnn];
350
                        $a->given = 0;
351
                        return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
352
                    }
353
                    $output[] = array('fn'=>$fn, 'fnn'=>$fnn, 'argcount'=>0); // send function to output
354
                    $index++;
355
                    $expecting_op = true;
356
                } else {
357
                    return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
358
                }
359
            //===============
360
            } elseif (in_array($op, $ops) and !$expecting_op) { // miscellaneous error checking
361
                return $this->trigger(get_string('unexpectedoperator', 'mathslib', $op));
362
            } else { // I don't even want to know what you did to get here
363
                return $this->trigger(get_string('anunexpectederroroccured', 'mathslib'));
364
            }
365
            if ($index == strlen($expr)) {
366
                if (in_array($op, $ops)) { // did we end with an operator? bad.
367
                    return $this->trigger(get_string('operatorlacksoperand', 'mathslib', $op));
368
                } else {
369
                    break;
370
                }
371
            }
372
            while (substr($expr, $index, 1) == ' ') { // step the index past whitespace (pretty much turns whitespace
373
                $index++;                             // into implicit multiplication if no operator is there)
374
            }
375
 
376
        }
377
        while (!is_null($op = $stack->pop())) { // pop everything off the stack and push onto output
378
            if ($op == '(') return $this->trigger(get_string('expectingaclosingbracket', 'mathslib')); // if there are (s on the stack, ()s were unbalanced
379
            $output[] = $op;
380
        }
381
        return $output;
382
    }
383
    /**
384
     *
385
     * @param string $fnn
386
     * @return string|boolean false if function name unknown.
387
     */
388
    function get_native_function_name($fnn) {
389
        if (array_key_exists($fnn, $this->fcsynonyms)) {
390
            return $this->fcsynonyms[$fnn];
391
        } else if (array_key_exists($fnn, $this->fc)) {
392
            return $fnn;
393
        } else {
394
            return false;
395
        }
396
    }
397
    // evaluate postfix notation
398
    function pfx($tokens, $vars = array()) {
399
 
400
        if ($tokens == false) return false;
401
 
402
        $stack = new EvalMathStack;
403
 
404
        foreach ($tokens as $token) { // nice and easy
405
 
406
            // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
407
            if (is_array($token)) { // it's a function!
408
                $fnn = $token['fnn'];
409
                $count = $token['argcount'];
410
                if (in_array($fnn, $this->fb)) { // built-in function:
411
                    if (is_null($op1 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
412
                    $fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms
413
                    if ($fnn == 'ln') $fnn = 'log';
414
                    eval('$stack->push(' . $fnn . '($op1));'); // perfectly safe eval()
415
                } elseif ($this->get_native_function_name($fnn)) { // calc emulation function
416
                    $fnn = $this->get_native_function_name($fnn); // Resolve synonyms.
417
                    // get args
418
                    $args = array();
419
                    for ($i = $count-1; $i >= 0; $i--) {
420
                        if (is_null($args[] = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
421
                    }
422
                    $res = call_user_func_array(array('EvalMathFuncs', $fnn), array_reverse($args));
423
                    if ($res === FALSE) {
424
                        return $this->trigger(get_string('internalerror', 'mathslib'));
425
                    }
426
                    $stack->push($res);
427
                } elseif (array_key_exists($fnn, $this->f)) { // user function
428
                    // get args
429
                    $args = array();
430
                    for ($i = count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {
431
                        if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
432
                    }
433
                    $stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
434
                }
435
            // if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
436
            } elseif (in_array($token, array('+', '-', '*', '/', '^', '>', '<', '==', '<=', '>=', '%'), true)) {
437
                if (is_null($op2 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
438
                if (is_null($op1 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
439
                switch ($token) {
440
                    case '+':
441
                        $stack->push($op1+$op2); break;
442
                    case '-':
443
                        $stack->push($op1-$op2); break;
444
                    case '*':
445
                        $stack->push($op1*$op2); break;
446
                    case '/':
447
                        if ($op2 == 0) return $this->trigger(get_string('divisionbyzero', 'mathslib'));
448
                        $stack->push($op1/$op2); break;
449
                    case '^':
450
                        $stack->push(pow($op1, $op2)); break;
451
                    case '>':
452
                        $stack->push((int)($op1 > $op2)); break;
453
                    case '<':
454
                        $stack->push((int)($op1 < $op2)); break;
455
                    case '==':
456
                        $stack->push((int)($op1 == $op2)); break;
457
                    case '<=':
458
                        $stack->push((int)($op1 <= $op2)); break;
459
                    case '>=':
460
                        $stack->push((int)($op1 >= $op2)); break;
461
                    case '%':
462
                        $stack->push($op1%$op2); break;
463
                }
464
            // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
465
            } elseif ($token == "_") {
466
                $stack->push(-1*$stack->pop());
467
            // if the token is a number or variable, push it on the stack
468
            } else {
469
                if (is_numeric($token)) {
470
                    $stack->push($token);
471
                } elseif (array_key_exists($token, $this->v)) {
472
                    $stack->push($this->v[$token]);
473
                } elseif (array_key_exists($token, $vars)) {
474
                    $stack->push($vars[$token]);
475
                } else {
476
                    return $this->trigger(get_string('undefinedvariable', 'mathslib', $token));
477
                }
478
            }
479
        }
480
        // when we're out of tokens, the stack should have a single element, the final result
481
        if ($stack->count != 1) return $this->trigger(get_string('internalerror', 'mathslib'));
482
        return $stack->pop();
483
    }
484
 
485
    // trigger an error, but nicely, if need be
486
    function trigger($msg) {
487
        $this->last_error = $msg;
488
        if (!$this->suppress_errors) trigger_error($msg, E_USER_WARNING);
489
        return false;
490
    }
491
 
492
}
493
 
494
// for internal use
495
class EvalMathStack {
496
 
497
    var $stack = array();
498
    var $count = 0;
499
 
500
    function push($val) {
501
        $this->stack[$this->count] = $val;
502
        $this->count++;
503
    }
504
 
505
    function pop() {
506
        if ($this->count > 0) {
507
            $this->count--;
508
            return $this->stack[$this->count];
509
        }
510
        return null;
511
    }
512
 
513
    function last($n=1) {
514
        if ($this->count - $n >= 0) {
515
            return $this->stack[$this->count-$n];
516
        }
517
        return null;
518
    }
519
}
520
 
521
 
522
// spreadsheet functions emulation
523
class EvalMathFuncs {
524
    /**
525
     * MDL-14274 new conditional function.
526
     * @param boolean $condition boolean for conditional.
527
     * @param variant $then value if condition is true.
528
     * @param unknown $else value if condition is false.
529
     * @author Juan Pablo de Castro <juan.pablo.de.castro@gmail.com>
530
     * @return unknown
531
     */
532
    static function ifthenelse($condition, $then, $else) {
533
        if ($condition == true) {
534
            return $then;
535
        } else {
536
            return $else;
537
        }
538
    }
539
 
540
    static function cond_and() {
541
        $args = func_get_args();
542
        foreach($args as $a) {
543
            if ($a == false) {
544
                return 0;
545
            }
546
        }
547
        return 1;
548
    }
549
 
550
    static function cond_or() {
551
        $args = func_get_args();
552
        foreach($args as $a) {
553
            if($a == true) {
554
                return 1;
555
            }
556
        }
557
        return 0;
558
    }
559
 
560
    static function average() {
561
        $args = func_get_args();
562
        return (call_user_func_array(array(self::class, 'sum'), $args) / count($args));
563
    }
564
 
565
    static function max() {
566
        $args = func_get_args();
567
        $res = array_pop($args);
568
        foreach($args as $a) {
569
            if ($res < $a) {
570
                $res = $a;
571
            }
572
        }
573
        return $res;
574
    }
575
 
576
    static function min() {
577
        $args = func_get_args();
578
        $res = array_pop($args);
579
        foreach($args as $a) {
580
            if ($res > $a) {
581
                $res = $a;
582
            }
583
        }
584
        return $res;
585
    }
586
 
587
    static function mod($op1, $op2) {
588
        return $op1 % $op2;
589
    }
590
 
591
    static function pi() {
592
        return pi();
593
    }
594
 
595
    static function power($op1, $op2) {
596
        return pow($op1, $op2);
597
    }
598
 
599
    static function round($val, $precision = 0) {
600
        return round($val, $precision);
601
    }
602
 
603
    static function sum() {
604
        $args = func_get_args();
605
        $res = 0;
606
        foreach($args as $a) {
607
           $res += $a;
608
        }
609
        return $res;
610
    }
611
 
612
    protected static $randomseed = null;
613
 
614
    static function set_random_seed($randomseed) {
615
        self::$randomseed = $randomseed;
616
    }
617
 
618
    static function get_random_seed() {
619
        if (is_null(self::$randomseed)){
620
            return microtime();
621
        } else {
622
            return self::$randomseed;
623
        }
624
    }
625
 
626
    static function rand_int($min, $max){
627
        if ($min >= $max) {
628
            return false; //error
629
        }
630
        $noofchars = ceil(log($max + 1 - $min, '16'));
631
        $md5string = md5(self::get_random_seed());
632
        $stringoffset = 0;
633
        do {
634
            while (($stringoffset + $noofchars) > strlen($md5string)){
635
                $md5string .= md5($md5string);
636
            }
637
            $randomno = hexdec(substr($md5string, $stringoffset, $noofchars));
638
            $stringoffset += $noofchars;
639
        } while (($min + $randomno) > $max);
640
        return $min + $randomno;
641
    }
642
 
643
    static function rand_float() {
644
        $randomvalues = unpack('v', md5(self::get_random_seed(), true));
645
        return array_shift($randomvalues) / 65536;
646
    }
647
}