Passed
Pull Request — master (#4240)
by Owen
18:54 queued 08:08
created

DataValidator::numericOperator()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 19
c 1
b 0
f 0
dl 0
loc 26
ccs 22
cts 22
cp 1
rs 9.6333
cc 4
nc 2
nop 3
crap 4
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Cell;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
7
use PhpOffice\PhpSpreadsheet\Exception;
8
9
/**
10
 * Validate a cell value according to its validation rules.
11
 */
12
class DataValidator
13
{
14
    /**
15
     * Does this cell contain valid value?
16
     *
17
     * @param Cell $cell Cell to check the value
18
     */
19 13
    public function isValid(Cell $cell): bool
20
    {
21 13
        if (!$cell->hasDataValidation() || $cell->getDataValidation()->getType() === DataValidation::TYPE_NONE) {
22 2
            return true;
23
        }
24
25 12
        $cellValue = $cell->getValue();
26 12
        $dataValidation = $cell->getDataValidation();
27
28 12
        if (!$dataValidation->getAllowBlank() && ($cellValue === null || $cellValue === '')) {
29 1
            return false;
30
        }
31
32 12
        $returnValue = false;
33 12
        $type = $dataValidation->getType();
34 12
        if ($type === DataValidation::TYPE_LIST) {
35 8
            $returnValue = $this->isValueInList($cell);
36 7
        } elseif ($type === DataValidation::TYPE_WHOLE) {
37 6
            if (!is_numeric($cellValue) || fmod((float) $cellValue, 1) != 0) {
38 3
                $returnValue = false;
39
            } else {
40 6
                $returnValue = $this->numericOperator($dataValidation, (int) $cellValue, $cell);
41
            }
42 4
        } elseif ($type === DataValidation::TYPE_DECIMAL || $type === DataValidation::TYPE_DATE || $type === DataValidation::TYPE_TIME) {
43 3
            if (!is_numeric($cellValue)) {
44 3
                $returnValue = false;
45
            } else {
46 3
                $returnValue = $this->numericOperator($dataValidation, (float) $cellValue, $cell);
47
            }
48 4
        } elseif ($type === DataValidation::TYPE_TEXTLENGTH) {
49 3
            $returnValue = $this->numericOperator($dataValidation, mb_strlen($cell->getValueString()), $cell);
50
        }
51
52 12
        return $returnValue;
53
    }
54
55
    private const TWO_FORMULAS = [DataValidation::OPERATOR_BETWEEN, DataValidation::OPERATOR_NOTBETWEEN];
56
57 6
    private static function evaluateNumericFormula(mixed $formula, Cell $cell): mixed
58
    {
59 6
        if (!is_numeric($formula)) {
60 2
            $calculation = Calculation::getInstance($cell->getWorksheet()->getParent());
61
62
            try {
63 2
                $result = $calculation
64 2
                    ->calculateFormula("=$formula", $cell->getCoordinate(), $cell);
65 1
                while (is_array($result)) {
66 1
                    $result = array_pop($result);
67
                }
68 1
                $formula = $result;
69 1
            } catch (Exception) {
70
                // do nothing
71
            }
72
        }
73
74 6
        return $formula;
75
    }
76
77 6
    private function numericOperator(DataValidation $dataValidation, int|float $cellValue, Cell $cell): bool
78
    {
79 6
        $operator = $dataValidation->getOperator();
80 6
        $formula1 = self::evaluateNumericFormula(
81 6
            $dataValidation->getFormula1(),
82 6
            $cell
83 6
        );
84
85 6
        $formula2 = 0;
86 6
        if (in_array($operator, self::TWO_FORMULAS, true)) {
87 5
            $formula2 = self::evaluateNumericFormula(
88 5
                $dataValidation->getFormula2(),
89 5
                $cell
90 5
            );
91
        }
92
93 6
        return match ($operator) {
94 5
            DataValidation::OPERATOR_BETWEEN => $cellValue >= $formula1 && $cellValue <= $formula2,
95 3
            DataValidation::OPERATOR_NOTBETWEEN => $cellValue < $formula1 || $cellValue > $formula2,
96 4
            DataValidation::OPERATOR_EQUAL => $cellValue == $formula1,
97 3
            DataValidation::OPERATOR_NOTEQUAL => $cellValue != $formula1,
98 3
            DataValidation::OPERATOR_LESSTHAN => $cellValue < $formula1,
99 3
            DataValidation::OPERATOR_LESSTHANOREQUAL => $cellValue <= $formula1,
100 3
            DataValidation::OPERATOR_GREATERTHAN => $cellValue > $formula1,
101 3
            DataValidation::OPERATOR_GREATERTHANOREQUAL => $cellValue >= $formula1,
102 6
            default => false,
103 6
        };
104
    }
105
106
    /**
107
     * Does this cell contain valid value, based on list?
108
     *
109
     * @param Cell $cell Cell to check the value
110
     */
111 8
    private function isValueInList(Cell $cell): bool
112
    {
113 8
        $cellValueString = $cell->getValueString();
114 8
        $dataValidation = $cell->getDataValidation();
115
116 8
        $formula1 = $dataValidation->getFormula1();
117 8
        if (!empty($formula1)) {
118
            // inline values list
119 8
            if ($formula1[0] === '"') {
120 4
                return in_array(strtolower($cellValueString), explode(',', strtolower(trim($formula1, '"'))), true);
121
            }
122 8
            $calculation = Calculation::getInstance($cell->getWorksheet()->getParent());
123
124
            try {
125 8
                $result = $calculation->calculateFormula("=$formula1", $cell->getCoordinate(), $cell);
126 8
                $result = is_array($result) ? Functions::flattenArray($result) : [$result];
127 8
                foreach ($result as $oneResult) {
128 8
                    if (is_scalar($oneResult) && strcasecmp((string) $oneResult, $cellValueString) === 0) {
129 8
                        return true;
130
                    }
131
                }
132 1
            } catch (Exception) {
133
                // do nothing
134
            }
135
136 8
            return false;
137
        }
138
139 1
        return true;
140
    }
141
}
142