Passed
Push — main ( 3e7f40...759879 )
by Thierry
02:03
created

TableSelectAdmin::getSelectEntity()   B

Complexity

Conditions 10
Paths 18

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 18
c 1
b 0
f 0
nc 18
nop 10
dl 0
loc 30
rs 7.6666

How to fix   Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
17
/**
18
 * Admin table select functions
19
 */
20
class TableSelectAdmin extends AbstractAdmin
21
{
22
    /**
23
     * Print columns box in select
24
     * @param array $select Result of processSelectColumns()[0]
25
     * @param array $columns Selectable columns
26
     * @param array $options
27
     * @return array
28
     */
29
    private function getColumnsOptions(array $select, array $columns, array $options): array
30
    {
31
        return [
32
            'select' => $select,
33
            'values' => (array)$options["columns"],
34
            'columns' => $columns,
35
            'functions' => $this->driver->functions(),
36
            'grouping' => $this->driver->grouping(),
37
        ];
38
    }
39
40
    /**
41
     * Print search box in select
42
     *
43
     * @param array $columns Selectable columns
44
     * @param array $indexes
45
     * @param array $options
46
     *
47
     * @return array
48
     */
49
    private function getFiltersOptions(array $columns, array $indexes, array $options): array
50
    {
51
        $fulltexts = [];
52
        foreach ($indexes as $i => $index) {
53
            $fulltexts[$i] = $index->type == "FULLTEXT" ? $this->util->html($options["fulltext"][$i]) : '';
54
        }
55
        return [
56
            // 'where' => $where,
57
            'values' => (array)$options["where"],
58
            'columns' => $columns,
59
            'indexes' => $indexes,
60
            'operators' => $this->driver->operators(),
61
            'fulltexts' => $fulltexts,
62
        ];
63
    }
64
65
    /**
66
     * Print order box in select
67
     *
68
     * @param array $columns Selectable columns
69
     * @param array $options
70
     *
71
     * @return array
72
     */
73
    private function getSortingOptions(array $columns, array $options): array
74
    {
75
        $values = [];
76
        $descs = (array)$options["desc"];
77
        foreach ((array)$options["order"] as $key => $value) {
78
            $values[] = [
79
                'col' => $value,
80
                'desc' => $descs[$key] ?? 0,
81
            ];
82
        }
83
        return [
84
            // 'order' => $order,
85
            'values' => $values,
86
            'columns' => $columns,
87
        ];
88
    }
89
90
    /**
91
     * Print limit box in select
92
     *
93
     * @param string $limit Result of processSelectLimit()
94
     *
95
     * @return array
96
     */
97
    private function getLimitOptions(string $limit): array
98
    {
99
        return ['value' => $this->util->html($limit)];
100
    }
101
102
    /**
103
     * Print text length box in select
104
     *
105
     * @param int $textLength Result of processSelectLength()
106
     *
107
     * @return array
108
     */
109
    private function getLengthOptions($textLength): array
110
    {
111
        return [
112
            'value' => $textLength === 0 ? 0 : $this->util->html($textLength),
113
        ];
114
    }
115
116
    /**
117
     * Print action box in select
118
     *
119
     * @param array $indexes
120
     *
121
     * @return array
122
     */
123
    // private function getActionOptions(array $indexes)
124
    // {
125
    //     $columns = [];
126
    //     foreach ($indexes as $index) {
127
    //         $current_key = \reset($index->columns);
128
    //         if ($index->type != "FULLTEXT" && $current_key) {
129
    //             $columns[$current_key] = 1;
130
    //         }
131
    //     }
132
    //     $columns[""] = 1;
133
    //     return ['columns' => $columns];
134
    // }
135
136
    /**
137
     * Print command box in select
138
     *
139
     * @return bool whether to print default commands
140
     */
141
    // private function getCommandOptions()
142
    // {
143
    //     return !$this->driver->isInformationSchema($this->driver->database());
144
    // }
145
146
    /**
147
     * Print import box in select
148
     *
149
     * @return bool whether to print default import
150
     */
151
    // private function getImportOptions()
152
    // {
153
    //     return !$this->driver->isInformationSchema($this->driver->database());
154
    // }
155
156
    /**
157
     * Print extra text in the end of a select form
158
     *
159
     * @param array $emailFields Fields holding e-mails
160
     * @param array $columns Selectable columns
161
     *
162
     * @return array
163
     */
164
    // private function getEmailOptions(array $emailFields, array $columns)
165
    // {
166
    // }
167
168
    /**
169
     * @param array $queryOptions
170
     *
171
     * @return int
172
     */
173
    private function setDefaultOptions(array &$queryOptions): int
174
    {
175
        $defaultOptions = [
176
            'columns' => [],
177
            'where' => [],
178
            'order' => [],
179
            'desc' => [],
180
            'fulltext' => [],
181
            'limit' => '50',
182
            'text_length' => '100',
183
            'page' => '1',
184
        ];
185
        foreach ($defaultOptions as $name => $value) {
186
            if (!isset($queryOptions[$name])) {
187
                $queryOptions[$name] = $value;
188
            }
189
        }
190
        $page = intval($queryOptions['page']);
191
        if ($page > 0) {
192
            $page -= 1; // Page numbers start at 0 here, instead of 1.
193
        }
194
        $queryOptions['page'] = $page;
195
        return $page;
196
    }
197
198
    /**
199
     * @param array $fields
200
     *
201
     * @return array
202
     */
203
    private function getFieldsOptions(array $fields): array
204
    {
205
        $rights = []; // privilege => 0
206
        $columns = []; // selectable columns
207
        $textLength = 0;
208
        foreach ($fields as $key => $field) {
209
            $name = $this->util->fieldName($field);
210
            if (isset($field->privileges["select"]) && $name != "") {
211
                $columns[$key] = \html_entity_decode(\strip_tags($name), ENT_QUOTES);
212
                if ($this->util->isShortable($field)) {
213
                    $textLength = $this->util->processSelectLength();
214
                }
215
            }
216
            $rights[] = $field->privileges;
217
        }
218
        return [$rights, $columns, $textLength];
219
    }
220
221
    /**
222
     * @param array $indexes
223
     * @param array $select
224
     * @param mixed $tableStatus
225
     *
226
     * @return array|null
227
     */
228
    private function setPrimaryKey(array &$indexes, array $select, $tableStatus)
229
    {
230
        $primary = $unselected = null;
231
        foreach ($indexes as $index) {
232
            if ($index->type == "PRIMARY") {
233
                $primary = \array_flip($index->columns);
234
                $unselected = ($select ? $primary : []);
235
                foreach ($unselected as $key => $val) {
236
                    if (\in_array($this->driver->escapeId($key), $select)) {
237
                        unset($unselected[$key]);
238
                    }
239
                }
240
                break;
241
            }
242
        }
243
244
        $oid = $tableStatus->oid;
245
        if ($oid && !$primary) {
246
            /*$primary = */$unselected = [$oid => 0];
247
            $indexes[] = ["type" => "PRIMARY", "columns" => [$oid]];
248
        }
249
250
        return $unselected;
251
    }
252
253
    /**
254
     * @param array $select
255
     * @param array $columns
256
     * @param array $indexes
257
     * @param int $limit
258
     * @param int $textLength
259
     * @param array $queryOptions
260
     *
261
     * @return array
262
     */
263
    private function getAllOptions(array $select, array $columns, array $indexes,
264
        int $limit, int $textLength, array $queryOptions): array
265
    {
266
        return [
267
            'columns' => $this->getColumnsOptions($select, $columns, $queryOptions),
268
            'filters' => $this->getFiltersOptions($columns, $indexes, $queryOptions),
269
            'sorting' => $this->getSortingOptions($columns, $queryOptions),
270
            'limit' => $this->getLimitOptions($limit),
271
            'length' => $this->getLengthOptions($textLength),
272
            // 'action' => $this->getActionOptions($indexes),
273
        ];
274
    }
275
276
    /**
277
     * @param string $table
278
     * @param array $columns
279
     * @param array $fields
280
     * @param array $select
281
     * @param array $group
282
     * @param array $where
283
     * @param array $order
284
     * @param array $unselected
285
     * @param int $limit
286
     * @param int $page
287
     *
288
     * @return TableSelectEntity
289
     */
290
    private function getSelectEntity(string $table, array $columns, array $fields, array $select,
291
        array $group, array $where, array $order, array $unselected, int $limit, int $page): TableSelectEntity
292
    {
293
        $select2 = $select;
294
        $group2 = $group;
295
        if (!$select2) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $select2 of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
296
            $select2[] = "*";
297
            $convert_fields = $this->driver->convertFields($columns, $fields, $select);
298
            if ($convert_fields) {
299
                $select2[] = \substr($convert_fields, 2);
300
            }
301
        }
302
        foreach ($select as $key => $val) {
303
            $field = $fields[$this->driver->unescapeId($val)] ?? null;
304
            if ($field && ($as = $this->driver->convertField($field))) {
305
                $select2[$key] = "$as AS $val";
306
            }
307
        }
308
        $isGroup = count($group) < count($select);
309
        if (!$isGroup && $unselected) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $unselected of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
310
            foreach ($unselected as $key => $val) {
311
                $select2[] = $this->driver->escapeId($key);
312
                if ($group2) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $group2 of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
313
                    $group2[] = $this->driver->escapeId($key);
314
                }
315
            }
