Passed
Push — main ( 65d634...249587 )
by Thierry
19:37 queued 17:18
created

SelectFacade::getResultHeaders()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 18
rs 9.9
1
<?php
2
3
namespace Lagdo\DbAdmin\Db\Facades;
4
5
use Exception;
6
use Lagdo\DbAdmin\Db\Facades\Select\SelectEntity;
7
use Lagdo\DbAdmin\Db\Facades\Select\SelectQuery;
8
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
9
use Lagdo\Facades\Logger;
0 ignored issues
show
Bug introduced by
The type Lagdo\Facades\Logger was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
11
use function array_map;
12
use function compact;
13
use function count;
14
use function current;
15
use function key;
16
use function max;
17
use function md5;
18
use function microtime;
19
use function next;
20
use function preg_match;
21
use function strlen;
22
use function strpos;
23
use function trim;
24
25
/**
26
 * Facade to table select functions
27
 */
28
class SelectFacade extends AbstractFacade
29
{
30
    /**
31
     * @var SelectQuery
32
     */
33
    private SelectQuery $selectQuery;
34
35
    /**
36
     * @var SelectEntity|null
37
     */
38
    private SelectEntity|null $selectEntity = null;
39
40
    /**
41
     * @param AbstractFacade $dbFacade
42
     */
43
    public function __construct(AbstractFacade $dbFacade)
44
    {
45
        parent::__construct($dbFacade);
46
47
        $this->selectQuery = new SelectQuery($dbFacade);
48
    }
49
50
    /**
51
     * @param string $table The table name
52
     * @param array $queryOptions The query options
53
     *
54
     * @return void
55
     * @throws Exception
56
     */
57
    private function setSelectEntity(string $table, array $queryOptions = []): void
58
    {
59
        $tableStatus = $this->driver->tableStatusOrName($table);
60
        $tableName = $this->admin->tableName($tableStatus);
61
        $this->selectEntity = new SelectEntity($table,
62
            $tableName, $tableStatus, $queryOptions);
63
        $this->selectQuery->prepareSelect($this->selectEntity);
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 SelectEntity
73
     * @throws Exception
74
     */
75
    public function getSelectData(string $table, array $queryOptions = []): SelectEntity
76
    {
77
        $this->setSelectEntity($table, $queryOptions);
78
        return $this->selectEntity;
79
    }
80
81
    /**
82
     * @return void
83
     */
84
    private function executeSelect(): void
85
    {
86
        // From driver.inc.php
87
        $startTimestamp = microtime(true);
88
        $statement = $this->driver->execute($this->selectEntity->query);
89
        $this->selectEntity->duration = max(0, microtime(true) - $startTimestamp);
90
91
        // From adminer.inc.php
92
        if (!$statement) {
93
            $this->selectEntity = $this->driver->error();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->driver->error() of type string is incompatible with the declared type Lagdo\DbAdmin\Db\Facades\Select\SelectEntity|null of property $selectEntity.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
94
            return;
95
        }
96
97
        // From select.inc.php
98
        $this->selectEntity->rows = [];
99
        while (($row = $statement->fetchAssoc())) {
100
            if ($this->selectEntity->page && $this->driver->jush() == "oracle") {
101
                unset($row["RNUM"]);
102
            }
103
            $this->selectEntity->rows[] = $row;
104
        }
105
    }
106
107
    /**
108
     * @param string $key
109
     * @param int $rank
110
     *
111
     * @return array
112
     */
113
    private function getResultHeaderItem(string $key, int $rank): array
114
    {
115
        $valueKey = key($this->selectEntity->select);
116
        $value = $this->selectEntity->queryOptions["columns"][$valueKey] ?? [];
117
118
        $fun = $value["fun"] ?? '';
119
        $fieldKey = !$this->selectEntity->select ? $key :
120
            ($value["col"] ?? current($this->selectEntity->select));
121
        $field = $this->selectEntity->fields[$fieldKey];
122
        $name = !$field ? ($fun ? "*" : $key) :
123
            $this->admin->fieldName($field, $rank);
124
125
        return [$fun, $name, $field];
126
    }
127
128
    /**
129
     * @param string $key
130
     * @param mixed $value
131
     * @param int $rank
132
     *
133
     * @return array
134
     */
135
    private function getResultHeader(string $key, $value, int $rank): array
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

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

135
    private function getResultHeader(string $key, /** @scrutinizer ignore-unused */ $value, int $rank): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
136
    {
137
        if (isset($this->selectEntity->unselected[$key])) {
138
            return [];
139
        }
140
141
        [$fun, $name, $field] = $this->getResultHeaderItem($key, $rank);
142
        $header = compact('field', 'name');
143
        if ($name != "") {
144
            $this->selectEntity->names[$key] = $name;
145
            // $href = remove_from_uri('(order|desc)[^=]*|page') . '&order%5B0%5D=' . urlencode($key);
146
            // $desc = "&desc%5B0%5D=1";
147
            $header['column'] = $this->driver->escapeId($key);
148
            // $header['key'] = $this->utils->str
149
            //     ->html($this->driver->bracketEscape($key));
150
            //! columns looking like functions
151
            $header['title'] = $this->selectQuery->applySqlFunction($fun, $name);
152
        }
153
        // $functions[$key] = $fun;
154
        next($this->selectEntity->select);
155
        return $header;
156
    }
157
158
    /**
159
     * Get the result headers from the first result row
160
     * @return void
161
     */
162
    private function getResultHeaders(): void
163
    {
164
        // Results headers
165
        $this->selectEntity->headers = [
166
            '', // !$group && $select ? '' : lang('Modify');
167
        ];
168
        $this->selectEntity->names = [];
169
        // $this->selectEntity->functions = [];
170
        reset($this->selectEntity->select);
171
172
        $rank = 1;
173
        $firstResultRow = $this->selectEntity->rows[0];
174
        foreach ($firstResultRow as $key => $value) {
175
            $header = $this->getResultHeader($key, $value, $rank);
176
            if ($header['name'] ?? '' !== '') {
177
                $rank++;
178
            }
179
            $this->selectEntity->headers[] = $header;
180
        }
181
    }
182
183
    /**
184
     * @param array $rows
185
     * @param array $queryOptions
186
     *
187
     * @return array
188
     */
189
    /*private function getValuesLengths(array $rows, array $queryOptions): array
190
    {
191
        $lengths = [];
192
        if($queryOptions["modify"])
193
        {
194
            foreach($rows as $row)
195
            {
196
                foreach($row as $key => $value)
197
                {
198
                    $lengths[$key] = \max($lengths[$key], \min(40, strlen(\utf8_decode($value))));
199
                }
200
            }
201
        }
202
        return $lengths;
203
    }*/
204
205
    /**
206
     * @param array $row
207
     *
208
     * @return array
209
     */
210
    private function getUniqueIds(array $row): array
211
    {
212
        $uniqueIds = $this->admin->uniqueIds($row, $this->selectEntity->indexes);
213
        if (empty($uniqueIds)) {
214
            $pattern = '~^(COUNT\((\*|(DISTINCT )?`(?:[^`]|``)+`)\)' .
215
                '|(AVG|GROUP_CONCAT|MAX|MIN|SUM)\(`(?:[^`]|``)+`\))$~';
216
            foreach ($row as $key => $value) {
217
                if (!preg_match($pattern, $key)) {
218
                    //! columns looking like functions
219
                    $uniqueIds[$key] = $value;
220
                }
221
            }
222
        }
223
        return $uniqueIds;
224
    }
225
226
    /**
227
     * @param string $type
228
     * @param string $value
229
     *
230
     * @return bool
231
     */
232
    private function shouldEncodeRowId(string $type, string $value): bool
233
    {
234
        $jush = $this->driver->jush();
235
        return ($jush === "sql" || $jush === "pgsql") &&
236
            strlen($value) > 64 &&
237
            preg_match('~char|text|enum|set~', $type);
238
    }
239
240
    /**
241
     * @param string $key
242
     * @param string $collation
243
     *
244
     * @return string
245
     */
246
    private function getRowIdMd5Key(string $key, string $collation): string
247
    {
248
        return $this->driver->jush() != 'sql' ||
249
            preg_match("~^utf8~", $collation) ? $key :
250
                "CONVERT($key USING " . $this->driver->charset() . ")";
251
    }
252
253
    /**
254
     * @param string $key
255
     * @param string $value
256
     *
257
     * @return array
258
     */
259
    private function getRowIdValue(string $key, string $value): array
260
    {
261
        $key = trim($key);
262
        $type = '';
263
        $collation = '';
264
        if (isset($this->selectEntity->fields[$key])) {
265
            $type = $this->selectEntity->fields[$key]->type;
266
            $collation = $this->selectEntity->fields[$key]->collation;
267
        }
268
        if ($this->shouldEncodeRowId($type, $value)) {
269
            if (!strpos($key, '(')) {
270
                //! columns looking like functions
271
                $key = $this->driver->escapeId($key);
272
            }
273
            $key = "MD5(" . $this->getRowIdMd5Key($key, $collation) . ")";
274
            $value = md5($value);
275
        }
276
        return [$key, $value];
277
    }
278
279
    /**
280
     * @param array $row
281
     *
282
     * @return array
283
     */
284
    private function getRowIds(array $row): array
285
    {
286
        $uniqueIds = $this->getUniqueIds($row);
287
        // Unique identifier to edit returned data.
288
        // $unique_idf = "";
289
        $rowIds = ['where' => [], 'null' => []];
290
        foreach ($uniqueIds as $key => $value) {
291
            [$key, $value] = $this->getRowIdValue($key, $value);
292
            // $unique_idf .= "&" . ($value !== null ? \urlencode("where[" .
293
            // $this->driver->bracketEscape($key) . "]") . "=" .
294
            // \urlencode($value) : \urlencode("null[]") . "=" . \urlencode($key));
295
            if ($value === null) {
296
                $rowIds['null'][] = $this->driver->bracketEscape($key);
297
                continue;
298
            }
299
            $rowIds['where'][$this->driver->bracketEscape($key)] = $value;
300
        }
301
        return $rowIds;
302
    }
303
304
    /**
305
     * @param string $key
306
     * @param mixed $value
307
     *
308
     * @return array
309
     */
310
    private function getRowColumn(string $key, $value): array
311
    {
312
        $field = $this->selectEntity->fields[$key] ?? new TableFieldEntity();
313
        $value = $this->driver->value($value, $field);
314
        /*if ($value != "" && (!isset($email_fields[$key]) || $email_fields[$key] != "")) {
315
            //! filled e-mails can be contained on other pages
316
            $email_fields[$key] = ($this->admin->isMail($value) ? $names[$key] : "");
317
        }*/
318
        $length = $this->selectEntity->textLength;
319
        $value = $this->admin->selectValue($field, $value, $length);
320
        return [
321
            // 'id',
322
            'text' => preg_match('~text|lob~', $field->type),
323
            'value' => $value,
324
            // 'editable' => false,
325
        ];
326
    }
327
328
    /**
329
     * @param array $row
330
     *
331
     * @return array
332
     */
333
    private function getRowColumns(array $row): array
334
    {
335
        $cols = [];
336
        foreach ($row as $key => $value) {
337
            if (isset($this->selectEntity->names[$key])) {
338
                $cols[] = $this->getRowColumn($key, $value);
339
            }
340
        }
341
        return $cols;
342
    }
343
344
    /**
345
     * @return bool
346
     */
347
    private function hasGroupsInFields(): bool
348
    {
349
        return count($this->selectEntity->group) <
350
            count($this->selectEntity->select);
351
    }
352
353
    /**
354
     * Get required data for create/update on tables
355
     *
356
     * @param string $table The table name
357
     * @param array $queryOptions The query options
358
     *
359
     * @return int
360
     */
361
    public function countSelect(string $table, array $queryOptions): int
362
    {
363
        $this->setSelectEntity($table, $queryOptions);
364
365
        try {
366
            $query = $this->driver->getRowCountQuery($table,
367
                $this->selectEntity->where, $this->hasGroupsInFields(),
368
                $this->selectEntity->group);
369
            return (int)$this->driver->result($query);
370
        } catch(Exception $_) {
371
            return -1;
372
        }
373
    }
374
375
    /**
376
     * Get required data for create/update on tables
377
     *
378
     * @param string $table The table name
379
     * @param array $queryOptions The query options
380
     *
381
     * @return array
382
     * @throws Exception
383
     */
384
    public function execSelect(string $table, array $queryOptions): array
385
    {
386
        $this->setSelectEntity($table, $queryOptions);
387
388
        $this->executeSelect();
389
        if (!$this->selectEntity->rows) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->selectEntity->rows 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...
390
            return ['message' => $this->utils->trans->lang('No rows.')];
391
        }
392
        // $backward_keys = $this->driver->backwardKeys($table, $tableName);
393
        // lengths = $this->getValuesLengths($rows, $this->selectEntity->queryOptions);
394
395
        $this->getResultHeaders();
396
397
        return [
398
            'headers' => $this->selectEntity->headers,
399
            'query' => $this->selectEntity->query,
400
            'limit' => $this->selectEntity->limit,
401
            'duration' => $this->selectEntity->duration,
402
            'message' => null,
403
            'rows' => array_map(fn($row) => [
404
                // Unique identifier to edit returned data.
405
                'ids' => $this->getRowIds($row),
406
                'cols' => $this->getRowColumns($row),
407
            ], $this->selectEntity->rows),
408
        ];
409
    }
410
}
411