InsertEdit   F
last analyzed

Complexity

Total Complexity 203

Size/Duplication

Total Lines 1857
Duplicated Lines 0 %

Test Coverage

Coverage 86.5%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 203
eloc 820
dl 0
loc 1857
ccs 743
cts 859
cp 0.865
rs 1.78
c 1
b 0
f 0

83 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A getInsertRows() 0 4 1
A isColumn() 0 9 3
A hasUniqueCondition() 0 5 1
A loadFirstRow() 0 5 1
B getFormParametersForInsertForm() 0 31 7
A analyzeWhereClauses() 0 38 4
A getColumnTitle() 0 9 2
A showTypeOrFunctionLabel() 0 6 1
B showTypeOrFunction() 0 30 6
getContinueInsertionForm() 0 15 ?
A hp$0 ➔ getContinueInsertionForm() 0 15 1
A hp$0 ➔ getValueColumnForOtherDatatypes() 0 52 4
A hp$0 ➔ getMaxUploadSize() 0 17 1
getIntegerRange() 0 19 ?
getValueColumnForOtherDatatypes() 0 52 ?
A getNullifyCodeForNullColumn() 0 18 6
isWhereClauseNumeric() 0 21 ?
A hp$0 ➔ getHeadAndFootOfInsertRowTable() 0 18 3
getSelectOptionForUpload() 0 23 ?
A hp$0 ➔ getSelectOptionForUpload() 0 23 3
A hp$0 ➔ __construct() 0 5 1
getHeadAndFootOfInsertRowTable() 0 18 ?
getMaxUploadSize() 0 17 ?
getColumnSize() 0 23 ?
A hp$0 ➔ getIntegerRange() 0 19 1
B getTextarea() 0 40 6
A hp$0 ➔ getColumnSize() 0 23 3
A hp$0 ➔ isWhereClauseNumeric() 0 21 5
executeSqlQuery() 0 48 ?
A hp$0 ➔ getGisFromTextFunctions() 0 22 2
A hp$0 ➔ getErrorUrl() 0 3 1
A hp$0 ➔ setSessionForEditNext() 0 17 2
A hp$0 ➔ getDisplayValueForForeignTableColumn() 0 26 5
A hp$0 ➔ transformEditedValues() 0 38 5
getQueryValueForUpdate() 0 29 ?
formatAsSqlValueBasedOnType() 0 71 ?
F hp$0 ➔ getHtmlForInsertEditFormColumn() 0 270 34
A hp$0 ➔ getWarningMessages() 0 8 2
B hp$0 ➔ executeSqlQuery() 0 48 8
isColumnBinary() 0 9 ?
A hp$0 ➔ getValueFormattedAsSql() 0 13 3
transformEditedValues() 0 38 ?
B hp$0 ➔ getQueryValueForUpdate() 0 29 8
formatAsSqlFunction() 0 51 ?
A hp$0 ➔ getColumnDefaultValues() 0 11 1
B hp$0 ➔ getDefaultValue() 0 21 6
setSessionForEditNext() 0 17 ?
getSpecialCharsAndBackupFieldForExistingRow() 0 72 ?
getCommentsMap() 0 7 ?
getErrorUrl() 0 3 ?
A hp$0 ➔ getHtmlForIgnoreOption() 0 9 2
B hp$0 ➔ getGotoInclude() 0 26 10
determineInsertOrEdit() 0 50 ?
C hp$0 ➔ formatAsSqlFunction() 0 51 17
A hp$0 ➔ getCommentsMap() 0 7 2
A hp$0 ➔ getQueryValueForInsert() 0 17 5
getHtmlForInsertEditFormHeader() 0 7 ?
getColumnDefaultValues() 0 11 ?
A hp$0 ➔ getTableColumns() 0 5 1
verifyWhetherValueCanBeTruncatedAndAppendExtraData() 0 37 ?
getGotoInclude() 0 26 ?
B hp$0 ➔ getLinkForRelationalDisplayField() 0 49 5
B hp$0 ➔ verifyWhetherValueCanBeTruncatedAndAppendExtraData() 0 37 6
getWarningMessages() 0 8 ?
getQueryValueForInsert() 0 17 ?
getHtmlForInsertEditRow() 0 58 ?
B hp$0 ➔ determineInsertOrEdit() 0 50 8
getDisplayValueForForeignTableColumn() 0 26 ?
getTableColumns() 0 5 ?
B hp$0 ➔ getHtmlForInsertEditRow() 0 58 3
getValueFormattedAsSql() 0 13 ?
getDefaultValue() 0 21 ?
getGisFromWKBFunctions() 0 22 ?
C hp$0 ➔ getSpecialCharsAndBackupFieldForExistingRow() 0 72 16
A hp$0 ➔ getHtmlForInsertEditFormHeader() 0 7 1
getGisFromTextFunctions() 0 22 ?
A hp$0 ➔ getGisFromWKBFunctions() 0 22 2
D hp$0 ➔ formatAsSqlValueBasedOnType() 0 71 18
getLinkForRelationalDisplayField() 0 49 ?
B hp$0 ➔ isColumnBinary() 0 9 8
getHtmlForIgnoreOption() 0 9 ?
getHtmlForInsertEditFormColumn() 0 270 ?

How to fix   Complexity   

Complex Class

Complex classes like InsertEdit often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use InsertEdit, and based on these observations, apply Extract Interface, too.

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
            in_array($column->trueType, ['timestamp', 'datetime', 'time'], true)
683 4
            && str_contains($currentValue, '.')
684
        ) {
685
            $currentValue = $asIs
686
                ? $currentValue
687
                : Util::addMicroseconds($currentValue);
688
            $specialChars = htmlspecialchars($currentValue, ENT_COMPAT);
689 4
        } elseif (in_array($column->trueType, Gis::getDataTypes(), true)) {
690
            // Convert gis data to Well Know Text format
691 4
            $currentValue = $asIs
692
                ? $currentValue
693 4
                : Gis::convertToWellKnownText($currentValue, true);
694 4
            $specialChars = htmlspecialchars($currentValue, ENT_COMPAT);
695
        } else {
696
            // special binary "characters"
697 4
            if ($column->isBinary || ($column->isBlob && $this->config->settings['ProtectBinary'] !== 'all')) {
698 4
                $currentValue = $asIs
699
                    ? $currentValue
700 4
                    : bin2hex($currentValue);
701
            }
702
703 4
            $specialChars = htmlspecialchars($currentValue, ENT_COMPAT);
704
705
            //We need to duplicate the first \n or otherwise we will lose
706
            //the first newline entered in a VARCHAR or TEXT column
707 4
            $specialCharsEncoded = Util::duplicateFirstNewline($specialChars);
708
709 4
            $data = $currentValue;
710
        }
711
712
        /** @var string $defaultAction */
713 4
        $defaultAction = $_POST['default_action'] ?? $_GET['default_action'] ?? '';
714
        if (
715 4
            $defaultAction === 'insert'
716 4
            && $column->key === 'PRI'
717 4
            && str_contains($column->extra, 'auto_increment')
718
        ) {
719
            // When copying row, it is useful to empty auto-increment column to prevent duplicate key error.
720 4
            $data = $specialCharsEncoded = $specialChars = '';
721
        }
