1<?php
2
3namespace PhpOffice\PhpSpreadsheet\Calculation;
4
5class Logical
6{
7    /**
8     * TRUE.
9     *
10     * Returns the boolean TRUE.
11     *
12     * Excel Function:
13     *        =TRUE()
14     *
15     * @category Logical Functions
16     *
17     * @return bool True
18     */
19    public static function true()
20    {
21        return true;
22    }
23
24    /**
25     * FALSE.
26     *
27     * Returns the boolean FALSE.
28     *
29     * Excel Function:
30     *        =FALSE()
31     *
32     * @category Logical Functions
33     *
34     * @return bool False
35     */
36    public static function false()
37    {
38        return false;
39    }
40
41    private static function countTrueValues(array $args)
42    {
43        $returnValue = 0;
44
45        foreach ($args as $arg) {
46            // Is it a boolean value?
47            if (is_bool($arg)) {
48                $returnValue += $arg;
49            } elseif ((is_numeric($arg)) && (!is_string($arg))) {
50                $returnValue += ((int) $arg != 0);
51            } elseif (is_string($arg)) {
52                $arg = strtoupper($arg);
53                if (($arg == 'TRUE') || ($arg == Calculation::getTRUE())) {
54                    $arg = true;
55                } elseif (($arg == 'FALSE') || ($arg == Calculation::getFALSE())) {
56                    $arg = false;
57                } else {
58                    return Functions::VALUE();
59                }
60                $returnValue += ($arg != 0);
61            }
62        }
63
64        return $returnValue;
65    }
66
67    /**
68     * LOGICAL_AND.
69     *
70     * Returns boolean TRUE if all its arguments are TRUE; returns FALSE if one or more argument is FALSE.
71     *
72     * Excel Function:
73     *        =AND(logical1[,logical2[, ...]])
74     *
75     *        The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
76     *            or references that contain logical values.
77     *
78     *        Boolean arguments are treated as True or False as appropriate
79     *        Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
80     *        If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds
81     *            the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
82     *
83     * @category Logical Functions
84     *
85     * @param mixed ...$args Data values
86     *
87     * @return bool|string the logical AND of the arguments
88     */
89    public static function logicalAnd(...$args)
90    {
91        $args = Functions::flattenArray($args);
92
93        if (count($args) == 0) {
94            return Functions::VALUE();
95        }
96
97        $args = array_filter($args, function ($value) {
98            return $value !== null || (is_string($value) && trim($value) == '');
99        });
100        $argCount = count($args);
101
102        $returnValue = self::countTrueValues($args);
103        if (is_string($returnValue)) {
104            return $returnValue;
105        }
106
107        return ($returnValue > 0) && ($returnValue == $argCount);
108    }
109
110    /**
111     * LOGICAL_OR.
112     *
113     * Returns boolean TRUE if any argument is TRUE; returns FALSE if all arguments are FALSE.
114     *
115     * Excel Function:
116     *        =OR(logical1[,logical2[, ...]])
117     *
118     *        The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
119     *            or references that contain logical values.
120     *
121     *        Boolean arguments are treated as True or False as appropriate
122     *        Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
123     *        If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds
124     *            the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
125     *
126     * @category Logical Functions
127     *
128     * @param mixed $args Data values
129     *
130     * @return bool|string the logical OR of the arguments
131     */
132    public static function logicalOr(...$args)
133    {
134        $args = Functions::flattenArray($args);
135
136        if (count($args) == 0) {
137            return Functions::VALUE();
138        }
139
140        $args = array_filter($args, function ($value) {
141            return $value !== null || (is_string($value) && trim($value) == '');
142        });
143
144        $returnValue = self::countTrueValues($args);
145        if (is_string($returnValue)) {
146            return $returnValue;
147        }
148
149        return $returnValue > 0;
150    }
151
152    /**
153     * LOGICAL_XOR.
154     *
155     * Returns the Exclusive Or logical operation for one or more supplied conditions.
156     * i.e. the Xor function returns TRUE if an odd number of the supplied conditions evaluate to TRUE, and FALSE otherwise.
157     *
158     * Excel Function:
159     *        =XOR(logical1[,logical2[, ...]])
160     *
161     *        The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
162     *            or references that contain logical values.
163     *
164     *        Boolean arguments are treated as True or False as appropriate
165     *        Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
166     *        If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds
167     *            the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
168     *
169     * @category Logical Functions
170     *
171     * @param mixed $args Data values
172     *
173     * @return bool|string the logical XOR of the arguments
174     */
175    public static function logicalXor(...$args)
176    {
177        $args = Functions::flattenArray($args);
178
179        if (count($args) == 0) {
180            return Functions::VALUE();
181        }
182
183        $args = array_filter($args, function ($value) {
184            return $value !== null || (is_string($value) && trim($value) == '');
185        });
186
187        $returnValue = self::countTrueValues($args);
188        if (is_string($returnValue)) {
189            return $returnValue;
190        }
191
192        return $returnValue % 2 == 1;
193    }
194
195    /**
196     * NOT.
197     *
198     * Returns the boolean inverse of the argument.
199     *
200     * Excel Function:
201     *        =NOT(logical)
202     *
203     *        The argument must evaluate to a logical value such as TRUE or FALSE
204     *
205     *        Boolean arguments are treated as True or False as appropriate
206     *        Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
207     *        If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds
208     *            the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
209     *
210     * @category Logical Functions
211     *
212     * @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE
213     *
214     * @return bool|string the boolean inverse of the argument
215     */
216    public static function NOT($logical = false)
217    {
218        $logical = Functions::flattenSingleValue($logical);
219
220        if (is_string($logical)) {
221            $logical = strtoupper($logical);
222            if (($logical == 'TRUE') || ($logical == Calculation::getTRUE())) {
223                return false;
224            } elseif (($logical == 'FALSE') || ($logical == Calculation::getFALSE())) {
225                return true;
226            }
227
228            return Functions::VALUE();
229        }
230
231        return !$logical;
232    }
233
234    /**
235     * STATEMENT_IF.
236     *
237     * Returns one value if a condition you specify evaluates to TRUE and another value if it evaluates to FALSE.
238     *
239     * Excel Function:
240     *        =IF(condition[,returnIfTrue[,returnIfFalse]])
241     *
242     *        Condition is any value or expression that can be evaluated to TRUE or FALSE.
243     *            For example, A10=100 is a logical expression; if the value in cell A10 is equal to 100,
244     *            the expression evaluates to TRUE. Otherwise, the expression evaluates to FALSE.
245     *            This argument can use any comparison calculation operator.
246     *        ReturnIfTrue is the value that is returned if condition evaluates to TRUE.
247     *            For example, if this argument is the text string "Within budget" and the condition argument evaluates to TRUE,
248     *            then the IF function returns the text "Within budget"
249     *            If condition is TRUE and ReturnIfTrue is blank, this argument returns 0 (zero). To display the word TRUE, use
250     *            the logical value TRUE for this argument.
251     *            ReturnIfTrue can be another formula.
252     *        ReturnIfFalse is the value that is returned if condition evaluates to FALSE.
253     *            For example, if this argument is the text string "Over budget" and the condition argument evaluates to FALSE,
254     *            then the IF function returns the text "Over budget".
255     *            If condition is FALSE and ReturnIfFalse is omitted, then the logical value FALSE is returned.
256     *            If condition is FALSE and ReturnIfFalse is blank, then the value 0 (zero) is returned.
257     *            ReturnIfFalse can be another formula.
258     *
259     * @category Logical Functions
260     *
261     * @param mixed $condition Condition to evaluate
262     * @param mixed $returnIfTrue Value to return when condition is true
263     * @param mixed $returnIfFalse Optional value to return when condition is false
264     *
265     * @return mixed The value of returnIfTrue or returnIfFalse determined by condition
266     */
267    public static function statementIf($condition = true, $returnIfTrue = 0, $returnIfFalse = false)
268    {
269        $condition = ($condition === null) ? true : (bool) Functions::flattenSingleValue($condition);
270        $returnIfTrue = ($returnIfTrue === null) ? 0 : Functions::flattenSingleValue($returnIfTrue);
271        $returnIfFalse = ($returnIfFalse === null) ? false : Functions::flattenSingleValue($returnIfFalse);
272
273        return ($condition) ? $returnIfTrue : $returnIfFalse;
274    }
275
276    /**
277     * IFERROR.
278     *
279     * Excel Function:
280     *        =IFERROR(testValue,errorpart)
281     *
282     * @category Logical Functions
283     *
284     * @param mixed $testValue Value to check, is also the value returned when no error
285     * @param mixed $errorpart Value to return when testValue is an error condition
286     *
287     * @return mixed The value of errorpart or testValue determined by error condition
288     */
289    public static function IFERROR($testValue = '', $errorpart = '')
290    {
291        $testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue);
292        $errorpart = ($errorpart === null) ? '' : Functions::flattenSingleValue($errorpart);
293
294        return self::statementIf(Functions::isError($testValue), $errorpart, $testValue);
295    }
296}
297