Passed
Push — main ( 0a20e8...37b358 )
by Thierry
02:18
created

TableQueryAdmin::getEntryFunction()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 14
c 0
b 0
f 0
nc 10
nop 4
dl 0
loc 21
rs 9.2222
1
<?php
2
3
namespace Lagdo\DbAdmin\DbAdmin;
4
5
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
6
use Exception;
7
8
use function compact;
9
use function is_array;
10
use function in_array;
11
use function json_encode;
12
use function reset;
13
use function count;
14
use function preg_match;
15
use function preg_match_all;
16
use function stripcslashes;
17
use function str_replace;
18
use function is_int;
19
use function is_bool;
20
use function array_sum;
21
use function explode;
22
use function substr_count;
23
use function min;
24
25
/**
26
 * Admin table query functions
27
 */
28
class TableQueryAdmin extends AbstractAdmin
29
{
30
    private function getEntryFunction(TableFieldEntity $field, $name, $function, $functions): array
31
    {
32
        // Input for functions
33
        if ($field->type == "enum") {
34
            return [
35
                'type' => 'name',
36
                'name' => $this->util->html($functions[""] ?? ''),
37
            ];
38
        }
39
        if (count($functions) > 1) {
40
            $hasFunction = (in_array($function, $functions) || isset($functions[$function]));
41
            return [
42
                'type' => 'select',
43
                'name' => "function[$name]",
44
                'options' => $functions,
45
                'selected' => $function === null || $hasFunction ? $function : "",
46
            ];
47
        }
48
        return [
49
            'type' => 'name',
50
            'name' => $this->util->html(reset($functions)),
51
        ];
52
    }
53
54
    private function getEntryInput(TableFieldEntity $field, $name, $value, $function, $functions)
55
    {
56
        $attrs = ['name' => "fields[$name]"];
57
        if ($field->type == "enum") {
58
            return ['type' => 'radio', 'attrs' => $attrs, 'values' => [isset($options["select"]), $field, $attrs, $value]];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $options seems to never exist and therefore isset should always be false.
Loading history...
59
        }
60
        if (preg_match('~bool~', $field->type)) {
61
            return ['type' => 'checkbox', 'attrs' => $attrs, 'values' => [preg_match('~^(1|t|true|y|yes|on)$~i', $value)]];
62
        }
63
        if ($field->type == "set") {
64
            $values = [];
65
            preg_match_all("~'((?:[^']|'')*)'~", $field->length, $matches);
66
            foreach ($matches[1] as $i => $val) {
67
                $val = stripcslashes(str_replace("''", "'", $val));
68
                $checked = (is_int($value) ? ($value >> $i) & 1 : in_array($val, explode(",", $value), true));
69
                $values[] = [$this->util->html($val), $checked];
70
            }
71
            return ['type' => 'checkbox', 'attrs' => $attrs, 'values' => $values];
72
        }
73
        if (preg_match('~blob|bytea|raw|file~', $field->type) && $this->util->iniBool("file_uploads")) {
74
            return ['type' => 'upload', 'attrs' => $attrs, 'value' => $name];
75
        }
76
        if (($text = preg_match('~text|lob|memo~i', $field->type)) || preg_match("~\n~", $value)) {
77
            if ($text && $this->driver->jush() != "sqlite") {
78
                $attrs['cols'] = 50;
79
                $attrs['rows'] = 12;
80
            } else {
81
                $rows = min(12, substr_count($value, "\n") + 1);
82
                $attrs['cols'] = 30;
83
                $attrs['rows'] = $rows;
84
                if ($rows == 1) {
85
                    $attrs['style'] = 'height: 1.2em;';
86
                }
87
            }
88
            return ['type' => 'blob', 'attrs' => $attrs, 'value' => $this->util->html($value)];
89
        }
90
        if ($function == "json" || preg_match('~^jsonb?$~', $field->type)) {
91
            $attrs['cols'] = 50;
92
            $attrs['rows'] = 12;
93
            return ['type' => 'json', 'attrs' => $attrs, 'value' => $this->util->html($value)];
94
        }
95
        $unsigned = $field->unsigned ?? false;
96
        // int(3) is only a display hint
97
        $maxlength = (!preg_match('~int~', $field->type) &&
98
        preg_match('~^(\d+)(,(\d+))?$~', $field->length, $match) ?
99
            ((preg_match("~binary~", $field->type) ? 2 : 1) * $match[1] + (($match[3] ?? null) ? 1 : 0) +
100
                (($match[2] ?? false) && !$unsigned ? 1 : 0)) :
101
            ($this->driver->typeExists($field->type) ? $this->driver->type($field->type) + ($unsigned ? 0 : 1) : 0));
102
        if ($this->driver->jush() == 'sql' && $this->driver->minVersion(5.6) && preg_match('~time~', $field->type)) {
103
            $maxlength += 7; // microtime
104
        }
105
        if ($maxlength > 0) {
106
            $attrs['data-maxlength'] = $maxlength;
107
        }
108
        // type='date' and type='time' display localized value which may be confusing,
109
        // type='datetime' uses 'T' as date and time separator
110
        $hasFunction = (in_array($function, $functions) || isset($functions[$function]));
111
        if ((!$hasFunction || $function === "") &&
112
            preg_match('~(?<!o)int(?!er)~', $field->type) &&
113
            !preg_match('~\[\]~', $field->fullType)) {
114
            $attrs['type'] = 'number';
115
        }
116
        if (preg_match('~char|binary~', $field->type) && $maxlength > 20) {
117
            $attrs['size'] = 40;
118
        }
119
        return ['type' => 'input', 'attrs' => $attrs, 'value' => $this->util->html($value)];
120
    }
121
122
    /**
123
     * Get data for an input field
124
     */
125
    protected function getFieldInput($field, $value, $function, $options)
126
    {
127
        // From functions.inc.php (function input($field, $value, $function))
128
        $name = $this->util->html($this->util->bracketEscape($field->name));
129
        $save = $options["save"];
130
        $reset = ($this->driver->jush() == "mssql" && $field->autoIncrement);
131
        if (is_array($value) && !$function) {
132
            $value = json_encode($value, JSON_PRETTY_PRINT);
133
            $function = "json";
134
        }
135
        if ($reset && !$save) {
136
            $function = null;
137
        }
138
        $functions = [];
139
        if ($reset) {
140
            $functions["orig"] = $this->trans->lang('original');
141
        }
142
        $functions += $this->util->editFunctions($field);
143
        return [
144
            'type' => $this->util->html($field->fullType),
145
            'name' => $name,
146
            'field' => [
147
                'type' => $field->type,
148
            ],
149
            'function' => $this->getEntryFunction($field, $name, $function, $functions),
150
            'input' => $this->getEntryInput($field, $name, $value, $function, $functions),
151
        ];
152
153
        // Input for value
154
        // The HTML code generated by Adminer is kept here.
155
        /*$attrs = " name='fields[$name]'";
156
        $entry['input'] = ['type' => ''];
157
        if ($field->type == "enum") {
158
            $entry['input']['type'] = 'radio';
159
            $entry['input']['value'] = $this->util->editInput(isset($options["select"]), $field, $attrs, $value);
160
        } elseif (preg_match('~bool~', $field->type)) {
161
            $entry['input']['type'] = 'checkbox';
162
            $entry['input']['value'] = ["<input type='hidden'$attrs value='0'>" . "<input type='checkbox'" .
163
                (preg_match('~^(1|t|true|y|yes|on)$~i', $value) ? " checked='checked'" : "") . "$attrs value='1'>"];
164
        } elseif ($field->type == "set") {
165
            $entry['input']['type'] = 'checkbox';
166
            $entry['input']['value'] = [];
167
            preg_match_all("~'((?:[^']|'')*)'~", $field->length, $matches);
168
            foreach ($matches[1] as $i => $val) {
169
                $val = \stripcslashes(\str_replace("''", "'", $val));
170
                $checked = (is_int($value) ? ($value >> $i) & 1 : in_array($val, explode(",", $value), true));
171
                $entry['input']['value'][] = "<label><input type='checkbox' name='fields[$name][$i]' value='" . (1 << $i) . "'" .
172
                    ($checked ? ' checked' : '') . ">" . $this->util->html($val) . '</label>';
173
            }
174
        } elseif (preg_match('~blob|bytea|raw|file~', $field->type) && $this->util->iniBool("file_uploads")) {
175
            $entry['input']['value'] = "<input type='file' name='fields-$name'>";
176
        } elseif (($text = preg_match('~text|lob|memo~i', $field->type)) || preg_match("~\n~", $value)) {
177
            if ($text && $this->driver->jush() != "sqlite") {
178
                $attrs .= " cols='50' rows='12'";
179
            } else {
180
                $rows = min(12, substr_count($value, "\n") + 1);
181
                $attrs .= " cols='30' rows='$rows'" . ($rows == 1 ? " style='height: 1.2em;'" : ""); // 1.2em - line-height
182
            }
183
            $entry['input']['value'] = "<textarea$attrs>" . $this->util->html($value) . '</textarea>';
184
        } elseif ($function == "json" || preg_match('~^jsonb?$~', $field->type)) {
185
            $entry['input']['value'] = "<textarea$attrs cols='50' rows='12' class='jush-js'>" .
186
                $this->util->html($value) . '</textarea>';
187
        } else {
188
            $unsigned = $field->unsigned ?? false;
189
            // int(3) is only a display hint
190
            $maxlength = (!preg_match('~int~', $field->type) &&
191
                preg_match('~^(\d+)(,(\d+))?$~', $field->length, $match) ?
192
                ((preg_match("~binary~", $field->type) ? 2 : 1) * $match[1] + (($match[3] ?? null) ? 1 : 0) +
193
                (($match[2] ?? false) && !$unsigned ? 1 : 0)) :
194
                ($this->driver->typeExists($field->type) ? $this->driver->type($field->type) + ($unsigned ? 0 : 1) : 0));
195
            if ($this->driver->jush() == 'sql' && $this->driver->minVersion(5.6) && preg_match('~time~', $field->type)) {
196
                $maxlength += 7; // microtime
197
            }
198
            // type='date' and type='time' display localized value which may be confusing,
199
            // type='datetime' uses 'T' as date and time separator
200
            $hasFunction = (in_array($function, $functions) || isset($functions[$function]));
201
            $entry['input']['value'] = "<input" . ((!$hasFunction || $function === "") &&
202
                preg_match('~(?<!o)int(?!er)~', $field->type) &&
203
                !preg_match('~\[\]~', $field->fullType) ? " type='number'" : "") . " value='" .
204
                $this->util->html($value) . "'" . ($maxlength ? " data-maxlength='$maxlength'" : "") .
205
                (preg_match('~char|binary~', $field->type) && $maxlength > 20 ? " size='40'" : "") . "$attrs>";
206
        }
207
208
        return $entry;*/
209
    }
210
211
    /**
212
     * Get the table fields
213
     *
214
     * @param string $table         The table name
215
     * @param array  $queryOptions  The query options
216
     *
217
     * @return array
218
     */
219
    private function getFields(string $table, array $queryOptions): array
220
    {
221
        // From edit.inc.php
222
        $fields = $this->driver->fields($table);
223
224
        //!!!! $queryOptions["select"] is never set here !!!!//
225
226
        $where = $this->admin->where($queryOptions, $fields);
227
        $update = $where;
228
        foreach ($fields as $name => $field) {
229
            $generated = $field->generated ?? false;
230
            if (!isset($field->privileges[$update ? "update" : "insert"]) ||
231
                $this->util->fieldName($field) == "" || $generated) {
232
                unset($fields[$name]);
233
            }
234
        }
235
236
        return [$fields, $where, $update];
237
    }
238
239
    /**
240
     * Get data for insert/update on a table
241
     *
242
     * @param string $table         The table name
243
     * @param array  $queryOptions  The query options
244
     *
245
     * @return array
246
     */
247
    public function getQueryData(string $table, array $queryOptions = []): array
248
    {
249
        $isInsert = (count($queryOptions) === 0); // True only on insert.
250
        // Default options
251
        $queryOptions['clone'] = false;
252
        $queryOptions['save'] = false;
253
254
        list($fields, $where, $update) = $this->getFields($table, $queryOptions);
255
256
        // From edit.inc.php
257
        $row = null;
258
        if (($where)) {
259
            $select = [];
260
            foreach ($fields as $name => $field) {
261
                if (isset($field->privileges["select"])) {
262
                    $as = $this->driver->convertField($field);
263
                    if ($queryOptions["clone"] && $field->autoIncrement) {
264
                        $as = "''";
265
                    }
266
                    if ($this->driver->jush() == "sql" && preg_match("~enum|set~", $field->type)) {
267
                        $as = "1*" . $this->driver->escapeId($name);
268
                    }
269
                    $select[] = ($as ? "$as AS " : "") . $this->driver->escapeId($name);
270
                }
271
            }
272
            $row = [];
273
            if (!$this->driver->support("table")) {
274
                $select = ["*"];
275
            }
276
            if ($select) {
277
                $statement = $this->driver->select($table, $select, [$where], $select, [],
278
                    (isset($queryOptions["select"]) ? 2 : 1));
279
                if (($statement)) {
280
                    $row = $statement->fetchAssoc();
281
                }/* else {
282
                    $error = $this->driver->error();
283
                }*/
284
                // if(isset($queryOptions["select"]) && (!$row || $statement->fetchAssoc()))
285
                // {
286
                //     // $statement->rowCount() != 1 isn't available in all drivers
287
                //     $row = null;
288
                // }
289
            }
290
        }
291
292
        if (!$this->driver->support("table") && empty($fields)) {
293
            $primary = ''; // $this->driver->primaryIdName();
294
            if (!$where) {
295
                // insert
296
                $statement = $this->driver->select($table, ["*"], [$where], ["*"]);
297
                $row = ($statement ? $statement->fetchAssoc() : false);
298
                if (!$row) {
299
                    $row = [$primary => ""];
300
                }
301
            }
302
            if ($row) {
303
                foreach ($row as $key => $val) {
304
                    if (!$where) {
305
                        $row[$key] = null;
306
                    }
307
                    $fields[$key] = [
308
                        "name" => $key,
309
                        "null" => ($key !== $primary),
310
                        "autoIncrement" => ($key === $primary)
311
                    ];
312
                }
313
            }
314
        }
315
316
        // From functions.inc.php (function edit_form($table, $fields, $row, $update))
317
        $entries = [];
318
        $tableName = $this->util->tableName($this->driver->tableStatusOrName($table, true));
319
        $error = null;
320
        if (($where) && $row === null) { // No row found to edit.
321
            $error = $this->trans->lang('No rows.');
322
        } elseif (!$fields) {
323
            $error = $this->trans->lang('You have no privileges to update this table.');
324
        } else {
325
            foreach ($fields as $name => $field) {
326
                // $default = $queryOptions["set"][$this->util->bracketEscape($name)] ?? null;
327
                // if($default === null)
328
                // {
329
                $default = $field->default;
330
                if ($field->type == "bit" && preg_match("~^b'([01]*)'\$~", $default, $regs)) {
331
                    $default = $regs[1];
332
                }
333
                // }
334
                $value = ($row !== null ?
335
                    ($row[$name] != "" && $this->driver->jush() == "sql" && preg_match("~enum|set~", $field->type) ?
336
                    (is_array($row[$name]) ? array_sum($row[$name]) : +$row[$name]) :
337
                    (is_bool($row[$name]) ? +$row[$name] : $row[$name])) :
338
                    (!$update && $field->autoIncrement ? "" : (isset($queryOptions["select"]) ? false : $default)));
339
                $function = ($queryOptions["save"] ? (string)$queryOptions["function"][$name] :
340
                    ($update && preg_match('~^CURRENT_TIMESTAMP~i', $field->onUpdate) ? "now" :
341
                    ($value === false ? null : ($value !== null ? '' : 'NULL'))));
342
                if (!$update && $value == $field->default && preg_match('~^[\w.]+\(~', $value)) {
343
                    $function = "SQL";
344
                }
345
                if (preg_match("~time~", $field->type) && preg_match('~^CURRENT_TIMESTAMP~i', $value)) {
346
                    $value = "";
347
                    $function = "now";
348
                }
349
350
                $entries[$name] = $this->getFieldInput($field, $value, $function, $queryOptions);
351
            }
352
        }
353
354
        $mainActions = [
355
            'query-back' => $this->trans->lang('Back'),
356
            'query-save' => $this->trans->lang('Save'),
357
        ];
358
        if ($isInsert) {
359
            $mainActions['query-save-select'] = $this->trans->lang('Save and select');
360
        }
361
362
        $fields = $entries;
363
        return compact('mainActions', 'tableName', 'error', 'fields');
364
    }
365
366
    /**
367
     * Insert a new item in a table
368
     *
369
     * @param string $table         The table name
370
     * @param array  $queryOptions  The query options
371
     *
372
     * @return array
373
     */
374
    public function insertItem(string $table, array $queryOptions): array
375
    {
376
        list($fields, $where, $update) = $this->getFields($table, $queryOptions);
377
378
        // From edit.inc.php
379
        $values = [];
380
        foreach ($fields as $name => $field) {
381
            $val = $this->util->processInput($field, $queryOptions);
382
            if ($val !== false && $val !== null) {
383
                $values[$this->driver->escapeId($name)] = $val;
384
            }
385
        }
386
387
        $result = $this->driver->insert($table, $values);
388
        $lastId = ($result ? $this->driver->lastAutoIncrementId() : 0);
389
        $message = $this->trans->lang('Item%s has been inserted.', ($lastId ? " $lastId" : ""));
390
391
        $error = $this->driver->error();
392
393
        return compact('result', 'message', 'error');
394
    }
395
396
    /**
397
     * Update one or more items in a table
398
     *
399
     * @param string $table         The table name
400
     * @param array  $queryOptions  The query options
401
     *
402
     * @return array
403
     */
404
    public function updateItem(string $table, array $queryOptions): array
405
    {
406
        list($fields, $where, $update) = $this->getFields($table, $queryOptions);
407
408
        // From edit.inc.php
409
        $indexes = $this->driver->indexes($table);
410
        $uniqueIds = $this->util->uniqueIds($queryOptions["where"], $indexes);
411
        $queryWhere = "\nWHERE $where";
412
413
        $values = [];
414
        foreach ($fields as $name => $field) {
415
            $val = $this->util->processInput($field, $queryOptions);
416
            if ($val !== false && $val !== null) {
417
                $values[$this->driver->escapeId($name)] = $val;
418
            }
419
        }
420
421
        $result = $this->driver->update($table, $values, $queryWhere, count($uniqueIds));
422
        $message = $this->trans->lang('Item has been updated.');
423
424
        $error = $this->driver->error();
425
426
        return compact('result', 'message', 'error');
427
    }
428
429
    /**
430
     * Delete one or more items in a table
431
     *
432
     * @param string $table         The table name
433
     * @param array  $queryOptions  The query options
434
     *
435
     * @return array
436
     */
437
    public function deleteItem(string $table, array $queryOptions): array
438
    {
439
        list($fields, $where, $update) = $this->getFields($table, $queryOptions);
440
441
        // From edit.inc.php
442
        $indexes = $this->driver->indexes($table);
443
        $uniqueIds = $this->util->uniqueIds($queryOptions["where"], $indexes);
444
        $queryWhere = "\nWHERE $where";
445
446
        $result = $this->driver->delete($table, $queryWhere, count($uniqueIds));
447
        $message = $this->trans->lang('Item has been deleted.');
448
449
        $error = $this->driver->error();
450
451
        return compact('result', 'message', 'error');
452
    }
453
}
454