722
723
        // If a timestamp field value is not included in an update
724
        // statement MySQL auto-update it to the current timestamp;
725
        // however, things have changed since MySQL 4.1, so
726
        // it's better to set a fields_prev in this situation
727 4
        $backupField = '<input type="hidden" name="fields_prev'
728 4
            . $columnNameAppendix . '" value="'
729 4
            . htmlspecialchars($currentValue, ENT_COMPAT) . '">';
730
731 4
        return [$realNullValue, $specialCharsEncoded, $specialChars, $data, $backupField];
732
    }
733
734
    /**
735
     * display default values
736
     */
737 48
    private function getDefaultValue(
738
        string|null $defaultValue,
739
        string $trueType,
740
    ): string {
741 48
        if ($defaultValue === null) {
742 12
            $defaultValue = '';
743
        }
744
745 48
        if ($trueType === 'bit') {
746 4
            return Util::convertBitDefaultValue($defaultValue);
747
        }
748
749 44
        if (in_array($trueType, ['timestamp', 'datetime', 'time'], true)) {
750 20
            return Util::addMicroseconds($defaultValue);
751
        }
752
753 28
        if ($trueType === 'binary' || $trueType === 'varbinary') {
754
            return bin2hex($defaultValue);
755
        }
756
757 28
        return $defaultValue;
758
    }
759
760
    /**
761
     * set $_SESSION for edit_next
762
     *
763
     * @param string $oneWhereClause one where clause from where clauses array
764
     */
765 4
    public function setSessionForEditNext(string $oneWhereClause): void
766
    {
767 4
        $localQuery = 'SELECT * FROM ' . Util::backquote(Current::$database)
768 4
            . '.' . Util::backquote(Current::$table) . ' WHERE '
769 4
            . str_replace('` =', '` >', $oneWhereClause) . ' LIMIT 1;';
770
771 4
        $res = $this->dbi->query($localQuery);
772 4
        $row = $res->fetchRow();
773 4
        $meta = $this->dbi->getFieldsMeta($res);
774
        // must find a unique condition based on unique key,
775
        // not a combination of all fields
776 4
        $uniqueCondition = (new UniqueCondition($meta, $row, true))->getWhereClause();
777 4
        if ($uniqueCondition === '') {
778
            return;
779
        }
780
781 4
        $_SESSION['edit_next'] = $uniqueCondition;
782
    }
783
784
    /**
785
     * set $goto_include variable for different cases and retrieve like,
786
     * if UrlParams::$goto empty, if $goto_include previously not defined
787
     * and new_insert, same_insert, edit_next
788
     *
789
     * @param string|false $gotoInclude store some script for include, otherwise it is
790
     *                                   boolean false
791
     */
792 4
    public function getGotoInclude(string|false $gotoInclude): string
793
    {
794 4
        $validOptions = ['new_insert', 'same_insert', 'edit_next'];
795 4
        if (isset($_POST['after_insert']) && in_array($_POST['after_insert'], $validOptions, true)) {
796 4
            return '/table/change';
797
        }
798
799 4
        if (UrlParams::$goto !== '') {
800 4
            if (preg_match('@^[a-z_]+\.php$@', UrlParams::$goto) !== 1) {
801
                // this should NOT happen
802
                //UrlParams::$goto = false;
803 4
                $gotoInclude = str_contains(UrlParams::$goto, 'index.php?route=/sql') ? '/sql' : false;
804
            } else {
805
                $gotoInclude = UrlParams::$goto;
806
            }
807
808 4
            if (UrlParams::$goto === 'index.php?route=/database/sql' && Current::$table !== '') {
809 4
                Current::$table = '';
810
            }
811
        }
812
813 4
        if (! $gotoInclude) {
814 4
            $gotoInclude = Current::$table === '' ? '/database/sql' : '/table/sql';
815
        }
816
817 4
        return $gotoInclude;
818
    }
819
820
    /**
821
     * Defines the url to return in case of failure of the query
822
     *
823
     * @param mixed[] $urlParams url parameters
824
     *
825
     * @return string           error url for query failure
826
     */
827 4
    public function getErrorUrl(array $urlParams): string
828
    {
829 4
        return $_POST['err_url'] ?? Url::getFromRoute('/table/change', $urlParams);
830
    }
831
832
    /**
833
     * Executes the sql query and get the result, then move back to the calling page
834
     *
835
     * @param string[] $query built query from buildSqlQuery()
836
     *
837
     * @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...
838
     */
839 8
    public function executeSqlQuery(array $query): array
840
    {
841 8
        Current::$sqlQuery = implode('; ', $query) . ';';
842
        // to ensure that the query is displayed in case of
843
        // "insert as new row" and then "insert another new row"
844 8
        Current::$displayQuery = Current::$sqlQuery;
845
846 8
        $totalAffectedRows = 0;
847 8
        $lastMessages = [];
848 8
        $warningMessages = [];
849 8
        $errorMessages = [];
850
851 8
        foreach ($query as $singleQuery) {
852 8
            if (isset($_POST['submit_type']) && $_POST['submit_type'] === 'showinsert') {
853
                $lastMessages[] = Message::notice(__('Showing SQL query'));
854
                continue;
855
            }
856
857 8
            if ($this->config->settings['IgnoreMultiSubmitErrors']) {
858 4
                $result = $this->dbi->tryQuery($singleQuery);
859
            } else {
860 4
                $result = $this->dbi->query($singleQuery);
861
            }
862
863 8
            if (! $result) {
864
                $errorMessages[] = $this->dbi->getError();
865
            } else {
866 8
                $totalAffectedRows += (int) $this->dbi->affectedRows();
867
868 8
                $insertId = $this->dbi->insertId();
869 8
                if ($insertId !== 0) {
870
                    // insert_id is id of FIRST record inserted in one insert, so if we
871
                    // inserted multiple rows, we had to increment this
872
873
                    if ($totalAffectedRows > 0) {
874
                        $insertId += $totalAffectedRows - 1;
875
                    }
876
877
                    $lastMessage = Message::notice(__('Inserted row id: %1$d'));
878
                    $lastMessage->addParam($insertId);
879
                    $lastMessages[] = $lastMessage;
880
                }
881
            }
882
883 8
            $warningMessages = $this->getWarningMessages();
884
        }
885
886 8
        return [$totalAffectedRows, $lastMessages, $warningMessages, $errorMessages];
887
    }
888
889
    /**
890
     * get the warning messages array
891
     *
892
     * @return string[]
893
     */
894 12
    private function getWarningMessages(): array
895
    {
896 12
        $warningMessages = [];
897 12
        foreach ($this->dbi->getWarnings() as $warning) {
898 4
            $warningMessages[] = htmlspecialchars((string) $warning);
899
        }
900
901 12
        return $warningMessages;
902
    }
903
904
    /**
905
     * Column to display from the foreign table?
906
     *
907
     * @param string  $whereComparison string that contain relation field value
908
     * @param mixed[] $foreigners      all Relations to foreign tables for a given
909
     *                                     table or optionally a given column in a table
910
     * @param string  $relationField   relation field
911
     *
912
     * @return string display value from the foreign table
913
     */
