Passed
Push — master ( 653645...08f2f1 )
by
unknown
16:09 queued 04:49
created

DataValidator::evaluateNumericFormula()   A

Complexity

Conditions 4
Paths 7

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.0119

Importance

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