316
        }
317
318
        // From driver.inc.php
319
        return new TableSelectEntity($table, $select2, $where, $group2, $order, $limit, $page);
320
    }
321
322
    /**
323
     * Get required data for create/update on tables
324
     *
325
     * @param string $table The table name
326
     * @param array $queryOptions The query options
327
     *
328
     * @return array
329
     * @throws Exception
330
     */
331
    private function prepareSelect(string $table, array &$queryOptions = []): array
332
    {
333
        $page = $this->setDefaultOptions($queryOptions);
334
        $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

334
        $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...
335
336
        // From select.inc.php
337
        $fields = $this->driver->fields($table);
338
        list(, $columns, $textLength) = $this->getFieldsOptions($fields);
339
        if (!$columns && $this->driver->support("table")) {
340
            throw new Exception($this->trans->lang('Unable to select the table') .
341
                ($fields ? "." : ": " . $this->driver->error()));
342
        }
343
344
        $indexes = $this->driver->indexes($table);
345
        $foreignKeys = $this->admin->columnForeignKeys($table);
346
        list($select, $group) = $this->util->processSelectColumns();
347
        $where = $this->util->processSelectSearch($fields, $indexes);
348
        $order = $this->util->processSelectOrder();
349
        $limit = $this->util->processSelectLimit();
350
        $tableStatus = $this->driver->tableStatusOrName($table);
351
        $unselected = $this->setPrimaryKey($indexes, $select, $tableStatus);
352
        $tableName = $this->util->tableName($tableStatus);
353
354
        // $set = null;
355
        // if(isset($rights["insert"]) || !support("table")) {
356
        //     $set = "";
357
        //     foreach((array) $queryOptions["where"] as $val) {
358
        //         if($foreignKeys[$val["col"]] && count($foreignKeys[$val["col"]]) == 1 && ($val["op"] == "="
359
        //             || (!$val["op"] && !preg_match('~[_%]~', $val["val"])) // LIKE in Editor
360
        //         )) {
361
        //             $set .= "&set" . urlencode("[" . $this->util->bracketEscape($val["col"]) . "]") . "=" . urlencode($val["val"]);
362
        //         }
363
        //     }
364
        // }
365
        // $this->util->selectLinks($tableStatus, $set);
366
367
        // if($page == "last")
368
        // {
369
        //     $isGroup = count($group) < count($select);
370
        //     $found_rows = $this->driver->result($this->driver->sqlForRowCount($table, $where, $isGroup, $group));
371
        //     $page = \floor(\max(0, $found_rows - 1) / $limit);
372
        // }
373
374
        $options = $this->getAllOptions($select, $columns, $indexes, $limit, $textLength, $queryOptions);
375
        $entity = $this->getSelectEntity($table, $columns, $fields, $select, $group, $where, $order, $unselected, $limit, $page);
0 ignored issues
show
Bug introduced by
It seems like $unselected can also be of type null; however, parameter $unselected of Lagdo\DbAdmin\DbAdmin\Ta...dmin::getSelectEntity() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

375
        $entity = $this->getSelectEntity($table, $columns, $fields, $select, $group, $where, $order, /** @scrutinizer ignore-type */ $unselected, $limit, $page);
Loading history...
376
        $query = $this->driver->buildSelectQuery($entity);
377
        // From adminer.inc.php
378
        $query = str_replace("\n", " ", $query);
379
380
        return [$options, $query, $select, $fields, $foreignKeys, $columns, $indexes,
381
            $where, $group, $order, $limit, $page, $textLength, $tableName, $unselected];
382
    }