914 4
    public function getDisplayValueForForeignTableColumn(
915
        string $whereComparison,
916
        array $foreigners,
917
        string $relationField,
918
    ): string {
919 4
        $foreigner = $this->relation->searchColumnInForeigners($foreigners, $relationField);
920
921 4
        if (! is_array($foreigner)) {
922
            return '';
923
        }
924
925 4
        $displayField = $this->relation->getDisplayField($foreigner['foreign_db'], $foreigner['foreign_table']);
926
        // Field to display from the foreign table?
927 4
        if ($displayField !== '') {
928 4
            $dispsql = 'SELECT ' . Util::backquote($displayField)
929 4
                . ' FROM ' . Util::backquote($foreigner['foreign_db'])
930 4
                . '.' . Util::backquote($foreigner['foreign_table'])
931 4
                . ' WHERE ' . Util::backquote($foreigner['foreign_field'])
932 4
                . $whereComparison;
933 4
            $dispresult = $this->dbi->tryQuery($dispsql);
934 4
            if ($dispresult && $dispresult->numRows() > 0) {
935 4
                return (string) $dispresult->fetchValue();
936
            }
937
        }
938
939
        return '';
940
    }
941
942
    /**
943
     * Display option in the cell according to user choices
944
     *
945
     * @param mixed[] $foreigners         all Relations to foreign tables for a given
946
     *                                           table or optionally a given column in a table
947
     * @param string  $relationField      relation field
948
     * @param string  $whereComparison    string that contain relation field value
949
     * @param string  $dispval            display value from the foreign table
950
     * @param string  $relationFieldValue relation field value
951
     *
952
     * @return string HTML <a> tag
953
     */
954 4
    public function getLinkForRelationalDisplayField(
955
        array $foreigners,
956
        string $relationField,
957
        string $whereComparison,
958
        string $dispval,
959
        string $relationFieldValue,
960
    ): string {
961 4
        $foreigner = $this->relation->searchColumnInForeigners($foreigners, $relationField);
962
963 4
        if (! is_array($foreigner)) {
964
            return '';
965
        }
966
967 4
        if ($_SESSION['tmpval']['relational_display'] === 'K') {
968
            // user chose "relational key" in the display options, so
969
            // the title contains the display field
970 4
            $title = $dispval !== ''
971 4
                ? ' title="' . htmlspecialchars($dispval) . '"'
972
                : '';
973
        } else {
974 4
            $title = ' title="' . htmlspecialchars($relationFieldValue) . '"';
975
        }
976
977 4
        $sqlQuery = 'SELECT * FROM '
978 4
            . Util::backquote($foreigner['foreign_db'])
979 4
            . '.' . Util::backquote($foreigner['foreign_table'])
980 4
            . ' WHERE ' . Util::backquote($foreigner['foreign_field'])
981 4
            . $whereComparison;
982 4
        $urlParams = [
983 4
            'db' => $foreigner['foreign_db'],
984 4
            'table' => $foreigner['foreign_table'],
985 4
            'pos' => '0',
986 4
            'sql_signature' => Core::signSqlQuery($sqlQuery),
987 4
            'sql_query' => $sqlQuery,
988 4
        ];
989 4
        $output = '<a href="' . Url::getFromRoute('/sql', $urlParams) . '"' . $title . '>';
990
991 4
        if ($_SESSION['tmpval']['relational_display'] === 'D') {
992
            // user chose "relational display field" in the
993
            // display options, so show display field in the cell
994 4
            $output .= htmlspecialchars($dispval);
995
        } else {
996
            // otherwise display data in the cell
997 4
            $output .= htmlspecialchars($relationFieldValue);
998
        }
999
1000 4
        $output .= '</a>';
1001
1002 4
        return $output;
1003
    }
1004
1005
    /**
1006
     * Transform edited values
1007
     *
1008
     * @param string     $db           db name
1009
     * @param string     $table        table name
1010
     * @param string[][] $editedValues transform columns list and new values
1011
     * @param string     $file         file containing the transformation plugin
1012
     * @param string     $columnName   column name
1013
     * @param string[][] $extraData    extra data array
1014
     *
1015
     * @return string[][]
1016
     */
1017 4
    public function transformEditedValues(
1018
        string $db,
1019
        string $table,
1020
        string $transformationOption,
1021
        array &$editedValues,
1022
        string $file,
1023
        string $columnName,
1024
        array $extraData,
1025
    ): array {
1026
        // $cfg['SaveCellsAtOnce'] = true; JS code sends an array
1027 4
        $whereClause = is_array($_POST['where_clause']) ? $_POST['where_clause'][0] : $_POST['where_clause'];
1028 4
        $urlParams = [
1029 4
            'db' => $db,
1030 4
            'table' => $table,
1031 4
            'where_clause_sign' => Core::signSqlQuery($whereClause),
1032 4
            'where_clause' => $whereClause,
1033 4
            'transform_key' => $columnName,
1034 4
        ];
1035 4
        $transformOptions = $this->transformations->getOptions($transformationOption);
1036 4
        $transformOptions['wrapper_link'] = Url::getCommon($urlParams);
1037 4
        $transformOptions['wrapper_params'] = $urlParams;
1038
1039 4
        $transformationPlugin = $this->transformations->getPluginInstance($file);
1040 4
        if ($transformationPlugin instanceof TransformationsInterface) {
1041 4
            foreach ($editedValues as $cellIndex => $currCellEditedValues) {
1042 4
                if (! isset($currCellEditedValues[$columnName])) {
1043
                    continue;
1044
                }
1045
1046 4
                $extraData['transformations'][$cellIndex] = $transformationPlugin->applyTransformation(
1047 4
                    $currCellEditedValues[$columnName],
1048 4
                    $transformOptions,
1049 4
                );
1050 4
                $editedValues[$cellIndex][$columnName] = $extraData['transformations'][$cellIndex];
1051
            }
1052
        }
1053
1054 4
        return $extraData;
1055
    }
1056
1057
    /**
1058
     * Get value part if a function was specified
1059
     *
1060
     * @psalm-return non-empty-string
1061
     */
