1<?php
2namespace TYPO3\CMS\Core\Utility;
3
4/*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17/**
18 * Class with helper functions for mathematical calculations
19 */
20class MathUtility
21{
22    /**
23     * Forces the integer $theInt into the boundaries of $min and $max. If the $theInt is FALSE then the $defaultValue is applied.
24     *
25     * @param int $theInt Input value
26     * @param int $min Lower limit
27     * @param int $max Higher limit
28     * @param int $defaultValue Default value if input is FALSE.
29     * @return int The input value forced into the boundaries of $min and $max
30     */
31    public static function forceIntegerInRange($theInt, $min, $max = 2000000000, $defaultValue = 0)
32    {
33        // Returns $theInt as an integer in the integerspace from $min to $max
34        $theInt = (int)$theInt;
35        // If the input value is zero after being converted to integer,
36        // defaultValue may set another default value for it.
37        if ($defaultValue && !$theInt) {
38            $theInt = $defaultValue;
39        }
40        if ($theInt < $min) {
41            $theInt = $min;
42        }
43        if ($theInt > $max) {
44            $theInt = $max;
45        }
46        return $theInt;
47    }
48
49    /**
50     * Returns $theInt if it is greater than zero, otherwise returns zero.
51     *
52     * @param int $theInt Integer string to process
53     * @return int
54     */
55    public static function convertToPositiveInteger($theInt)
56    {
57        $theInt = (int)$theInt;
58        if ($theInt < 0) {
59            $theInt = 0;
60        }
61        return $theInt;
62    }
63
64    /**
65     * Tests if the input can be interpreted as integer.
66     *
67     * Note: Integer casting from objects or arrays is considered undefined and thus will return false.
68     *
69     * @see http://php.net/manual/en/language.types.integer.php#language.types.integer.casting.from-other
70     * @param mixed $var Any input variable to test
71     * @return bool Returns TRUE if string is an integer
72     */
73    public static function canBeInterpretedAsInteger($var)
74    {
75        if ($var === '' || is_object($var) || is_array($var)) {
76            return false;
77        }
78        return (string)(int)$var === (string)$var;
79    }
80
81    /**
82     * Tests if the input can be interpreted as float.
83     *
84     * Note: Float casting from objects or arrays is considered undefined and thus will return false.
85     *
86     * @see http://www.php.net/manual/en/language.types.float.php, section "Formally" for the notation
87     * @param mixed $var Any input variable to test
88     * @return bool Returns TRUE if string is a float
89     */
90    public static function canBeInterpretedAsFloat($var)
91    {
92        $pattern_lnum = '[0-9]+';
93        $pattern_dnum = '([0-9]*[\.]' . $pattern_lnum . ')|(' . $pattern_lnum . '[\.][0-9]*)';
94        $pattern_exp_dnum = '[+-]?((' . $pattern_lnum . '|' . $pattern_dnum . ')([eE][+-]?' . $pattern_lnum . ')?)';
95
96        if ($var === '' || is_object($var) || is_array($var)) {
97            return false;
98        }
99
100        $matches = preg_match('/^' . $pattern_exp_dnum . '$/', $var);
101        return $matches === 1;
102    }
103
104    /**
105     * Calculates the input by +,-,*,/,%,^ with priority to + and -
106     *
107     * @param string $string Input string, eg "123 + 456 / 789 - 4
108     * @return int Calculated value. Or error string.
109     * @see \TYPO3\CMS\Core\Utility\MathUtility::calculateWithParentheses()
110     */
111    public static function calculateWithPriorityToAdditionAndSubtraction($string)
112    {
113        // Removing all whitespace
114        $string = preg_replace('/[[:space:]]*/', '', $string);
115        // Ensuring an operator for the first entrance
116        $string = '+' . $string;
117        $qm = '\\*\\/\\+-^%';
118        $regex = '([' . $qm . '])([' . $qm . ']?[0-9\\.]*)';
119        // Split the expression here:
120        $reg = [];
121        preg_match_all('/' . $regex . '/', $string, $reg);
122        reset($reg[2]);
123        $number = 0;
124        $Msign = '+';
125        $err = '';
126        $buffer = (float)current($reg[2]);
127        // Advance pointer
128        $regSliced = array_slice($reg[2], 1, null, true);
129        foreach ($regSliced as $k => $v) {
130            $v = (float)$v;
131            $sign = $reg[1][$k];
132            if ($sign === '+' || $sign === '-') {
133                $Msign === '-' ? ($number -= $buffer) : ($number += $buffer);
134                $Msign = $sign;
135                $buffer = $v;
136            } else {
137                if ($sign === '/') {
138                    if ($v) {
139                        $buffer /= $v;
140                    } else {
141                        $err = 'dividing by zero';
142                    }
143                }
144                if ($sign === '%') {
145                    if ($v) {
146                        $buffer %= $v;
147                    } else {
148                        $err = 'dividing by zero';
149                    }
150                }
151                if ($sign === '*') {
152                    $buffer *= $v;
153                }
154                if ($sign === '^') {
155                    $buffer = pow($buffer, $v);
156                }
157            }
158        }
159        $number = $Msign === '-' ? ($number -= $buffer) : ($number += $buffer);
160        return $err ? 'ERROR: ' . $err : $number;
161    }
162
163    /**
164     * Calculates the input with parenthesis levels
165     *
166     * @param string $string Input string, eg "(123 + 456) / 789 - 4
167     * @return int Calculated value. Or error string.
168     * @see calculateWithPriorityToAdditionAndSubtraction(), \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::stdWrap()
169     */
170    public static function calculateWithParentheses($string)
171    {
172        $securC = 100;
173        do {
174            $valueLenO = strcspn($string, '(');
175            $valueLenC = strcspn($string, ')');
176            if ($valueLenC == strlen($string) || $valueLenC < $valueLenO) {
177                $value = self::calculateWithPriorityToAdditionAndSubtraction(substr($string, 0, $valueLenC));
178                $string = $value . substr($string, $valueLenC + 1);
179                return $string;
180            }
181            $string = substr($string, 0, $valueLenO) . self::calculateWithParentheses(substr($string, $valueLenO + 1));
182
183            // Security:
184            $securC--;
185            if ($securC <= 0) {
186                break;
187            }
188        } while ($valueLenO < strlen($string));
189        return $string;
190    }
191
192    /**
193     * Checks whether the given number $value is an integer in the range [$minimum;$maximum]
194     *
195     * @param int $value Integer value to check
196     * @param int $minimum Lower boundary of the range
197     * @param int $maximum Upper boundary of the range
198     * @return bool
199     */
200    public static function isIntegerInRange($value, $minimum, $maximum)
201    {
202        $value = filter_var($value, FILTER_VALIDATE_INT, [
203            'options' => [
204                'min_range' => $minimum,
205                'max_range' => $maximum
206            ]
207        ]);
208        $isInRange = is_int($value);
209        return $isInRange;
210    }
211}
212