383
384
    /**
385
     * Get required data for create/update on tables
386
     *
387
     * @param string $table The table name
388
     * @param array $queryOptions The query options
389
     *
390
     * @return array
391
     * @throws Exception
392
     */
393
    public function getSelectData(string $table, array $queryOptions = []): array
394
    {
395
        list($options, $query) = $this->prepareSelect($table, $queryOptions);
396
397
        $query = $this->util->html($query);
398
        $mainActions = [
399
            'select-exec' => $this->trans->lang('Execute'),
400
            'insert-table' => $this->trans->lang('New item'),
401
            'select-back' => $this->trans->lang('Back'),
402
        ];
403
404
        return compact('mainActions', 'options', 'query');
405
    }
406
407
    /**
408
     * Get required data for create/update on tables
409
     *
410
     * @param string $table The table name
411
     * @param array $queryOptions The query options
412
     *
413
     * @return array
414
     * @throws Exception
415
     */
416
    public function execSelect(string $table, array $queryOptions): array
417
    {
418
        list(, $query, $select, $fields, , , $indexes, $where, $group, , $limit, $page,
419
            $textLength, , $unselected) = $this->prepareSelect($table, $queryOptions);
420
421
        $error = null;
422
        // From driver.inc.php
423
        $start = microtime(true);
424
        $statement = $this->driver->execute($query);
425
        // From adminer.inc.php
426
        $duration = $this->trans->formatTime($start); // Compute and format the duration
427
428
        if (!$statement) {
429
            return ['error' => $this->driver->error()];
430
        }
431
        // From select.inc.php
432
        $rows = [];
433
        while (($row = $statement->fetchAssoc())) {
434
            if ($page && $this->driver->jush() == "oracle") {
435
                unset($row["RNUM"]);
436
            }
437
            $rows[] = $row;
438
        }
439
        if (!$rows) {
440
            return ['error' => $this->trans->lang('No rows.')];
441
        }
442
        // $backward_keys = $this->driver->backwardKeys($table, $tableName);
443
444
        // Results headers
445
        $headers = [
446
            '', // !$group && $select ? '' : lang('Modify');
447
        ];
448
        $names = [];
449
        // $functions = [];
450
        reset($select);
451
        $rank = 1;
452
        foreach ($rows[0] as $key => $value) {
453
            $header = [];
454
            if (!isset($unselected[$key])) {
455
                $value = $queryOptions["columns"][key($select)] ?? [];
456
                $fun = $value["fun"] ?? '';
457
                $field = $fields[$select ? ($value ? $value["col"] : current($select)) : $key];
458
                $name = ($field ? $this->util->fieldName($field, $rank) : ($fun ? "*" : $key));
459
                $header = compact('value', 'field', 'name');
460
                if ($name != "") {
461
                    $rank++;
462
                    $names[$key] = $name;
463
                    $column = $this->driver->escapeId($key);
464
                    // $href = remove_from_uri('(order|desc)[^=]*|page') . '&order%5B0%5D=' . urlencode($key);
465
                    // $desc = "&desc%5B0%5D=1";
466
                    $header['column'] = $column;
467
                    $header['key'] = $this->util->html($this->util->bracketEscape($key));
468
                    $header['sql'] = $this->util->applySqlFunction($fun, $name); //! columns looking like functions
469
                }
470
                // $functions[$key] = $fun;
471
                next($select);
472
            }
473
            $headers[] = $header;
474
        }
475
476
        // $lengths = [];
477
        // if($queryOptions["modify"])
478
        // {
479
        //     foreach($rows as $row)
480
        //     {
481
        //         foreach($row as $key => $value)
482
        //         {
483
        //             $lengths[$key] = \max($lengths[$key], \min(40, strlen(\utf8_decode($value))));
484
        //         }
485
        //     }
486
        // }
487
488
        $results = [];
489
        foreach ($rows as $n => $row) {
490
            $uniqueIds = $this->util->uniqueIds($rows[$n], $indexes);
491
            if (empty($uniqueIds)) {
492
                foreach ($row as $key => $value) {
493
                    if (!\preg_match('~^(COUNT\((\*|(DISTINCT )?`(?:[^`]|``)+`)\)|(AVG|GROUP_CONCAT|MAX|MIN|SUM)\(`(?:[^`]|``)+`\))$~', $key)) {
494
                        //! columns looking like functions
495
                        $uniqueIds[$key] = $value;
496
                    }
497
                }
498
            }
499
500
            // Unique identifier to edit returned data.
501
            // $unique_idf = "";
502
            $rowIds = [
503
                'where' => [],
504
                'null' => [],
505
            ];
506
            foreach ($uniqueIds as $key => $value) {
507
                $key = \trim($key);
508
                $type = '';
509
                $collation = '';
510
                if (isset($fields[$key])) {
511
                    $type = $fields[$key]->type;
512
                    $collation = $fields[$key]->collation;
513
                }
514
                if (($this->driver->jush() == "sql" || $this->driver->jush() == "pgsql") &&
515
                    \preg_match('~char|text|enum|set~', $type) && strlen($value) > 64) {
516
                    $key = (\strpos($key, '(') ? $key : $this->driver->escapeId($key)); //! columns looking like functions
517
                    $key = "MD5(" . ($this->driver->jush() != 'sql' || \preg_match("~^utf8~", $collation) ?
518
                        $key : "CONVERT($key USING " . $this->driver->charset() . ")") . ")";
519
                    $value = \md5($value);
520
                }
521
                if ($value !== null) {
522
                    $rowIds['where'][$this->util->bracketEscape($key)] = $value;
523
                } else {
524
                    $rowIds['null'][] = $this->util->bracketEscape($key);
525
                }
526
                // $unique_idf .= "&" . ($value !== null ? \urlencode("where[" . $this->util->bracketEscape($key) . "]") .
527
                //     "=" . \urlencode($value) : \urlencode("null[]") . "=" . \urlencode($key));
528
            }
529
530
            $cols = [];
531
            foreach ($row as $key => $value) {
532
                if (isset($names[$key])) {
533
                    $field = $fields[$key] ?? new TableFieldEntity();
534
                    $value = $this->driver->value($value, $field);
535
                    if ($value != "" && (!isset($email_fields[$key]) || $email_fields[$key] != "")) {
536
                        //! filled e-mails can be contained on other pages
537
                        $email_fields[$key] = ($this->util->isMail($value) ? $names[$key] : "");
538
                    }
539
540
                    $link = "";
541
542
                    $value = $this->util->selectValue($value, $link, $field, $textLength);
543
                    $text = preg_match('~text|lob~', $field->type);
544
545
                    $cols[] = compact(/*'id', */'text', 'value'/*, 'editable'*/);
546
                }
547
            }
548
            $results[] = ['ids' => $rowIds, 'cols' => $cols];
549
        }
550
551
        $isGroup = count($group) < count($select);
552
        $total = $this->driver->result($this->driver->sqlForRowCount($table, $where, $isGroup, $group));
553
554
        $rows = $results;
555
        return compact('duration', 'headers', 'query', 'rows', 'limit', 'total', 'error');
556
    }
557
}
558