1062 4
    private function formatAsSqlFunction(
1063
        EditField $editField,
1064
    ): string {
1065 4
        if ($editField->function === 'PHP_PASSWORD_HASH') {
1066 4
            $hash = password_hash($editField->value, PASSWORD_DEFAULT);
1067
1068 4
            return $this->dbi->quoteString($hash);
1069
        }
1070
1071 4
        if ($editField->function === 'UUID') {
1072
            /* This way user will know what UUID new row has */
1073 4
            $uuid = (string) $this->dbi->fetchValue('SELECT UUID()');
1074
1075 4
            return $this->dbi->quoteString($uuid);
1076
        }
1077
1078
        if (
1079 4
            in_array($editField->function, $this->getGisFromTextFunctions(), true)
1080 4
            || in_array($editField->function, $this->getGisFromWKBFunctions(), true)
1081
        ) {
1082 4
            preg_match('/^(\'?)(.*?)\1(?:,(\d+))?$/', $editField->value, $matches);
1083 4
            $escapedParams = $this->dbi->quoteString($matches[2]) . (isset($matches[3]) ? ',' . $matches[3] : '');
1084
1085 4
            return $editField->function . '(' . $escapedParams . ')';
1086
        }
1087
1088
        if (
1089 4
            ! in_array($editField->function, self::FUNC_NO_PARAM, true)
1090 4
            || ($editField->value !== '' && in_array($editField->function, self::FUNC_OPTIONAL_PARAM, true))
1091
        ) {
1092
            if (
1093 4
                ($editField->salt !== null
1094 4
                    && in_array($editField->function, ['AES_ENCRYPT', 'AES_DECRYPT', 'SHA2'], true))
1095 4
                || ($editField->salt
1096 4
                    && in_array($editField->function, ['DES_ENCRYPT', 'DES_DECRYPT', 'ENCRYPT'], true))
1097
            ) {
1098 4
                return $editField->function . '(' . $this->dbi->quoteString($editField->value) . ','
1099 4
                    . $this->dbi->quoteString($editField->salt) . ')';
1100
            }
1101
1102
            if (
1103 4
                $editField->function === 'NOW'
1104 4
                && (is_numeric($editField->value) && $editField->value >= 0 && $editField->value <= 6)
1105
            ) {
1106
                return $editField->function . '(' . (int) $editField->value . ')';
1107
            }
1108
1109 4
            return $editField->function . '(' . $this->dbi->quoteString($editField->value) . ')';
1110
        }
1111
1112 4
        return $editField->function . '()';
1113
    }
1114
1115
    /**
1116
     * Get the field value formatted for use in a SQL statement.
1117
     * Used in both INSERT and UPDATE statements.
1118
     */
1119 8
    private function getValueFormattedAsSql(
1120
        EditField $editField,
1121
        string $protectedValue = '',
1122
    ): string {
1123 8
        if ($editField->isUploaded) {
1124 4
            return $editField->value;
1125
        }
1126
1127 8
        if ($editField->function !== '') {
1128 4
            return $this->formatAsSqlFunction($editField);
1129
        }
1130
1131 8
        return $this->formatAsSqlValueBasedOnType($editField, $protectedValue);
1132
    }
1133
1134
    /**
1135
     * Get query values array and query fields array for insert and update in multi edit
1136
     *
1137
     * @param string|int $whereClause Either a positional index or string representing selected row
1138
     */
1139 4
    public function getQueryValueForInsert(
1140
        EditField $editField,
1141
        bool $usingKey,
1142
        string|int $whereClause,
1143
    ): string {
1144 4
        $protectedValue = '';
1145 4
        if ($editField->type === 'protected' && $usingKey && $whereClause !== '') {
1146
            // Fetch the current values of a row to use in case we have a protected field
1147 4
            $protectedValue = $this->dbi->fetchValue(
1148 4
                'SELECT ' . Util::backquote($editField->columnName)
1149 4
                . ' FROM ' . Util::backquote(Current::$table)
1150 4
                . ' WHERE ' . $whereClause,
1151 4
            );
1152 4
            $protectedValue = is_string($protectedValue) ? $protectedValue : '';
1153
        }
1154
1155 4
        return $this->getValueFormattedAsSql($editField, $protectedValue);
1156
    }
1157
1158
    /**
1159
     * Get field-value pairs for update SQL.
1160
     * During update, we build the SQL only with the fields that should be updated.
1161
     */
1162 4
    public function getQueryValueForUpdate(EditField $editField): string
1163
    {
1164 4
        $currentValueFormattedAsSql = $this->getValueFormattedAsSql($editField);
1165
1166
        // avoid setting a field to NULL when it's already NULL
1167
        // (field had the null checkbox before the update; field still has the null checkbox)
1168 4
        if ($editField->wasPreviouslyNull && $editField->isNull) {
1169 4
            return '';
1170
        }
1171
1172
        // A blob field that hasn't been changed will have no value
1173 4
        if ($currentValueFormattedAsSql === '') {
1174 4
            return '';
1175
        }
1176
1177
        if (
1178
            // Field had the null checkbox before the update; field no longer has the null checkbox
1179 4
            $editField->wasPreviouslyNull ||
1180
            // Field was marked as NULL (the value will be unchanged if it was an empty string)
1181 4
            $editField->isNull ||
1182
            // A function was applied to the field
1183 4
            $editField->function !== '' ||
1184
            // The value was changed
1185 4
            $editField->value !== $editField->previousValue
1186
        ) {
1187 4
            return Util::backquote($editField->columnName) . ' = ' . $currentValueFormattedAsSql;
1188
        }
1189
1190 4
        return '';
1191
    }
1192
1193
    /**
1194
     * Get the current column value in the form for different data types
1195
     */
1196 8
    private function formatAsSqlValueBasedOnType(
1197
        EditField $editField,
1198
        string $protectedValue,
1199
    ): string {
1200 8
        if ($editField->type === 'protected') {
1201
            // here we are in protected mode (asked in the config)
1202
            // so tbl_change has put this special value in the
1203
            // columns array, so we do not change the column value
1204
            // but we can still handle column upload
1205
1206
            // when in UPDATE mode, do not alter field's contents. When in INSERT
1207
            // mode, insert empty field because no values were submitted.
1208
            // If protected blobs were set, insert original field's content.
1209 8
            if ($protectedValue !== '') {
1210 4
                return '0x' . bin2hex($protectedValue);
1211
            }
1212
1213 8
            if ($editField->isNull) {
1214 4
                return 'NULL';
1215
            }
1216
1217
            // The Null checkbox was unchecked for this field
1218 8
            if ($editField->wasPreviouslyNull) {
1219 4
                return "''";
1220
            }
1221
1222 8
            return '';
1223
        }
1224
1225 8
        if ($editField->value === '') {
1226
            // When the field is autoIncrement, the best way to avoid problems
1227
            // in strict mode is to set the value to null (works also in non-strict mode)
1228
1229
            // If the value is empty and the null checkbox is checked, set it to null
1230 8
            return $editField->autoIncrement || $editField->isNull ? 'NULL' : "''";
1231
        }
1232
1233 8
        if ($editField->type === 'hex') {
1234 4
            if (! str_starts_with($editField->value, '0x')) {
1235 4
                return '0x' . $editField->value;
1236
            }
1237
1238 4
            return $editField->value;
1239
        }
1240
1241 8
        if ($editField->type === 'bit') {
1242 4
            $currentValue = (string) preg_replace('/[^01]/', '0', $editField->value);
1243
1244 4
            return 'b' . $this->dbi->quoteString($currentValue);
1245
        }
1246
1247
        // For uuid type, generate uuid value
1248
        // if empty value but not set null or value is uuid() function
1249
        if (
1250 8
            $editField->type === 'uuid'
1251 8
                && ! $editField->isNull
1252 8
                && in_array($editField->value, ["''", '', "'uuid()'", 'uuid()'], true)
1253
        ) {
1254 4
            return 'uuid()';
1255
        }
1256
1257
        if (
1258 8
            ($editField->type !== 'datetime' && $editField->type !== 'timestamp' && $editField->type !== 'date')
1259 8
            || preg_match('/^current_timestamp(\([0-6]?\))?$/i', $editField->value) !== 1
1260
        ) {
1261 8
            return $this->dbi->quoteString($editField->value);
1262
        }
1263
1264
        // If there is a value, we ignore the Null checkbox;
1265
        // this could be possible if Javascript is disabled in the browser
1266 4
        return $editField->value;
1267
    }
