Issues (37)

src/Db/Facades/SelectFacade.php (3 issues)

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
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 is_string;
16
use function key;
17
use function max;
18
use function md5;
19
use function microtime;
20
use function next;
21
use function preg_match;
22
use function strlen;
23
use function strpos;
24
use function trim;
25
26
/**
27
 * Facade to table select functions
28
 */
29
class SelectFacade extends AbstractFacade
30
{
31
    /**
32
     * @var SelectQuery
33
     */
34
    private SelectQuery $selectQuery;
35
36
    /**
37
     * @var SelectEntity|null
38
     */
39
    private SelectEntity|null $selectEntity = null;
40
41
    /**
42
     * @param AbstractFacade $dbFacade
43
     */
44
    public function __construct(AbstractFacade $dbFacade)
45
    {
46
        parent::__construct($dbFacade);
47
48
        $this->selectQuery = new SelectQuery($dbFacade);
49
    }
50
51
    /**
52
     * @param string $table The table name
53
     * @param array $queryOptions The query options
54
     *
55
     * @return void
56
     * @throws Exception
57
     */
58
    private function setSelectEntity(string $table, array $queryOptions = []): void
59
    {
60
        $tableStatus = $this->driver->tableStatusOrName($table);
61
        $tableName = $this->admin->tableName($tableStatus);
62
        $this->selectEntity = new SelectEntity($table,
63
            $tableName, $tableStatus, $queryOptions);
64
        $this->selectQuery->prepareSelect($this->selectEntity);
65
    }
66
67
    /**
68
     * Get required data for create/update on tables
69
     *
70
     * @param string $table The table name
71
     * @param array $queryOptions The query options
72
     *
73
     * @return SelectEntity
74
     * @throws Exception
75
     */
76
    public function getSelectData(string $table, array $queryOptions = []): SelectEntity
77
    {
78
        $this->setSelectEntity($table, $queryOptions);
79
        return $this->selectEntity;
80
    }
81
82
    /**
83
     * @return void
84
     */
85
    private function executeSelect(): void
86
    {
87
        // From driver.inc.php
88
        $startTimestamp = microtime(true);
89
        $statement = $this->driver->execute($this->selectEntity->query);
90
        $this->selectEntity->duration = max(0, microtime(true) - $startTimestamp);
91
        $this->selectEntity->rows = [];
92
93
        // From adminer.inc.php
94
        if (!$statement) {
95
            $this->selectEntity->error = $this->driver->error();
96
            return;
97
        }
98
99
        // From select.inc.php
100
        $this->selectEntity->rows = [];
101
        while (($row = $statement->fetchAssoc())) {
102
            if ($this->selectEntity->page && $this->driver->jush() == "oracle") {
103
                unset($row["RNUM"]);
104
            }
105
            $this->selectEntity->rows[] = $row;
106
        }
107
    }
108
109
    /**
110
     * @param string $key
111
     * @param int $rank
112
     *
113
     * @return array
114
     */
115
    private function getResultHeaderItem(string $key, int $rank): array
116
    {
117
        $valueKey = key($this->selectEntity->select);
118
        $value = $this->selectEntity->queryOptions["columns"][$valueKey] ?? [];
119
120
        $fun = $value["fun"] ?? '';
121
        $fieldKey = !$this->selectEntity->select ? $key :
122
            ($value["col"] ?? current($this->selectEntity->select));
123
        $field = $this->selectEntity->fields[$fieldKey];
124
        $name = !$field ? ($fun ? "*" : $key) :
125
            $this->admin->fieldName($field, $rank);
126
127
        return [$fun, $name, $field];
128
    }
129
130
    /**
131
     * @param string $key
132
     * @param mixed $value
133
     * @param int $rank
134
     *
135
     * @return array
136
     */
137
    private function getResultHeader(string $key, $value, int $rank): array
0 ignored issues
show
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

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