InsertEdit::getHtmlForInsertEditFormHeader()
last analyzed

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
ccs 0
cts 5
cp 0
c 0
b 0
f 0
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin;
6
7
use PhpMyAdmin\ConfigStorage\Relation;
8
use PhpMyAdmin\Dbal\DatabaseInterface;
9
use PhpMyAdmin\Dbal\ResultInterface;
10
use PhpMyAdmin\Html\Generator;
11
use PhpMyAdmin\I18n\LanguageManager;
12
use PhpMyAdmin\Plugins\IOTransformationsPlugin;
13
use PhpMyAdmin\Plugins\TransformationsInterface;
14
use PhpMyAdmin\Utils\Gis;
15
16
use function __;
17
use function array_fill;
18
use function array_key_exists;
19
use function array_merge;
20
use function array_values;
21
use function bin2hex;
22
use function count;
23
use function htmlspecialchars;
24
use function implode;
25
use function in_array;
26
use function is_array;
27
use function is_numeric;
28
use function is_string;
29
use function json_encode;
30
use function max;
31
use function mb_stripos;
32
use function mb_strlen;
33
use function min;
34
use function password_hash;
35
use function preg_match;
36
use function preg_replace;
37
use function str_contains;
38
use function str_replace;
39
use function str_starts_with;
40
use function stripslashes;
41
use function trim;
42
43
use const ENT_COMPAT;
44
use const PASSWORD_DEFAULT;
45
46
class InsertEdit
47
{
48
    private const FUNC_OPTIONAL_PARAM = ['NOW', 'RAND', 'UNIX_TIMESTAMP'];
49
50
    private const FUNC_NO_PARAM = [
51
        'CONNECTION_ID',
52
        'CURRENT_USER',
53
        'CURDATE',
54
        'CURTIME',
55
        'CURRENT_DATE',
56
        'CURRENT_TIME',
57
        'DATABASE',
58
        'LAST_INSERT_ID',
59
        'NOW',
60
        'PI',
61
        'RAND',
62
        'SYSDATE',
63
        'UNIX_TIMESTAMP',
64
        'USER',
65
        'UTC_DATE',
66
        'UTC_TIME',
67
        'UTC_TIMESTAMP',
68
        'UUID',
69
        'UUID_SHORT',
70
        'VERSION',
71
    ];
72
73
    private int $rowOffset = 0;
74
    private int $fieldIndex = 0;
75
76
    /** @var string[] */
77
    public static array $pluginScripts = [];
78
79 188
    public function __construct(
80
        private readonly DatabaseInterface $dbi,
81
        private readonly Relation $relation,
82
        private readonly Transformations $transformations,
83
        private readonly FileListing $fileListing,
84
        private readonly Template $template,
85
        private readonly Config $config,
86
    ) {
87 188
    }
88
89
    /**
90
     * Retrieve form parameters for insert/edit form
91
     *
92
     * @param string   $db               name of the database
93
     * @param string   $table            name of the table
94
     * @param string[] $whereClauseArray
95
     *
96
     * @return array<string, string> array of insert/edit form parameters
97
     */
98 8
    public function getFormParametersForInsertForm(
99
        string $db,
100
        string $table,
101
        array $whereClauseArray,
102
        string $errorUrl,
103
    ): array {
104 8
        $formParams = [
105 8
            'db' => $db,
106 8
            'table' => $table,
107 8
            'goto' => UrlParams::$goto,
108 8
            'err_url' => $errorUrl,
109 8
            'sql_query' => $_POST['sql_query'] ?? '',
110 8
        ];
111
112 8
        if ($formParams['sql_query'] === '' && isset($_GET['sql_query'], $_GET['sql_signature'])) {
113 4
            if (Core::checkSqlQuerySignature($_GET['sql_query'], $_GET['sql_signature'])) {
114 4
                $formParams['sql_query'] = $_GET['sql_query'];
115
            }
116
        }
117
118 8
        foreach ($whereClauseArray as $keyId => $whereClause) {
119 8
            $formParams['where_clause[' . $keyId . ']'] = trim($whereClause);
120
        }
121
122 8
        if (isset($_POST['clause_is_unique'])) {
123 4
            $formParams['clause_is_unique'] = $_POST['clause_is_unique'];
124 4
        } elseif (isset($_GET['clause_is_unique'])) {
125 4
            $formParams['clause_is_unique'] = $_GET['clause_is_unique'];
126
        }
127
128 8
        return $formParams;
129
    }
130
131
    /**
132
     * Analysing where clauses array
133
     *
134
     * @param string[] $whereClauseArray array of where clauses
135
     * @param string   $table            name of the table
136
     * @param string   $db               name of the database
137
     *
138
     * @return array{ResultInterface[], array<string|null>[], bool}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{ResultInterface[],...y<string|null>[], bool} at position 2 could not be parsed: Expected ':' at position 2, but found 'ResultInterface'.
Loading history...
139
     */
140 8
    private function analyzeWhereClauses(
141
        array $whereClauseArray,
142
        string $table,
143
        string $db,
144
    ): array {
145 8
        $rows = [];
146 8
        $result = [];
147 8
        $foundUniqueKey = false;
148 8
        foreach ($whereClauseArray as $keyId => $whereClause) {
149 8
            $localQuery = 'SELECT * FROM '
150 8
                . Util::backquote($db) . '.'
151 8
                . Util::backquote($table)
152 8
                . ' WHERE ' . $whereClause . ';';
153 8
            $result[$keyId] = $this->dbi->query($localQuery);
154 8
            $rows[$keyId] = $result[$keyId]->fetchAssoc();
155
156 8
            if ($rows[$keyId] === []) {
157 4
                ResponseRenderer::getInstance()->addHTML(
158 4
                    Generator::getMessage(
159 4
                        __('MySQL returned an empty result set (i.e. zero rows).'),
160 4
                        $localQuery,
161 4
                    ),
162 4
                );
163
                /**
164
                 * @todo not sure what should be done at this point, but we must not
165
                 * exit if we want the message to be displayed
166
                 */
167 4
                continue;
168
            }
169
170 4
            if (! $this->hasUniqueCondition($rows[$keyId], $result[$keyId])) {
171 4
                continue;
172
            }
173
174
            $foundUniqueKey = true;
175
        }
176
177 8
        return [$result, $rows, $foundUniqueKey];
178
    }
179
180
    /** @param array<string|null> $row */
181 8
    private function hasUniqueCondition(array $row, ResultInterface $result): bool
182
    {
183 8
        $meta = $this->dbi->getFieldsMeta($result);
184
185 8
        return (bool) (new UniqueCondition($meta, $row, true))->getWhereClause();
186
    }
187
188
    /**
189
     * No primary key given, just load first row
190
     */
191 8
    private function loadFirstRow(string $table, string $db): ResultInterface
192
    {
193 8
        return $this->dbi->query(
194 8
            'SELECT * FROM ' . Util::backquote($db)
195 8
            . '.' . Util::backquote($table) . ' LIMIT 1;',
196 8
        );
197
    }
198
199
    /** @return never[][] */
200 12
    private function getInsertRows(): array
201
    {
202
        // Can be a string on some old configuration storage settings
203 12
        return array_fill(0, $this->config->settings['InsertRows'], []);
204
    }
205
206
    /**
207
     * Show type information or function selectors in Insert/Edit
208
     *
209
     * @param string                         $which     function|type
210
     * @param array<string, bool|int|string> $urlParams containing url parameters
211
     * @param bool                           $isShow    whether to show the element in $which
212
     *
213
     * @return string an HTML snippet
214
     */
215 16
    public function showTypeOrFunction(string $which, array $urlParams, bool $isShow): string
216
    {
217 16
        $params = [];
218
219
        switch ($which) {
220 16
            case 'function':
221 16
                $params['ShowFunctionFields'] = $isShow ? 0 : 1;
222 16
                $params['ShowFieldTypesInDataEditView'] = $this->config->settings['ShowFieldTypesInDataEditView'];
223 16
                break;
224 16
            case 'type':
225 16
                $params['ShowFieldTypesInDataEditView'] = $isShow ? 0 : 1;
226 16
                $params['ShowFunctionFields'] = $this->config->settings['ShowFunctionFields'];
227 16
                break;
228
        }
229
230 16
        $params['goto'] = Url::getFromRoute('/sql');
231 16
        $thisUrlParams = array_merge($urlParams, $params);
232
233 16
        if (! $isShow) {
234 4
            return ' : <a href="' . Url::getFromRoute('/table/change') . '" data-post="'
235 4
                . Url::getCommon($thisUrlParams, '', false) . '">'
236 4
                . $this->showTypeOrFunctionLabel($which)
237 4
                . '</a>';
238
        }
239
240 16
        return '<th><a href="' . Url::getFromRoute('/table/change') . '" data-post="'
241 16
            . Url::getCommon($thisUrlParams, '', false)
242 16
            . '" title="' . __('Hide') . '">'
243 16
            . $this->showTypeOrFunctionLabel($which)
244 16
            . '</a></th>';
245
    }
246
247
    /**
248
     * Show type information or function selectors labels in Insert/Edit
249
     *
250
     * @param string $which function|type
251
     *
252
     * @return string an HTML snippet
253
     */
254 16
    private function showTypeOrFunctionLabel(string $which): string
255
    {
256 16
        return match ($which) {
257 16
            'function' => __('Function'),
258 16
            'type' => __('Type'),
259 16
            default => '',
260 16
        };
261
    }
262
263
    /**
264
     * Retrieve the column title
265
     *
266
     * @param string   $fieldName   name of the column
267
     * @param string[] $commentsMap comments for every column that has a comment
268
     *
269
     * @return string              column title
270
     */
271 16
    private function getColumnTitle(string $fieldName, array $commentsMap): string
272
    {
273 16
        if (isset($commentsMap[$fieldName])) {
274 4
            return '<span style="border-bottom: 1px dashed black;" title="'
275 4
                . htmlspecialchars($commentsMap[$fieldName]) . '">'
276 4
                . htmlspecialchars($fieldName) . '</span>';
277
        }
278
279 16
        return htmlspecialchars($fieldName);
280
    }
281
282
    /**
283
     * check whether the column is of a certain type
284
     * the goal is to ensure that types such as "enum('one','two','binary',..)"
285
     * or "enum('one','two','varbinary',..)" are not categorized as binary
286
     *
287
     * @param string   $columnType column type as specified in the column definition
288
     * @param string[] $types      the types to verify
289
     */
290 16
    public function isColumn(string $columnType, array $types): bool
291
    {
292 16
        foreach ($types as $oneType) {
293 16
            if (mb_stripos($columnType, $oneType) === 0) {
294 8
                return true;
295
            }
296
        }
297
298 16
        return false;
299
    }
300
301
    /**
302
     * Retrieve the nullify code for the null column
303
     *
304
     * @param InsertEditColumn $column     description of column in given table
305
     * @param mixed[]          $foreigners keys into foreign fields
306
     */
307 16
    private function getNullifyCodeForNullColumn(
308
        InsertEditColumn $column,
0 ignored issues
show
Bug introduced by
The type PhpMyAdmin\InsertEditColumn 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...
309
        array $foreigners,
310
        bool $foreignLink,
311
    ): string {
312 16
        if ($column->trueType === 'enum') {
313 4
            return mb_strlen($column->type) > 20 ? '1' : '2';
314
        }
315
316 16
        if ($column->trueType === 'set') {
317 4
            return '3';
318
        }
319
320 16
        if ($this->relation->searchColumnInForeigners($foreigners, $column->field) !== false) {
321 4
            return $foreignLink ? '6' : '4';
322
        }
323
324 12
        return '5';
325
    }
326
327
    /**
328
     * Get HTML textarea for insert form
329
     *
330
     * @param InsertEditColumn $column              column information
331
     * @param string           $backupField         hidden input field
332
     * @param string           $columnNameAppendix  the name attribute
333
     * @param string           $onChangeClause      onchange clause for fields
334
     * @param string           $specialCharsEncoded replaced char if the string starts
335
     *                                                with a \r\n pair (0x0d0a) add an extra \n
336
     * @param TypeClass        $dataType            the html5 data-* attribute type
337
     *
338
     * @return string                       an html snippet
339
     */
340 8
    private function getTextarea(
341
        InsertEditColumn $column,
342
        string $backupField,
343
        string $columnNameAppendix,
344
        string $onChangeClause,
345
        string $specialCharsEncoded,
346
        TypeClass $dataType,
347
    ): string {
348 8
        $theClass = '';
349 8
        $textAreaRows = $this->config->settings['TextareaRows'];
350 8
        $textareaCols = $this->config->settings['TextareaCols'];
351
352 8
        if ($column->isChar) {
353
            /**
354
             * @todo clarify the meaning of the "textfield" class and explain
355
             *       why character columns have the "char" class instead
356
             */
357 8
            $theClass = 'charField';
358 8
            $textAreaRows = $this->config->settings['CharTextareaRows'];
359 8
            $textareaCols = $this->config->settings['CharTextareaCols'];
360 8
            $extractedColumnspec = Util::extractColumnSpec($column->type);
361 8
            $maxlength = $extractedColumnspec['spec_in_brackets'];
362
        } elseif ($this->config->settings['LongtextDoubleTextarea'] && $column->trueType === 'longtext') {
363
            $textAreaRows = $this->config->settings['TextareaRows'] * 2;
364
            $textareaCols = $this->config->settings['TextareaCols'] * 2;
365
        }
366
367 8
        return $backupField . "\n"
368 8
            . '<textarea name="fields' . $columnNameAppendix . '"'
369 8
            . ' class="' . $theClass . '"'
370 8
            . (isset($maxlength) ? ' data-maxlength="' . $maxlength . '"' : '')
371 8
            . ' rows="' . $textAreaRows . '"'
372 8
            . ' cols="' . $textareaCols . '"'
373 8
            . ' dir="' . LanguageManager::$textDirection->value . '"'
374 8
            . ' id="field_' . $this->fieldIndex . '_3"'
375 8
            . ($onChangeClause !== '' ? ' onchange="' . htmlspecialchars($onChangeClause, ENT_COMPAT) . '"' : '')
376 8
            . ' tabindex="' . $this->fieldIndex . '"'
377 8
            . ' data-type="' . $dataType->value . '">'
378 8
            . $specialCharsEncoded
379 8
            . '</textarea>';
380
    }
381
382
    /** @return object{isInteger: bool, minValue: string, maxValue: string} */
383 16
    private function getIntegerRange(InsertEditColumn $column): object
384
    {
385 16
        $minValue = '';
386 16
        $maxValue = '';
387 16
        $isInteger = in_array($column->trueType, $this->dbi->types->getIntegerTypes(), true);
388 16
        if ($isInteger) {
389
            $extractedColumnSpec = Util::extractColumnSpec($column->type);
390
            $isUnsigned = $extractedColumnSpec['unsigned'];
391
            $minMaxValues = $this->dbi->types->getIntegerRange($column->trueType, ! $isUnsigned);
392
            $minValue = $minMaxValues[0];
393
            $maxValue = $minMaxValues[1];
394
        }
395
396 16
        return new class ($isInteger, $minValue, $maxValue) {
397
            public function __construct(
398
                public readonly bool $isInteger,
399
                public readonly string $minValue,
400
                public readonly string $maxValue,
401
            ) {
402 16
            }
403 16
        };
404
    }
405
406
    /**
407
     * Get HTML select option for upload
408
     *
409
     * @param string $vkey         [multi_edit]['row_id']
410
     * @param string $fieldHashMd5 array index as an MD5 to avoid having special characters
411
     *
412
     * @return string an HTML snippet
413
     */
414
    private function getSelectOptionForUpload(string $vkey, string $fieldHashMd5): string
415
    {
416
        $files = $this->fileListing->getFileSelectOptions(
417
            Util::userDir($this->config->settings['UploadDir'] ?? ''),
418
        );
419
420
        if ($files === false) {
0 ignored issues
show
introduced by
The condition $files === false is always true.
Loading history...
421
            return '<span style="color:red">' . __('Error') . '</span><br>' . "\n"
422
                . __('The directory you set for upload work cannot be reached.') . "\n";
423
        }
424
425
        if ($files === '') {
426
            return '';
427
        }
428
429
        return "<br>\n"
430
            . '<i>' . __('Or') . '</i> '
431
            . __('web server upload directory:') . '<br>' . "\n"
432
            . '<select size="1" name="fields_uploadlocal'
433
            . $vkey . '[' . $fieldHashMd5 . ']">' . "\n"
434
            . '<option value="" selected></option>' . "\n"
435
            . $files
436
            . '</select>' . "\n";
437
    }
438
439
    /**
440
     * Retrieve the maximum upload file size
441
     */
442 4
    private function getMaxUploadSize(string $type): string
443
    {
444
        // find maximum upload size, based on field type
445
        /**
446
         * @todo with functions this is not so easy, as you can basically
447
         * process any data with function like MD5
448
         */
449 4
        $maxFieldSize = match ($type) {
450 4
            'tinyblob' => 256,
451 4
            'blob' => 65536,
452
            'mediumblob' => 16777216,
453
            'longblob' => 4294967296,// yeah, really
454 4
        };
455
456 4
        $thisFieldMaxSize = Util::getUploadSizeInBytes();
457
458 4
        return Util::getFormattedMaximumUploadSize(min($thisFieldMaxSize, $maxFieldSize)) . "\n";
459
    }
460
461
    /**
462
     * Get HTML for the Value column of other datatypes
463
     * (here, "column" is used in the sense of HTML column in HTML table)
464
     *
465
     * @param InsertEditColumn $column              description of column in given table
466
     * @param string           $defaultCharEditing  default char editing mode which is stored
467
     *                                                 in the config.inc.php script
468
     * @param string           $backupField         hidden input field
469
     * @param string           $columnNameAppendix  the name attribute
470
     * @param string           $onChangeClause      onchange clause for fields
471
     * @param string           $specialChars        special characters
472
     * @param string           $specialCharsEncoded replaced char if the string starts
473
     *                                                with a \r\n pair (0x0d0a) add an extra \n
474
     * @param string           $data                data to edit
475
     *
476
     * @return string an html snippet
477
     */
478 16
    private function getValueColumnForOtherDatatypes(
479
        InsertEditColumn $column,
480
        string $defaultCharEditing,
481
        string $backupField,
482
        string $columnNameAppendix,
483
        string $onChangeClause,
484
        string $specialChars,
485
        string $specialCharsEncoded,
486
        string $data,
487
        string $specInBrackets,
488
    ): string {
489
        // HTML5 data-* attribute data-type
490 16
        $dataType = $this->dbi->types->getTypeClass($column->trueType);
491 16
        $fieldsize = $this->getColumnSize($column, $specInBrackets);
492
493 16
        $input = [];
494 16
        $textareaHtml = '';
495 16
        $isTextareaRequired = $column->isChar
496 16
            && ($this->config->settings['CharEditing'] === 'textarea' || str_contains($data, "\n"));
497 16
        if ($isTextareaRequired) {
498 4
            $this->config->settings['CharEditing'] = $defaultCharEditing;
499 4
            $textareaHtml = $this->getTextarea(
500 4
                $column,
501 4
                $backupField,
502 4
                $columnNameAppendix,
503 4
                $onChangeClause,
504 4
                $specialCharsEncoded,
505 4
                $dataType,
506 4
            );
507
        } else {
508 16
            $integerRange = $this->getIntegerRange($column);
509 16
            $input = [
510 16
                'value' => $specialChars,
511 16
                'size' => $fieldsize,
512 16
                'is_char' => $column->isChar,
513 16
                'is_integer' => $integerRange->isInteger,
514 16
                'min' => $integerRange->minValue,
515 16
                'max' => $integerRange->maxValue,
516 16
                'data_type' => $dataType->value,
517 16
                'on_change_clause' => $onChangeClause,
518 16
                'field_index' => $this->fieldIndex,
519 16
            ];
520
        }
521
522 16
        return $this->template->render('table/insert/value_column_for_other_datatype', [
523 16
            'input' => $input,
524 16
            'textarea_html' => $textareaHtml,
525 16
            'backup_field' => $backupField,
526 16
            'is_textarea' => $isTextareaRequired,
527 16
            'column_name_appendix' => $columnNameAppendix,
528 16
            'extra' => $column->extra,
529 16
            'true_type' => $column->trueType,
530 16
        ]);
531
    }
532
533
    /**
534
     * Get the field size
535
     *
536
     * @param InsertEditColumn $column         description of column in given table
537
     * @param string           $specInBrackets text in brackets inside column definition
538
     *
539
     * @return int field size
540
     */
541 20
    private function getColumnSize(InsertEditColumn $column, string $specInBrackets): int
542
    {
543 20
        if ($column->isChar) {
544 8
            $fieldsize = (int) $specInBrackets;
545 8
            if ($fieldsize > $this->config->settings['MaxSizeForInputField']) {
546
                /**
547
                 * This case happens for CHAR or VARCHAR columns which have
548
                 * a size larger than the maximum size for input field.
549
                 */
550 4
                $this->config->settings['CharEditing'] = 'textarea';
551
            }
552
        } else {
553
            /**
554
             * This case happens for example for INT or DATE columns;
555
             * in these situations, the value returned in $column['len']
556
             * seems appropriate.
557
             */
558 20
            $fieldsize = $column->length;
559
        }
560
561 20
        return min(
562 20
            max($fieldsize, $this->config->settings['MinSizeForInputField']),
563 20
            $this->config->settings['MaxSizeForInputField'],
564 20
        );
565
    }
566
567
    /**
568
     * get html for continue insertion form
569
     *
570
     * @param string   $table            name of the table
571
     * @param string   $db               name of the database
572
     * @param string[] $whereClauseArray
573
     *
574
     * @return string                   an html snippet
575
     */
576 4
    public function getContinueInsertionForm(
577
        string $table,
578
        string $db,
579
        array $whereClauseArray,
580
        string $errorUrl,
581
    ): string {
582 4
        return $this->template->render('table/insert/continue_insertion_form', [
583 4
            'db' => $db,
584 4
            'table' => $table,
585 4
            'where_clause_array' => $whereClauseArray,
586 4
            'err_url' => $errorUrl,
587 4
            'goto' => UrlParams::$goto,
588 4
            'sql_query' => $_POST['sql_query'] ?? null,
589 4
            'has_where_clause' => isset($_POST['where_clause']),
590 4
            'insert_rows_default' => $this->config->settings['InsertRows'],
591 4
        ]);
592
    }
593
594
    /**
595
     * @param string[]|string|null $whereClause
596
     *
597
     * @psalm-pure
598
     */
599 4
    public static function isWhereClauseNumeric(array|string|null $whereClause): bool
600
    {
601 4
        if ($whereClause === null) {
0 ignored issues
show
introduced by
The condition $whereClause === null is always false.
Loading history...
602 4
            return false;
603
        }
604
605 4
        if (! is_array($whereClause)) {
0 ignored issues
show
introduced by
The condition is_array($whereClause) is always true.
Loading history...
606 4
            $whereClause = [$whereClause];
607
        }
608
609
        // If we have just numeric primary key, we can also edit next
610
        // we are looking for `table_name`.`field_name` = numeric_value
611 4
        foreach ($whereClause as $clause) {
612
            // preg_match() returns 1 if there is a match
613 4
            $isNumeric = preg_match('@^[\s]*`[^`]*`[\.]`[^`]*` = [0-9]+@', $clause) === 1;
614 4
            if ($isNumeric) {
615 4
                return true;
616
            }
617
        }
618
619 4
        return false;
620
    }
621
622
    /**
623
     * Get table head and table foot for insert row table
624
     *
625
     * @param array<string, bool|int|string> $urlParams url parameters
626
     *
627
     * @return string           an html snippet
628
     */
629 12
    private function getHeadAndFootOfInsertRowTable(array $urlParams): string
630
    {
631 12
        $type = '';
632 12
        $function = '';
633
634 12
        if ($this->config->settings['ShowFieldTypesInDataEditView']) {
635 12
            $type = $this->showTypeOrFunction('type', $urlParams, true);
636
        }
637
638 12
        if ($this->config->settings['ShowFunctionFields']) {
639 12
            $function = $this->showTypeOrFunction('function', $urlParams, true);
640
        }
641
642 12
        $template = new Template();
643
644 12
        return $template->render('table/insert/get_head_and_foot_of_insert_row_table', [
645 12
            'type' => $type,
646 12
            'function' => $function,
647 12
        ]);
648
    }
649
650
    /**
651
     * Prepares the field value and retrieve special chars, backup field and data array
652
     *
653
     * @param array<string|null> $currentRow         a row of the table
654
     * @param InsertEditColumn   $column             description of column in given table
655
     * @param string             $columnNameAppendix string to append to column name in input
656
     * @param bool               $asIs               use the data as is, used in repopulating
657
     *
658
     * @return array{bool, string, string, string, string}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{bool, string, string, string, string} at position 2 could not be parsed: Expected ':' at position 2, but found 'bool'.
Loading history...
659
     */
660 4
    private function getSpecialCharsAndBackupFieldForExistingRow(
661
        array $currentRow,
662
        InsertEditColumn $column,
663
        string $specInBrackets,
664
        string $columnNameAppendix,
665
        bool $asIs,
666
    ): array {
667 4
        $specialCharsEncoded = '';
668 4
        $data = '';
669 4
        $realNullValue = false;
670 4
        $currentValue = $currentRow[$column->field] ?? null;
671
        // (we are editing)
672 4
        if ($currentValue === null) {
673 4
            $realNullValue = true;
674 4
            $currentValue = '';
675 4
            $specialChars = '';
676 4
            $data = '';
677 4
        } elseif ($column->trueType === 'bit') {
678 4
            $specialChars = $asIs
679 4
                ? $currentValue
680 4
                : Util::printableBitValue((int) $currentValue, (int) $specInBrackets);
681
        } elseif (
682 4
            ($column->trueType === 'timestamp'
683 4
                || $column->trueType === 'datetime'
684 4
                || $column->trueType === 'time')
685 4
            && (str_contains($currentValue, '.'))
686
        ) {
687
            $currentValue = $asIs
688
                ? $currentValue
689
                : Util::addMicroseconds($currentValue);
690
            $specialChars = htmlspecialchars($currentValue, ENT_COMPAT);
691 4
        } elseif (in_array($column->trueType, Gis::getDataTypes(), true)) {
692
            // Convert gis data to Well Know Text format
693 4
            $currentValue = $asIs
694
                ? $currentValue
695 4
                : Gis::convertToWellKnownText($currentValue, true);
696 4
            $specialChars = htmlspecialchars($currentValue, ENT_COMPAT);
697
        } else {
698
            // special binary "characters"
699 4
            if ($column->isBinary || ($column->isBlob && $this->config->settings['ProtectBinary'] !== 'all')) {
700 4
                $currentValue = $asIs
701
                    ? $currentValue
702 4
                    : bin2hex($currentValue);
703
            }
704
705 4
            $specialChars = htmlspecialchars($currentValue, ENT_COMPAT);
706
707
            //We need to duplicate the first \n or otherwise we will lose
708
            //the first newline entered in a VARCHAR or TEXT column
709 4
            $specialCharsEncoded = Util::duplicateFirstNewline($specialChars);
710
711 4
            $data = $currentValue;
712
        }
713
714
        /** @var string $defaultAction */
715 4
        $defaultAction = $_POST['default_action'] ?? $_GET['default_action'] ?? '';
716
        if (
717 4
            $defaultAction === 'insert'
718 4
            && $column->key === 'PRI'
719 4
            && str_contains($column->extra, 'auto_increment')
720
        ) {
721
            // When copying row, it is useful to empty auto-increment column to prevent duplicate key error.
722 4
            $data = $specialCharsEncoded = $specialChars = '';
723
        }
724
725
        // If a timestamp field value is not included in an update
726
        // statement MySQL auto-update it to the current timestamp;
727
        // however, things have changed since MySQL 4.1, so
728
        // it's better to set a fields_prev in this situation
729 4
        $backupField = '<input type="hidden" name="fields_prev'
730 4
            . $columnNameAppendix . '" value="'
731 4
            . htmlspecialchars($currentValue, ENT_COMPAT) . '">';
732
733 4
        return [$realNullValue, $specialCharsEncoded, $specialChars, $data, $backupField];
734
    }
735
736
    /**
737
     * display default values
738
     */
739 48
    private function getDefaultValue(
740
        string|null $defaultValue,
741
        string $trueType,
742
    ): string {
743 48
        if ($defaultValue === null) {
744 12
            $defaultValue = '';
745
        }
746
747 48
        if ($trueType === 'bit') {
748 4
            return Util::convertBitDefaultValue($defaultValue);
749
        }
750
751 44
        if ($trueType === 'timestamp' || $trueType === 'datetime' || $trueType === 'time') {
752 20
            return Util::addMicroseconds($defaultValue);
753
        }
754
755 28
        if ($trueType === 'binary' || $trueType === 'varbinary') {
756
            return bin2hex($defaultValue);
757
        }
758
759 28
        return $defaultValue;
760
    }
761
762
    /**
763
     * set $_SESSION for edit_next
764
     *
765
     * @param string $oneWhereClause one where clause from where clauses array
766
     */
767 4
    public function setSessionForEditNext(string $oneWhereClause): void
768
    {
769 4
        $localQuery = 'SELECT * FROM ' . Util::backquote(Current::$database)
770 4
            . '.' . Util::backquote(Current::$table) . ' WHERE '
771 4
            . str_replace('` =', '` >', $oneWhereClause) . ' LIMIT 1;';
772
773 4
        $res = $this->dbi->query($localQuery);
774 4
        $row = $res->fetchRow();
775 4
        $meta = $this->dbi->getFieldsMeta($res);
776
        // must find a unique condition based on unique key,
777
        // not a combination of all fields
778 4
        $uniqueCondition = (new UniqueCondition($meta, $row, true))->getWhereClause();
779 4
        if ($uniqueCondition === '') {
780
            return;
781
        }
782
783 4
        $_SESSION['edit_next'] = $uniqueCondition;
784
    }
785
786
    /**
787
     * set $goto_include variable for different cases and retrieve like,
788
     * if UrlParams::$goto empty, if $goto_include previously not defined
789
     * and new_insert, same_insert, edit_next
790
     *
791
     * @param string|false $gotoInclude store some script for include, otherwise it is
792
     *                                   boolean false
793
     */
794 4
    public function getGotoInclude(string|false $gotoInclude): string
795
    {
796 4
        $validOptions = ['new_insert', 'same_insert', 'edit_next'];
797 4
        if (isset($_POST['after_insert']) && in_array($_POST['after_insert'], $validOptions, true)) {
798 4
            return '/table/change';
799
        }
800
801 4
        if (UrlParams::$goto !== '') {
802 4
            if (preg_match('@^[a-z_]+\.php$@', UrlParams::$goto) !== 1) {
803
                // this should NOT happen
804
                //UrlParams::$goto = false;
805 4
                $gotoInclude = str_contains(UrlParams::$goto, 'index.php?route=/sql') ? '/sql' : false;
806
            } else {
807
                $gotoInclude = UrlParams::$goto;
808
            }
809
810 4
            if (UrlParams::$goto === 'index.php?route=/database/sql' && Current::$table !== '') {
811 4
                Current::$table = '';
812
            }
813
        }
814
815 4
        if (! $gotoInclude) {
816 4
            $gotoInclude = Current::$table === '' ? '/database/sql' : '/table/sql';
817
        }
818
819 4
        return $gotoInclude;
820
    }
821
822
    /**
823
     * Defines the url to return in case of failure of the query
824
     *
825
     * @param mixed[] $urlParams url parameters
826
     *
827
     * @return string           error url for query failure
828
     */
829 4
    public function getErrorUrl(array $urlParams): string
830
    {
831 4
        return $_POST['err_url'] ?? Url::getFromRoute('/table/change', $urlParams);
832
    }
833
834
    /**
835
     * Executes the sql query and get the result, then move back to the calling page
836
     *
837
     * @param string[] $query built query from buildSqlQuery()
838
     *
839
     * @return array{int, Message[], string[], string[]}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{int, Message[], string[], string[]} at position 2 could not be parsed: Expected ':' at position 2, but found 'int'.
Loading history...
840
     */
841 8
    public function executeSqlQuery(array $query): array
842
    {
843 8
        Current::$sqlQuery = implode('; ', $query) . ';';
844
        // to ensure that the query is displayed in case of
845
        // "insert as new row" and then "insert another new row"
846 8
        Current::$displayQuery = Current::$sqlQuery;
847
848 8
        $totalAffectedRows = 0;
849 8
        $lastMessages = [];
850 8
        $warningMessages = [];
851 8
        $errorMessages = [];
852
853 8
        foreach ($query as $singleQuery) {
854 8
            if (isset($_POST['submit_type']) && $_POST['submit_type'] === 'showinsert') {
855
                $lastMessages[] = Message::notice(__('Showing SQL query'));
856
                continue;
857
            }
858
859 8
            if ($this->config->settings['IgnoreMultiSubmitErrors']) {
860 4
                $result = $this->dbi->tryQuery($singleQuery);
861
            } else {
862 4
                $result = $this->dbi->query($singleQuery);
863
            }
864
865 8
            if (! $result) {
866
                $errorMessages[] = $this->dbi->getError();
867
            } else {
868 8
                $totalAffectedRows += (int) $this->dbi->affectedRows();
869
870 8
                $insertId = $this->dbi->insertId();
871 8
                if ($insertId !== 0) {
872
                    // insert_id is id of FIRST record inserted in one insert, so if we
873
                    // inserted multiple rows, we had to increment this
874
875
                    if ($totalAffectedRows > 0) {
876
                        $insertId += $totalAffectedRows - 1;
877
                    }
878
879
                    $lastMessage = Message::notice(__('Inserted row id: %1$d'));
880
                    $lastMessage->addParam($insertId);
881
                    $lastMessages[] = $lastMessage;
882
                }
883
            }
884
885 8
            $warningMessages = $this->getWarningMessages();
886
        }
887
888 8
        return [$totalAffectedRows, $lastMessages, $warningMessages, $errorMessages];
889
    }
890
891
    /**
892
     * get the warning messages array
893
     *
894
     * @return string[]
895
     */
896 12
    private function getWarningMessages(): array
897
    {
898 12
        $warningMessages = [];
899 12
        foreach ($this->dbi->getWarnings() as $warning) {
900 4
            $warningMessages[] = htmlspecialchars((string) $warning);
901
        }
902
903 12
        return $warningMessages;
904
    }
905
906
    /**
907
     * Column to display from the foreign table?
908
     *
909
     * @param string  $whereComparison string that contain relation field value
910
     * @param mixed[] $foreigners      all Relations to foreign tables for a given
911
     *                                     table or optionally a given column in a table
912
     * @param string  $relationField   relation field
913
     *
914
     * @return string display value from the foreign table
915
     */
916 4
    public function getDisplayValueForForeignTableColumn(
917
        string $whereComparison,
918
        array $foreigners,
919
        string $relationField,
920
    ): string {
921 4
        $foreigner = $this->relation->searchColumnInForeigners($foreigners, $relationField);
922
923 4
        if (! is_array($foreigner)) {
924
            return '';
925
        }
926
927 4
        $displayField = $this->relation->getDisplayField($foreigner['foreign_db'], $foreigner['foreign_table']);
928
        // Field to display from the foreign table?
929 4
        if ($displayField !== '') {
930 4
            $dispsql = 'SELECT ' . Util::backquote($displayField)
931 4
                . ' FROM ' . Util::backquote($foreigner['foreign_db'])
932 4
                . '.' . Util::backquote($foreigner['foreign_table'])
933 4
                . ' WHERE ' . Util::backquote($foreigner['foreign_field'])
934 4
                . $whereComparison;
935 4
            $dispresult = $this->dbi->tryQuery($dispsql);
936 4
            if ($dispresult && $dispresult->numRows() > 0) {
937 4
                return (string) $dispresult->fetchValue();
938
            }
939
        }
940
941
        return '';
942
    }
943
944
    /**
945
     * Display option in the cell according to user choices
946
     *
947
     * @param mixed[] $foreigners         all Relations to foreign tables for a given
948
     *                                           table or optionally a given column in a table
949
     * @param string  $relationField      relation field
950
     * @param string  $whereComparison    string that contain relation field value
951
     * @param string  $dispval            display value from the foreign table
952
     * @param string  $relationFieldValue relation field value
953
     *
954
     * @return string HTML <a> tag
955
     */
956 4
    public function getLinkForRelationalDisplayField(
957
        array $foreigners,
958
        string $relationField,
959
        string $whereComparison,
960
        string $dispval,
961
        string $relationFieldValue,
962
    ): string {
963 4
        $foreigner = $this->relation->searchColumnInForeigners($foreigners, $relationField);
964
965 4
        if (! is_array($foreigner)) {
966
            return '';
967
        }
968
969 4
        if ($_SESSION['tmpval']['relational_display'] === 'K') {
970
            // user chose "relational key" in the display options, so
971
            // the title contains the display field
972 4
            $title = $dispval !== ''
973 4
                ? ' title="' . htmlspecialchars($dispval) . '"'
974
                : '';
975
        } else {
976 4
            $title = ' title="' . htmlspecialchars($relationFieldValue) . '"';
977
        }
978
979 4
        $sqlQuery = 'SELECT * FROM '
980 4
            . Util::backquote($foreigner['foreign_db'])
981 4
            . '.' . Util::backquote($foreigner['foreign_table'])
982 4
            . ' WHERE ' . Util::backquote($foreigner['foreign_field'])
983 4
            . $whereComparison;
984 4
        $urlParams = [
985 4
            'db' => $foreigner['foreign_db'],
986 4
            'table' => $foreigner['foreign_table'],
987 4
            'pos' => '0',
988 4
            'sql_signature' => Core::signSqlQuery($sqlQuery),
989 4
            'sql_query' => $sqlQuery,
990 4
        ];
991 4
        $output = '<a href="' . Url::getFromRoute('/sql', $urlParams) . '"' . $title . '>';
992
993 4
        if ($_SESSION['tmpval']['relational_display'] === 'D') {
994
            // user chose "relational display field" in the
995
            // display options, so show display field in the cell
996 4
            $output .= htmlspecialchars($dispval);
997
        } else {
998
            // otherwise display data in the cell
999 4
            $output .= htmlspecialchars($relationFieldValue);
1000
        }
1001
1002 4
        $output .= '</a>';
1003
1004 4
        return $output;
1005
    }
1006
1007
    /**
1008
     * Transform edited values
1009
     *
1010
     * @param string     $db           db name
1011
     * @param string     $table        table name
1012
     * @param string[][] $editedValues transform columns list and new values
1013
     * @param string     $file         file containing the transformation plugin
1014
     * @param string     $columnName   column name
1015
     * @param string[][] $extraData    extra data array
1016
     *
1017
     * @return string[][]
1018
     */
1019 4
    public function transformEditedValues(
1020
        string $db,
1021
        string $table,
1022
        string $transformationOption,
1023
        array &$editedValues,
1024
        string $file,
1025
        string $columnName,
1026
        array $extraData,
1027
    ): array {
1028
        // $cfg['SaveCellsAtOnce'] = true; JS code sends an array
1029 4
        $whereClause = is_array($_POST['where_clause']) ? $_POST['where_clause'][0] : $_POST['where_clause'];
1030 4
        $urlParams = [
1031 4
            'db' => $db,
1032 4
            'table' => $table,
1033 4
            'where_clause_sign' => Core::signSqlQuery($whereClause),
1034 4
            'where_clause' => $whereClause,
1035 4
            'transform_key' => $columnName,
1036 4
        ];
1037 4
        $transformOptions = $this->transformations->getOptions($transformationOption);
1038 4
        $transformOptions['wrapper_link'] = Url::getCommon($urlParams);
1039 4
        $transformOptions['wrapper_params'] = $urlParams;
1040
1041 4
        $transformationPlugin = $this->transformations->getPluginInstance($file);
1042 4
        if ($transformationPlugin instanceof TransformationsInterface) {
1043 4
            foreach ($editedValues as $cellIndex => $currCellEditedValues) {
1044 4
                if (! isset($currCellEditedValues[$columnName])) {
1045
                    continue;
1046
                }
1047
1048 4
                $extraData['transformations'][$cellIndex] = $transformationPlugin->applyTransformation(
1049 4
                    $currCellEditedValues[$columnName],
1050 4
                    $transformOptions,
1051 4
                );
1052 4
                $editedValues[$cellIndex][$columnName] = $extraData['transformations'][$cellIndex];
1053
            }
1054
        }
1055
1056 4
        return $extraData;
1057
    }
1058
1059
    /**
1060
     * Get value part if a function was specified
1061
     *
1062
     * @psalm-return non-empty-string
1063
     */
1064 4
    private function formatAsSqlFunction(
1065
        EditField $editField,
1066
    ): string {
1067 4
        if ($editField->function === 'PHP_PASSWORD_HASH') {
1068 4
            $hash = password_hash($editField->value, PASSWORD_DEFAULT);
1069
1070 4
            return $this->dbi->quoteString($hash);
1071
        }
1072
1073 4
        if ($editField->function === 'UUID') {
1074
            /* This way user will know what UUID new row has */
1075 4
            $uuid = (string) $this->dbi->fetchValue('SELECT UUID()');
1076
1077 4
            return $this->dbi->quoteString($uuid);
1078
        }
1079
1080
        if (
1081 4
            in_array($editField->function, $this->getGisFromTextFunctions(), true)
1082 4
            || in_array($editField->function, $this->getGisFromWKBFunctions(), true)
1083
        ) {
1084 4
            preg_match('/^(\'?)(.*?)\1(?:,(\d+))?$/', $editField->value, $matches);
1085 4
            $escapedParams = $this->dbi->quoteString($matches[2]) . (isset($matches[3]) ? ',' . $matches[3] : '');
1086
1087 4
            return $editField->function . '(' . $escapedParams . ')';
1088
        }
1089
1090
        if (
1091 4
            ! in_array($editField->function, self::FUNC_NO_PARAM, true)
1092 4
            || ($editField->value !== '' && in_array($editField->function, self::FUNC_OPTIONAL_PARAM, true))
1093
        ) {
1094
            if (
1095 4
                ($editField->salt !== null
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($editField->salt !== nu...>function === 'ENCRYPT', Probably Intended Meaning: $editField->salt !== nul...function === 'ENCRYPT')
Loading history...
1096 4
                    && ($editField->function === 'AES_ENCRYPT'
1097 4
                        || $editField->function === 'AES_DECRYPT'
1098 4
                        || $editField->function === 'SHA2'))
1099 4
                || ($editField->salt
1100 4
                    && ($editField->function === 'DES_ENCRYPT'
1101 4
                        || $editField->function === 'DES_DECRYPT'
1102 4
                        || $editField->function === 'ENCRYPT'))
1103
            ) {
1104 4
                return $editField->function . '(' . $this->dbi->quoteString($editField->value) . ','
1105 4
                    . $this->dbi->quoteString($editField->salt) . ')';
1106
            }
1107
1108
            if (
1109 4
                $editField->function === 'NOW'
1110 4
                && (is_numeric($editField->value) && $editField->value >= 0 && $editField->value <= 6)
1111
            ) {
1112
                return $editField->function . '(' . (int) $editField->value . ')';
1113
            }
1114
1115 4
            return $editField->function . '(' . $this->dbi->quoteString($editField->value) . ')';
1116
        }
1117
1118 4
        return $editField->function . '()';
1119
    }
1120
1121
    /**
1122
     * Get the field value formatted for use in a SQL statement.
1123
     * Used in both INSERT and UPDATE statements.
1124
     */
1125 8
    private function getValueFormattedAsSql(
1126
        EditField $editField,
1127
        string $protectedValue = '',
1128
    ): string {
1129 8
        if ($editField->isUploaded) {
1130 4
            return $editField->value;
1131
        }
1132
1133 8
        if ($editField->function !== '') {
1134 4
            return $this->formatAsSqlFunction($editField);
1135
        }
1136
1137 8
        return $this->formatAsSqlValueBasedOnType($editField, $protectedValue);
1138
    }
1139
1140
    /**
1141
     * Get query values array and query fields array for insert and update in multi edit
1142
     *
1143
     * @param string|int $whereClause Either a positional index or string representing selected row
1144
     */
1145 4
    public function getQueryValueForInsert(
1146
        EditField $editField,
1147
        bool $usingKey,
1148
        string|int $whereClause,
1149
    ): string {
1150 4
        $protectedValue = '';
1151 4
        if ($editField->type === 'protected' && $usingKey && $whereClause !== '') {
1152
            // Fetch the current values of a row to use in case we have a protected field
1153 4
            $protectedValue = $this->dbi->fetchValue(
1154 4
                'SELECT ' . Util::backquote($editField->columnName)
1155 4
                . ' FROM ' . Util::backquote(Current::$table)
1156 4
                . ' WHERE ' . $whereClause,
1157 4
            );
1158 4
            $protectedValue = is_string($protectedValue) ? $protectedValue : '';
1159
        }
1160
1161 4
        return $this->getValueFormattedAsSql($editField, $protectedValue);
1162
    }
1163
1164
    /**
1165
     * Get field-value pairs for update SQL.
1166
     * During update, we build the SQL only with the fields that should be updated.
1167
     */
1168 4
    public function getQueryValueForUpdate(EditField $editField): string
1169
    {
1170 4
        $currentValueFormattedAsSql = $this->getValueFormattedAsSql($editField);
1171
1172
        // avoid setting a field to NULL when it's already NULL
1173
        // (field had the null checkbox before the update; field still has the null checkbox)
1174 4
        if ($editField->wasPreviouslyNull && $editField->isNull) {
1175 4
            return '';
1176
        }
1177
1178
        // A blob field that hasn't been changed will have no value
1179 4
        if ($currentValueFormattedAsSql === '') {
1180 4
            return '';
1181
        }
1182
1183
        if (
1184
            // Field had the null checkbox before the update; field no longer has the null checkbox
1185 4
            $editField->wasPreviouslyNull ||
1186
            // Field was marked as NULL (the value will be unchanged if it was an empty string)
1187 4
            $editField->isNull ||
1188
            // A function was applied to the field
1189 4
            $editField->function !== '' ||
1190
            // The value was changed
1191 4
            $editField->value !== $editField->previousValue
1192
        ) {
1193 4
            return Util::backquote($editField->columnName) . ' = ' . $currentValueFormattedAsSql;
1194
        }
1195
1196 4
        return '';
1197
    }
1198
1199
    /**
1200
     * Get the current column value in the form for different data types
1201
     */
1202 8
    private function formatAsSqlValueBasedOnType(
1203
        EditField $editField,
1204
        string $protectedValue,
1205
    ): string {
1206 8
        if ($editField->type === 'protected') {
1207
            // here we are in protected mode (asked in the config)
1208
            // so tbl_change has put this special value in the
1209
            // columns array, so we do not change the column value
1210
            // but we can still handle column upload
1211
1212
            // when in UPDATE mode, do not alter field's contents. When in INSERT
1213
            // mode, insert empty field because no values were submitted.
1214
            // If protected blobs were set, insert original field's content.
1215 8
            if ($protectedValue !== '') {
1216 4
                return '0x' . bin2hex($protectedValue);
1217
            }
1218
1219 8
            if ($editField->isNull) {
1220 4
                return 'NULL';
1221
            }
1222
1223
            // The Null checkbox was unchecked for this field
1224 8
            if ($editField->wasPreviouslyNull) {
1225 4
                return "''";
1226
            }
1227
1228 8
            return '';
1229
        }
1230
1231 8
        if ($editField->value === '') {
1232
            // When the field is autoIncrement, the best way to avoid problems
1233
            // in strict mode is to set the value to null (works also in non-strict mode)
1234
1235
            // If the value is empty and the null checkbox is checked, set it to null
1236 8
            return $editField->autoIncrement || $editField->isNull ? 'NULL' : "''";
1237
        }
1238
1239 8
        if ($editField->type === 'hex') {
1240 4
            if (! str_starts_with($editField->value, '0x')) {
1241 4
                return '0x' . $editField->value;
1242
            }
1243
1244 4
            return $editField->value;
1245
        }
1246
1247 8
        if ($editField->type === 'bit') {
1248 4
            $currentValue = (string) preg_replace('/[^01]/', '0', $editField->value);
1249
1250 4
            return 'b' . $this->dbi->quoteString($currentValue);
1251
        }
1252
1253
        // For uuid type, generate uuid value
1254
        // if empty value but not set null or value is uuid() function
1255
        if (
1256 8
            $editField->type === 'uuid'
1257 8
                && ! $editField->isNull
1258 8
                && in_array($editField->value, ["''", '', "'uuid()'", 'uuid()'], true)
1259
        ) {
1260 4
            return 'uuid()';
1261
        }
1262
1263
        if (
1264 8
            ($editField->type !== 'datetime' && $editField->type !== 'timestamp' && $editField->type !== 'date')
1265 8
            || preg_match('/^current_timestamp(\([0-6]?\))?$/i', $editField->value) !== 1
1266
        ) {
1267 8
            return $this->dbi->quoteString($editField->value);
1268
        }
1269
1270
        // If there is a value, we ignore the Null checkbox;
1271
        // this could be possible if Javascript is disabled in the browser
1272 4
        return $editField->value;
1273
    }
1274
1275
    /**
1276
     * Check whether inline edited value can be truncated or not,
1277
     * and add additional parameters for extra_data array  if needed
1278
     *
1279
     * @param string  $db         Database name
1280
     * @param string  $table      Table name
1281
     * @param string  $columnName Column name
1282
     * @param mixed[] $extraData  Extra data for ajax response
1283
     */
1284 4
    public function verifyWhetherValueCanBeTruncatedAndAppendExtraData(
1285
        string $db,
1286
        string $table,
1287
        string $columnName,
1288
        array &$extraData,
1289
    ): void {
1290 4
        $extraData['isNeedToRecheck'] = false;
1291
1292 4
        $sqlForRealValue = 'SELECT ' . Util::backquote($table) . '.'
1293 4
            . Util::backquote($columnName)
1294 4
            . ' FROM ' . Util::backquote($db) . '.'
1295 4
            . Util::backquote($table)
1296 4
            . ' WHERE ' . $_POST['where_clause'][0];
1297
1298 4
        $result = $this->dbi->tryQuery($sqlForRealValue);
1299
1300 4
        if (! $result) {
1301
            return;
1302
        }
1303
1304 4
        $metadata = $this->dbi->getFieldsMeta($result)[0];
1305 4
        $newValue = $result->fetchValue();
1306
1307 4
        if ($newValue === false) {
1308 4
            return;
1309
        }
1310
1311 4
        if ($newValue !== null) {
0 ignored issues
show
introduced by
The condition $newValue !== null is always true.
Loading history...
1312 4
            if ($metadata->isTimeType()) {
1313 4
                $newValue = Util::addMicroseconds($newValue);
1314 4
            } elseif ($metadata->isBinary()) {
1315
                $newValue = '0x' . bin2hex($newValue);
1316
            }
1317
        }
1318
1319 4
        $extraData['isNeedToRecheck'] = true;
1320 4
        $extraData['truncatableFieldValue'] = $newValue;
1321
    }
1322
1323
    /**
1324
     * Function to get the columns of a table
1325
     *
1326
     * @param string $db    current db
1327
     * @param string $table current table
1328
     *
1329
     * @return list<Column>
1330
     */
1331 4
    public function getTableColumns(string $db, string $table): array
1332
    {
1333 4
        $this->dbi->selectDb($db);
1334
1335 4
        return array_values($this->dbi->getColumns($db, $table));
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_values($thi...etColumns($db, $table)) returns the type array which is incompatible with the documented return type PhpMyAdmin\list.
Loading history...
1336
    }
1337
1338
    /**
1339
     * Function to determine Insert/Edit rows
1340
     *
1341
     * @param string[]|string|null $whereClause where clause
1342
     * @param string               $db          current database
1343
     * @param string               $table       current table
1344
     *
1345
     * @return array{
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{ at position 2 could not be parsed: the token is null at position 2.
Loading history...
1346
     *     bool,
1347
     *     string[]|string|null,
1348
     *     ResultInterface[]|ResultInterface,
1349
     *     array<string, string|null>[],
1350
     *     bool,
1351
     *     string|null
1352
     * }
1353
     */
1354 4
    public function determineInsertOrEdit(array|string|null $whereClause, string $db, string $table): array
1355
    {
1356 4
        if (isset($_POST['where_clause'])) {
1357 4
            $whereClause = $_POST['where_clause'];
1358
        }
1359
1360 4
        if (isset($_SESSION['edit_next'])) {
1361 4
            $whereClause = $_SESSION['edit_next'];
1362 4
            unset($_SESSION['edit_next']);
1363 4
            $afterInsert = 'edit_next';
1364
        }
1365
1366 4
        if (isset($_POST['ShowFunctionFields'])) {
1367 4
            $this->config->settings['ShowFunctionFields'] = $_POST['ShowFunctionFields'];
1368
        }
1369
1370 4
        if (isset($_POST['ShowFieldTypesInDataEditView'])) {
1371 4
            $this->config->settings['ShowFieldTypesInDataEditView'] = $_POST['ShowFieldTypesInDataEditView'];
1372
        }
1373
1374 4
        if (isset($_POST['after_insert'])) {
1375 4
            $afterInsert = $_POST['after_insert'];
1376
        }
1377
1378 4
        if ($whereClause !== null) {
1379
            // we are editing
1380 4
            $insertMode = false;
1381 4
            [$result, $rows, $foundUniqueKey] = $this->analyzeWhereClauses((array) $whereClause, $table, $db);
1382
        } else {
1383
            // we are inserting
1384 4
            $insertMode = true;
1385 4
            $result = $this->loadFirstRow($table, $db);
1386 4
            $rows = $this->getInsertRows();
1387 4
            $foundUniqueKey = false;
1388
        }
1389
1390
        /** @var string $defaultAction */
1391 4
        $defaultAction = $_POST['default_action'] ?? $_GET['default_action'] ?? '';
1392 4
        if ($defaultAction === 'insert') {
1393
            // Copying a row - fetched data will be inserted as a new row, therefore the where clause is needless.
1394 4
            $whereClause = null;
1395
        }
1396
1397 4
        return [
1398 4
            $insertMode,
1399 4
            $whereClause,
1400 4
            $result,
1401 4
            $rows,
1402 4
            $foundUniqueKey,
1403 4
            $afterInsert ?? null,
1404 4
        ];
1405
    }
1406
1407
    /**
1408
     * Function to get comments for the table columns
1409
     *
1410
     * @param string $db    current database
1411
     * @param string $table current table
1412
     *
1413
     * @return string[] comments for columns
1414
     */
1415 4
    public function getCommentsMap(string $db, string $table): array
1416
    {
1417 4
        if ($this->config->settings['ShowPropertyComments']) {
1418 4
            return $this->relation->getComments($db, $table);
1419
        }
1420
1421 4
        return [];
1422
    }
1423
1424
    /**
1425
     * Function to get html for the ignore option in insert mode
1426
     *
1427
     * @param int  $rowId   row id
1428
     * @param bool $checked ignore option is checked or not
1429
     */
1430 4
    public function getHtmlForIgnoreOption(int $rowId, bool $checked = true): string
1431
    {
1432 4
        return '<input type="checkbox"'
1433 4
            . ($checked ? ' checked' : '')
1434 4
            . ' name="insert_ignore_' . $rowId . '"'
1435 4
            . ' id="insert_ignore_' . $rowId . '">'
1436 4
            . '<label for="insert_ignore_' . $rowId . '">'
1437 4
            . __('Ignore')
1438 4
            . '</label><br>' . "\n";
1439
    }
1440
1441
    /**
1442
     * Function to get html for the insert edit form header
1443
     *
1444
     * @param bool $hasBlobField whether has blob field
1445
     * @param bool $isUpload     whether is upload
1446
     */
1447
    public function getHtmlForInsertEditFormHeader(bool $hasBlobField, bool $isUpload): string
1448
    {
1449
        $template = new Template();
1450
1451
        return $template->render('table/insert/get_html_for_insert_edit_form_header', [
1452
            'has_blob_field' => $hasBlobField,
1453
            'is_upload' => $isUpload,
1454
        ]);
1455
    }
1456
1457
    /**
1458
     * Function to get html for each insert/edit column
1459
     *
1460
     * @param Column             $tableColumn        column
1461
     * @param int                $columnNumber       column index in table_columns
1462
     * @param string[]           $commentsMap        comments map
1463
     * @param int                $columnLength       length of the current column taken from field metadata
1464
     * @param bool               $insertMode         whether insert mode
1465
     * @param array<string|null> $currentRow         current row
1466
     * @param int                $columnsCnt         columns count
1467
     * @param bool               $isUpload           whether upload
1468
     * @param mixed[]            $foreigners         foreigners
1469
     * @param string             $table              table
1470
     * @param string             $db                 database
1471
     * @param int                $rowId              row id
1472
     * @param string             $defaultCharEditing default char editing mode which is stored in config.inc.php
1473
     * @param mixed[]            $repopulate         the data to be repopulated
1474
     * @param string[]           $columnMime         the mime information of column
1475
     * @param string             $whereClause        the where clause
1476
     */
1477 12
    private function getHtmlForInsertEditFormColumn(
1478
        Column $tableColumn,
0 ignored issues
show
Bug introduced by
The type PhpMyAdmin\Column 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...
1479
        int $columnNumber,
1480
        array $commentsMap,
1481
        int $columnLength,
1482
        bool $insertMode,
1483
        array $currentRow,
1484
        int $columnsCnt,
1485
        bool $isUpload,
1486
        array $foreigners,
1487
        string $table,
1488
        string $db,
1489
        int $rowId,
1490
        string $defaultCharEditing,
1491
        array $repopulate,
1492
        array $columnMime,
1493
        string $whereClause,
1494
    ): string {
1495 12
        $column = new InsertEditColumn(
1496 12
            $tableColumn->field,
1497 12
            $tableColumn->type,
1498 12
            $tableColumn->isNull,
1499 12
            $tableColumn->key,
1500 12
            $tableColumn->default,
1501 12
            $tableColumn->extra,
1502 12
            $columnLength,
1503 12
            $this->isColumn($tableColumn->type, ['binary', 'varbinary']),
1504 12
            $this->isColumn($tableColumn->type, ['blob', 'tinyblob', 'mediumblob', 'longblob']),
1505 12
            $this->isColumn($tableColumn->type, ['char', 'varchar']),
1506 12
            $insertMode,
1507 12
        );
1508
1509 12
        $asIs = false;
1510 12
        $fieldHashMd5 = $column->md5;
1511 12
        if ($repopulate !== [] && array_key_exists($fieldHashMd5, $currentRow)) {
1512
            $currentRow[$column->field] = $repopulate[$fieldHashMd5];
1513
            $asIs = true;
1514
        }
1515
1516 12
        $extractedColumnspec = Util::extractColumnSpec($column->type);
1517
1518
        //Call validation when the form submitted...
1519 12
        $onChangeClause = 'return verificationsAfterFieldChange('
1520 12
            . json_encode($fieldHashMd5) . ', '
1521 12
            . json_encode((string) $rowId) . ',' . json_encode($column->type) . ')';
1522
1523 12
        $vkey = '[multi_edit][' . $rowId . ']';
1524
        // Use an MD5 as an array index to avoid having special characters
1525
        // in the name attribute (see bug #1746964 )
1526 12
        $columnNameAppendix = $vkey . '[' . $fieldHashMd5 . ']';
1527
1528
        // Prepares the field value
1529 12
        if ($currentRow !== []) {
1530
            // (we are editing)
1531
            [
1532
                $realNullValue,
1533
                $specialCharsEncoded,
1534
                $specialChars,
1535
                $data,
1536
                $backupField,
1537
            ] = $this->getSpecialCharsAndBackupFieldForExistingRow(
1538
                $currentRow,
1539
                $column,
1540
                $extractedColumnspec['spec_in_brackets'],
1541
                $columnNameAppendix,
1542
                $asIs,
1543
            );
1544
        } else {
1545
            // (we are inserting)
1546
            // display default values
1547 12
            $defaultValue = $repopulate[$fieldHashMd5] ?? $column->default ?? null;
1548
1549 12
            $realNullValue = $defaultValue === null;
1550 12
            $data = (string) $defaultValue;
1551 12
            $specialChars = htmlspecialchars($this->getDefaultValue($defaultValue, $column->trueType));
1552 12
            $specialCharsEncoded = Util::duplicateFirstNewline($specialChars);
1553 12
            $backupField = '';
1554
        }
1555
1556 12
        $this->fieldIndex = ($this->rowOffset * $columnsCnt) + $columnNumber + 1;
1557
1558
        // The function column
1559
        // -------------------
1560 12
        $foreignData = $this->relation->getForeignData($foreigners, $column->field, false, '', '');
1561 12
        $isColumnBinary = $this->isColumnBinary($column, $isUpload);
1562 12
        $functionOptions = '';
1563
1564 12
        if ($this->config->settings['ShowFunctionFields']) {
1565 12
            $defaultFunction = Generator::getDefaultFunctionForField(
1566 12
                $column->trueType,
1567 12
                $column->firstTimestamp,
1568 12
                $column->default,
1569 12
                $column->extra,
1570 12
                $column->isNull,
1571 12
                $column->key,
1572 12
                $column->type,
1573 12
                $insertMode,
1574 12
            );
1575 12
            $functionOptions = Generator::getFunctionsForField($defaultFunction, $foreignData->foreignField);
1576
        }
1577
1578
        // nullify code is needed by the js nullify() function to be able to generate calls to nullify() in jQuery
1579 12
        $nullifyCode = $this->getNullifyCodeForNullColumn($column, $foreigners, $foreignData->foreignLink);
1580
1581
        // The value column (depends on type)
1582
        // ----------------
1583
        // See bug #1667887 for the reason why we don't use the maxlength
1584
        // HTML attribute
1585
1586
        // Check input transformation of column
1587 12
        $transformedHtml = '';
1588 12
        if (! empty($columnMime['input_transformation'])) {
1589 4
            $transformationPlugin = $this->transformations->getPluginInstance($columnMime['input_transformation']);
1590 4
            if ($transformationPlugin instanceof IOTransformationsPlugin) {
1591 4
                $transformationOptions = $this->transformations->getOptions(
1592 4
                    $columnMime['input_transformation_options'],
1593 4
                );
1594 4
                $urlParams = [
1595 4
                    'db' => $db,
1596 4
                    'table' => $table,
1597 4
                    'transform_key' => $column->field,
1598 4
                    'where_clause_sign' => Core::signSqlQuery($whereClause),
1599 4
                    'where_clause' => $whereClause,
1600 4
                ];
1601 4
                $transformationOptions['wrapper_link'] = Url::getCommon($urlParams);
1602 4
                $transformationOptions['wrapper_params'] = $urlParams;
1603
1604 4
                $transformedHtml = $transformationPlugin->getInputHtml(
1605 4
                    $columnNameAppendix,
1606 4
                    $transformationOptions,
1607 4
                    $currentRow[$column->field] ?? '',
1608 4
                    $this->fieldIndex,
1609 4
                );
1610
1611 4
                self::$pluginScripts = array_merge(self::$pluginScripts, $transformationPlugin->getScripts());
1612
            }
1613
        }
1614
1615 12
        $columnValue = '';
1616 12
        $foreignDropdown = '';
1617 12
        $dataType = TypeClass::Unknown;
1618 12
        $textAreaRows = $this->config->settings['TextareaRows'];
1619 12
        $textareaCols = $this->config->settings['TextareaCols'];
1620 12
        $maxlength = '';
1621 12
        $enumSelectedValue = '';
1622 12
        $enumValues = [];
1623 12
        $columnSetValues = [];
1624 12
        $setSelectSize = 0;
1625 12
        $isColumnProtectedBlob = false;
1626 12
        $blobValue = '';
1627 12
        $blobValueUnit = '';
1628 12
        $maxUploadSize = 0;
1629 12
        $selectOptionForUpload = '';
1630 12
        $hexInputSize = 0;
1631 12
        if ($transformedHtml === '') {
1632 12
            if ($foreignData->dispRow !== null) {
1633
                $foreignDropdown = $this->relation->foreignDropdown(
1634
                    $foreignData->dispRow,
1635
                    $foreignData->foreignField,
1636
                    $foreignData->foreignDisplay,
1637
                    $data,
1638
                    $this->config->settings['ForeignKeyMaxLimit'],
1639
                );
1640
            }
1641
1642 12
            $dataType = $this->dbi->types->getTypeClass($column->trueType);
1643
1644 12
            if ($column->isChar) {
1645
                $textAreaRows = max($this->config->settings['CharTextareaRows'], 7);
1646
                $textareaCols = $this->config->settings['CharTextareaCols'];
1647
                $maxlength = $extractedColumnspec['spec_in_brackets'];
1648 12
            } elseif ($this->config->settings['LongtextDoubleTextarea'] && $column->trueType === 'longtext') {
1649 8
                $textAreaRows = $this->config->settings['TextareaRows'] * 2;
1650 8
                $textareaCols = $this->config->settings['TextareaCols'] * 2;
1651
            }
1652
1653 12
            if ($column->trueType === 'enum') {
1654
                $enumValues = $extractedColumnspec['enum_set_values'];
1655
1656
                foreach ($enumValues as $enumValue) {
1657
                    if (
1658
                        $data == $enumValue || ($data == ''
1659
                            && (! isset($_POST['where_clause']) || ! $column->isNull)
1660
                            && isset($column->default) && $enumValue == $column->default)
1661
                    ) {
1662
                        $enumSelectedValue = $enumValue;
1663
                        break;
1664
                    }
1665
                }
1666 12
            } elseif ($column->trueType === 'set') {
1667
                $columnSetValues = $extractedColumnspec['enum_set_values'];
1668
                $setSelectSize = min(4, count($extractedColumnspec['enum_set_values']));
1669 12
            } elseif ($column->isBinary || $column->isBlob) {
1670
                $isColumnProtectedBlob = ($this->config->settings['ProtectBinary'] === 'blob' && $column->isBlob)
1671
                    || ($this->config->settings['ProtectBinary'] === 'all')
1672
                    || ($this->config->settings['ProtectBinary'] === 'noblob' && ! $column->isBlob);
1673
                if ($isColumnProtectedBlob) {
1674
                    [$blobValue, $blobValueUnit] = Util::formatByteDown(mb_strlen(stripslashes($data)), 3, 1);
1675
                }
1676
1677
                if ($isUpload && $column->isBlob) {
1678
                    $maxUploadSize = $this->getMaxUploadSize($column->trueType);
1679
                }
1680
1681
                if (! empty($this->config->settings['UploadDir'])) {
1682
                    $selectOptionForUpload = $this->getSelectOptionForUpload($vkey, $fieldHashMd5);
1683
                }
1684
1685
                if (
1686
                    ! $isColumnProtectedBlob
1687
                    && ! ($column->isBlob || ($column->length > $this->config->settings['LimitChars']))
1688
                ) {
1689
                    $hexInputSize = min(max($column->length * 2, 4), $this->config->settings['LimitChars']);
1690
                }
1691
            } else {
1692 12
                $columnValue = $this->getValueColumnForOtherDatatypes(
1693 12
                    $column,
1694 12
                    $defaultCharEditing,
1695 12
                    $backupField,
1696 12
                    $columnNameAppendix,
1697 12
                    $onChangeClause,
1698 12
                    $specialChars,
1699 12
                    $specialCharsEncoded,
1700 12
                    $data,
1701 12
                    $extractedColumnspec['spec_in_brackets'],
1702 12
                );
1703
            }
1704
        }
1705
1706 12
        return $this->template->render('table/insert/column_row', [
1707 12
            'db' => $db,
1708 12
            'table' => $table,
1709 12
            'column' => $column,
1710 12
            'row_id' => $rowId,
1711 12
            'show_field_types_in_data_edit_view' => $this->config->settings['ShowFieldTypesInDataEditView'],
1712 12
            'show_function_fields' => $this->config->settings['ShowFunctionFields'],
1713 12
            'is_column_binary' => $isColumnBinary,
1714 12
            'function_options' => $functionOptions,
1715 12
            'nullify_code' => $nullifyCode,
1716 12
            'real_null_value' => $realNullValue,
1717 12
            'id_index' => $this->fieldIndex,
1718 12
            'type' => $column->trueType,
1719 12
            'displayType' => $column->getDisplayType(),
1720 12
            'decimals' => $column->getFractionalSecondsPrecision(),
1721 12
            'special_chars' => $specialChars,
1722 12
            'transformed_value' => $transformedHtml,
1723 12
            'value' => $columnValue,
1724 12
            'is_value_foreign_link' => $foreignData->foreignLink,
1725 12
            'backup_field' => $backupField,
1726 12
            'data' => $data,
1727 12
            'gis_data_types' => Gis::getDataTypes(),
1728 12
            'foreign_dropdown' => $foreignDropdown,
1729 12
            'data_type' => $dataType->value,
1730 12
            'textarea_cols' => $textareaCols,
1731 12
            'textarea_rows' => $textAreaRows,
1732 12
            'max_length' => $maxlength,
1733 12
            'longtext_double_textarea' => $this->config->settings['LongtextDoubleTextarea'],
1734 12
            'enum_selected_value' => $enumSelectedValue,
1735 12
            'enum_values' => $enumValues,
1736 12
            'set_values' => $columnSetValues,
1737 12
            'set_select_size' => $setSelectSize,
1738 12
            'is_column_protected_blob' => $isColumnProtectedBlob,
1739 12
            'blob_value' => $blobValue,
1740 12
            'blob_value_unit' => $blobValueUnit,
1741 12
            'is_upload' => $isUpload,
1742 12
            'max_upload_size' => $maxUploadSize,
1743 12
            'select_option_for_upload' => $selectOptionForUpload,
1744 12
            'limit_chars' => $this->config->settings['LimitChars'],
1745 12
            'hex_input_size' => $hexInputSize,
1746 12
            'field_title' => $this->getColumnTitle($column->field, $commentsMap),
1747 12
        ]);
1748
    }
1749
1750 12
    private function isColumnBinary(InsertEditColumn $column, bool $isUpload): bool
1751
    {
1752 12
        if (! $this->config->settings['ShowFunctionFields']) {
1753
            return false;
1754
        }
1755
1756 12
        return ($this->config->settings['ProtectBinary'] === 'blob' && $column->isBlob && ! $isUpload)
1757 12
            || ($this->config->settings['ProtectBinary'] === 'all' && $column->isBinary)
1758 12
            || ($this->config->settings['ProtectBinary'] === 'noblob' && $column->isBinary);
1759
    }
1760
1761
    /**
1762
     * Function to get html for each insert/edit row
1763
     *
1764
     * @param array<string, bool|int|string> $urlParams        url parameters
1765
     * @param list<Column>                   $tableColumns     table columns
1766
     * @param string[]                       $commentsMap      comments map
1767
     * @param FieldMetadata[]                $fieldMetadata    current result's field metadata
1768
     * @param bool                           $insertMode       whether insert mode
1769
     * @param array<string|null>             $currentRow       current row
1770
     * @param bool                           $isUpload         whether upload
1771
     * @param mixed[]                        $foreigners       foreigners
1772
     * @param string                         $table            table
1773
     * @param string                         $db               database
1774
     * @param int                            $rowId            row id
1775
     * @param mixed[]                        $repopulate       the data to be repopulated
1776
     * @param string[]                       $whereClauseArray the array of where clauses
1777
     */
1778 8
    public function getHtmlForInsertEditRow(
1779
        array $urlParams,
1780
        array $tableColumns,
1781
        array $commentsMap,
1782
        array $fieldMetadata,
1783
        bool $insertMode,
1784
        array $currentRow,
1785
        bool $isUpload,
1786
        array $foreigners,
1787
        string $table,
1788
        string $db,
1789
        int $rowId,
1790
        array $repopulate,
1791
        array $whereClauseArray,
1792
    ): string {
1793 8
        $htmlOutput = $this->getHeadAndFootOfInsertRowTable($urlParams)
1794 8
            . '<tbody>';
1795
1796
        //store the default value for CharEditing
1797 8
        $defaultCharEditing = $this->config->settings['CharEditing'];
1798 8
        $mimeMap = $this->transformations->getMime($db, $table);
1799 8
        $whereClause = $whereClauseArray[$rowId] ?? '';
1800
1801 8
        $columnCount = count($tableColumns);
1802 8
        for ($columnNumber = 0; $columnNumber < $columnCount; $columnNumber++) {
1803 8
            $tableColumn = $tableColumns[$columnNumber];
1804 8
            $columnMime = $mimeMap[$tableColumn->field] ?? [];
1805
1806 8
            $virtual = ['VIRTUAL', 'PERSISTENT', 'VIRTUAL GENERATED', 'STORED GENERATED'];
1807 8
            if (in_array($tableColumn->extra, $virtual, true)) {
1808
                continue;
1809
            }
1810
1811 8
            $htmlOutput .= $this->getHtmlForInsertEditFormColumn(
1812 8
                $tableColumn,
1813 8
                $columnNumber,
1814 8
                $commentsMap,
1815 8
                $fieldMetadata[$columnNumber]->length,
1816 8
                $insertMode,
1817 8
                $currentRow,
1818 8
                $columnCount,
1819 8
                $isUpload,
1820 8
                $foreigners,
1821 8
                $table,
1822 8
                $db,
1823 8
                $rowId,
1824 8
                $defaultCharEditing,
1825 8
                $repopulate,
1826 8
                $columnMime,
1827 8
                $whereClause,
1828 8
            );
1829
        }
1830
1831 8
        $this->rowOffset++;
1832
1833 8
        return $htmlOutput . '  </tbody>'
1834 8
            . '</table></div><br>'
1835 8
            . '<div class="clearfloat"></div>';
1836
    }
1837
1838
    /** @return array<string|null> */
1839
    public function getColumnDefaultValues(string $database, string $table): array
1840
    {
1841
        $sql = 'SELECT COLUMN_NAME, CASE WHEN INSTR(EXTRA, \'DEFAULT_GENERATED\')'
1842
            . ' THEN COLUMN_DEFAULT '
1843
            . ' ELSE CONCAT(\'\'\'\', COLUMN_DEFAULT, \'\'\'\')'
1844
            . ' END AS COLUMN_DEFAULT'
1845
            . ' FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '
1846
            . $this->dbi->quoteString($table)
1847
            . ' AND TABLE_SCHEMA = ' . $this->dbi->quoteString($database);
1848
1849
        return $this->dbi->query($sql)->fetchAllKeyPair();
1850
    }
1851
1852
    /**
1853
     * Returns list of function names that accept WKB as text
1854
     *
1855
     * @return string[]
1856
     */
1857 4
    private function getGisFromTextFunctions(): array
1858
    {
1859 4
        return $this->dbi->getVersion() >= 50600 ?
1860 4
        [
1861 4
            'ST_GeomFromText',
1862 4
            'ST_GeomCollFromText',
1863 4
            'ST_LineFromText',
1864 4
            'ST_MLineFromText',
1865 4
            'ST_PointFromText',
1866 4
            'ST_MPointFromText',
1867 4
            'ST_PolyFromText',
1868 4
            'ST_MPolyFromText',
1869 4
        ] :
1870 4
        [
1871 4
            'GeomFromText',
1872 4
            'GeomCollFromText',
1873 4
            'LineFromText',
1874 4
            'MLineFromText',
1875 4
            'PointFromText',
1876 4
            'MPointFromText',
1877 4
            'PolyFromText',
1878 4
            'MPolyFromText',
1879 4
        ];
1880
    }
1881
1882
    /**
1883
     * Returns list of function names that accept WKB as binary
1884
     *
1885
     * @return string[]
1886
     */
1887 4
    private function getGisFromWKBFunctions(): array
1888
    {
1889 4
        return $this->dbi->getVersion() >= 50600 ?
1890 4
        [
1891 4
            'ST_GeomFromWKB',
1892 4
            'ST_GeomCollFromWKB',
1893 4
            'ST_LineFromWKB',
1894 4
            'ST_MLineFromWKB',
1895 4
            'ST_PointFromWKB',
1896 4
            'ST_MPointFromWKB',
1897 4
            'ST_PolyFromWKB',
1898 4
            'ST_MPolyFromWKB',
1899 4
        ] :
1900 4
        [
1901 4
            'GeomFromWKB',
1902 4
            'GeomCollFromWKB',
1903 4
            'LineFromWKB',
1904 4
            'MLineFromWKB',
1905 4
            'PointFromWKB',
1906 4
            'MPointFromWKB',
1907 4
            'PolyFromWKB',
1908 4
            'MPolyFromWKB',
1909 4
        ];
1910
    }
1911
}
1912