1268
1269
    /**
1270
     * Check whether inline edited value can be truncated or not,
1271
     * and add additional parameters for extra_data array  if needed
1272
     *
1273
     * @param string  $db         Database name
1274
     * @param string  $table      Table name
1275
     * @param string  $columnName Column name
1276
     * @param mixed[] $extraData  Extra data for ajax response
1277
     */
1278 4
    public function verifyWhetherValueCanBeTruncatedAndAppendExtraData(
1279
        string $db,
1280
        string $table,
1281
        string $columnName,
1282
        array &$extraData,
1283
    ): void {
1284 4
        $extraData['isNeedToRecheck'] = false;
1285
1286 4
        $sqlForRealValue = 'SELECT ' . Util::backquote($table) . '.'
1287 4
            . Util::backquote($columnName)
1288 4
            . ' FROM ' . Util::backquote($db) . '.'
1289 4
            . Util::backquote($table)
1290 4
            . ' WHERE ' . $_POST['where_clause'][0];
1291
1292 4
        $result = $this->dbi->tryQuery($sqlForRealValue);
1293
1294 4
        if (! $result) {
1295
            return;
1296
        }
1297
1298 4
        $metadata = $this->dbi->getFieldsMeta($result)[0];
1299 4
        $newValue = $result->fetchValue();
1300
1301 4
        if ($newValue === false) {
1302 4
            return;
1303
        }
1304
1305 4
        if ($newValue !== null) {
0 ignored issues
show
introduced by
The condition $newValue !== null is always true.
Loading history...
1306 4
            if ($metadata->isTimeType()) {
1307 4
                $newValue = Util::addMicroseconds($newValue);
1308 4
            } elseif ($metadata->isBinary()) {
1309
                $newValue = '0x' . bin2hex($newValue);
1310
            }
1311
        }
1312
1313 4
        $extraData['isNeedToRecheck'] = true;
1314 4
        $extraData['truncatableFieldValue'] = $newValue;
1315
    }
1316
1317
    /**
1318
     * Function to get the columns of a table
1319
     *
1320
     * @param string $db    current db
1321
     * @param string $table current table
1322
     *
1323
     * @return list<Column>
1324
     */
1325 4
    public function getTableColumns(string $db, string $table): array
1326
    {
1327 4
        $this->dbi->selectDb($db);
1328
1329 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...
1330
    }
1331
1332
    /**
1333
     * Function to determine Insert/Edit rows
1334
     *
1335
     * @param string[]|string|null $whereClause where clause
1336
     * @param string               $db          current database
1337
     * @param string               $table       current table
1338
     *
1339
     * @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...
1340
     *     bool,
1341
     *     string[]|string|null,
1342
     *     ResultInterface[]|ResultInterface,
1343
     *     array<string, string|null>[],
1344
     *     bool,
1345
     *     string|null
1346
     * }
1347
     */
1348 4
    public function determineInsertOrEdit(array|string|null $whereClause, string $db, string $table): array
1349
    {
1350 4
        if (isset($_POST['where_clause'])) {
1351 4
            $whereClause = $_POST['where_clause'];
1352
        }
1353
1354 4
        if (isset($_SESSION['edit_next'])) {
1355 4
            $whereClause = $_SESSION['edit_next'];
1356 4
            unset($_SESSION['edit_next']);
1357 4
            $afterInsert = 'edit_next';
1358
        }
1359
1360 4
        if (isset($_POST['ShowFunctionFields'])) {
1361 4
            $this->config->settings['ShowFunctionFields'] = $_POST['ShowFunctionFields'];
1362
        }
1363
1364 4
        if (isset($_POST['ShowFieldTypesInDataEditView'])) {
1365 4
            $this->config->settings['ShowFieldTypesInDataEditView'] = $_POST['ShowFieldTypesInDataEditView'];
1366
        }
1367
1368 4
        if (isset($_POST['after_insert'])) {
1369 4
            $afterInsert = $_POST['after_insert'];
1370
        }
1371
1372 4
        if ($whereClause !== null) {
1373
            // we are editing
1374 4
            $insertMode = false;
1375 4
            [$result, $rows, $foundUniqueKey] = $this->analyzeWhereClauses((array) $whereClause, $table, $db);
1376
        } else {
1377
            // we are inserting
1378 4
            $insertMode = true;
1379 4
            $result = $this->loadFirstRow($table, $db);
1380 4
            $rows = $this->getInsertRows();
1381 4
            $foundUniqueKey = false;
1382
        }
1383
1384
        /** @var string $defaultAction */
1385 4
        $defaultAction = $_POST['default_action'] ?? $_GET['default_action'] ?? '';
1386 4
        if ($defaultAction === 'insert') {
1387
            // Copying a row - fetched data will be inserted as a new row, therefore the where clause is needless.
1388 4
            $whereClause = null;
1389
        }
1390
1391 4
        return [
1392 4
            $insertMode,
1393 4
            $whereClause,
1394 4
            $result,
1395 4
            $rows,
1396 4
            $foundUniqueKey,
1397 4
            $afterInsert ?? null,
1398 4
        ];
1399
    }
1400
1401
    /**
1402
     * Function to get comments for the table columns
1403
     *
1404
     * @param string $db    current database
1405
     * @param string $table current table
1406
     *
1407
     * @return string[] comments for columns
1408
     */
1409 4
    public function getCommentsMap(string $db, string $table): array
1410
    {
1411 4
        if ($this->config->settings['ShowPropertyComments']) {
1412 4
            return $this->relation->getComments($db, $table);
1413
        }
1414
1415 4
        return [];
1416
    }
1417
1418
    /**
1419
     * Function to get html for the ignore option in insert mode
1420
     *
1421
     * @param int  $rowId   row id
1422
     * @param bool $checked ignore option is checked or not
1423
     */
1424 4
    public function getHtmlForIgnoreOption(int $rowId, bool $checked = true): string
1425
    {
1426 4
        return '<input type="checkbox"'
1427 4
            . ($checked ? ' checked' : '')
1428 4
            . ' name="insert_ignore_' . $rowId . '"'
1429 4
            . ' id="insert_ignore_' . $rowId . '">'
1430 4
            . '<label for="insert_ignore_' . $rowId . '">'
1431 4
            . __('Ignore')
1432 4
            . '</label><br>' . "\n";
1433
    }
1434
1435
    /**
1436
     * Function to get html for the insert edit form header
1437
     *
1438
     * @param bool $hasBlobField whether has blob field
1439
     * @param bool $isUpload     whether is upload
1440
     */
