Passed
Push — main ( 561b46...364dc0 )
by Thierry
02:06
created

TableSelectAdmin::getFiltersOptions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 9
c 1
b 0
f 0
nc 3
nop 3
dl 0
loc 13
rs 9.9666
1
<?php
2
3
namespace Lagdo\DbAdmin\DbAdmin;
4
5
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
6
use Lagdo\DbAdmin\Driver\Entity\TableSelectEntity;
7
8
use Exception;
9
10
use function intval;
11
use function count;
12
use function str_replace;
13
use function compact;
14
use function preg_match;
15
use function microtime;
16
use function html_entity_decode;
17
use function strip_tags;
18
use function array_flip;
19
use function in_array;
20
use function trim;
21
use function md5;
22
use function strlen;
23
use function strpos;
24
25
/**
26
 * Admin table select functions
27
 */
28
class TableSelectAdmin extends AbstractAdmin
29
{
30
    /**
31
     * Print columns box in select
32
     * @param array $select Result of processSelectColumns()[0]
33
     * @param array $columns Selectable columns
34
     * @param array $options
35
     * @return array
36
     */
37
    private function getColumnsOptions(array $select, array $columns, array $options): array
38
    {
39
        return [
40
            'select' => $select,
41
            'values' => (array)$options["columns"],
42
            'columns' => $columns,
43
            'functions' => $this->driver->functions(),
44
            'grouping' => $this->driver->grouping(),
45
        ];
46
    }
47
48
    /**
49
     * Print search box in select
50
     *
51
     * @param array $columns Selectable columns
52
     * @param array $indexes
53
     * @param array $options
54
     *
55
     * @return array
56
     */
57
    private function getFiltersOptions(array $columns, array $indexes, array $options): array
58
    {
59
        $fulltexts = [];
60
        foreach ($indexes as $i => $index) {
61
            $fulltexts[$i] = $index->type == "FULLTEXT" ? $this->util->html($options["fulltext"][$i]) : '';
62
        }
63
        return [
64
            // 'where' => $where,
65
            'values' => (array)$options["where"],
66
            'columns' => $columns,
67
            'indexes' => $indexes,
68
            'operators' => $this->driver->operators(),
69
            'fulltexts' => $fulltexts,
70
        ];
71
    }
72
73
    /**
74
     * Print order box in select
75
     *
76
     * @param array $columns Selectable columns
77
     * @param array $options
78
     *
79
     * @return array
80
     */
81
    private function getSortingOptions(array $columns, array $options): array
82
    {
83
        $values = [];
84
        $descs = (array)$options["desc"];
85
        foreach ((array)$options["order"] as $key => $value) {
86
            $values[] = [
87
                'col' => $value,
88
                'desc' => $descs[$key] ?? 0,
89
            ];
90
        }
91
        return [
92
            // 'order' => $order,
93
            'values' => $values,
94
            'columns' => $columns,
95
        ];
96
    }
97
98
    /**
99
     * Print limit box in select
100
     *
101
     * @param string $limit Result of processSelectLimit()
102
     *
103
     * @return array
104
     */
105
    private function getLimitOptions(string $limit): array
106
    {
107
        return ['value' => $this->util->html($limit)];
108
    }
109
110
    /**
111
     * Print text length box in select
112
     *
113
     * @param int $textLength Result of processSelectLength()
114
     *
115
     * @return array
116
     */
117
    private function getLengthOptions($textLength): array
118
    {
119
        return [
120
            'value' => $textLength === 0 ? 0 : $this->util->html($textLength),
121
        ];
122
    }
123
124
    /**
125
     * Print action box in select
126
     *
127
     * @param array $indexes
128
     *
129
     * @return array
130
     */
131
    // private function getActionOptions(array $indexes)
132
    // {
133
    //     $columns = [];
134
    //     foreach ($indexes as $index) {
135
    //         $current_key = \reset($index->columns);
136
    //         if ($index->type != "FULLTEXT" && $current_key) {
137
    //             $columns[$current_key] = 1;
138
    //         }
139
    //     }
140
    //     $columns[""] = 1;
141
    //     return ['columns' => $columns];
142
    // }
143
144
    /**
145
     * Print command box in select
146
     *
147
     * @return bool whether to print default commands
148
     */
149
    // private function getCommandOptions()
150
    // {
151
    //     return !$this->driver->isInformationSchema($this->driver->database());
152
    // }
153
154
    /**
155
     * Print import box in select
156
     *
157
     * @return bool whether to print default import
158
     */
159
    // private function getImportOptions()
160
    // {
161
    //     return !$this->driver->isInformationSchema($this->driver->database());
162
    // }
163
164
    /**
165
     * Print extra text in the end of a select form
166
     *
167
     * @param array $emailFields Fields holding e-mails
168
     * @param array $columns Selectable columns
169
     *
170
     * @return array
171
     */
172
    // private function getEmailOptions(array $emailFields, array $columns)
173
    // {
174
    // }
175
176
    /**
177
     * @param array $queryOptions
178
     *
179
     * @return int
180
     */
181
    private function setDefaultOptions(array &$queryOptions): int
182
    {
183
        $defaultOptions = [
184
            'columns' => [],
185
            'where' => [],
186
            'order' => [],
187
            'desc' => [],
188
            'fulltext' => [],
189
            'limit' => '50',
190
            'text_length' => '100',
191
            'page' => '1',
192
        ];
193
        foreach ($defaultOptions as $name => $value) {
194
            if (!isset($queryOptions[$name])) {
195
                $queryOptions[$name] = $value;
196
            }
197
        }
198
        $page = intval($queryOptions['page']);
199
        if ($page > 0) {
200
            $page -= 1; // Page numbers start at 0 here, instead of 1.
201
        }
202
        $queryOptions['page'] = $page;
203
        return $page;
204
    }
205
206
    /**
207
     * @param array $fields
208
     *
209
     * @return array
210
     */
211
    private function getFieldsOptions(array $fields): array
212
    {
213
        $rights = []; // privilege => 0
214
        $columns = []; // selectable columns
215
        $textLength = 0;
216
        foreach ($fields as $key => $field) {
217
            $name = $this->util->fieldName($field);
218
            if (isset($field->privileges["select"]) && $name != "") {
219
                $columns[$key] = html_entity_decode(strip_tags($name), ENT_QUOTES);
220
                if ($this->util->isShortable($field)) {
221
                    $textLength = $this->util->processSelectLength();
222
                }
223
            }
224
            $rights[] = $field->privileges;
225
        }
226
        return [$rights, $columns, $textLength];
227
    }
228
229
    /**
230
     * @param array $indexes
231
     * @param array $select
232
     * @param mixed $tableStatus
233
     *
234
     * @return array
235
     */
236
    private function setPrimaryKey(array &$indexes, array $select, $tableStatus): array
237
    {
238
        $primary = null;
239
        $unselected = [];
240
        foreach ($indexes as $index) {
241
            if ($index->type == "PRIMARY") {
242
                $primary = array_flip($index->columns);
243
                $unselected = ($select ? $primary : []);
244
                foreach ($unselected as $key => $val) {
245
                    if (in_array($this->driver->escapeId($key), $select)) {
246
                        unset($unselected[$key]);
247
                    }
248
                }
249
                break;
250
            }
251
        }
252
253
        $oid = $tableStatus->oid;
254
        if ($oid && !$primary) {
255
            /*$primary = */$unselected = [$oid => 0];
256
            $indexes[] = ["type" => "PRIMARY", "columns" => [$oid]];
257
        }
258
259
        return $unselected;
260
    }
261
262
    /**
263
     * @param array $select
264
     * @param array $columns
265
     * @param array $indexes
266
     * @param int $limit
267
     * @param int $textLength
268
     * @param array $queryOptions
269
     *
270
     * @return array
271
     */
272
    private function getAllOptions(array $select, array $columns, array $indexes,
273
        int $limit, int $textLength, array $queryOptions): array
274
    {
275
        return [
276
            'columns' => $this->getColumnsOptions($select, $columns, $queryOptions),
277
            'filters' => $this->getFiltersOptions($columns, $indexes, $queryOptions),
278
            'sorting' => $this->getSortingOptions($columns, $queryOptions),
279
            'limit' => $this->getLimitOptions($limit),
280
            'length' => $this->getLengthOptions($textLength),
281
            // 'action' => $this->getActionOptions($indexes),
282
        ];
283
    }
284
285
    /**
286
     * @param string $table
287
     * @param array $columns
288
     * @param array $fields
289
     * @param array $select
290
     * @param array $group
291
     * @param array $where
292
     * @param array $order
293
     * @param array $unselected
294
     * @param int $limit
295
     * @param int $page
296
     *
297
     * @return TableSelectEntity
298
     */
299
    private function getSelectEntity(string $table, array $columns, array $fields, array $select,
300
        array $group, array $where, array $order, array $unselected, int $limit, int $page): TableSelectEntity
301
    {
302
        $select2 = $select;
303
        $group2 = $group;
304
        if (empty($select2)) {
305
            $select2[] = "*";
306
            $convert_fields = $this->driver->convertFields($columns, $fields, $select);
307
            if ($convert_fields) {
308
                $select2[] = \substr($convert_fields, 2);
309
            }
310
        }
311
        foreach ($select as $key => $val) {
312
            $field = $fields[$this->driver->unescapeId($val)] ?? null;
313
            if ($field && ($as = $this->driver->convertField($field))) {
314
                $select2[$key] = "$as AS $val";
315
            }
316
        }
317
        $isGroup = count($group) < count($select);
318
        if (!$isGroup && !empty($unselected)) {
319
            foreach ($unselected as $key => $val) {
320
                $select2[] = $this->driver->escapeId($key);
321
                if (!empty($group2)) {
322
                    $group2[] = $this->driver->escapeId($key);
323
                }
324
            }
325
        }
326
327
        // From driver.inc.php
328
        return new TableSelectEntity($table, $select2, $where, $group2, $order, $limit, $page);
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 array
338
     * @throws Exception
339
     */
340
    private function prepareSelect(string $table, array &$queryOptions = []): array
341
    {
342
        $page = $this->setDefaultOptions($queryOptions);
343
        $this->util->input()->setValues($queryOptions);
0 ignored issues
show
Bug introduced by
The method setValues() does not exist on Lagdo\DbAdmin\Driver\InputInterface. ( Ignorable by Annotation )

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

343
        $this->util->input()->/** @scrutinizer ignore-call */ setValues($queryOptions);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
344
345
        // From select.inc.php
346
        $fields = $this->driver->fields($table);
347
        list(, $columns, $textLength) = $this->getFieldsOptions($fields);
348
        if (!$columns && $this->driver->support("table")) {
349
            throw new Exception($this->trans->lang('Unable to select the table') .
350
                ($fields ? "." : ": " . $this->driver->error()));
351
        }
352
353
        $indexes = $this->driver->indexes($table);
354
        $foreignKeys = $this->admin->columnForeignKeys($table);
355
        list($select, $group) = $this->util->processSelectColumns();
356
        $where = $this->util->processSelectSearch($fields, $indexes);
357
        $order = $this->util->processSelectOrder();
358
        $limit = $this->util->processSelectLimit();
359
        $tableStatus = $this->driver->tableStatusOrName($table);
360
        $unselected = $this->setPrimaryKey($indexes, $select, $tableStatus);
361
        $tableName = $this->util->tableName($tableStatus);
362
363
        // $set = null;
364
        // if(isset($rights["insert"]) || !support("table")) {
365
        //     $set = "";
366
        //     foreach((array) $queryOptions["where"] as $val) {
367
        //         if($foreignKeys[$val["col"]] && count($foreignKeys[$val["col"]]) == 1 && ($val["op"] == "="
368
        //             || (!$val["op"] && !preg_match('~[_%]~', $val["val"])) // LIKE in Editor
369
        //         )) {
370
        //             $set .= "&set" . urlencode("[" . $this->util->bracketEscape($val["col"]) . "]") . "=" . urlencode($val["val"]);
371
        //         }
372
        //     }
373
        // }
374
        // $this->util->selectLinks($tableStatus, $set);
375
376
        // if($page == "last")
377
        // {
378
        //     $isGroup = count($group) < count($select);
379
        //     $found_rows = $this->driver->result($this->driver->sqlForRowCount($table, $where, $isGroup, $group));
380
        //     $page = \floor(\max(0, $found_rows - 1) / $limit);
381
        // }
382
383
        $options = $this->getAllOptions($select, $columns, $indexes, $limit, $textLength, $queryOptions);
384
        $entity = $this->getSelectEntity($table, $columns, $fields, $select, $group, $where, $order, $unselected, $limit, $page);
385
        $query = $this->driver->buildSelectQuery($entity);
386
        // From adminer.inc.php
387
        $query = str_replace("\n", " ", $query);
388
389
        return [$options, $query, $select, $fields, $foreignKeys, $columns, $indexes,
390
            $where, $group, $order, $limit, $page, $textLength, $tableName, $unselected];
391
    }
392
393
    /**
394
     * Get required data for create/update on tables
395
     *
396
     * @param string $table The table name
397
     * @param array $queryOptions The query options
398
     *
399
     * @return array
400
     * @throws Exception
401
     */
402
    public function getSelectData(string $table, array $queryOptions = []): array
403
    {
404
        list($options, $query) = $this->prepareSelect($table, $queryOptions);
405
406
        $query = $this->util->html($query);
407
        $mainActions = [
408
            'select-exec' => $this->trans->lang('Execute'),
409
            'insert-table' => $this->trans->lang('New item'),
410
            'select-back' => $this->trans->lang('Back'),
411
        ];
412
413
        return compact('mainActions', 'options', 'query');
414
    }
415
416
    /**
417
     * @param string $query
418
     * @param int $page
419
     *
420
     * @return array
421
     */
422
    private function executeQuery(string $query, int $page): array
423
    {
424
        // From driver.inc.php
425
        $start = microtime(true);
426
        $statement = $this->driver->execute($query);
427
        // From adminer.inc.php
428
        $duration = $this->trans->formatTime($start); // Compute and format the duration
429
430
        if (!$statement) {
431
            return ['error' => $this->driver->error()];
432
        }
433
        // From select.inc.php
434
        $rows = [];
435
        while (($row = $statement->fetchAssoc())) {
436
            if ($page && $this->driver->jush() == "oracle") {
437
                unset($row["RNUM"]);
438
            }
439
            $rows[] = $row;
440
        }
441
442
        return [$rows, $duration];
443
    }
444
445
    /**
446
     * @param array $rows
447
     * @param array $select
448
     * @param array $fields
449
     * @param array $unselected
450
     * @param array $queryOptions
451
     *
452
     * @return array
453
     */
454
    private function getResultHeaders(array $rows, array $select, array $fields, array $unselected, array $queryOptions): array
455
    {
456
        // Results headers
457
        $headers = [
458
            '', // !$group && $select ? '' : lang('Modify');
459
        ];
460
        $names = [];
461
        // $functions = [];
462
        reset($select);
463
        $rank = 1;
464
        foreach ($rows[0] as $key => $value) {
465
            $header = [];
466
            if (!isset($unselected[$key])) {
467
                $value = $queryOptions["columns"][key($select)] ?? [];
468
                $fun = $value["fun"] ?? '';
469
                $field = $fields[$select ? ($value ? $value["col"] : current($select)) : $key];
470
                $name = ($field ? $this->util->fieldName($field, $rank) : ($fun ? "*" : $key));
471
                $header = compact('value', 'field', 'name');
472
                if ($name != "") {
473
                    $rank++;
474
                    $names[$key] = $name;
475
                    $column = $this->driver->escapeId($key);
476
                    // $href = remove_from_uri('(order|desc)[^=]*|page') . '&order%5B0%5D=' . urlencode($key);
477
                    // $desc = "&desc%5B0%5D=1";
478
                    $header['column'] = $column;
479
                    $header['key'] = $this->util->html($this->util->bracketEscape($key));
480
                    $header['sql'] = $this->util->applySqlFunction($fun, $name); //! columns looking like functions
481
                }
482
                // $functions[$key] = $fun;
483
                next($select);
484
            }
485
            $headers[] = $header;
486
        }
487
        return [$headers, $names];
488
    }
489
490
    /**
491
     * @param array $rows
492
     * @param array $queryOptions
493
     *
494
     * @return array
495
     */
496
    /*private function getValuesLengths(array $rows, array $queryOptions): array
497
    {
498
         $lengths = [];
499
         if($queryOptions["modify"])
500
         {
501
             foreach($rows as $row)
502
             {
503
                 foreach($row as $key => $value)
504
                 {
505
                     $lengths[$key] = \max($lengths[$key], \min(40, strlen(\utf8_decode($value))));
506
                 }
507
             }
508
         }
509
         return $lengths;
510
    }*/
511
512
    /**
513
     * @param array $row
514
     * @param array $indexes
515
     *
516
     * @return array
517
     */
518
    private function getUniqueIds(array $row, array $indexes): array
519
    {
520
        $uniqueIds = $this->util->uniqueIds($row, $indexes);
521
        if (empty($uniqueIds)) {
522
            $pattern = '~^(COUNT\((\*|(DISTINCT )?`(?:[^`]|``)+`)\)|(AVG|GROUP_CONCAT|MAX|MIN|SUM)\(`(?:[^`]|``)+`\))$~';
523
            foreach ($row as $key => $value) {
524
                if (!preg_match($pattern, $key)) {
525
                    //! columns looking like functions
526
                    $uniqueIds[$key] = $value;
527
                }
528
            }
529
        }
530
        return $uniqueIds;
531
    }
532
533
    /**
534
     * @param array $row
535
     * @param array $fields
536
     * @param array $indexes
537
     *
538
     * @return array
539
     */
540
    private function getRowIds(array $row, array $fields, array $indexes): array
541
    {
542
        $uniqueIds = $this->getUniqueIds($row, $indexes);
543
        // Unique identifier to edit returned data.
544
        // $unique_idf = "";
545
        $rowIds = ['where' => [], 'null' => []];
546
        foreach ($uniqueIds as $key => $value) {
547
            $key = trim($key);
548
            $type = '';
549
            $collation = '';
550
            if (isset($fields[$key])) {
551
                $type = $fields[$key]->type;
552
                $collation = $fields[$key]->collation;
553
            }
554
            if (($this->driver->jush() == "sql" || $this->driver->jush() == "pgsql") &&
555
                preg_match('~char|text|enum|set~', $type) && strlen($value) > 64) {
556
                $key = (strpos($key, '(') ? $key : $this->driver->escapeId($key)); //! columns looking like functions
557
                $key = "MD5(" . ($this->driver->jush() != 'sql' || preg_match("~^utf8~", $collation) ?
558
                        $key : "CONVERT($key USING " . $this->driver->charset() . ")") . ")";
559
                $value = md5($value);
560
            }
561
            if ($value !== null) {
562
                $rowIds['where'][$this->util->bracketEscape($key)] = $value;
563
            } else {
564
                $rowIds['null'][] = $this->util->bracketEscape($key);
565
            }
566
            // $unique_idf .= "&" . ($value !== null ? \urlencode("where[" . $this->util->bracketEscape($key) . "]") .
567
            //     "=" . \urlencode($value) : \urlencode("null[]") . "=" . \urlencode($key));
568
        }
569
        return $rowIds;
570
    }
571
572
    /**
573
     * @param array $row
574
     * @param array $fields
575
     * @param array $names
576
     * @param int $textLength
577
     *
578
     * @return array
579
     */
580
    private function getRowColumns(array $row, array $fields, array $names, int $textLength): array
581
    {
582
        $cols = [];
583
        foreach ($row as $key => $value) {
584
            if (isset($names[$key])) {
585
                $field = $fields[$key] ?? new TableFieldEntity();
586
                $value = $this->driver->value($value, $field);
587
                /*if ($value != "" && (!isset($email_fields[$key]) || $email_fields[$key] != "")) {
588
                    //! filled e-mails can be contained on other pages
589
                    $email_fields[$key] = ($this->util->isMail($value) ? $names[$key] : "");
590
                }*/
591
                $link = "";
592
                $cols[] = [
593
                    // 'id',
594
                    'text' => preg_match('~text|lob~', $field->type),
595
                    'value' => $this->util->selectValue($value, $link, $field, $textLength),
596
                    // 'editable' => false,
597
                ];
598
            }
599
        }
600
        return $cols;
601
    }
602
603
    /**
604
     * Get required data for create/update on tables
605
     *
606
     * @param string $table The table name
607
     * @param array $queryOptions The query options
608
     *
609
     * @return array
610
     * @throws Exception
611
     */
612
    public function execSelect(string $table, array $queryOptions): array
613
    {
614
        list(, $query, $select, $fields, , , $indexes, $where, $group, , $limit, $page,
615
            $textLength, , $unselected) = $this->prepareSelect($table, $queryOptions);
616
617
        list($rows, $duration) = $this->executeQuery($query, $page);
618
        if (!$rows) {
619
            return ['error' => $this->trans->lang('No rows.')];
620
        }
621
        // $backward_keys = $this->driver->backwardKeys($table, $tableName);
622
        // lengths = $this->getValuesLengths($rows, $queryOptions);
623
624
        list($headers, $names) = $this->getResultHeaders($rows, $select, $fields, $unselected, $queryOptions);
625
626
        $results = [];
627
        foreach ($rows as $row) {
628
            // Unique identifier to edit returned data.
629
            $rowIds = $this->getRowIds($row, $fields, $indexes);
630
            $cols = $this->getRowColumns($row, $fields, $names, $textLength);
631
            $results[] = ['ids' => $rowIds, 'cols' => $cols];
632
        }
633
634
        $isGroup = count($group) < count($select);
635
        $total = $this->driver->result($this->driver->sqlForRowCount($table, $where, $isGroup, $group));
636
637
        $rows = $results;
638
        $error = null;
639
        return compact('duration', 'headers', 'query', 'rows', 'limit', 'total', 'error');
640
    }
641
}
642