Issues (28)

src/Db/Facades/SelectFacade.php (1 issue)

Severity
1
<?php
2
3
namespace Lagdo\DbAdmin\Db\Facades;
4
5
use Exception;
6
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
7
8
use function count;
9
use function str_replace;
10
use function compact;
11
use function preg_match;
12
use function microtime;
13
use function trim;
14
use function md5;
15
use function strlen;
16
use function strpos;
17
18
/**
19
 * Facade to table select functions
20
 */
21
class SelectFacade extends AbstractFacade
22
{
23
    use Traits\SelectTrait;
0 ignored issues
show
The trait Lagdo\DbAdmin\Db\Facades\Traits\SelectTrait requires some properties which are not provided by Lagdo\DbAdmin\Db\Facades\SelectFacade: $privileges, $oid, $str, $columns, $type
Loading history...
24
25
    /**
26
     * @param array $select
27
     * @param array $columns
28
     * @param array $indexes
29
     * @param int $limit
30
     * @param int $textLength
31
     * @param array $queryOptions
32
     *
33
     * @return array
34
     */
35
    private function getAllOptions(array $select, array $columns, array $indexes,
36
        int $limit, int $textLength, array $queryOptions): array
37
    {
38
        return [
39
            'columns' => $this->getColumnsOptions($select, $columns, $queryOptions),
40
            'filters' => $this->getFiltersOptions($columns, $indexes, $queryOptions),
41
            'sorting' => $this->getSortingOptions($columns, $queryOptions),
42
            'limit' => $this->getLimitOptions($limit),
43
            'length' => $this->getLengthOptions($textLength),
44
            // 'action' => $this->getActionOptions($indexes),
45
        ];
46
    }
47
48
    /**
49
     * Find out foreign keys for each column
50
     *
51
     * @param string $table
52
     *
53
     * @return array
54
     */
55
    private function foreignKeys(string $table): array
56
    {
57
        $keys = [];
58
        foreach ($this->driver->foreignKeys($table) as $foreignKey) {
59
            foreach ($foreignKey->source as $val) {
60
                $keys[$val][] = $foreignKey;
61
            }
62
        }
63
        return $keys;
64
    }
65
66
    /**
67
     * Get required data for create/update on tables
68
     *
69
     * @param string $table The table name
70
     * @param array $queryOptions The query options
71
     *
72
     * @return array
73
     * @throws Exception
74
     */
75
    private function prepareSelect(string $table, array &$queryOptions = []): array
76
    {
77
        $page = $this->setDefaultOptions($queryOptions);
78
        $this->utils->input->setValues($queryOptions);
79
80
        // From select.inc.php
81
        $fields = $this->driver->fields($table);
82
        [, $columns, $textLength] = $this->getFieldsOptions($fields);
83
        if (!$columns && $this->driver->support("table")) {
84
            throw new Exception($this->utils->trans->lang('Unable to select the table') .
85
                ($fields ? "." : ": " . $this->driver->error()));
86
        }
87
88
        $indexes = $this->driver->indexes($table);
89
        $foreignKeys = $this->foreignKeys($table);
90
        [$select, $group] = $this->admin->processSelectColumns();
91
        $where = $this->admin->processSelectWhere($fields, $indexes);
92
        $order = $this->admin->processSelectOrder();
93
        $limit = $this->admin->processSelectLimit();
94
        $tableStatus = $this->driver->tableStatusOrName($table);
95
        $unselected = $this->setPrimaryKey($indexes, $select, $tableStatus);
96
        $tableName = $this->admin->tableName($tableStatus);
97
98
        // $set = null;
99
        // if(isset($rights["insert"]) || !this->driver->support("table")) {
100
        //     $set = "";
101
        //     foreach((array) $queryOptions["where"] as $val) {
102
        //         if($foreignKeys[$val["col"]] && count($foreignKeys[$val["col"]]) == 1 && ($val["op"] == "="
103
        //             || (!$val["op"] && !preg_match('~[_%]~', $val["val"])) // LIKE in Editor
104
        //         )) {
105
        //             $set .= "&set" . urlencode("[" . $this->driver->bracketEscape($val["col"]) . "]") . "=" . urlencode($val["val"]);
106
        //         }
107
        //     }
108
        // }
109
        // $this->admin->selectLinks($tableStatus, $set);
110
111
        // if($page == "last")
112
        // {
113
        //     $isGroup = count($group) < count($select);
114
        //     $found_rows = $this->driver->result($this->driver->getRowCountQuery($table, $where, $isGroup, $group));
115
        //     $page = \floor(\max(0, $found_rows - 1) / $limit);
116
        // }
117
118
        $options = $this->getAllOptions($select, $columns,
119
            $indexes, $limit, $textLength, $queryOptions);
120
        $entity = $this->getSelectEntity($table, $columns, $fields,
121
            $select, $group, $where, $order, $unselected, $limit, $page);
122
        $query = $this->driver->buildSelectQuery($entity);
123
        // From adminer.inc.php
124
        $query = str_replace("\n", " ", $query);
125
126
        return [$options, $query, $select, $fields, $foreignKeys, $columns, $indexes,
127
            $where, $group, $order, $limit, $page, $textLength, $tableName, $unselected];
128
    }
129
130
    /**
131
     * Get required data for create/update on tables
132
     *
133
     * @param string $table The table name
134
     * @param array $queryOptions The query options
135
     *
136
     * @return array
137
     * @throws Exception
138
     */
139
    public function getSelectData(string $table, array $queryOptions = []): array
140
    {
141
        [$options, $query, , , , , , , , , $limit, $page] = $this->prepareSelect($table, $queryOptions);
142
        $query = $this->utils->str->html($query);
143
144
        return compact('options', 'query', 'limit', 'page');
145
    }
146
147
    /**
148
     * @param string $query
149
     * @param int $page
150
     *
151
     * @return array
152
     */
153
    private function executeSelect(string $query, int $page): array
154
    {
155
        // From driver.inc.php
156
        $statement = $this->driver->execute($query);
157
        // From adminer.inc.php
158
159
        if (!$statement) {
160
            return ['error' => $this->driver->error()];
161
        }
162
        // From select.inc.php
163
        $rows = [];
164
        while (($row = $statement->fetchAssoc())) {
165
            if ($page && $this->driver->jush() == "oracle") {
166
                unset($row["RNUM"]);
167
            }
168
            $rows[] = $row;
169
        }
170
171
        return [$rows, 0];
172
    }
173
174
    /**
175
     * @param array $rows
176
     * @param array $select
177
     * @param array $fields
178
     * @param array $unselected
179
     * @param array $queryOptions
180
     *
181
     * @return array
182
     */
183
    private function getResultHeaders(array $rows, array $select, array $fields, array $unselected, array $queryOptions): array
184
    {
185
        // Results headers
186
        $headers = [
187
            '', // !$group && $select ? '' : lang('Modify');
188
        ];
189
        $names = [];
190
        // $functions = [];
191
        reset($select);
192
        $rank = 1;
193
        foreach ($rows[0] as $key => $value) {
194
            $header = [];
195
            if (!isset($unselected[$key])) {
196
                $value = $queryOptions["columns"][key($select)] ?? [];
197
                $fun = $value["fun"] ?? '';
198
                $field = $fields[$select ? ($value ? $value["col"] : current($select)) : $key];
199
                $name = ($field ? $this->admin->fieldName($field, $rank) : ($fun ? "*" : $key));
200
                $header = compact('value', 'field', 'name');
201
                if ($name != "") {
202
                    $rank++;
203
                    $names[$key] = $name;
204
                    $column = $this->driver->escapeId($key);
205
                    // $href = remove_from_uri('(order|desc)[^=]*|page') . '&order%5B0%5D=' . urlencode($key);
206
                    // $desc = "&desc%5B0%5D=1";
207
                    $header['column'] = $column;
208
                    $header['key'] = $this->utils->str->html($this->driver->bracketEscape($key));
209
                    $header['sql'] = $this->admin->applySqlFunction($fun, $name); //! columns looking like functions
210
                }
211
                // $functions[$key] = $fun;
212
                next($select);
213
            }
214
            $headers[] = $header;
215
        }
216
        return [$headers, $names];
217
    }
218
219
    /**
220
     * @param array $rows
221
     * @param array $queryOptions
222
     *
223
     * @return array
224
     */
225
    /*private function getValuesLengths(array $rows, array $queryOptions): array
226
    {
227
        $lengths = [];
228
        if($queryOptions["modify"])
229
        {
230
            foreach($rows as $row)
231
            {
232
                foreach($row as $key => $value)
233
                {
234
                    $lengths[$key] = \max($lengths[$key], \min(40, strlen(\utf8_decode($value))));
235
                }
236
            }
237
        }
238
        return $lengths;
239
    }*/
240
241
    /**
242
     * @param array $row
243
     * @param array $indexes
244
     *
245
     * @return array
246
     */
247
    private function getUniqueIds(array $row, array $indexes): array
248
    {
249
        $uniqueIds = $this->admin->uniqueIds($row, $indexes);
250
        if (empty($uniqueIds)) {
251
            $pattern = '~^(COUNT\((\*|(DISTINCT )?`(?:[^`]|``)+`)\)|(AVG|GROUP_CONCAT|MAX|MIN|SUM)\(`(?:[^`]|``)+`\))$~';
252
            foreach ($row as $key => $value) {
253
                if (!preg_match($pattern, $key)) {
254
                    //! columns looking like functions
255
                    $uniqueIds[$key] = $value;
256
                }
257
            }
258
        }
259
        return $uniqueIds;
260
    }
261
262
    /**
263
     * @param array $row
264
     * @param array $fields
265
     * @param array $indexes
266
     *
267
     * @return array
268
     */
269
    private function getRowIds(array $row, array $fields, array $indexes): array
270
    {
271
        $uniqueIds = $this->getUniqueIds($row, $indexes);
272
        // Unique identifier to edit returned data.
273
        // $unique_idf = "";
274
        $rowIds = ['where' => [], 'null' => []];
275
        foreach ($uniqueIds as $key => $value) {
276
            $key = trim($key);
277
            $type = '';
278
            $collation = '';
279
            if (isset($fields[$key])) {
280
                $type = $fields[$key]->type;
281
                $collation = $fields[$key]->collation;
282
            }
283
            if (($this->driver->jush() == "sql" || $this->driver->jush() == "pgsql") &&
284
                preg_match('~char|text|enum|set~', $type) && strlen($value) > 64) {
285
                $key = (strpos($key, '(') ? $key : $this->driver->escapeId($key)); //! columns looking like functions
286
                $key = "MD5(" . ($this->driver->jush() != 'sql' || preg_match("~^utf8~", $collation) ?
287
                        $key : "CONVERT($key USING " . $this->driver->charset() . ")") . ")";
288
                $value = md5($value);
289
            }
290
            if ($value !== null) {
291
                $rowIds['where'][$this->driver->bracketEscape($key)] = $value;
292
            } else {
293
                $rowIds['null'][] = $this->driver->bracketEscape($key);
294
            }
295
            // $unique_idf .= "&" . ($value !== null ? \urlencode("where[" . $this->driver->bracketEscape($key) . "]") .
296
            //     "=" . \urlencode($value) : \urlencode("null[]") . "=" . \urlencode($key));
297
        }
298
        return $rowIds;
299
    }
300
301
    /**
302
     * @param array $row
303
     * @param array $fields
304
     * @param array $names
305
     * @param int $textLength
306
     *
307
     * @return array
308
     */
309
    private function getRowColumns(array $row, array $fields, array $names, int $textLength): array
310
    {
311
        $cols = [];
312
        foreach ($row as $key => $value) {
313
            if (isset($names[$key])) {
314
                $field = $fields[$key] ?? new TableFieldEntity();
315
                $value = $this->driver->value($value, $field);
316
                /*if ($value != "" && (!isset($email_fields[$key]) || $email_fields[$key] != "")) {
317
                    //! filled e-mails can be contained on other pages
318
                    $email_fields[$key] = ($this->admin->isMail($value) ? $names[$key] : "");
319
                }*/
320
                $cols[] = [
321
                    // 'id',
322
                    'text' => preg_match('~text|lob~', $field->type),
323
                    'value' => $this->admin->selectValue($field, $value, $textLength),
324
                    // 'editable' => false,
325
                ];
326
            }
327
        }
328
        return $cols;
329
    }
330
331
    /**
332
     * Get required data for create/update on tables
333
     *
334
     * @param string $table The table name
335
     * @param array $queryOptions The query options
336
     *
337
     * @return int
338
     */
339
    public function countSelect(string $table, array $queryOptions): int
340
    {
341
        [, , $select, , , , , $where, $group] = $this->prepareSelect($table, $queryOptions);
342
343
        try {
344
            $isGroup = count($group) < count($select);
345
            $query = $this->driver->getRowCountQuery($table, $where, $isGroup, $group);
346
            return (int)$this->driver->result($query);
347
        } catch(Exception $_) {
348
            return -1;
349
        }
350
    }
351
352
    /**
353
     * Get required data for create/update on tables
354
     *
355
     * @param string $table The table name
356
     * @param array $queryOptions The query options
357
     *
358
     * @return array
359
     * @throws Exception
360
     */
361
    public function execSelect(string $table, array $queryOptions): array
362
    {
363
        [, $query, $select, $fields, , , $indexes, $where, $group, , $limit, $page,
364
            $textLength, , $unselected] = $this->prepareSelect($table, $queryOptions);
365
366
        [$rows, $duration] = $this->executeSelect($query, $page);
367
        if (!$rows) {
368
            return ['message' => $this->utils->trans->lang('No rows.')];
369
        }
370
        // $backward_keys = $this->driver->backwardKeys($table, $tableName);
371
        // lengths = $this->getValuesLengths($rows, $queryOptions);
372
373
        [$headers, $names] = $this->getResultHeaders($rows, $select, $fields, $unselected, $queryOptions);
374
375
        $results = [];
376
        foreach ($rows as $row) {
377
            // Unique identifier to edit returned data.
378
            $rowIds = $this->getRowIds($row, $fields, $indexes);
379
            $cols = $this->getRowColumns($row, $fields, $names, $textLength);
380
            $results[] = ['ids' => $rowIds, 'cols' => $cols];
381
        }
382
383
        $rows = $results;
384
        $message = null;
385
        return compact('duration', 'headers', 'query', 'rows', 'limit', 'message');
386
    }
387
}
388