1441
    public function getHtmlForInsertEditFormHeader(bool $hasBlobField, bool $isUpload): string
1442
    {
1443
        $template = new Template();
1444
1445
        return $template->render('table/insert/get_html_for_insert_edit_form_header', [
1446
            'has_blob_field' => $hasBlobField,
1447
            'is_upload' => $isUpload,
1448
        ]);
1449
    }
1450
1451
    /**
1452
     * Function to get html for each insert/edit column
1453
     *
1454
     * @param Column             $tableColumn        column
1455
     * @param int                $columnNumber       column index in table_columns
1456
     * @param string[]           $commentsMap        comments map
1457
     * @param int                $columnLength       length of the current column taken from field metadata
1458
     * @param bool               $insertMode         whether insert mode
1459
     * @param array<string|null> $currentRow         current row
1460
     * @param int                $columnsCnt         columns count
1461
     * @param bool               $isUpload           whether upload
1462
     * @param mixed[]            $foreigners         foreigners
1463
     * @param string             $table              table
1464
     * @param string             $db                 database
1465
     * @param int                $rowId              row id
1466
     * @param string             $defaultCharEditing default char editing mode which is stored in config.inc.php
1467
     * @param mixed[]            $repopulate         the data to be repopulated
1468
     * @param string[]           $columnMime         the mime information of column
1469
     * @param string             $whereClause        the where clause
1470
     */
