Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
 
3
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
4
 
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
7
use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
8
 
9
abstract class DatabaseAbstract
10
{
11
    abstract public static function evaluate(array $database, array|null|int|string $field, array $criteria): null|float|int|string;
12
 
13
    /**
14
     * fieldExtract.
15
     *
16
     * Extracts the column ID to use for the data field.
17
     *
18
     * @param mixed[] $database The range of cells that makes up the list or database.
19
     *                                        A database is a list of related data in which rows of related
20
     *                                        information are records, and columns of data are fields. The
21
     *                                        first row of the list contains labels for each column.
22
     * @param mixed $field Indicates which column is used in the function. Enter the
23
     *                                        column label enclosed between double quotation marks, such as
24
     *                                        "Age" or "Yield," or a number (without quotation marks) that
25
     *                                        represents the position of the column within the list: 1 for
26
     *                                        the first column, 2 for the second column, and so on.
27
     */
28
    protected static function fieldExtract(array $database, mixed $field): ?int
29
    {
30
        $field = strtoupper(Functions::flattenSingleValue($field) ?? '');
31
        if ($field === '') {
32
            return null;
33
        }
34
 
35
        $fieldNames = array_map('strtoupper', array_shift($database));
36
        if (is_numeric($field)) {
37
            $field = (int) $field - 1;
38
            if ($field < 0 || $field >= count($fieldNames)) {
39
                return null;
40
            }
41
 
42
            return $field;
43
        }
44
        $key = array_search($field, array_values($fieldNames), true);
45
 
46
        return ($key !== false) ? (int) $key : null;
47
    }
48
 
49
    /**
50
     * filter.
51
     *
52
     * Parses the selection criteria, extracts the database rows that match those criteria, and
53
     * returns that subset of rows.
54
     *
55
     * @param mixed[] $database The range of cells that makes up the list or database.
56
     *                                        A database is a list of related data in which rows of related
57
     *                                        information are records, and columns of data are fields. The
58
     *                                        first row of the list contains labels for each column.
59
     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
60
     *                                        You can use any range for the criteria argument, as long as it
61
     *                                        includes at least one column label and at least one cell below
62
     *                                        the column label in which you specify a condition for the
63
     *                                        column.
64
     *
65
     * @return mixed[]
66
     */
67
    protected static function filter(array $database, array $criteria): array
68
    {
69
        $fieldNames = array_shift($database);
70
        $criteriaNames = array_shift($criteria);
71
 
72
        //    Convert the criteria into a set of AND/OR conditions with [:placeholders]
73
        $query = self::buildQuery($criteriaNames, $criteria);
74
 
75
        //    Loop through each row of the database
76
        return self::executeQuery($database, $query, $criteriaNames, $fieldNames);
77
    }
78
 
79
    protected static function getFilteredColumn(array $database, ?int $field, array $criteria): array
80
    {
81
        //    reduce the database to a set of rows that match all the criteria
82
        $database = self::filter($database, $criteria);
83
        $defaultReturnColumnValue = ($field === null) ? 1 : null;
84
 
85
        //    extract an array of values for the requested column
86
        $columnData = [];
87
        foreach ($database as $rowKey => $row) {
88
            $keys = array_keys($row);
89
            $key = $keys[$field] ?? null;
90
            $columnKey = $key ?? 'A';
91
            $columnData[$rowKey][$columnKey] = $row[$key] ?? $defaultReturnColumnValue;
92
        }
93
 
94
        return $columnData;
95
    }
96
 
97
    private static function buildQuery(array $criteriaNames, array $criteria): string
98
    {
99
        $baseQuery = [];
100
        foreach ($criteria as $key => $criterion) {
101
            foreach ($criterion as $field => $value) {
102
                $criterionName = $criteriaNames[$field];
103
                if ($value !== null) {
104
                    $condition = self::buildCondition($value, $criterionName);
105
                    $baseQuery[$key][] = $condition;
106
                }
107
            }
108
        }
109
 
110
        $rowQuery = array_map(
111
            fn ($rowValue): string => (count($rowValue) > 1) ? 'AND(' . implode(',', $rowValue) . ')' : ($rowValue[0] ?? ''), // @phpstan-ignore-line
112
            $baseQuery
113
        );
114
 
115
        return (count($rowQuery) > 1) ? 'OR(' . implode(',', $rowQuery) . ')' : ($rowQuery[0] ?? '');
116
    }
117
 
118
    private static function buildCondition(mixed $criterion, string $criterionName): string
119
    {
120
        $ifCondition = Functions::ifCondition($criterion);
121
 
122
        // Check for wildcard characters used in the condition
123
        $result = preg_match('/(?<operator>[^"]*)(?<operand>".*[*?].*")/ui', $ifCondition, $matches);
124
        if ($result !== 1) {
125
            return "[:{$criterionName}]{$ifCondition}";
126
        }
127
 
128
        $trueFalse = ($matches['operator'] !== '<>');
129
        $wildcard = WildcardMatch::wildcard($matches['operand']);
130
        $condition = "WILDCARDMATCH([:{$criterionName}],{$wildcard})";
131
        if ($trueFalse === false) {
132
            $condition = "NOT({$condition})";
133
        }
134
 
135
        return $condition;
136
    }
137
 
138
    private static function executeQuery(array $database, string $query, array $criteria, array $fields): array
139
    {
140
        foreach ($database as $dataRow => $dataValues) {
141
            //    Substitute actual values from the database row for our [:placeholders]
142
            $conditions = $query;
143
            foreach ($criteria as $criterion) {
144
                $conditions = self::processCondition($criterion, $fields, $dataValues, $conditions);
145
            }
146
 
147
            //    evaluate the criteria against the row data
148
            $result = Calculation::getInstance()->_calculateFormulaValue('=' . $conditions);
149
 
150
            //    If the row failed to meet the criteria, remove it from the database
151
            if ($result !== true) {
152
                unset($database[$dataRow]);
153
            }
154
        }
155
 
156
        return $database;
157
    }
158
 
159
    private static function processCondition(string $criterion, array $fields, array $dataValues, string $conditions): string
160
    {
161
        $key = array_search($criterion, $fields, true);
162
 
163
        $dataValue = 'NULL';
164
        if (is_bool($dataValues[$key])) {
165
            $dataValue = ($dataValues[$key]) ? 'TRUE' : 'FALSE';
166
        } elseif ($dataValues[$key] !== null) {
167
            $dataValue = $dataValues[$key];
168
            // escape quotes if we have a string containing quotes
169
            if (is_string($dataValue) && str_contains($dataValue, '"')) {
170
                $dataValue = str_replace('"', '""', $dataValue);
171
            }
172
            $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue;
173
        }
174
 
175
        return str_replace('[:' . $criterion . ']', $dataValue, $conditions);
176
    }
177
}