Passed
Push — main ( 6a63fe...cd781c )
by Thierry
07:05
created

Util::uniqueIds()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 5
nop 2
dl 0
loc 15
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
namespace Lagdo\DbAdmin\Db;
4
5
use Lagdo\DbAdmin\Driver\UtilInterface;
6
use Lagdo\DbAdmin\Driver\DriverInterface;
7
use Lagdo\DbAdmin\Driver\Entity\TableEntity;
8
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
9
10
class Util implements UtilInterface
11
{
12
    /**
13
     * @var DriverInterface
14
     */
15
    public $driver;
16
17
    /**
18
     * @var Translator
19
     */
20
    protected $trans;
21
22
    /**
23
     * @var Input
24
     */
25
    public $input;
26
27
    /**
28
     * The constructor
29
     *
30
     * @param Translator $trans
31
     */
32
    public function __construct(Translator $trans)
33
    {
34
        $this->trans = $trans;
35
        $this->input = new Input();
36
    }
37
38
    /**
39
     * Set the driver
40
     *
41
     * @param DriverInterface $driver
42
     *
43
     * @return void
44
     */
45
    public function setDriver(DriverInterface $driver)
46
    {
47
        $this->driver = $driver;
48
    }
49
50
    /**
51
     * Get a target="_blank" attribute
52
     *
53
     * @return string
54
     */
55
    public function blankTarget()
56
    {
57
        return ' target="_blank" rel="noreferrer noopener"';
58
    }
59
60
    /**
61
     * Name in title and navigation
62
     *
63
     * @return string
64
     */
65
    public function name()
66
    {
67
        return "<a href='https://www.adminer.org/'" . $this->blankTarget() . " id='h1'>Adminer</a>";
68
    }
69
70
    /**
71
     * @inheritDoc
72
     */
73
    public function input()
74
    {
75
        return $this->input;
76
    }
77
78
    /**
79
     * @inheritDoc
80
     */
81
    public function html(?string $string)
82
    {
83
        return \str_replace("\0", "&#0;", \htmlspecialchars($string, ENT_QUOTES, 'utf-8'));
0 ignored issues
show
Bug introduced by
It seems like $string can also be of type null; however, parameter $string of htmlspecialchars() does only seem to accept string, 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

83
        return \str_replace("\0", "&#0;", \htmlspecialchars(/** @scrutinizer ignore-type */ $string, ENT_QUOTES, 'utf-8'));
Loading history...
84
    }
85
86
    /**
87
     * @inheritDoc
88
     */
89
    public function number(string $value)
90
    {
91
        return \preg_replace('~[^0-9]+~', '', $value);
92
    }
93
94
    /**
95
     * @inheritDoc
96
     */
97
    public function isUtf8(string $value)
98
    {
99
        // don't print control chars except \t\r\n
100
        return (\preg_match('~~u', $value) && !\preg_match('~[\0-\x8\xB\xC\xE-\x1F]~', $value));
101
    }
102
103
    /**
104
     * Get escaped error message
105
     *
106
     * @return string
107
     */
108
    public function error()
109
    {
110
        return $this->html($this->driver->error());
111
    }
112
113
    /**
114
     * Check if the string is e-mail address
115
     *
116
     * @param string $email
117
     *
118
     * @return bool
119
     */
120
    public function isMail(string $email)
121
    {
122
        $atom = '[-a-z0-9!#$%&\'*+/=?^_`{|}~]'; // characters of local-name
123
        $domain = '[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])'; // one domain component
124
        $pattern = "$atom+(\\.$atom+)*@($domain?\\.)+$domain";
125
        return \is_string($email) && \preg_match("(^$pattern(,\\s*$pattern)*\$)i", $email);
126
    }
127
128
    /**
129
     * Check if the string is URL address
130
     *
131
     * @param mixed $string
132
     *
133
     * @return bool
134
     */
135
    public function isUrl($string)
136
    {
137
        $domain = '[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])'; // one domain component //! IDN
138
        //! restrict path, query and fragment characters
139
        return \preg_match("~^(https?)://($domain?\\.)+$domain(:\\d+)?(/.*)?(\\?.*)?(#.*)?\$~i", $string);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match('~^(ht...)?(#.*)?\$~i', $string) returns the type integer which is incompatible with the documented return type boolean.
Loading history...
140
    }
141
142
    /**
143
     * Check if field should be shortened
144
     *
145
     * @param TableFieldEntity $field
146
     *
147
     * @return bool
148
     */
149
    public function isShortable(TableFieldEntity $field)
150
    {
151
        return \preg_match('~char|text|json|lob|geometry|point|linestring|polygon|string|bytea~', $field->type);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match('~char...|bytea~', $field->type) returns the type integer which is incompatible with the documented return type boolean.
Loading history...
152
    }
153
154
    /**
155
     * @inheritDoc
156
     */
157
    public function iniBool(string $ini)
158
    {
159
        $value = \ini_get($ini);
160
        return (\preg_match('~^(on|true|yes)$~i', $value) || (int) $value); // boolean values set by php_value are strings
161
    }
162
163
    /**
164
     * Get INI bytes value
165
     *
166
     * @param string
167
     *
168
     * @return int
169
     */
170
    public function iniBytes(string $ini)
171
    {
172
        $value = \ini_get($ini);
173
        $unit = \strtolower(\substr($value, -1)); // Get the last char
174
        $ival = \intval(\substr($value, 0, -1)); // Remove the last char
175
        switch ($unit) {
176
            case 'g': $value = $ival * 1024 * 1024 * 1024; break;
177
            case 'm': $value = $ival * 1024 * 1024; break;
178
            case 'k': $value = $ival * 1024; break;
179
        }
180
        return $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value also could return the type string which is incompatible with the documented return type integer.
Loading history...
181
    }
182
183
    /**
184
     * @inheritDoc
185
     */
186
    public function convertEolToHtml(string $string)
187
    {
188
        return \str_replace("\n", "<br>", $string); // nl2br() uses XHTML before PHP 5.3
189
    }
190
191
    /**
192
     * Escape column key used in where()
193
     *
194
     * @param string
195
     *
196
     * @return string
197
     */
198
    public function escapeKey(string $key)
199
    {
200
        if (\preg_match('(^([\w(]+)(' .
201
            \str_replace("_", ".*", \preg_quote($this->driver->escapeId("_"))) . ')([ \w)]+)$)', $key, $match)) {
202
            //! columns looking like functions
203
            return $match[1] . $this->driver->escapeId($this->driver->unescapeId($match[2])) . $match[3]; //! SQL injection
204
        }
205
        return $this->driver->escapeId($key);
206
    }
207
208
    /**
209
     * @inheritDoc
210
     */
211
    public function getFieldsFromEdit()
212
    {
213
        $fields = [];
214
        $values = $this->input->values;
215
        foreach ((array) $values["field_keys"] as $key => $value) {
216
            if ($value != "") {
217
                $value = $this->bracketEscape($value);
218
                $values["function"][$value] = $values["field_funs"][$key];
219
                $values["fields"][$value] = $values["field_vals"][$key];
220
            }
221
        }
222
        foreach ((array) $values["fields"] as $key => $value) {
223
            $name = $this->bracketEscape($key, 1); // 1 - back
224
            $fields[$name] = [
225
                "name" => $name,
226
                "privileges" => ["insert" => 1, "update" => 1],
227
                "null" => 1,
228
                "autoIncrement" => false, // ($key == $this->driver->primaryIdName()),
229
            ];
230
        }
231
        return $fields;
232
    }
233
234
    /**
235
     * Create repeat pattern for preg
236
     *
237
     * @param string $pattern
238
     * @param int $length
239
     *
240
     * @return string
241
     */
242
    public function repeatPattern(string $pattern, int $length)
243
    {
244
        // fix for Compilation failed: number too big in {} quantifier
245
        // can create {0,0} which is OK
246
        return \str_repeat("$pattern{0,65535}", $length / 65535) . "$pattern{0," . ($length % 65535) . "}";
247
    }
248
249
    /**
250
     * Shorten UTF-8 string
251
     *
252
     * @param string $string
253
     * @param int $length
254
     * @param string $suffix
255
     *
256
     * @return string
257
     */
258
    public function shortenUtf8(string $string, int $length = 80, string $suffix = "")
259
    {
260
        if (!\preg_match("(^(" . $this->repeatPattern("[\t\r\n -\x{10FFFF}]", $length) . ")($)?)u", $string, $match)) {
261
            // ~s causes trash in $match[2] under some PHP versions, (.|\n) is slow
262
            \preg_match("(^(" . $this->repeatPattern("[\t\r\n -~]", $length) . ")($)?)", $string, $match);
263
        }
264
        return $this->html($match[1]) . $suffix . (isset($match[2]) ? "" : "<i>…</i>");
265
    }
266
267
    /**
268
     * Escape or unescape string to use inside form []
269
     *
270
     * @param string $idf
271
     * @param bool $back
272
     *
273
     * @return string
274
     */
275
    public function bracketEscape(string $idf, bool $back = false)
276
    {
277
        // escape brackets inside name="x[]"
278
        static $trans = [':' => ':1', ']' => ':2', '[' => ':3', '"' => ':4'];
279
        return \strtr($idf, ($back ? \array_flip($trans) : $trans));
280
    }
281
282
    /**
283
     * Find unique identifier of a row
284
     *
285
     * @param array $row
286
     * @param array $indexes Result of indexes()
287
     *
288
     * @return array
289
     */
290
    public function uniqueIds(array $row, array $indexes)
291
    {
292
        foreach ($indexes as $index) {
293
            if (\preg_match("~PRIMARY|UNIQUE~", $index->type)) {
294
                $ids = [];
295
                foreach ($index->columns as $key) {
296
                    if (!isset($row[$key])) { // NULL is ambiguous
297
                        continue 2;
298
                    }
299
                    $ids[$key] = $row[$key];
300
                }
301
                return $ids;
302
            }
303
        }
304
        return [];
305
    }
306
307
    /**
308
     * Table caption used in navigation and headings
309
     *
310
     * @param TableEntity $table
311
     *
312
     * @return string
313
     */
314
    public function tableName(TableEntity $table)
315
    {
316
        return $this->html($table->name);
317
    }
318
319
    /**
320
     * Field caption used in select and edit
321
     *
322
     * @param TableFieldEntity $field Single field returned from fields()
323
     * @param int $order Order of column in select
324
     *
325
     * @return string
326
     */
327
    public function fieldName(TableFieldEntity $field, int $order = 0)
0 ignored issues
show
Unused Code introduced by
The parameter $order 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

327
    public function fieldName(TableFieldEntity $field, /** @scrutinizer ignore-unused */ int $order = 0)

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...
328
    {
329
        return '<span title="' . $this->html($field->fullType) . '">' . $this->html($field->name) . '</span>';
330
    }
331
332
    /**
333
     * Returns export format options
334
     *
335
     * @return array
336
     */
337
    public function dumpFormat()
338
    {
339
        return ['sql' => 'SQL', 'csv' => 'CSV,', 'csv;' => 'CSV;', 'tsv' => 'TSV'];
340
    }
341
342
    /**
343
     * Returns export output options
344
     *
345
     * @return array
346
     */
347
    public function dumpOutput()
348
    {
349
        $output = ['text' => $this->trans->lang('open'), 'file' => $this->trans->lang('save')];
350
        if (\function_exists('gzencode')) {
351
            $output['gz'] = 'gzip';
352
        }
353
        return $output;
354
    }
355
356
    /**
357
     * Set the path of the file for webserver load
358
     *
359
     * @return string
360
     */
361
    public function importServerPath()
362
    {
363
        return "adminer.sql";
364
    }
365
366
    /**
367
     * Export database structure
368
     *
369
     * @param string
370
     *
371
     * @return null prints data
372
     */
373
    // public function dumpDatabase($database) {
374
    // }
375
376
    /**
377
     * Print before edit form
378
     *
379
     * @param string $table
380
     * @param array $fields
381
     * @param mixed $row
382
     * @param bool $update
383
     *
384
     * @return null
385
     */
386
    public function editRowPrint(string $table, array $fields, $row, bool $update)
0 ignored issues
show
Unused Code introduced by
The parameter $table 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

386
    public function editRowPrint(/** @scrutinizer ignore-unused */ string $table, array $fields, $row, bool $update)

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...
Unused Code introduced by
The parameter $row 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

386
    public function editRowPrint(string $table, array $fields, /** @scrutinizer ignore-unused */ $row, bool $update)

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...
Unused Code introduced by
The parameter $update 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

386
    public function editRowPrint(string $table, array $fields, $row, /** @scrutinizer ignore-unused */ bool $update)

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...
Unused Code introduced by
The parameter $fields 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

386
    public function editRowPrint(string $table, /** @scrutinizer ignore-unused */ array $fields, $row, bool $update)

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...
387
    {
388
    }
389
390
    /**
391
     * Functions displayed in edit form
392
     *
393
     * @param TableFieldEntity $field Single field from fields()
394
     *
395
     * @return array
396
     */
397
    public function editFunctions(TableFieldEntity $field)
398
    {
399
        $update = isset($this->input->values["select"]); // || $this->where([]);
400
        if ($field->autoIncrement && !$update) {
401
            return [$this->trans->lang('Auto Increment')];
402
        }
403
404
        $clauses = ($field->null ? "NULL/" : "");
405
        foreach ($this->driver->editFunctions() as $key => $functions) {
406
            if (!$key || (!isset($this->input->values["call"]) && $update)) { // relative functions
407
                foreach ($functions as $pattern => $value) {
408
                    if (!$pattern || \preg_match("~$pattern~", $field->type)) {
409
                        $clauses .= "/$value";
410
                    }
411
                }
412
            }
413
            if ($key && !\preg_match('~set|blob|bytea|raw|file|bool~', $field->type)) {
414
                $clauses .= "/SQL";
415
            }
416
        }
417
        return \explode("/", $clauses);
418
    }
419
420
    /**
421
     * Get hint for edit field
422
     *
423
     * @param string $table     Table name
424
     * @param TableFieldEntity $field   Single field from fields()
425
     * @param string $value
426
     *
427
     * @return string
428
     */
429
    public function editHint(string $table, TableFieldEntity $field, string $value)
0 ignored issues
show
Unused Code introduced by
The parameter $field 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

429
    public function editHint(string $table, /** @scrutinizer ignore-unused */ TableFieldEntity $field, string $value)

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...
Unused Code introduced by
The parameter $table 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

429
    public function editHint(/** @scrutinizer ignore-unused */ string $table, TableFieldEntity $field, string $value)

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...
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

429
    public function editHint(string $table, TableFieldEntity $field, /** @scrutinizer ignore-unused */ string $value)

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...
430
    {
431
        return "";
432
    }
433
434
    /**
435
     * Get a link to use in select table
436
     *
437
     * @param string $value     Raw value of the field
438
     * @param TableFieldEntity $field   Single field returned from fields()
439
     *
440
     * @return string|null
441
     */
442
    private function selectLink(string $value, TableFieldEntity $field)
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

442
    private function selectLink(/** @scrutinizer ignore-unused */ string $value, TableFieldEntity $field)

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...
Unused Code introduced by
The parameter $field 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

442
    private function selectLink(string $value, /** @scrutinizer ignore-unused */ TableFieldEntity $field)

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...
443
    {
444
    }
445
446
    /**
447
     * Value conversion used in select and edit
448
     *
449
     * @param string $value     Raw value of the field
450
     * @param TableFieldEntity $field   Single field returned from fields()
451
     *
452
     * @return string
453
     */
454
    public function editValue(string $value, TableFieldEntity $field)
0 ignored issues
show
Unused Code introduced by
The parameter $field 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

454
    public function editValue(string $value, /** @scrutinizer ignore-unused */ TableFieldEntity $field)

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...
455
    {
456
        return $value;
457
    }
458
459
    /**
460
     * Print enum input field
461
     *
462
     * @param string $type Field type: "radio" or "checkbox"
463
     * @param string $attrs
464
     * @param TableFieldEntity $field
465
     * @param mixed $value int|string|array
466
     * @param string $empty
467
     *
468
     * @return null
469
     */
470
    // public function enum_input(string $type, string $attrs, TableFieldEntity $field, $value, string $empty = null)
471
    // {
472
    //     \preg_match_all("~'((?:[^']|'')*)'~", $field->length, $matches);
473
    //     $input = ($empty !== null ? "<label><input type='$type'$attrs value='$empty'" .
474
    //         ((is_array($value) ? in_array($empty, $value) : $value === 0) ? " checked" : "") .
475
    //         "><i>" . $this->trans->lang('empty') . "</i></label>" : "");
476
    //     foreach ($matches[1] as $i => $val) {
477
    //         $val = stripcslashes(str_replace("''", "'", $val));
478
    //         $checked = (is_int($value) ? $value == $i+1 : (is_array($value) ? in_array($i+1, $value) : $value === $val));
479
    //         $input .= " <label><input type='$type'$attrs value='" . ($i+1) . "'" .
480
    //             ($checked ? ' checked' : '') . '>' . $this->util->html($adminer->editValue($val, $field)) . '</label>';
481
    //     }
482
    //     return $input;
483
    // }
484
485
    /**
486
     * Get options to display edit field
487
     *
488
     * @param string $table Table name
489
     * @param bool $select
490
     * @param TableFieldEntity $field Single field from fields()
491
     * @param string $attrs Attributes to use inside the tag
492
     * @param string $value
493
     *
494
     * @return array
495
     */
496
    public function editInput(string $table, bool $select, TableFieldEntity $field, string $attrs, string $value)
0 ignored issues
show
Unused Code introduced by
The parameter $table 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

496
    public function editInput(/** @scrutinizer ignore-unused */ string $table, bool $select, TableFieldEntity $field, string $attrs, string $value)

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...
497
    {
498
        if ($field->type !== "enum") {
499
            return [];
500
        }
501
        $inputs = [];
502
        if (($select)) {
503
            $inputs[] = "<label><input type='radio'$attrs value='-1' checked><i>" .
504
                $this->trans->lang('original') . "</i></label> ";
505
        }
506
        if (($field->null)) {
507
            $inputs[] = "<label><input type='radio'$attrs value=''" .
508
                ($value !== null || ($select) ? "" : " checked") . "><i>NULL</i></label> ";
0 ignored issues
show
introduced by
The condition $value !== null is always true.
Loading history...
509
        }
510
511
        // From functions.inc.php (function enum_input())
512
        $empty = 0; // 0 - empty
513
        $type = 'radio';
514
        $inputs[] = "<label><input type='$type'$attrs value='$empty'" .
515
            ((\is_array($value) ? \in_array($empty, $value) : $value === 0) ? " checked" : "") .
0 ignored issues
show
introduced by
The condition is_array($value) is always false.
Loading history...
516
            "><i>" . $this->trans->lang('empty') . "</i></label>";
517
518
        \preg_match_all("~'((?:[^']|'')*)'~", $field->length, $matches);
519
        foreach ($matches[1] as $i => $val) {
520
            $val = \stripcslashes(\str_replace("''", "'", $val));
521
            $checked = (\is_int($value) ? $value == $i + 1 :
522
                (\is_array($value) ? \in_array($i+1, $value) : $value === $val));
523
            $inputs[] = "<label><input type='$type'$attrs value='" . ($i+1) . "'" .
524
                ($checked ? ' checked' : '') . '>' . $this->html($this->editValue($val, $field)) . '</label>';
525
        }
526
527
        return $inputs;
528
    }
529
530
    /**
531
     * Get file contents from $_FILES
532
     *
533
     * @param string $key
534
     * @param bool $decompress
535
     *
536
     * @return int|string
537
     */
538
    private function getFile(string $key, bool $decompress = false)
539
    {
540
        $file = $_FILES[$key];
541
        if (!$file) {
542
            return null;
543
        }
544
        foreach ($file as $key => $val) {
545
            $file[$key] = (array) $val;
546
        }
547
        $queries = '';
548
        foreach ($file["error"] as $key => $error) {
549
            if ($error) {
550
                return $error;
551
            }
552
            $name = $file["name"][$key];
553
            $tmpName = $file["tmp_name"][$key];
554
            $content = \file_get_contents($decompress && \preg_match('~\.gz$~', $name) ?
555
                "compress.zlib://$tmpName" : $tmpName); //! may not be reachable because of open_basedir
556
            if ($decompress) {
557
                $start = \substr($content, 0, 3);
558
                if (\function_exists("iconv") && \preg_match("~^\xFE\xFF|^\xFF\xFE~", $start, $regs)) {
559
                    // not ternary operator to save memory
560
                    $content = \iconv("utf-16", "utf-8", $content);
561
                } elseif ($start == "\xEF\xBB\xBF") { // UTF-8 BOM
562
                    $content = \substr($content, 3);
563
                }
564
                $queries .= $content . "\n\n";
565
            } else {
566
                $queries .= $content;
567
            }
568
        }
569
        //! support SQL files not ending with semicolon
570
        return $queries;
571
    }
572
573
    /**
574
     * Filter length value including enums
575
     *
576
     * @param string $length
577
     *
578
     * @return string
579
     */
580
    public function processLength(string $length)
581
    {
582
        if (!$length) {
583
            return '';
584
        }
585
        $enumLength = $this->driver->enumLength();
586
        return (\preg_match("~^\\s*\\(?\\s*$enumLength(?:\\s*,\\s*$enumLength)*+\\s*\\)?\\s*\$~", $length) &&
587
            \preg_match_all("~$enumLength~", $length, $matches) ? "(" . \implode(",", $matches[0]) . ")" :
588
            \preg_replace('~^[0-9].*~', '(\0)', \preg_replace('~[^-0-9,+()[\]]~', '', $length))
589
        );
590
    }
591
592
    /**
593
     * Create SQL string from field type
594
     *
595
     * @param TableFieldEntity $field
596
     * @param string $collate
597
     *
598
     * @return string
599
     */
600
    private function processType(TableFieldEntity $field, string $collate = "COLLATE")
601
    {
602
        $values = [
603
            'unsigned' => $field->unsigned,
604
            'collation' => $field->collation,
605
        ];
606
        return " " . $field->type . $this->processLength($field->length) .
607
            (\preg_match($this->driver->numberRegex(), $field->type) &&
608
            \in_array($values["unsigned"], $this->driver->unsigned()) ?
609
            " $values[unsigned]" : "") . (\preg_match('~char|text|enum|set~', $field->type) &&
610
            $values["collation"] ? " $collate " . $this->driver->quote($values["collation"]) : "");
611
    }
612
613
    /**
614
     * @inheritDoc
615
     */
616
    public function processField(TableFieldEntity $field, TableFieldEntity $typeField)
617
    {
618
        return [
619
            $this->driver->escapeId(trim($field->name)),
620
            $this->processType($typeField),
621
            ($field->null ? " NULL" : " NOT NULL"), // NULL for timestamp
622
            $this->driver->defaultValue($field),
623
            (\preg_match('~timestamp|datetime~', $field->type) && $field->onUpdate ?
624
                " ON UPDATE {$field->onUpdate}" : ""),
625
            ($this->driver->support("comment") && $field->comment != "" ?
626
                " COMMENT " . $this->driver->quote($field->comment) : ""),
627
            ($field->autoIncrement ? $this->driver->autoIncrement() : null),
628
        ];
629
    }
630
631
    /**
632
     * Process edit input field
633
     *
634
     * @param TableFieldEntity $field
635
     * @param array $inputs The user inputs
636
     *
637
     * @return string|false
638
     */
639
    public function processInput(TableFieldEntity $field, array $inputs)
640
    {
641
        $idf = $this->bracketEscape($field->name);
642
        $function = $inputs["function"][$idf] ?? '';
643
        $value = $inputs["fields"][$idf];
644
        if ($field->type == "enum") {
645
            if ($value == -1) {
646
                return false;
647
            }
648
            if ($value == "") {
649
                return "NULL";
650
            }
651
            return +$value;
652
        }
653
        if ($field->autoIncrement && $value == "") {
654
            return null;
655
        }
656
        if ($function == "orig") {
657
            return (\preg_match('~^CURRENT_TIMESTAMP~i', $field->onUpdate) ?
658
                $this->driver->escapeId($field->name) : false);
659
        }
660
        if ($function == "NULL") {
661
            return "NULL";
662
        }
663
        if ($field->type == "set") {
664
            return \array_sum((array) $value);
665
        }
666
        if ($function == "json") {
667
            $function = "";
0 ignored issues
show
Unused Code introduced by
The assignment to $function is dead and can be removed.
Loading history...
668
            $value = \json_decode($value, true);
669
            if (!\is_array($value)) {
670
                return false; //! report errors
671
            }
672
            return $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value returns the type array which is incompatible with the documented return type false|string.
Loading history...
673
        }
674
        if (\preg_match('~blob|bytea|raw|file~', $field->type) && $this->iniBool("file_uploads")) {
675
            $file = $this->getFile("fields-$idf");
676
            if (!\is_string($file)) {
677
                return false; //! report errors
678
            }
679
            return $this->driver->quoteBinary($file);
0 ignored issues
show
Bug introduced by
The method quoteBinary() does not exist on Lagdo\DbAdmin\Driver\DriverInterface. ( Ignorable by Annotation )

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

679
            return $this->driver->/** @scrutinizer ignore-call */ quoteBinary($file);

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...
680
        }
681
        return $this->_processInput($field, $value, $function);
682
    }
683
684
    /**
685
     * Process columns box in select
686
     *
687
     * @param array $columns Selectable columns
688
     * @param array $indexes
689
     *
690
     * @return array
691
     */
692
    public function processSelectColumns(array $columns, array $indexes)
0 ignored issues
show
Unused Code introduced by
The parameter $indexes 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

692
    public function processSelectColumns(array $columns, /** @scrutinizer ignore-unused */ array $indexes)

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...
Unused Code introduced by
The parameter $columns 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

692
    public function processSelectColumns(/** @scrutinizer ignore-unused */ array $columns, array $indexes)

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...
693
    {
694
        $select = []; // select expressions, empty for *
695
        $group = []; // expressions without aggregation - will be used for GROUP BY if an aggregation function is used
696
        foreach ((array) $this->input->values["columns"] as $key => $val) {
697
            if ($val["fun"] == "count" ||
698
                ($val["col"] != "" && (!$val["fun"] ||
699
                \in_array($val["fun"], $this->driver->functions()) ||
700
                \in_array($val["fun"], $this->driver->grouping())))) {
701
                $select[$key] = $this->admin->applySqlFunction(
0 ignored issues
show
Bug Best Practice introduced by
The property admin does not exist on Lagdo\DbAdmin\Db\Util. Did you maybe forget to declare it?
Loading history...
702
                    $val["fun"],
703
                    ($val["col"] != "" ? $this->driver->escapeId($val["col"]) : "*")
704
                );
705
                if (!in_array($val["fun"], $this->driver->grouping())) {
706
                    $group[] = $select[$key];
707
                }
708
            }
709
        }
710
        return [$select, $group];
711
    }
712
713
    /**
714
     * Process sent input
715
     *
716
     * @param TableFieldEntity $field Single field from fields()
717
     * @param string $value
718
     * @param string $function
719
     *
720
     * @return string
721
     */
722
    private function _processInput(TableFieldEntity $field, string $value, string $function = "")
723
    {
724
        if ($function == "SQL") {
725
            return $value; // SQL injection
726
        }
727
        $name = $field->name;
728
        $expression = $this->driver->quote($value);
729
        if (\preg_match('~^(now|getdate|uuid)$~', $function)) {
730
            $expression = "$function()";
731
        } elseif (\preg_match('~^current_(date|timestamp)$~', $function)) {
732
            $expression = $function;
733
        } elseif (\preg_match('~^([+-]|\|\|)$~', $function)) {
734
            $expression = $this->driver->escapeId($name) . " $function $expression";
735
        } elseif (\preg_match('~^[+-] interval$~', $function)) {
736
            $expression = $this->driver->escapeId($name) . " $function " .
737
                (\preg_match("~^(\\d+|'[0-9.: -]') [A-Z_]+\$~i", $value) ? $value : $expression);
738
        } elseif (\preg_match('~^(addtime|subtime|concat)$~', $function)) {
739
            $expression = "$function(" . $this->driver->escapeId($name) . ", $expression)";
740
        } elseif (\preg_match('~^(md5|sha1|password|encrypt)$~', $function)) {
741
            $expression = "$function($expression)";
742
        }
743
        return $this->driver->unconvertField($field, $expression);
744
    }
745
746
    /**
747
     * Process search box in select
748
     *
749
     * @param array $fields
750
     * @param array $indexes
751
     *
752
     * @return array
753
     */
754
    public function processSelectSearch(array $fields, array $indexes)
755
    {
756
        $expressions = [];
757
        foreach ($indexes as $i => $index) {
758
            if ($index->type == "FULLTEXT" && $this->input->values["fulltext"][$i] != "") {
759
                $columns = \array_map(function ($column) {
760
                    return $this->driver->escapeId($column);
761
                }, $index->columns);
762
                $expressions[] = "MATCH (" . \implode(", ", $columns) . ") AGAINST (" .
763
                    $this->driver->quote($this->input->values["fulltext"][$i]) .
764
                    (isset($this->input->values["boolean"][$i]) ? " IN BOOLEAN MODE" : "") . ")";
765
            }
766
        }
767
        foreach ((array) $this->input->values["where"] as $key => $val) {
768
            if ("$val[col]$val[val]" != "" && in_array($val["op"], $this->driver->operators())) {
769
                $prefix = "";
770
                $cond = " $val[op]";
771
                if (\preg_match('~IN$~', $val["op"])) {
772
                    $in = $this->processLength($val["val"]);
773
                    $cond .= " " . ($in != "" ? $in : "(NULL)");
774
                } elseif ($val["op"] == "SQL") {
775
                    $cond = " $val[val]"; // SQL injection
776
                } elseif ($val["op"] == "LIKE %%") {
777
                    $cond = " LIKE " . $this->_processInput($fields[$val["col"]], "%$val[val]%");
778
                } elseif ($val["op"] == "ILIKE %%") {
779
                    $cond = " ILIKE " . $this->_processInput($fields[$val["col"]], "%$val[val]%");
780
                } elseif ($val["op"] == "FIND_IN_SET") {
781
                    $prefix = "$val[op](" . $this->driver->quote($val["val"]) . ", ";
782
                    $cond = ")";
783
                } elseif (!\preg_match('~NULL$~', $val["op"])) {
784
                    $cond .= " " . $this->_processInput($fields[$val["col"]], $val["val"]);
785
                }
786
                if ($val["col"] != "") {
787
                    $expressions[] = $prefix . $this->driver->convertSearch(
788
                        $this->driver->escapeId($val["col"]),
789
                        $val,
790
                        $fields[$val["col"]]
791
                    ) . $cond;
792
                } else {
793
                    // find anywhere
794
                    $cols = [];
795
                    foreach ($fields as $name => $field) {
796
                        if ((\preg_match('~^[-\d.' . (\preg_match('~IN$~', $val["op"]) ? ',' : '') . ']+$~', $val["val"]) ||
797
                            !\preg_match('~' . $this->driver->numberRegex() . '|bit~', $field->type)) &&
798
                            (!\preg_match("~[\x80-\xFF]~", $val["val"]) || \preg_match('~char|text|enum|set~', $field->type)) &&
799
                            (!\preg_match('~date|timestamp~', $field->type) || \preg_match('~^\d+-\d+-\d+~', $val["val"]))
800
                        ) {
801
                            $cols[] = $prefix . $this->driver->convertSearch($this->driver->escapeId($name), $val, $field) . $cond;
802
                        }
803
                    }
804
                    $expressions[] = ($cols ? "(" . \implode(" OR ", $cols) . ")" : "1 = 0");
805
                }
806
            }
807
        }
808
        return $expressions;
809
    }
810
811
    /**
812
     * Process order box in select
813
     *
814
     * @param array $fields
815
     * @param array $indexes
816
     *
817
     * @return array Expressions to join by comma
818
     */
819
    public function processSelectOrder(array $fields, array $indexes)
0 ignored issues
show
Unused Code introduced by
The parameter $indexes 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

819
    public function processSelectOrder(array $fields, /** @scrutinizer ignore-unused */ array $indexes)

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...
Unused Code introduced by
The parameter $fields 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

819
    public function processSelectOrder(/** @scrutinizer ignore-unused */ array $fields, array $indexes)

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...
820
    {
821
        $expressions = [];
822
        foreach ((array) $this->input->values["order"] as $key => $val) {
823
            if ($val != "") {
824
                $regexp = '~^((COUNT\(DISTINCT |[A-Z0-9_]+\()(`(?:[^`]|``)+`|"(?:[^"]|"")+")\)|COUNT\(\*\))$~';
825
                $expressions[] = (\preg_match($regexp, $val) ? $val : $this->driver->escapeId($val)) . //! MS SQL uses []
826
                    (isset($this->input->values["desc"][$key]) ? " DESC" : "");
827
            }
828
        }
829
        return $expressions;
830
    }
831
832
    /**
833
     * Process limit box in select
834
     *
835
     * @return string
836
     */
837
    public function processSelectLimit()
838
    {
839
        return (isset($this->input->values["limit"]) ? $this->input->values["limit"] : "50");
840
    }
841
842
    /**
843
     * Process length box in select
844
     *
845
     * @return string
846
     */
847
    public function processSelectLength()
848
    {
849
        return (isset($this->input->values["text_length"]) ? $this->input->values["text_length"] : "100");
850
    }
851
852
    /**
853
     * Process extras in select form
854
     *
855
     * @param array $where AND conditions
856
     * @param array $foreignKeys
857
     *
858
     * @return bool
859
     */
860
    public function processSelectEmail(array $where, array $foreignKeys)
0 ignored issues
show
Unused Code introduced by
The parameter $where 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

860
    public function processSelectEmail(/** @scrutinizer ignore-unused */ array $where, array $foreignKeys)

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...
Unused Code introduced by
The parameter $foreignKeys 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

860
    public function processSelectEmail(array $where, /** @scrutinizer ignore-unused */ array $foreignKeys)

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...
861
    {
862
        return false;
863
    }
864
865
    /**
866
     * Value printed in select table
867
     *
868
     * @param string $value HTML-escaped value to print
869
     * @param string $link Link to foreign key
870
     * @param string $type Field type
871
     * @param string $original Original value before applying editValue() and escaping
872
     *
873
     * @return string
874
     */
875
    private function _selectValue(string $value, string $link, string $type, string $original)
876
    {
877
        $clause = ($value === null ? "<i>NULL</i>" :
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
878
            (\preg_match("~char|binary|boolean~", $type) && !\preg_match("~var~", $type) ?
879
            "<code>$value</code>" : $value));
880
        if (\preg_match('~blob|bytea|raw|file~', $type) && !$this->isUtf8($value)) {
881
            $clause = "<i>" . $this->trans->lang('%d byte(s)', \strlen($original)) . "</i>";
882
        }
883
        if (\preg_match('~json~', $type)) {
884
            $clause = "<code class='jush-js'>$clause</code>";
885
        }
886
        return ($link ? "<a href='" . $this->html($link) . "'" .
887
            ($this->isUrl($link) ? $this->blankTarget() : "") . ">$clause</a>" : $clause);
888
    }
889
890
    /**
891
     * Format value to use in select
892
     *
893
     * @param string $value
894
     * @param string $link
895
     * @param TableFieldEntity $field
896
     * @param int $textLength
897
     *
898
     * @return string
899
     */
900
    public function selectValue(string $value, string $link, TableFieldEntity $field, int $textLength)
901
    {
902
        // if (\is_array($value)) {
903
        //     $expression = "";
904
        //     foreach ($value as $k => $v) {
905
        //         $expression .= "<tr>" . ($value != \array_values($value) ?
906
        //             "<th>" . $this->html($k) :
907
        //             "") . "<td>" . $this->selectValue($v, $link, $field, $textLength);
908
        //     }
909
        //     return "<table cellspacing='0'>$expression</table>";
910
        // }
911
        if (!$link) {
912
            $link = $this->selectLink($value, $field);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $link is correct as $this->selectLink($value, $field) targeting Lagdo\DbAdmin\Db\Util::selectLink() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
913
        }
914
        if ($link === null) {
915
            if ($this->isMail($value)) {
916
                $link = "mailto:$value";
917
            }
918
            elseif ($this->isUrl($value)) {
919
                $link = $value; // IE 11 and all modern browsers hide referrer
920
            }
921
            else {
922
                $link = '';
923
            }
924
        }
925
        $expression = $this->editValue($value, $field);
926
        if ($expression !== null) {
0 ignored issues
show
introduced by
The condition $expression !== null is always true.
Loading history...
927
            if (!$this->isUtf8($expression)) {
928
                $expression = "\0"; // htmlspecialchars of binary data returns an empty string
929
            } elseif ($textLength != "" && $this->isShortable($field)) {
930
                // usage of LEFT() would reduce traffic but complicate query -
931
                // expected average speedup: .001 s VS .01 s on local network
932
                $expression = $this->shortenUtf8($expression, \max(0, +$textLength));
933
            } else {
934
                $expression = $this->html($expression);
935
            }
936
        }
937
        return $this->_selectValue($expression, $link, $field->type, $value);
938
    }
939
940
    /**
941
     * Query printed in SQL command before execution
942
     *
943
     * @param string $query Query to be executed
944
     *
945
     * @return string
946
     */
947
    public function sqlCommandQuery(string $query)
948
    {
949
        return $this->shortenUtf8(trim($query), 1000);
950
    }
951
}
952