1471 12
    private function getHtmlForInsertEditFormColumn(
1472
        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...
1473
        int $columnNumber,
1474
        array $commentsMap,
1475
        int $columnLength,
1476
        bool $insertMode,
1477
        array $currentRow,
1478
        int $columnsCnt,
1479
        bool $isUpload,
1480
        array $foreigners,
1481
        string $table,
1482
        string $db,
1483
        int $rowId,
1484
        string $defaultCharEditing,
1485
        array $repopulate,
1486
        array $columnMime,
1487
        string $whereClause,
1488
    ): string {
1489 12
        $column = new InsertEditColumn(
1490 12
            $tableColumn->field,
1491 12
            $tableColumn->type,
1492 12
            $tableColumn->isNull,
1493 12
            $tableColumn->key,
1494 12
            $tableColumn->default,
1495 12
            $tableColumn->extra,
1496 12
            $columnLength,
1497 12
            $this->isColumn($tableColumn->type, ['binary', 'varbinary']),
1498 12
            $this->isColumn($tableColumn->type, ['blob', 'tinyblob', 'mediumblob', 'longblob']),
1499 12
            $this->isColumn($tableColumn->type, ['char', 'varchar']),
1500 12
            $insertMode,
1501 12
        );
1502
1503 12
        $asIs = false;
1504 12
        $fieldHashMd5 = $column->md5;
1505 12
        if ($repopulate !== [] && array_key_exists($fieldHashMd5, $currentRow)) {
1506
            $currentRow[$column->field] = $repopulate[$fieldHashMd5];
1507
            $asIs = true;
1508
        }
1509
1510 12
        $extractedColumnspec = Util::extractColumnSpec($column->type);
1511
1512
        //Call validation when the form submitted...
1513 12
        $onChangeClause = 'return verificationsAfterFieldChange('
1514 12
            . json_encode($fieldHashMd5) . ', '
1515 12
            . json_encode((string) $rowId) . ',' . json_encode($column->type) . ')';
1516
1517 12
        $vkey = '[multi_edit][' . $rowId . ']';
1518
        // Use an MD5 as an array index to avoid having special characters
1519
        // in the name attribute (see bug #1746964 )
1520 12
        $columnNameAppendix = $vkey . '[' . $fieldHashMd5 . ']';
1521
1522
        // Prepares the field value
1523 12
        if ($currentRow !== []) {
1524
            // (we are editing)
1525
            [
1526
                $realNullValue,
1527
                $specialCharsEncoded,
1528
                $specialChars,
1529
                $data,
1530
                $backupField,
1531
            ] = $this->getSpecialCharsAndBackupFieldForExistingRow(
1532
                $currentRow,
1533
                $column,
1534
                $extractedColumnspec['spec_in_brackets'],
1535
                $columnNameAppendix,
1536
                $asIs,
1537
            );
1538
        } else {
1539
            // (we are inserting)
1540
            // display default values
1541 12
            $defaultValue = $repopulate[$fieldHashMd5] ?? $column->default ?? null;
1542
1543 12
            $realNullValue = $defaultValue === null;
1544 12
            $data = (string) $defaultValue;
1545 12
            $specialChars = htmlspecialchars($this->getDefaultValue($defaultValue, $column->trueType));
1546 12
            $specialCharsEncoded = Util::duplicateFirstNewline($specialChars);
1547 12
            $backupField = '';
1548
        }
1549
1550 12
        $this->fieldIndex = ($this->rowOffset * $columnsCnt) + $columnNumber + 1;
1551
1552
        // The function column
1553
        // -------------------
1554 12
        $foreignData = $this->relation->getForeignData($foreigners, $column->field, false, '', '');
1555 12
        $isColumnBinary = $this->isColumnBinary($column, $isUpload);
1556 12
        $functionOptions = '';
1557
1558 12
        if ($this->config->settings['ShowFunctionFields']) {
1559 12
            $defaultFunction = Generator::getDefaultFunctionForField(
1560 12
                $column->trueType,
1561 12
                $column->firstTimestamp,
1562 12
                $column->default,
1563 12
                $column->extra,
1564 12
                $column->isNull,
1565 12
                $column->key,
1566 12
                $column->type,
1567 12
                $insertMode,
1568 12
            );
1569 12
            $functionOptions = Generator::getFunctionsForField($defaultFunction, true);
1570
        }
1571
1572
        // nullify code is needed by the js nullify() function to be able to generate calls to nullify() in jQuery
1573 12
        $nullifyCode = $this->getNullifyCodeForNullColumn($column, $foreigners, $foreignData->foreignLink);
1574
1575
        // The value column (depends on type)
1576
        // ----------------
1577
        // See bug #1667887 for the reason why we don't use the maxlength
1578
        // HTML attribute
1579
1580
        // Check input transformation of column
1581 12
        $transformedHtml = '';
1582 12
        if (! empty($columnMime['input_transformation'])) {
1583 4
            $transformationPlugin = $this->transformations->getPluginInstance($columnMime['input_transformation']);
1584 4
            if ($transformationPlugin instanceof IOTransformationsPlugin) {
1585 4
                $transformationOptions = $this->transformations->getOptions(
1586 4
                    $columnMime['input_transformation_options'],
1587 4
                );
1588 4
                $urlParams = [
1589 4
                    'db' => $db,
1590 4
                    'table' => $table,
1591 4
                    'transform_key' => $column->field,
1592 4
                    'where_clause_sign' => Core::signSqlQuery($whereClause),
1593 4
                    'where_clause' => $whereClause,
1594 4
                ];
1595 4
                $transformationOptions['wrapper_link'] = Url::getCommon($urlParams);
1596 4
                $transformationOptions['wrapper_params'] = $urlParams;
1597
1598 4
                $transformedHtml = $transformationPlugin->getInputHtml(
1599 4
                    $columnNameAppendix,
1600 4
                    $transformationOptions,
1601 4
                    $currentRow[$column->field] ?? '',
1602 4
                    $this->fieldIndex,
1603 4
                );
1604
1605 4
                self::$pluginScripts = array_merge(self::$pluginScripts, $transformationPlugin->getScripts());
1606
            }
1607
        }
1608
1609 12
        $columnValue = '';
1610 12
        $foreignDropdown = '';
1611 12
        $dataType = TypeClass::Unknown;
1612 12
        $textAreaRows = $this->config->settings['TextareaRows'];
1613 12
        $textareaCols = $this->config->settings['TextareaCols'];
1614 12
        $maxlength = '';
1615 12
        $enumSelectedValue = '';
1616 12
        $enumValues = [];
1617 12
        $columnSetValues = [];
1618 12
        $setSelectSize = 0;
1619 12
        $isColumnProtectedBlob = false;
1620 12
        $blobValue = '';
1621 12
        $blobValueUnit = '';
1622 12
        $maxUploadSize = 0;
1623 12
        $selectOptionForUpload = '';
1624 12
        $hexInputSize = 0;
1625 12
        if ($transformedHtml === '') {
1626 12
            if ($foreignData->dispRow !== null) {
1627
                $foreignDropdown = $this->relation->foreignDropdown(
1628
                    $foreignData->dispRow,
1629
                    $foreignData->foreignField,
1630
                    $foreignData->foreignDisplay,
1631
                    $data,
1632
                    $this->config->settings['ForeignKeyMaxLimit'],
1633
                );
1634
            }
1635
1636 12
            $dataType = $this->dbi->types->getTypeClass($column->trueType);
1637
1638 12
            if ($column->isChar) {
1639
                $textAreaRows = max($this->config->settings['CharTextareaRows'], 7);
1640
                $textareaCols = $this->config->settings['CharTextareaCols'];
1641
                $maxlength = $extractedColumnspec['spec_in_brackets'];
1642 12
            } elseif ($this->config->settings['LongtextDoubleTextarea'] && $column->trueType === 'longtext') {
1643 8
                $textAreaRows = $this->config->settings['TextareaRows'] * 2;
1644 8
                $textareaCols = $this->config->settings['TextareaCols'] * 2;
1645
            }
1646
1647 12
            if ($column->trueType === 'enum') {
1648
                $enumValues = $extractedColumnspec['enum_set_values'];
1649
1650
                foreach ($enumValues as $enumValue) {
1651
                    if (
1652
                        $data == $enumValue || ($data == ''
1653
                            && (! isset($_POST['where_clause']) || ! $column->isNull)
1654
                            && isset($column->default) && $enumValue == $column->default)
1655
                    ) {
1656
                        $enumSelectedValue = $enumValue;
1657
                        break;
1658
                    }
1659
                }
1660 12
            } elseif ($column->trueType === 'set') {
1661
                $columnSetValues = $extractedColumnspec['enum_set_values'];
1662
                $setSelectSize = min(4, count($extractedColumnspec['enum_set_values']));
1663 12
            } elseif ($column->isBinary || $column->isBlob) {
1664
                $isColumnProtectedBlob = ($this->config->settings['ProtectBinary'] === 'blob' && $column->isBlob)
1665
                    || ($this->config->settings['ProtectBinary'] === 'all')
1666
                    || ($this->config->settings['ProtectBinary'] === 'noblob' && ! $column->isBlob);
1667
                if ($isColumnProtectedBlob) {
1668
                    [$blobValue, $blobValueUnit] = Util::formatByteDown(mb_strlen(stripslashes($data)), 3, 1);
1669
                }
1670
1671
                if ($isUpload && $column->isBlob) {
1672
                    $maxUploadSize = $this->getMaxUploadSize($column->trueType);
1673
                }
1674
1675
                if (! empty($this->config->settings['UploadDir'])) {
1676
                    $selectOptionForUpload = $this->getSelectOptionForUpload($vkey, $fieldHashMd5);
1677
                }
1678
1679
                if (
1680
                    ! $isColumnProtectedBlob
1681
                    && ! ($column->isBlob || ($column->length > $this->config->settings['LimitChars']))
1682
                ) {
1683
                    $hexInputSize = min(max($column->length * 2, 4), $this->config->settings['LimitChars']);
1684
                }
1685
            } else {
1686 12
                $columnValue = $this->getValueColumnForOtherDatatypes(
1687 12
                    $column,
1688 12
                    $defaultCharEditing,
1689 12
                    $backupField,
1690 12
                    $columnNameAppendix,
1691 12
                    $onChangeClause,
1692 12
                    $specialChars,
1693 12
                    $specialCharsEncoded,
1694 12
                    $data,
1695 12
                    $extractedColumnspec['spec_in_brackets'],
1696 12
                );
1697
            }
1698
        }
1699
1700 12
        return $this->template->render('table/insert/column_row', [
1701 12
            'db' => $db,
1702 12
            'table' => $table,
1703 12
            'column' => $column,
1704 12
            'row_id' => $rowId,
1705 12
            'show_field_types_in_data_edit_view' => $this->config->settings['ShowFieldTypesInDataEditView'],
1706 12
            'show_function_fields' => $this->config->settings['ShowFunctionFields'],
1707 12
            'is_column_binary' => $isColumnBinary,
1708 12
            'function_options' => $functionOptions,
1709 12
            'nullify_code' => $nullifyCode,
1710 12
            'real_null_value' => $realNullValue,
1711 12
            'id_index' => $this->fieldIndex,
1712 12
            'type' => $column->trueType,
1713 12
            'displayType' => $column->getDisplayType(),
1714 12
            'decimals' => $column->getFractionalSecondsPrecision(),
1715 12
            'special_chars' => $specialChars,
1716 12
            'transformed_value' => $transformedHtml,
1717 12
            'value' => $columnValue,
1718 12
            'is_value_foreign_link' => $foreignData->foreignLink,
1719 12
            'backup_field' => $backupField,
1720 12
            'data' => $data,
1721 12
            'gis_data_types' => Gis::getDataTypes(),
1722 12
            'foreign_dropdown' => $foreignDropdown,
1723 12
            'data_type' => $dataType->value,
1724 12
            'textarea_cols' => $textareaCols,
1725 12
            'textarea_rows' => $textAreaRows,
1726 12
            'max_length' => $maxlength,
1727 12
            'longtext_double_textarea' => $this->config->settings['LongtextDoubleTextarea'],
1728 12
            'enum_selected_value' => $enumSelectedValue,
1729 12
            'enum_values' => $enumValues,
1730 12
            'set_values' => $columnSetValues,
1731 12
            'set_select_size' => $setSelectSize,
1732 12
            'is_column_protected_blob' => $isColumnProtectedBlob,
1733 12
            'blob_value' => $blobValue,
1734 12
            'blob_value_unit' => $blobValueUnit,
1735 12
            'is_upload' => $isUpload,
1736 12
            'max_upload_size' => $maxUploadSize,
1737 12
            'select_option_for_upload' => $selectOptionForUpload,
1738 12
            'limit_chars' => $this->config->settings['LimitChars'],
1739 12
            'hex_input_size' => $hexInputSize,
1740 12
            'field_title' => $this->getColumnTitle($column->field, $commentsMap),
1741 12
        ]);
1742
    }
