Passed
Push — main ( 58bb3a...1a0bc1 )
by Thierry
01:57
created

SelectQueryTrait::processSelectLength()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 1
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Lagdo\DbAdmin\Db\Traits;
4
5
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
6
7
trait SelectQueryTrait
8
{
9
    /**
10
     * Filter length value including enums
11
     *
12
     * @param string $length
13
     *
14
     * @return string
15
     */
16
    public function processLength(string $length): string
17
    {
18
        if (!$length) {
19
            return '';
20
        }
21
        $enumLength = $this->driver->enumLength();
22
        return (\preg_match("~^\\s*\\(?\\s*$enumLength(?:\\s*,\\s*$enumLength)*+\\s*\\)?\\s*\$~", $length) &&
23
        \preg_match_all("~$enumLength~", $length, $matches) ? '(' . \implode(',', $matches[0]) . ')' :
24
            \preg_replace('~^[0-9].*~', '(\0)', \preg_replace('~[^-0-9,+()[\]]~', '', $length))
25
        );
26
    }
27
28
    /**
29
     * Create SQL string from field type
30
     *
31
     * @param TableFieldEntity $field
32
     * @param string $collate
33
     *
34
     * @return string
35
     */
36
    private function processType(TableFieldEntity $field, string $collate = 'COLLATE'): string
37
    {
38
        $values = [
39
            'unsigned' => $field->unsigned,
40
            'collation' => $field->collation,
41
        ];
42
        return ' ' . $field->type . $this->processLength($field->length) .
43
            (\preg_match($this->driver->numberRegex(), $field->type) &&
44
            \in_array($values['unsigned'], $this->driver->unsigned()) ?
45
                " {$values['unsigned']}" : '') . (\preg_match('~char|text|enum|set~', $field->type) &&
46
            $values['collation'] ? " $collate " . $this->driver->quote($values['collation']) : '');
47
    }
48
49
    /**
50
     * Create SQL string from field
51
     *
52
     * @param TableFieldEntity $field Basic field information
53
     * @param TableFieldEntity $typeField Information about field type
54
     *
55
     * @return array
56
     */
57
    public function processField(TableFieldEntity $field, TableFieldEntity $typeField): array
58
    {
59
        return [
60
            $this->driver->escapeId(trim($field->name)),
61
            $this->processType($typeField),
62
            ($field->null ? ' NULL' : ' NOT NULL'), // NULL for timestamp
63
            $this->driver->defaultValue($field),
64
            (\preg_match('~timestamp|datetime~', $field->type) && $field->onUpdate ?
65
                " ON UPDATE {$field->onUpdate}" : ''),
66
            ($this->driver->support('comment') && $field->comment != '' ?
67
                ' COMMENT ' . $this->driver->quote($field->comment) : ''),
68
            ($field->autoIncrement ? $this->driver->autoIncrement() : null),
69
        ];
70
    }
71
72
    /**
73
     * Apply SQL function
74
     *
75
     * @param string $function
76
     * @param string $column escaped column identifier
77
     *
78
     * @return string
79
     */
80
    public function applySqlFunction(string $function, string $column)
81
    {
82
        return ($function ? ($function == 'unixepoch' ?
83
            "DATETIME($column, '$function')" : ($function == 'count distinct' ?
84
                'COUNT(DISTINCT ' : strtoupper("$function(")) . "$column)") : $column);
85
    }
86
87
    /**
88
     * Process columns box in select
89
     *
90
     * @return array
91
     */
92
    public function processSelectColumns(): array
93
    {
94
        $select = []; // select expressions, empty for *
95
        $group = []; // expressions without aggregation - will be used for GROUP BY if an aggregation function is used
96
        foreach ((array) $this->input->values['columns'] as $key => $val) {
97
            if ($val['fun'] == 'count' ||
98
                ($val['col'] != '' && (!$val['fun'] ||
99
                        \in_array($val['fun'], $this->driver->functions()) ||
100
                        \in_array($val['fun'], $this->driver->grouping())))) {
101
                $select[$key] = $this->applySqlFunction(
102
                    $val['fun'],
103
                    ($val['col'] != '' ? $this->driver->escapeId($val['col']) : '*')
104
                );
105
                if (!in_array($val['fun'], $this->driver->grouping())) {
106
                    $group[] = $select[$key];
107
                }
108
            }
109
        }
110
        return [$select, $group];
111
    }
112
113
    /**
114
     * Process search box in select
115
     *
116
     * @param array $fields
117
     * @param array $indexes
118
     *
119
     * @return array
120
     */
121
    public function processSelectSearch(array $fields, array $indexes): array
122
    {
123
        $expressions = [];
124
        foreach ($indexes as $i => $index) {
125
            if ($index->type == 'FULLTEXT' && $this->input->values['fulltext'][$i] != '') {
126
                $columns = \array_map(function ($column) {
127
                    return $this->driver->escapeId($column);
128
                }, $index->columns);
129
                $expressions[] = 'MATCH (' . \implode(', ', $columns) . ') AGAINST (' .
130
                    $this->driver->quote($this->input->values['fulltext'][$i]) .
131
                    (isset($this->input->values['boolean'][$i]) ? ' IN BOOLEAN MODE' : '') . ')';
132
            }
133
        }
134
        foreach ((array) $this->input->values['where'] as $key => $val) {
135
            if ("$val[col]$val[val]" != '' && in_array($val['op'], $this->driver->operators())) {
136
                $prefix = '';
137
                $cond = " $val[op]";
138
                if (\preg_match('~IN$~', $val['op'])) {
139
                    $in = $this->processLength($val['val']);
140
                    $cond .= ' ' . ($in != '' ? $in : '(NULL)');
141
                } elseif ($val['op'] == 'SQL') {
142
                    $cond = " $val[val]"; // SQL injection
143
                } elseif ($val['op'] == 'LIKE %%') {
144
                    $cond = ' LIKE ' . $this->_processInput($fields[$val['col']], "%$val[val]%");
0 ignored issues
show
Bug introduced by
It seems like _processInput() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

144
                    $cond = ' LIKE ' . $this->/** @scrutinizer ignore-call */ _processInput($fields[$val['col']], "%$val[val]%");
Loading history...
145
                } elseif ($val['op'] == 'ILIKE %%') {
146
                    $cond = ' ILIKE ' . $this->_processInput($fields[$val['col']], "%$val[val]%");
147
                } elseif ($val['op'] == 'FIND_IN_SET') {
148
                    $prefix = "$val[op](" . $this->driver->quote($val['val']) . ', ';
149
                    $cond = ')';
150
                } elseif (!\preg_match('~NULL$~', $val['op'])) {
151
                    $cond .= ' ' . $this->_processInput($fields[$val['col']], $val['val']);
152
                }
153
                if ($val['col'] != '') {
154
                    $expressions[] = $prefix . $this->driver->convertSearch(
155
                            $this->driver->escapeId($val['col']),
156
                            $val,
157
                            $fields[$val['col']]
158
                        ) . $cond;
159
                } else {
160
                    // find anywhere
161
                    $cols = [];
162
                    foreach ($fields as $name => $field) {
163
                        if ((\preg_match('~^[-\d.' . (\preg_match('~IN$~', $val['op']) ? ',' : '') . ']+$~', $val['val']) ||
164
                                !\preg_match('~' . $this->driver->numberRegex() . '|bit~', $field->type)) &&
165
                            (!\preg_match("~[\x80-\xFF]~", $val['val']) || \preg_match('~char|text|enum|set~', $field->type)) &&
166
                            (!\preg_match('~date|timestamp~', $field->type) || \preg_match('~^\d+-\d+-\d+~', $val['val']))
167
                        ) {
168
                            $cols[] = $prefix . $this->driver->convertSearch($this->driver->escapeId($name), $val, $field) . $cond;
169
                        }
170
                    }
171
                    $expressions[] = ($cols ? '(' . \implode(' OR ', $cols) . ')' : '1 = 0');
172
                }
173
            }
174
        }
175
        return $expressions;
176
    }
177
178
    /**
179
     * Process order box in select
180
     *
181
     * @return array
182
     */
183
    public function processSelectOrder(): array
184
    {
185
        $expressions = [];
186
        foreach ((array) $this->input->values['order'] as $key => $val) {
187
            if ($val != '') {
188
                $regexp = '~^((COUNT\(DISTINCT |[A-Z0-9_]+\()(`(?:[^`]|``)+`|"(?:[^"]|"")+")\)|COUNT\(\*\))$~';
189
                $expressions[] = (\preg_match($regexp, $val) ? $val : $this->driver->escapeId($val)) . //! MS SQL uses []
190
                    (isset($this->input->values['desc'][$key]) ? ' DESC' : '');
191
            }
192
        }
193
        return $expressions;
194
    }
195
196
    /**
197
     * Process limit box in select
198
     *
199
     * @return int
200
     */
201
    public function processSelectLimit(): int
202
    {
203
        return (isset($this->input->values['limit']) ? intval($this->input->values['limit']) : 50);
204
    }
205
206
    /**
207
     * Process length box in select
208
     *
209
     * @return int
210
     */
211
    public function processSelectLength(): int
212
    {
213
        return (isset($this->input->values['text_length']) ? intval($this->input->values['text_length']) : 100);
214
    }
215
216
    /**
217
     * Process extras in select form
218
     *
219
     * @param array $where AND conditions
220
     * @param array $foreignKeys
221
     *
222
     * @return bool
223
     */
224
    // public function processSelectEmail(array $where, array $foreignKeys)
225
    // {
226
    //     return false;
227
    // }
228
}
229