1743
1744 12
    private function isColumnBinary(InsertEditColumn $column, bool $isUpload): bool
1745
    {
1746 12
        if (! $this->config->settings['ShowFunctionFields']) {
1747
            return false;
1748
        }
1749
1750 12
        return ($this->config->settings['ProtectBinary'] === 'blob' && $column->isBlob && ! $isUpload)
1751 12
            || ($this->config->settings['ProtectBinary'] === 'all' && $column->isBinary)
1752 12
            || ($this->config->settings['ProtectBinary'] === 'noblob' && $column->isBinary);
1753
    }
1754
1755
    /**
1756
     * Function to get html for each insert/edit row
1757
     *
1758
     * @param array<string, bool|int|string> $urlParams        url parameters
1759
     * @param list<Column>                   $tableColumns     table columns
1760
     * @param string[]                       $commentsMap      comments map
1761
     * @param FieldMetadata[]                $fieldMetadata    current result's field metadata
1762
     * @param bool                           $insertMode       whether insert mode
1763
     * @param array<string|null>             $currentRow       current row
1764
     * @param bool                           $isUpload         whether upload
1765
     * @param mixed[]                        $foreigners       foreigners
1766
     * @param string                         $table            table
1767
     * @param string                         $db               database
1768
     * @param int                            $rowId            row id
1769
     * @param mixed[]                        $repopulate       the data to be repopulated
1770
     * @param string[]                       $whereClauseArray the array of where clauses
1771
     */
1772 8
    public function getHtmlForInsertEditRow(
1773
        array $urlParams,
1774
        array $tableColumns,
1775
        array $commentsMap,
1776
        array $fieldMetadata,
1777
        bool $insertMode,
1778
        array $currentRow,
1779
        bool $isUpload,
1780
        array $foreigners,
1781
        string $table,
1782
        string $db,
1783
        int $rowId,
1784
        array $repopulate,
1785
        array $whereClauseArray,
1786
    ): string {
1787 8
        $htmlOutput = $this->getHeadAndFootOfInsertRowTable($urlParams)
1788 8
            . '<tbody>';
1789
1790
        //store the default value for CharEditing
1791 8
        $defaultCharEditing = $this->config->settings['CharEditing'];
1792 8
        $mimeMap = $this->transformations->getMime($db, $table);
1793 8
        $whereClause = $whereClauseArray[$rowId] ?? '';
1794
1795 8
        $columnCount = count($tableColumns);
1796 8
        for ($columnNumber = 0; $columnNumber < $columnCount; $columnNumber++) {
1797 8
            $tableColumn = $tableColumns[$columnNumber];
1798 8
            $columnMime = $mimeMap[$tableColumn->field] ?? [];
1799
1800 8
            $virtual = ['VIRTUAL', 'PERSISTENT', 'VIRTUAL GENERATED', 'STORED GENERATED'];
1801 8
            if (in_array($tableColumn->extra, $virtual, true)) {
1802
                continue;
1803
            }
1804
1805 8
            $htmlOutput .= $this->getHtmlForInsertEditFormColumn(
1806 8
                $tableColumn,
1807 8
                $columnNumber,
1808 8
                $commentsMap,
1809 8
                $fieldMetadata[$columnNumber]->length,
1810 8
                $insertMode,
1811 8
                $currentRow,
1812 8
                $columnCount,
1813 8
                $isUpload,
1814 8
                $foreigners,
1815 8
                $table,
1816 8
                $db,
1817 8
                $rowId,
1818 8
                $defaultCharEditing,
1819 8
                $repopulate,
1820 8
                $columnMime,
1821 8
                $whereClause,
1822 8
            );
1823
        }
1824
1825 8
        $this->rowOffset++;
1826
1827 8
        return $htmlOutput . '  </tbody>'
1828 8
            . '</table></div><br>'
1829 8
            . '<div class="clearfloat"></div>';
1830
    }
1831
1832
    /** @return array<string|null> */
1833
    public function getColumnDefaultValues(string $database, string $table): array
1834
    {
1835
        $sql = 'SELECT COLUMN_NAME, CASE WHEN INSTR(EXTRA, \'DEFAULT_GENERATED\')'
1836
            . ' THEN COLUMN_DEFAULT '
1837
            . ' ELSE CONCAT(\'\'\'\', COLUMN_DEFAULT, \'\'\'\')'
1838
            . ' END AS COLUMN_DEFAULT'
1839
            . ' FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '
1840
            . $this->dbi->quoteString($table)
1841
            . ' AND TABLE_SCHEMA = ' . $this->dbi->quoteString($database);
1842
1843
        return $this->dbi->query($sql)->fetchAllKeyPair();
1844
    }
1845
1846
    /**
1847
     * Returns list of function names that accept WKB as text
1848
     *
1849
     * @return string[]
1850
     */
1851 4
    private function getGisFromTextFunctions(): array
1852
    {
1853 4
        return $this->dbi->getVersion() >= 50600 ?
1854 4
        [
1855 4
            'ST_GeomFromText',
1856 4
            'ST_GeomCollFromText',
1857 4
            'ST_LineFromText',
1858 4
            'ST_MLineFromText',
1859 4
            'ST_PointFromText',
1860 4
            'ST_MPointFromText',
1861 4
            'ST_PolyFromText',
1862 4
            'ST_MPolyFromText',
1863 4
        ] :
1864 4
        [
1865 4
            'GeomFromText',
1866 4
            'GeomCollFromText',
1867 4
            'LineFromText',
1868 4
            'MLineFromText',
1869 4
            'PointFromText',
1870 4
            'MPointFromText',
1871 4
            'PolyFromText',
1872 4
            'MPolyFromText',
1873 4
        ];
1874
    }
1875
1876
    /**
1877
     * Returns list of function names that accept WKB as binary
1878
     *
1879
     * @return string[]
1880
     */
1881 4
    private function getGisFromWKBFunctions(): array
1882
    {
1883 4
        return $this->dbi->getVersion() >= 50600 ?
1884 4
        [
1885 4
            'ST_GeomFromWKB',
1886 4
            'ST_GeomCollFromWKB',
1887 4
            'ST_LineFromWKB',
1888 4
            'ST_MLineFromWKB',
1889 4
            'ST_PointFromWKB',
1890 4
            'ST_MPointFromWKB',
1891 4
            'ST_PolyFromWKB',
1892 4
            'ST_MPolyFromWKB',
1893 4
        ] :
1894 4
        [
1895 4
            'GeomFromWKB',
1896 4
            'GeomCollFromWKB',
1897 4
            'LineFromWKB',
1898 4
            'MLineFromWKB',
1899 4
            'PointFromWKB',
1900 4
            'MPointFromWKB',
1901 4
            'PolyFromWKB',
1902 4
            'MPolyFromWKB',
1903 4
        ];
1904
    }
1905
}
1906