Passed
Push — master ( e376c4...c3b892 )
by Maurício
08:52 queued 10s
created

InsertEdit::getValueColumnForOtherDatatypes()   C

Complexity

Conditions 11
Paths 49

Size

Total Lines 85
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 11.0105

Importance

Changes 0
Metric Value
cc 11
eloc 52
c 0
b 0
f 0
nc 49
nop 14
dl 0
loc 85
rs 6.9006
ccs 43
cts 45
cp 0.9556
crap 11.0105

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * set of functions with the insert/edit features in pma
4
 */
5
6
declare(strict_types=1);
7
8
namespace PhpMyAdmin;
9
10
use PhpMyAdmin\Controllers\Table\ChangeController;
11
use PhpMyAdmin\Html\Generator;
12
use PhpMyAdmin\Plugins\TransformationsPlugin;
13
use PhpMyAdmin\Utils\Gis;
14
15
use function array_fill;
16
use function array_merge;
17
use function array_values;
18
use function bin2hex;
19
use function class_exists;
20
use function count;
21
use function current;
22
use function date;
23
use function defined;
24
use function explode;
25
use function htmlspecialchars;
26
use function implode;
27
use function in_array;
28
use function is_array;
29
use function is_file;
30
use function is_numeric;
31
use function is_string;
32
use function max;
33
use function mb_stripos;
34
use function mb_strlen;
35
use function mb_strpos;
36
use function mb_strstr;
37
use function mb_substr;
38
use function md5;
39
use function method_exists;
40
use function min;
41
use function password_hash;
42
use function preg_match;
43
use function preg_replace;
44
use function str_replace;
45
use function stripcslashes;
46
use function stripslashes;
47
use function strlen;
48
use function strpos;
49
use function substr;
50
use function time;
51
use function trim;
52
53
use const ENT_COMPAT;
54
use const PASSWORD_DEFAULT;
55
56
/**
57
 * PhpMyAdmin\InsertEdit class
58
 */
59
class InsertEdit
60
{
61
    /**
62
     * DatabaseInterface instance
63
     *
64
     * @var DatabaseInterface
65
     */
66
    private $dbi;
67
68
    /** @var Relation */
69
    private $relation;
70
71
    /** @var Transformations */
72
    private $transformations;
73
74
    /** @var FileListing */
75
    private $fileListing;
76
77
    /** @var Template */
78
    public $template;
79
80
    /**
81
     * @param DatabaseInterface $dbi DatabaseInterface instance
82
     */
83 196
    public function __construct(DatabaseInterface $dbi)
84
    {
85 196
        $this->dbi = $dbi;
86 196
        $this->relation = new Relation($this->dbi);
87 196
        $this->transformations = new Transformations();
88 196
        $this->fileListing = new FileListing();
89 196
        $this->template = new Template();
90 196
    }
91
92
    /**
93
     * Retrieve form parameters for insert/edit form
94
     *
95
     * @param string     $db               name of the database
96
     * @param string     $table            name of the table
97
     * @param array|null $whereClauses     where clauses
98
     * @param array      $whereClauseArray array of where clauses
99
     * @param string     $errorUrl         error url
100
     *
101
     * @return array array of insert/edit form parameters
102
     */
103 4
    public function getFormParametersForInsertForm(
104
        $db,
105
        $table,
106
        ?array $whereClauses,
107
        array $whereClauseArray,
108
        $errorUrl
109
    ) {
110 1
        $formParams = [
111 4
            'db'        => $db,
112 4
            'table'     => $table,
113 4
            'goto'      => $GLOBALS['goto'],
114 4
            'err_url'   => $errorUrl,
115 4
            'sql_query' => $_POST['sql_query'],
116
        ];
117 4
        if (isset($whereClauses)) {
118 4
            foreach ($whereClauseArray as $keyId => $whereClause) {
119 4
                $formParams['where_clause[' . $keyId . ']'] = trim($whereClause);
120
            }
121
        }
122
123 4
        if (isset($_POST['clause_is_unique'])) {
124 4
            $formParams['clause_is_unique'] = $_POST['clause_is_unique'];
125
        }
126
127 4
        return $formParams;
128
    }
129
130
    /**
131
     * Creates array of where clauses
132
     *
133
     * @param array|string|null $whereClause where clause
134
     *
135
     * @return array whereClauseArray array of where clauses
136
     */
137 8
    private function getWhereClauseArray($whereClause)
138
    {
139 8
        if (! isset($whereClause)) {
140 4
            return [];
141
        }
142
143 8
        if (is_array($whereClause)) {
144 4
            return $whereClause;
145
        }
146
147 8
        return [0 => $whereClause];
148
    }
149
150
    /**
151
     * Analysing where clauses array
152
     *
153
     * @param array  $whereClauseArray array of where clauses
154
     * @param string $table            name of the table
155
     * @param string $db               name of the database
156
     *
157
     * @return array $where_clauses, $result, $rows, $found_unique_key
158
     */
159 8
    private function analyzeWhereClauses(
160
        array $whereClauseArray,
161
        $table,
162
        $db
163
    ) {
164 8
        $rows               = [];
165 8
        $result             = [];
166 8
        $whereClauses      = [];
167 8
        $foundUniqueKey   = false;
168 8
        foreach ($whereClauseArray as $keyId => $whereClause) {
169 2
            $localQuery     = 'SELECT * FROM '
170 8
                . Util::backquote($db) . '.'
171 8
                . Util::backquote($table)
172 8
                . ' WHERE ' . $whereClause . ';';
173 8
            $result[$keyId] = $this->dbi->query(
174 8
                $localQuery,
175 8
                DatabaseInterface::CONNECT_USER,
176 8
                DatabaseInterface::QUERY_STORE
177
            );
178 8
            $rows[$keyId] = $this->dbi->fetchAssoc($result[$keyId]);
179
180 8
            $whereClauses[$keyId] = str_replace('\\', '\\\\', $whereClause);
181 8
            $hasUniqueCondition = $this->showEmptyResultMessageOrSetUniqueCondition(
182 8
                $rows,
183 2
                $keyId,
184 2
                $whereClauseArray,
185 2
                $localQuery,
186 2
                $result
187
            );
188 8
            if (! $hasUniqueCondition) {
189 8
                continue;
190
            }
191
192
            $foundUniqueKey = true;
193
        }
194
195
        return [
196 8
            $whereClauses,
197 8
            $result,
198 8
            $rows,
199 8
            $foundUniqueKey,
200
        ];
201
    }
202
203
    /**
204
     * Show message for empty result or set the unique_condition
205
     *
206
     * @param array  $rows             MySQL returned rows
207
     * @param string $keyId            ID in current key
208
     * @param array  $whereClauseArray array of where clauses
209
     * @param string $localQuery       query performed
210
     * @param array  $result           MySQL result handle
211
     *
212
     * @return bool
213
     */
214 12
    private function showEmptyResultMessageOrSetUniqueCondition(
215
        array $rows,
216
        $keyId,
217
        array $whereClauseArray,
218
        $localQuery,
219
        array $result
220
    ) {
221 12
        $hasUniqueCondition = false;
222
223
        // No row returned
224 12
        if (! $rows[$keyId]) {
225 8
            unset($rows[$keyId], $whereClauseArray[$keyId]);
226 8
            Response::getInstance()->addHTML(
227 8
                Generator::getMessage(
228 8
                    __('MySQL returned an empty result set (i.e. zero rows).'),
229 8
                    $localQuery
230
                )
231
            );
232
            /**
233
             * @todo not sure what should be done at this point, but we must not
234
             * exit if we want the message to be displayed
235
             */
236
        } else {// end if (no row returned)
237 8
            $meta = $this->dbi->getFieldsMeta($result[$keyId]) ?? [];
238
239 8
            [$uniqueCondition, $tmpClauseIsUnique] = Util::getUniqueCondition(
240 8
                $result[$keyId],
241 8
                count($meta),
242 8
                $meta,
243 8
                $rows[$keyId],
244 8
                true
245
            );
246
247 8
            if (! empty($uniqueCondition)) {
248 4
                $hasUniqueCondition = true;
249
            }
250
251 8
            unset($uniqueCondition, $tmpClauseIsUnique);
252
        }
253
254 12
        return $hasUniqueCondition;
255
    }
256
257
    /**
258
     * No primary key given, just load first row
259
     *
260
     * @param string $table name of the table
261
     * @param string $db    name of the database
262
     *
263
     * @return array containing $result and $rows arrays
264
     */
265 8
    private function loadFirstRow($table, $db)
266
    {
267 8
        $result = $this->dbi->query(
268 8
            'SELECT * FROM ' . Util::backquote($db)
269 8
            . '.' . Util::backquote($table) . ' LIMIT 1;',
270 8
            DatabaseInterface::CONNECT_USER,
271 8
            DatabaseInterface::QUERY_STORE
272
        );
273 8
        $rows = array_fill(0, $GLOBALS['cfg']['InsertRows'], false);
274
275
        return [
276 8
            $result,
277 8
            $rows,
278
        ];
279
    }
280
281
    /**
282
     * Add some url parameters
283
     *
284
     * @param array $urlParams        containing $db and $table as url parameters
285
     * @param array $whereClauseArray where clauses array
286
     *
287
     * @return array Add some url parameters to $url_params array and return it
288
     */
289 4
    public function urlParamsInEditMode(
290
        array $urlParams,
291
        array $whereClauseArray
292
    ): array {
293 4
        foreach ($whereClauseArray as $whereClause) {
294 4
            $urlParams['where_clause'] = trim($whereClause);
295
        }
296
297 4
        if (! empty($_POST['sql_query'])) {
298 4
            $urlParams['sql_query'] = $_POST['sql_query'];
299
        }
300
301 4
        return $urlParams;
302
    }
303
304
    /**
305
     * Show type information or function selectors in Insert/Edit
306
     *
307
     * @param string $which     function|type
308
     * @param array  $urlParams containing url parameters
309
     * @param bool   $isShow    whether to show the element in $which
310
     *
311
     * @return string an HTML snippet
312
     */
313 16
    public function showTypeOrFunction($which, array $urlParams, $isShow)
314
    {
315 16
        $params = [];
316
317 16
        switch ($which) {
318 16
            case 'function':
319 16
                $params['ShowFunctionFields'] = ($isShow ? 0 : 1);
320 16
                $params['ShowFieldTypesInDataEditView'] = $GLOBALS['cfg']['ShowFieldTypesInDataEditView'];
321 16
                break;
322 16
            case 'type':
323 16
                $params['ShowFieldTypesInDataEditView'] = ($isShow ? 0 : 1);
324 16
                $params['ShowFunctionFields'] = $GLOBALS['cfg']['ShowFunctionFields'];
325 16
                break;
326
        }
327
328 16
        $params['goto'] = Url::getFromRoute('/sql');
329 16
        $thisUrlParams = array_merge($urlParams, $params);
330
331 16
        if (! $isShow) {
332 4
            return ' : <a href="' . Url::getFromRoute('/table/change') . '" data-post="'
333 4
                . Url::getCommon($thisUrlParams, '') . '">'
334 4
                . $this->showTypeOrFunctionLabel($which)
335 4
                . '</a>';
336
        }
337
338 16
        return '<th><a href="' . Url::getFromRoute('/table/change') . '" data-post="'
339 16
            . Url::getCommon($thisUrlParams, '')
340 16
            . '" title="' . __('Hide') . '">'
341 16
            . $this->showTypeOrFunctionLabel($which)
342 16
            . '</a></th>';
343
    }
344
345
    /**
346
     * Show type information or function selectors labels in Insert/Edit
347
     *
348
     * @param string $which function|type
349
     *
350
     * @return string|null an HTML snippet
351
     */
352 16
    private function showTypeOrFunctionLabel($which)
353
    {
354 16
        switch ($which) {
355 16
            case 'function':
356 16
                return __('Function');
357
358 16
            case 'type':
359 16
                return __('Type');
360
        }
361
362
        return null;
363
    }
364
365
     /**
366
      * Analyze the table column array
367
      *
368
      * @param array $column        description of column in given table
369
      * @param array $commentsMap   comments for every column that has a comment
370
      * @param bool  $timestampSeen whether a timestamp has been seen
371
      *
372
      * @return array                   description of column in given table
373
      */
374 16
    private function analyzeTableColumnsArray(
375
        array $column,
376
        array $commentsMap,
377
        $timestampSeen
378
    ) {
379 16
        $column['Field_html']    = htmlspecialchars($column['Field']);
380 16
        $column['Field_md5']     = md5($column['Field']);
381
        // True_Type contains only the type (stops at first bracket)
382 16
        $column['True_Type']     = preg_replace('@\(.*@s', '', $column['Type']);
383 16
        $column['len'] = preg_match('@float|double@', $column['Type']) ? 100 : -1;
384 16
        $column['Field_title']   = $this->getColumnTitle($column, $commentsMap);
385 16
        $column['is_binary']     = $this->isColumn(
386 16
            $column,
387
            [
388 16
                'binary',
389
                'varbinary',
390
            ]
391
        );
392 16
        $column['is_blob']       = $this->isColumn(
393 16
            $column,
394
            [
395 16
                'blob',
396
                'tinyblob',
397
                'mediumblob',
398
                'longblob',
399
            ]
400
        );
401 16
        $column['is_char']       = $this->isColumn(
402 16
            $column,
403
            [
404 16
                'char',
405
                'varchar',
406
            ]
407
        );
408
409
        [
410 16
            $column['pma_type'],
411 16
            $column['wrap'],
412 16
            $column['first_timestamp'],
413 16
        ] = $this->getEnumSetAndTimestampColumns($column, $timestampSeen);
414
415 16
        return $column;
416
    }
417
418
     /**
419
      * Retrieve the column title
420
      *
421
      * @param array $column      description of column in given table
422
      * @param array $commentsMap comments for every column that has a comment
423
      *
424
      * @return string              column title
425
      */
426 20
    private function getColumnTitle(array $column, array $commentsMap)
427
    {
428 20
        if (isset($commentsMap[$column['Field']])) {
429
            return '<span style="border-bottom: 1px dashed black;" title="'
430 4
                . htmlspecialchars($commentsMap[$column['Field']]) . '">'
431 4
                . $column['Field_html'] . '</span>';
432
        }
433
434 20
        return $column['Field_html'];
435
    }
436
437
     /**
438
      * check whether the column is of a certain type
439
      * the goal is to ensure that types such as "enum('one','two','binary',..)"
440
      * or "enum('one','two','varbinary',..)" are not categorized as binary
441
      *
442
      * @param array $column description of column in given table
443
      * @param array $types  the types to verify
444
      *
445
      * @return bool whether the column's type if one of the $types
446
      */
447 20
    public function isColumn(array $column, array $types)
448
    {
449 20
        foreach ($types as $oneType) {
450 20
            if (mb_stripos($column['Type'], $oneType) === 0) {
451 8
                return true;
452
            }
453
        }
454
455 20
        return false;
456
    }
457
458
    /**
459
     * Retrieve set, enum, timestamp table columns
460
     *
461
     * @param array $column        description of column in given table
462
     * @param bool  $timestampSeen whether a timestamp has been seen
463
     *
464
     * @return array $column['pma_type'], $column['wrap'], $column['first_timestamp']
465
     */
466 20
    private function getEnumSetAndTimestampColumns(array $column, $timestampSeen)
467
    {
468 20
        $column['first_timestamp'] = false;
469 20
        switch ($column['True_Type']) {
470 20
            case 'set':
471 4
                $column['pma_type'] = 'set';
472 4
                $column['wrap']  = '';
473 4
                break;
474 20
            case 'enum':
475 4
                $column['pma_type'] = 'enum';
476 4
                $column['wrap']  = '';
477 4
                break;
478 20
            case 'timestamp':
479 4
                if (! $timestampSeen) {   // can only occur once per table
480 4
                    $column['first_timestamp'] = true;
481
                }
482
483 4
                $column['pma_type'] = $column['Type'];
484 4
                $column['wrap']  = ' text-nowrap';
485 4
                break;
486
487
            default:
488 20
                $column['pma_type'] = $column['Type'];
489 20
                $column['wrap']  = ' text-nowrap';
490 20
                break;
491
        }
492
493
        return [
494 20
            $column['pma_type'],
495 20
            $column['wrap'],
496 20
            $column['first_timestamp'],
497
        ];
498
    }
499
500
    /**
501
     * Retrieve the nullify code for the null column
502
     *
503
     * @param array $column      description of column in given table
504
     * @param array $foreigners  keys into foreign fields
505
     * @param array $foreignData data about the foreign keys
506
     */
507 16
    private function getNullifyCodeForNullColumn(
508
        array $column,
509
        array $foreigners,
510
        array $foreignData
511
    ): string {
512 16
        $foreigner = $this->relation->searchColumnInForeigners($foreigners, $column['Field']);
513 16
        if (mb_strstr($column['True_Type'], 'enum')) {
514 4
            if (mb_strlen((string) $column['Type']) > 20) {
515 4
                $nullifyCode = '1';
516
            } else {
517 4
                $nullifyCode = '2';
518
            }
519 16
        } elseif (mb_strstr($column['True_Type'], 'set')) {
520 4
            $nullifyCode = '3';
521
        } elseif (
522 16
            ! empty($foreigners)
523 16
            && ! empty($foreigner)
524 16
            && $foreignData['foreign_link'] == false
525
        ) {
526
            // foreign key in a drop-down
527 4
            $nullifyCode = '4';
528
        } elseif (
529 12
            ! empty($foreigners)
530 12
            && ! empty($foreigner)
531 12
            && $foreignData['foreign_link'] == true
532
        ) {
533
            // foreign key with a browsing icon
534
            $nullifyCode = '6';
535
        } else {
536 12
            $nullifyCode = '5';
537
        }
538
539 16
        return $nullifyCode;
540
    }
541
542
    /**
543
     * Get HTML textarea for insert form
544
     *
545
     * @param array  $column              column information
546
     * @param string $backupField         hidden input field
547
     * @param string $columnNameAppendix  the name attribute
548
     * @param string $onChangeClause      onchange clause for fields
549
     * @param int    $tabindex            tab index
550
     * @param int    $tabindexForValue    offset for the values tabindex
551
     * @param int    $idindex             id index
552
     * @param string $textDir             text direction
553
     * @param string $specialCharsEncoded replaced char if the string starts
554
     *                                      with a \r\n pair (0x0d0a) add an extra \n
555
     * @param string $dataType            the html5 data-* attribute type
556
     * @param bool   $readOnly            is column read only or not
557
     *
558
     * @return string                       an html snippet
559
     */
560 8
    private function getTextarea(
561
        array $column,
562
        $backupField,
563
        $columnNameAppendix,
564
        $onChangeClause,
565
        $tabindex,
566
        $tabindexForValue,
567
        $idindex,
568
        $textDir,
569
        $specialCharsEncoded,
570
        $dataType,
571
        $readOnly
572
    ) {
573 8
        $theClass = '';
574 8
        $textAreaRows = $GLOBALS['cfg']['TextareaRows'];
575 8
        $textareaCols = $GLOBALS['cfg']['TextareaCols'];
576
577 8
        if ($column['is_char']) {
578
            /**
579
             * @todo clarify the meaning of the "textfield" class and explain
580
             *       why character columns have the "char" class instead
581
             */
582 8
            $theClass = 'char charField';
583 8
            $textAreaRows = max($GLOBALS['cfg']['CharTextareaRows'], 7);
584 8
            $textareaCols = $GLOBALS['cfg']['CharTextareaCols'];
585 8
            $extractedColumnspec = Util::extractColumnSpec(
586 8
                $column['Type']
587
            );
588 8
            $maxlength = $extractedColumnspec['spec_in_brackets'];
589
        } elseif (
590
            $GLOBALS['cfg']['LongtextDoubleTextarea']
591
            && mb_strstr($column['pma_type'], 'longtext')
592
        ) {
593
            $textAreaRows = $GLOBALS['cfg']['TextareaRows'] * 2;
594
            $textareaCols = $GLOBALS['cfg']['TextareaCols'] * 2;
595
        }
596
597 8
        return $backupField . "\n"
598 8
            . '<textarea name="fields' . $columnNameAppendix . '"'
599 8
            . ' class="' . $theClass . '"'
600 8
            . ($readOnly ? ' readonly="readonly"' : '')
601 8
            . (isset($maxlength) ? ' data-maxlength="' . $maxlength . '"' : '')
602 8
            . ' rows="' . $textAreaRows . '"'
603 8
            . ' cols="' . $textareaCols . '"'
604 8
            . ' dir="' . $textDir . '"'
605 8
            . ' id="field_' . $idindex . '_3"'
606 8
            . (! empty($onChangeClause) ? ' ' . $onChangeClause : '')
607 8
            . ' tabindex="' . ($tabindex + $tabindexForValue) . '"'
608 8
            . ' data-type="' . $dataType . '">'
609 8
            . $specialCharsEncoded
610 8
            . '</textarea>';
611
    }
612
613
    /**
614
     * Get column values
615
     *
616
     * @param array $column              description of column in given table
617
     * @param array $extractedColumnspec associative array containing type,
618
     *                                    spec_in_brackets and possibly enum_set_values
619
     *                                    (another array)
620
     *
621
     * @return array column values as an associative array
622
     */
623 4
    private function getColumnEnumValues(array $column, array $extractedColumnspec)
624
    {
625 4
        $column['values'] = [];
626 4
        foreach ($extractedColumnspec['enum_set_values'] as $val) {
627 4
            $column['values'][] = [
628 4
                'plain' => $val,
629 4
                'html'  => htmlspecialchars($val),
630
            ];
631
        }
632
633 4
        return $column['values'];
634
    }
635
636
    /**
637
     * Retrieve column 'set' value and select size
638
     *
639
     * @param array $column              description of column in given table
640
     * @param array $extractedColumnspec associative array containing type,
641
     *                                    spec_in_brackets and possibly enum_set_values
642
     *                                    (another array)
643
     *
644
     * @return array $column['values'], $column['select_size']
645
     */
646 4
    private function getColumnSetValueAndSelectSize(
647
        array $column,
648
        array $extractedColumnspec
649
    ) {
650 4
        if (! isset($column['values'])) {
651 4
            $column['values'] = [];
652 4
            foreach ($extractedColumnspec['enum_set_values'] as $val) {
653 4
                $column['values'][] = [
654 4
                    'plain' => $val,
655 4
                    'html'  => htmlspecialchars($val),
656
                ];
657
            }
658
659 4
            $column['select_size'] = min(4, count($column['values']));
660
        }
661
662
        return [
663 4
            $column['values'],
664 4
            $column['select_size'],
665
        ];
666
    }
667
668
    /**
669
     * Get HTML input type
670
     *
671
     * @param array  $column             description of column in given table
672
     * @param string $columnNameAppendix the name attribute
673
     * @param string $specialChars       special characters
674
     * @param int    $fieldsize          html field size
675
     * @param string $onChangeClause     onchange clause for fields
676
     * @param int    $tabindex           tab index
677
     * @param int    $tabindexForValue   offset for the values tabindex
678
     * @param int    $idindex            id index
679
     * @param string $dataType           the html5 data-* attribute type
680
     * @param bool   $readOnly           is column read only or not
681
     *
682
     * @return string                       an html snippet
683
     */
684 20
    private function getHtmlInput(
685
        array $column,
686
        $columnNameAppendix,
687
        $specialChars,
688
        $fieldsize,
689
        $onChangeClause,
690
        $tabindex,
691
        $tabindexForValue,
692
        $idindex,
693
        $dataType,
694
        $readOnly
695
    ) {
696 20
        $inputType = 'text';
697
        // do not use the 'date' or 'time' types here; they have no effect on some
698
        // browsers and create side effects (see bug #4218)
699
700 20
        $theClass = 'textfield';
701
        // verify True_Type which does not contain the parentheses and length
702 20
        if (! $readOnly) {
703 20
            if ($column['True_Type'] === 'date') {
704 4
                $theClass .= ' datefield';
705 20
            } elseif ($column['True_Type'] === 'time') {
706
                $theClass .= ' timefield';
707
            } elseif (
708 20
                $column['True_Type'] === 'datetime'
709 20
                || $column['True_Type'] === 'timestamp'
710
            ) {
711 12
                $theClass .= ' datetimefield';
712
            }
713
        }
714
715 20
        $inputMinMax = false;
716 20
        if (in_array($column['True_Type'], $this->dbi->types->getIntegerTypes())) {
717
            $extractedColumnspec = Util::extractColumnSpec(
718
                $column['Type']
719
            );
720
            $isUnsigned = $extractedColumnspec['unsigned'];
721
            $minMaxValues = $this->dbi->types->getIntegerRange(
722
                $column['True_Type'],
723
                ! $isUnsigned
724
            );
725
            $inputMinMax = 'min="' . $minMaxValues[0] . '" '
726
                . 'max="' . $minMaxValues[1] . '"';
727
            $dataType = 'INT';
728
        }
729
730 20
        return '<input type="' . $inputType . '"'
731 20
            . ' name="fields' . $columnNameAppendix . '"'
732 20
            . ' value="' . $specialChars . '" size="' . $fieldsize . '"'
733 20
            . (isset($column['is_char']) && $column['is_char']
734
            ? ' data-maxlength="' . $fieldsize . '"'
735 20
            : '')
736 20
            . ($readOnly ? ' readonly="readonly"' : '')
737 20
            . ($inputMinMax !== false ? ' ' . $inputMinMax : '')
738 20
            . ' data-type="' . $dataType . '"'
739 20
            . ($inputType === 'time' ? ' step="1"' : '')
0 ignored issues
show
introduced by
The condition $inputType === 'time' is always false.
Loading history...
740 20
            . ' class="' . $theClass . '" ' . $onChangeClause
741 20
            . ' tabindex="' . ($tabindex + $tabindexForValue) . '"'
742 20
            . ' id="field_' . $idindex . '_3">';
743
    }
744
745
    /**
746
     * Get HTML select option for upload
747
     *
748
     * @param string $vkey   [multi_edit]['row_id']
749
     * @param array  $column description of column in given table
750
     *
751
     * @return string|null an html snippet
752
     */
753
    private function getSelectOptionForUpload($vkey, array $column)
754
    {
755
        $files = $this->fileListing->getFileSelectOptions(
756
            Util::userDir($GLOBALS['cfg']['UploadDir'])
757
        );
758
759
        if ($files === false) {
0 ignored issues
show
introduced by
The condition $files === false is always true.
Loading history...
760
            return '<span style="color:red">' . __('Error') . '</span><br>' . "\n"
761
                . __('The directory you set for upload work cannot be reached.') . "\n";
762
        }
763
764
        if (! empty($files)) {
765
            return "<br>\n"
766
                . '<i>' . __('Or') . '</i> '
767
                . __('web server upload directory:') . '<br>' . "\n"
768
                . '<select size="1" name="fields_uploadlocal'
769
                . $vkey . '[' . $column['Field_md5'] . ']">' . "\n"
770
                . '<option value="" selected="selected"></option>' . "\n"
771
                . $files
772
                . '</select>' . "\n";
773
        }
774
775
        return null;
776
    }
777
778
    /**
779
     * Retrieve the maximum upload file size
780
     *
781
     * @param array $column             description of column in given table
782
     * @param int   $biggestMaxFileSize biggest max file size for uploading
783
     *
784
     * @return array an html snippet and $biggest_max_file_size
785
     */
786 4
    private function getMaxUploadSize(array $column, $biggestMaxFileSize)
787
    {
788
        // find maximum upload size, based on field type
789
        /**
790
         * @todo with functions this is not so easy, as you can basically
791
         * process any data with function like MD5
792
         */
793 4
        global $max_upload_size;
794 1
        $maxFieldSizes = [
795 3
            'tinyblob'   =>        '256',
796
            'blob'       =>      '65536',
797
            'mediumblob' =>   '16777216',
798
            'longblob'   => '4294967296',// yeah, really
799
        ];
800
801 4
        $thisFieldMaxSize = $max_upload_size; // from PHP max
802 4
        if ($thisFieldMaxSize > $maxFieldSizes[$column['pma_type']]) {
803 4
            $thisFieldMaxSize = $maxFieldSizes[$column['pma_type']];
804
        }
805
806 4
        $htmlOutput = Util::getFormattedMaximumUploadSize(
807 4
            $thisFieldMaxSize
808 4
        ) . "\n";
809
        // do not generate here the MAX_FILE_SIZE, because we should
810
        // put only one in the form to accommodate the biggest field
811 4
        if ($thisFieldMaxSize > $biggestMaxFileSize) {
812 4
            $biggestMaxFileSize = $thisFieldMaxSize;
813
        }
814
815
        return [
816 4
            $htmlOutput,
817 4
            $biggestMaxFileSize,
818
        ];
819
    }
820
821
    /**
822
     * Get HTML for the Value column of other datatypes
823
     * (here, "column" is used in the sense of HTML column in HTML table)
824
     *
825
     * @param array  $column              description of column in given table
826
     * @param string $defaultCharEditing  default char editing mode which is stored
827
     *                                      in the config.inc.php script
828
     * @param string $backupField         hidden input field
829
     * @param string $columnNameAppendix  the name attribute
830
     * @param string $onChangeClause      onchange clause for fields
831
     * @param int    $tabindex            tab index
832
     * @param string $specialChars        special characters
833
     * @param int    $tabindexForValue    offset for the values tabindex
834
     * @param int    $idindex             id index
835
     * @param string $textDir             text direction
836
     * @param string $specialCharsEncoded replaced char if the string starts
837
     *                                      with a \r\n pair (0x0d0a) add an extra \n
838
     * @param string $data                data to edit
839
     * @param array  $extractedColumnspec associative array containing type,
840
     *                                     spec_in_brackets and possibly
841
     *                                     enum_set_values (another array)
842
     * @param bool   $readOnly            is column read only or not
843
     *
844
     * @return string an html snippet
845
     */
846 16
    private function getValueColumnForOtherDatatypes(
847
        array $column,
848
        $defaultCharEditing,
849
        $backupField,
850
        $columnNameAppendix,
851
        $onChangeClause,
852
        $tabindex,
853
        $specialChars,
854
        $tabindexForValue,
855
        $idindex,
856
        $textDir,
857
        $specialCharsEncoded,
858
        $data,
859
        array $extractedColumnspec,
860
        $readOnly
861
    ) {
862
        // HTML5 data-* attribute data-type
863 16
        $dataType = $this->dbi->types->getTypeClass($column['True_Type']);
864 16
        $fieldsize = $this->getColumnSize($column, $extractedColumnspec);
865 16
        $htmlOutput = $backupField . "\n";
866
        if (
867 16
            $column['is_char']
868 4
            && ($GLOBALS['cfg']['CharEditing'] === 'textarea'
869 16
            || mb_strpos($data, "\n") !== false)
870
        ) {
871 4
            $htmlOutput .= "\n";
872 4
            $GLOBALS['cfg']['CharEditing'] = $defaultCharEditing;
873 4
            $htmlOutput .= $this->getTextarea(
874 4
                $column,
875 4
                $backupField,
876 4
                $columnNameAppendix,
877 4
                $onChangeClause,
878 4
                $tabindex,
879 4
                $tabindexForValue,
880 4
                $idindex,
881 4
                $textDir,
882 4
                $specialCharsEncoded,
883 4
                $dataType,
884 4
                $readOnly
885
            );
886
        } else {
887 16
            $htmlOutput .= $this->getHtmlInput(
888 16
                $column,
889 16
                $columnNameAppendix,
890 16
                $specialChars,
891 16
                $fieldsize,
892 16
                $onChangeClause,
893 16
                $tabindex,
894 16
                $tabindexForValue,
895 16
                $idindex,
896 16
                $dataType,
897 16
                $readOnly
898
            );
899
900
            if (
901 16
                preg_match('/(VIRTUAL|PERSISTENT|GENERATED)/', $column['Extra'])
902 16
                && strpos($column['Extra'], 'DEFAULT_GENERATED') === false
903
            ) {
904
                $htmlOutput .= '<input type="hidden" name="virtual'
905
                    . $columnNameAppendix . '" value="1">';
906
            }
907
908 16
            if ($column['Extra'] === 'auto_increment') {
909
                $htmlOutput .= '<input type="hidden" name="auto_increment'
910 4
                    . $columnNameAppendix . '" value="1">';
911
            }
912
913 16
            if (substr($column['pma_type'], 0, 9) === 'timestamp') {
914
                $htmlOutput .= '<input type="hidden" name="fields_type'
915 4
                    . $columnNameAppendix . '" value="timestamp">';
916
            }
917
918 16
            if (substr($column['pma_type'], 0, 4) === 'date') {
919 8
                $type = substr($column['pma_type'], 0, 8) === 'datetime' ? 'datetime' : 'date';
920
                $htmlOutput .= '<input type="hidden" name="fields_type'
921 8
                    . $columnNameAppendix . '" value="' . $type . '">';
922
            }
923
924 16
            if ($column['True_Type'] === 'bit') {
925
                $htmlOutput .= '<input type="hidden" name="fields_type'
926
                    . $columnNameAppendix . '" value="bit">';
927
            }
928
        }
929
930 16
        return $htmlOutput;
931
    }
932
933
    /**
934
     * Get the field size
935
     *
936
     * @param array $column              description of column in given table
937
     * @param array $extractedColumnspec associative array containing type,
938
     *                                    spec_in_brackets and possibly enum_set_values
939
     *                                    (another array)
940
     *
941
     * @return int field size
942
     */
943 20
    private function getColumnSize(array $column, array $extractedColumnspec)
944
    {
945 20
        if ($column['is_char']) {
946 8
            $fieldsize = $extractedColumnspec['spec_in_brackets'];
947 8
            if ($fieldsize > $GLOBALS['cfg']['MaxSizeForInputField']) {
948
                /**
949
                 * This case happens for CHAR or VARCHAR columns which have
950
                 * a size larger than the maximum size for input field.
951
                 */
952 8
                $GLOBALS['cfg']['CharEditing'] = 'textarea';
953
            }
954
        } else {
955
            /**
956
             * This case happens for example for INT or DATE columns;
957
             * in these situations, the value returned in $column['len']
958
             * seems appropriate.
959
             */
960 20
            $fieldsize = $column['len'];
961
        }
962
963 20
        return min(
964 20
            max($fieldsize, $GLOBALS['cfg']['MinSizeForInputField']),
965 20
            $GLOBALS['cfg']['MaxSizeForInputField']
966
        );
967
    }
968
969
    /**
970
     * get html for continue insertion form
971
     *
972
     * @param string $table            name of the table
973
     * @param string $db               name of the database
974
     * @param array  $whereClauseArray array of where clauses
975
     * @param string $errorUrl         error url
976
     *
977
     * @return string                   an html snippet
978
     */
979 4
    public function getContinueInsertionForm(
980
        $table,
981
        $db,
982
        array $whereClauseArray,
983
        $errorUrl
984
    ) {
985 4
        return $this->template->render('table/insert/continue_insertion_form', [
986 4
            'db' => $db,
987 4
            'table' => $table,
988 4
            'where_clause_array' => $whereClauseArray,
989 4
            'err_url' => $errorUrl,
990 4
            'goto' => $GLOBALS['goto'],
991 4
            'sql_query' => $_POST['sql_query'] ?? null,
992 4
            'has_where_clause' => isset($_POST['where_clause']),
993 4
            'insert_rows_default' => $GLOBALS['cfg']['InsertRows'],
994
        ]);
995
    }
996
997
    /**
998
     * @param string[]|string|null $whereClause
999
     *
1000
     * @psalm-pure
1001
     */
1002 4
    public static function isWhereClauseNumeric($whereClause): bool
1003
    {
1004 4
        if (! isset($whereClause)) {
1005 4
            return false;
1006
        }
1007
1008 4
        $isNumeric = false;
1009
1010 4
        if (! is_array($whereClause)) {
1011 4
            $whereClause = [$whereClause];
1012
        }
1013
1014
        // If we have just numeric primary key, we can also edit next
1015
        // we are looking for `table_name`.`field_name` = numeric_value
1016 4
        foreach ($whereClause as $clause) {
1017
            // preg_match() returns 1 if there is a match
1018 4
            $isNumeric = preg_match('@^[\s]*`[^`]*`[\.]`[^`]*` = [0-9]+@', $clause) === 1;
1019 4
            if ($isNumeric === true) {
1020 4
                break;
1021
            }
1022
        }
1023
1024 4
        return $isNumeric;
1025
    }
1026
1027
    /**
1028
     * Get table head and table foot for insert row table
1029
     *
1030
     * @param array $urlParams url parameters
1031
     *
1032
     * @return string           an html snippet
1033
     */
1034 12
    private function getHeadAndFootOfInsertRowTable(array $urlParams)
1035
    {
1036 3
        $htmlOutput = '<div class="table-responsive-lg">'
1037
            . '<table class="table table-light table-striped align-middle my-3 insertRowTable">'
1038
            . '<thead>'
1039
            . '<tr>'
1040 12
            . '<th>' . __('Column') . '</th>';
1041
1042 12
        if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) {
1043 12
            $htmlOutput .= $this->showTypeOrFunction('type', $urlParams, true);
1044
        }
1045
1046 12
        if ($GLOBALS['cfg']['ShowFunctionFields']) {
1047 12
            $htmlOutput .= $this->showTypeOrFunction('function', $urlParams, true);
1048
        }
1049
1050 12
        $htmlOutput .= '<th>' . __('Null') . '</th>'
1051 12
            . '<th class="w-50">' . __('Value') . '</th>'
1052 12
            . '</tr>'
1053 12
            . '</thead>'
1054 12
            . ' <tfoot>'
1055 12
            . '<tr>'
1056 12
            . '<th colspan="5" class="tblFooters text-end">'
1057 12
            . '<input class="btn btn-primary" type="submit" value="' . __('Go') . '">'
1058 12
            . '</th>'
1059 12
            . '</tr>'
1060 12
            . '</tfoot>';
1061
1062 12
        return $htmlOutput;
1063
    }
1064
1065
    /**
1066
     * Prepares the field value and retrieve special chars, backup field and data array
1067
     *
1068
     * @param array  $currentRow          a row of the table
1069
     * @param array  $column              description of column in given table
1070
     * @param array  $extractedColumnspec associative array containing type,
1071
     *                                     spec_in_brackets and possibly
1072
     *                                     enum_set_values (another array)
1073
     * @param bool   $realNullValue       whether column value null or not null
1074
     * @param array  $gisDataTypes        list of GIS data types
1075
     * @param string $columnNameAppendix  string to append to column name in input
1076
     * @param bool   $asIs                use the data as is, used in repopulating
1077
     *
1078
     * @return array $real_null_value, $data, $special_chars, $backup_field,
1079
     *               $special_chars_encoded
1080
     */
1081 4
    private function getSpecialCharsAndBackupFieldForExistingRow(
1082
        array $currentRow,
1083
        array $column,
1084
        array $extractedColumnspec,
1085
        $realNullValue,
1086
        array $gisDataTypes,
1087
        $columnNameAppendix,
1088
        $asIs
1089
    ) {
1090 4
        $specialCharsEncoded = '';
1091 4
        $data = null;
1092
        // (we are editing)
1093 4
        if (! isset($currentRow[$column['Field']])) {
1094 4
            $realNullValue = true;
1095 4
            $currentRow[$column['Field']] = '';
1096 4
            $specialChars = '';
1097 4
            $data = $currentRow[$column['Field']];
1098 4
        } elseif ($column['True_Type'] === 'bit') {
1099 4
            $specialChars = $asIs
1100 4
                ? $currentRow[$column['Field']]
1101 4
                : Util::printableBitValue(
1102 4
                    (int) $currentRow[$column['Field']],
1103 4
                    (int) $extractedColumnspec['spec_in_brackets']
1104
                );
1105
        } elseif (
1106 4
            (substr($column['True_Type'], 0, 9) === 'timestamp'
1107 4
            || $column['True_Type'] === 'datetime'
1108 4
            || $column['True_Type'] === 'time')
1109 4
            && (mb_strpos($currentRow[$column['Field']], '.') !== false)
1110
        ) {
1111
            $currentRow[$column['Field']] = $asIs
1112
                ? $currentRow[$column['Field']]
1113
                : Util::addMicroseconds(
1114
                    $currentRow[$column['Field']]
1115
                );
1116
            $specialChars = htmlspecialchars($currentRow[$column['Field']], ENT_COMPAT);
1117 4
        } elseif (in_array($column['True_Type'], $gisDataTypes)) {
1118
            // Convert gis data to Well Know Text format
1119 4
            $currentRow[$column['Field']] = $asIs
1120
                ? $currentRow[$column['Field']]
1121 4
                : Gis::convertToWellKnownText(
1122 4
                    $currentRow[$column['Field']],
1123 4
                    true
1124
                );
1125 4
            $specialChars = htmlspecialchars($currentRow[$column['Field']], ENT_COMPAT);
1126
        } else {
1127
            // special binary "characters"
1128
            if (
1129 4
                $column['is_binary']
1130 4
                || ($column['is_blob'] && $GLOBALS['cfg']['ProtectBinary'] !== 'all')
1131
            ) {
1132 4
                $currentRow[$column['Field']] = $asIs
1133
                    ? $currentRow[$column['Field']]
1134 4
                    : bin2hex(
1135 4
                        $currentRow[$column['Field']]
1136
                    );
1137
            }
1138
1139 4
            $specialChars = htmlspecialchars($currentRow[$column['Field']], ENT_COMPAT);
1140
1141
            //We need to duplicate the first \n or otherwise we will lose
1142
            //the first newline entered in a VARCHAR or TEXT column
1143 4
            $specialCharsEncoded = Util::duplicateFirstNewline($specialChars);
1144
1145 4
            $data = $currentRow[$column['Field']];
1146
        }
1147
1148
        //when copying row, it is useful to empty auto-increment column
1149
        // to prevent duplicate key error
1150
        if (
1151 4
            isset($_POST['default_action'])
1152 4
            && $_POST['default_action'] === 'insert'
1153
        ) {
1154
            if (
1155 4
                $column['Key'] === 'PRI'
1156 4
                && mb_strpos($column['Extra'], 'auto_increment') !== false
1157
            ) {
1158 4
                $data = $specialCharsEncoded = $specialChars = null;
1159
            }
1160
        }
1161
1162
        // If a timestamp field value is not included in an update
1163
        // statement MySQL auto-update it to the current timestamp;
1164
        // however, things have changed since MySQL 4.1, so
1165
        // it's better to set a fields_prev in this situation
1166 1
        $backupField = '<input type="hidden" name="fields_prev'
1167 4
            . $columnNameAppendix . '" value="'
1168 4
            . htmlspecialchars($currentRow[$column['Field']], ENT_COMPAT) . '">';
1169
1170
        return [
1171 4
            $realNullValue,
1172 4
            $specialCharsEncoded,
1173 4
            $specialChars,
1174 4
            $data,
1175 4
            $backupField,
1176
        ];
1177
    }
1178
1179
    /**
1180
     * display default values
1181
     *
1182
     * @param array $column        description of column in given table
1183
     * @param bool  $realNullValue whether column value null or not null
1184
     *
1185
     * @return array $real_null_value, $data, $special_chars,
1186
     *               $backup_field, $special_chars_encoded
1187
     */
1188 16
    private function getSpecialCharsAndBackupFieldForInsertingMode(
1189
        array $column,
1190
        $realNullValue
1191
    ) {
1192 16
        if (! isset($column['Default'])) {
1193 12
            $column['Default']    = '';
1194 12
            $realNullValue          = true;
1195 12
            $data                     = '';
1196
        } else {
1197 8
            $data                     = $column['Default'];
1198
        }
1199
1200 16
        $trueType = $column['True_Type'];
1201
1202 16
        if ($trueType === 'bit') {
1203 4
            $specialChars = Util::convertBitDefaultValue(
1204 4
                $column['Default']
1205
            );
1206
        } elseif (
1207 16
            substr($trueType, 0, 9) === 'timestamp'
1208 16
            || $trueType === 'datetime'
1209 16
            || $trueType === 'time'
1210
        ) {
1211 4
            $specialChars = Util::addMicroseconds($column['Default']);
1212 16
        } elseif ($trueType === 'binary' || $trueType === 'varbinary') {
1213
            $specialChars = bin2hex($column['Default']);
1214 16
        } elseif (substr($trueType, -4) === 'text') {
1215 8
            $textDefault = substr($column['Default'], 1, -1);
1216 8
            $specialChars = stripcslashes($textDefault !== false ? $textDefault : $column['Default']);
1217
        } else {
1218 12
            $specialChars = htmlspecialchars($column['Default']);
1219
        }
1220
1221 16
        $backupField = '';
1222 16
        $specialCharsEncoded = Util::duplicateFirstNewline(
1223 16
            $specialChars
1224
        );
1225
1226
        return [
1227 16
            $realNullValue,
1228 16
            $data,
1229 16
            $specialChars,
1230 16
            $backupField,
1231 16
            $specialCharsEncoded,
1232
        ];
1233
    }
1234
1235
    /**
1236
     * Prepares the update/insert of a row
1237
     *
1238
     * @return array $loop_array, $using_key, $is_insert, $is_insertignore
1239
     */
1240 8
    public function getParamsForUpdateOrInsert()
1241
    {
1242 8
        if (isset($_POST['where_clause'])) {
1243
            // we were editing something => use the WHERE clause
1244 8
            $loopArray = is_array($_POST['where_clause'])
1245 4
                ? $_POST['where_clause']
1246 7
                : [$_POST['where_clause']];
1247 8
            $usingKey  = true;
1248 8
            $isInsert  = isset($_POST['submit_type'])
1249 8
                          && ($_POST['submit_type'] === 'insert'
1250 8
                          || $_POST['submit_type'] === 'showinsert'
1251 8
                          || $_POST['submit_type'] === 'insertignore');
1252
        } else {
1253
            // new row => use indexes
1254 4
            $loopArray = [];
1255 4
            if (! empty($_POST['fields'])) {
1256 4
                foreach ($_POST['fields']['multi_edit'] as $key => $dummy) {
1257 4
                    $loopArray[] = $key;
1258
                }
1259
            }
1260
1261 4
            $usingKey  = false;
1262 4
            $isInsert  = true;
1263
        }
1264
1265 8
        $isInsertIgnore  = isset($_POST['submit_type'])
1266 8
            && $_POST['submit_type'] === 'insertignore';
1267
1268
        return [
1269 8
            $loopArray,
1270 8
            $usingKey,
1271 8
            $isInsert,
1272 8
            $isInsertIgnore,
1273
        ];
1274
    }
1275
1276
    /**
1277
     * Check wether insert row mode and if so include tbl_changen script and set
1278
     * global variables.
1279
     *
1280
     * @return void
1281
     */
1282 8
    public function isInsertRow()
1283
    {
1284 8
        global $containerBuilder;
1285
1286
        if (
1287 8
            ! isset($_POST['insert_rows'])
1288 4
            || ! is_numeric($_POST['insert_rows'])
1289 8
            || $_POST['insert_rows'] == $GLOBALS['cfg']['InsertRows']
1290
        ) {
1291 4
            return;
1292
        }
1293
1294 4
        $GLOBALS['cfg']['InsertRows'] = $_POST['insert_rows'];
1295 4
        $response = Response::getInstance();
1296 4
        $header = $response->getHeader();
1297 4
        $scripts = $header->getScripts();
1298 4
        $scripts->addFile('vendor/jquery/additional-methods.js');
1299 4
        $scripts->addFile('table/change.js');
1300 4
        if (! defined('TESTSUITE')) {
1301
            /** @var ChangeController $controller */
1302
            $controller = $containerBuilder->get(ChangeController::class);
1303
            $controller->index();
1304
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1305
        }
1306 4
    }
1307
1308
    /**
1309
     * set $_SESSION for edit_next
1310
     *
1311
     * @param string $oneWhereClause one where clause from where clauses array
1312
     *
1313
     * @return void
1314
     */
1315 4
    public function setSessionForEditNext($oneWhereClause)
1316
    {
1317 4
        $localQuery = 'SELECT * FROM ' . Util::backquote($GLOBALS['db'])
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquote($GLOBALS['db']) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

1317
        $localQuery = 'SELECT * FROM ' . /** @scrutinizer ignore-type */ Util::backquote($GLOBALS['db'])
Loading history...
1318 4
            . '.' . Util::backquote($GLOBALS['table']) . ' WHERE '
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquote($GLOBALS['table']) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

1318
            . '.' . /** @scrutinizer ignore-type */ Util::backquote($GLOBALS['table']) . ' WHERE '
Loading history...
1319 4
            . str_replace('` =', '` >', $oneWhereClause) . ' LIMIT 1;';
1320
1321 4
        $res = $this->dbi->query($localQuery);
1322 4
        $row = $this->dbi->fetchRow($res);
1323 4
        $meta = $this->dbi->getFieldsMeta($res) ?? [];
1324
        // must find a unique condition based on unique key,
1325
        // not a combination of all fields
1326 4
        [$uniqueCondition, $clauseIsUnique] = Util::getUniqueCondition(
1327 4
            $res,
1328 4
            count($meta),
1329 4
            $meta,
1330 4
            $row ?? [],
1331 4
            true
1332
        );
1333 4
        if (! empty($uniqueCondition)) {
1334 4
            $_SESSION['edit_next'] = $uniqueCondition;
1335
        }
1336
1337 4
        unset($uniqueCondition, $clauseIsUnique);
1338 4
    }
1339
1340
    /**
1341
     * set $goto_include variable for different cases and retrieve like,
1342
     * if $GLOBALS['goto'] empty, if $goto_include previously not defined
1343
     * and new_insert, same_insert, edit_next
1344
     *
1345
     * @param string|false $gotoInclude store some script for include, otherwise it is
1346
     *                                   boolean false
1347
     *
1348
     * @return string|false
1349
     */
1350 8
    public function getGotoInclude($gotoInclude)
1351
    {
1352 2
        $validOptions = [
1353 6
            'new_insert',
1354
            'same_insert',
1355
            'edit_next',
1356
        ];
1357
        if (
1358 8
            isset($_POST['after_insert'])
1359 8
            && in_array($_POST['after_insert'], $validOptions)
1360
        ) {
1361 4
            $gotoInclude = '/table/change';
1362 8
        } elseif (! empty($GLOBALS['goto'])) {
1363 8
            if (! preg_match('@^[a-z_]+\.php$@', $GLOBALS['goto'])) {
1364
                // this should NOT happen
1365
                //$GLOBALS['goto'] = false;
1366 8
                if ($GLOBALS['goto'] === 'index.php?route=/sql') {
1367 4
                    $gotoInclude = '/sql';
1368
                } else {
1369 8
                    $gotoInclude = false;
1370
                }
1371
            } else {
1372
                $gotoInclude = $GLOBALS['goto'];
1373
            }
1374
1375 8
            if ($GLOBALS['goto'] === 'index.php?route=/database/sql' && strlen($GLOBALS['table']) > 0) {
1376 4
                $GLOBALS['table'] = '';
1377
            }
1378
        }
1379
1380 8
        if (! $gotoInclude) {
1381 4
            if (strlen($GLOBALS['table']) === 0) {
1382 4
                $gotoInclude = '/database/sql';
1383
            } else {
1384 4
                $gotoInclude = '/table/sql';
1385
            }
1386
        }
1387
1388 8
        return $gotoInclude;
1389
    }
1390
1391
    /**
1392
     * Defines the url to return in case of failure of the query
1393
     *
1394
     * @param array $urlParams url parameters
1395
     *
1396
     * @return string           error url for query failure
1397
     */
1398 8
    public function getErrorUrl(array $urlParams)
1399
    {
1400 8
        if (isset($_POST['err_url'])) {
1401 4
            return $_POST['err_url'];
1402
        }
1403
1404 8
        return Url::getFromRoute('/table/change', $urlParams);
1405
    }
1406
1407
    /**
1408
     * Builds the sql query
1409
     *
1410
     * @param bool  $isInsertIgnore $_POST['submit_type'] === 'insertignore'
1411
     * @param array $queryFields    column names array
1412
     * @param array $valueSets      array of query values
1413
     *
1414
     * @return array of query
1415
     */
1416 4
    public function buildSqlQuery($isInsertIgnore, array $queryFields, array $valueSets)
1417
    {
1418 4
        if ($isInsertIgnore) {
1419 4
            $insertCommand = 'INSERT IGNORE ';
1420
        } else {
1421 4
            $insertCommand = 'INSERT ';
1422
        }
1423
1424
        return [
1425 4
            $insertCommand . 'INTO '
1426 4
            . Util::backquote($GLOBALS['table'])
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquote($GLOBALS['table']) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

1426
            . /** @scrutinizer ignore-type */ Util::backquote($GLOBALS['table'])
Loading history...
1427 4
            . ' (' . implode(', ', $queryFields) . ') VALUES ('
1428 4
            . implode('), (', $valueSets) . ')',
1429
        ];
1430
    }
1431
1432
    /**
1433
     * Executes the sql query and get the result, then move back to the calling page
1434
     *
1435
     * @param array $urlParams url parameters array
1436
     * @param array $query     built query from buildSqlQuery()
1437
     *
1438
     * @return array $url_params, $total_affected_rows, $last_messages
1439
     *               $warning_messages, $error_messages, $return_to_sql_query
1440
     */
1441 12
    public function executeSqlQuery(array $urlParams, array $query)
1442
    {
1443 12
        $returnToSqlQuery = '';
1444 12
        if (! empty($GLOBALS['sql_query'])) {
1445 8
            $urlParams['sql_query'] = $GLOBALS['sql_query'];
1446 8
            $returnToSqlQuery = $GLOBALS['sql_query'];
1447
        }
1448
1449 12
        $GLOBALS['sql_query'] = implode('; ', $query) . ';';
1450
        // to ensure that the query is displayed in case of
1451
        // "insert as new row" and then "insert another new row"
1452 12
        $GLOBALS['display_query'] = $GLOBALS['sql_query'];
1453
1454 12
        $totalAffectedRows = 0;
1455 12
        $lastMessages = [];
1456 12
        $warningMessages = [];
1457 12
        $errorMessages = [];
1458
1459 12
        foreach ($query as $singleQuery) {
1460 12
            if (isset($_POST['submit_type']) && $_POST['submit_type'] === 'showinsert') {
1461
                $lastMessages[] = Message::notice(__('Showing SQL query'));
1462
                continue;
1463
            }
1464
1465 12
            if ($GLOBALS['cfg']['IgnoreMultiSubmitErrors']) {
1466 4
                $result = $this->dbi->tryQuery($singleQuery);
1467
            } else {
1468 8
                $result = $this->dbi->query($singleQuery);
1469
            }
1470
1471 12
            if (! $result) {
1472
                $errorMessages[] = $this->dbi->getError();
1473
            } else {
1474 12
                $tmp = @$this->dbi->affectedRows();
1475
1476 12
                if ($tmp) {
1477
                    $totalAffectedRows += $tmp;
1478
                }
1479
1480 12
                unset($tmp);
1481
1482 12
                $insertId = $this->dbi->insertId();
1483 12
                if ($insertId != 0) {
1484
                    // insert_id is id of FIRST record inserted in one insert, so if we
1485
                    // inserted multiple rows, we had to increment this
1486
1487
                    if ($totalAffectedRows > 0) {
1488
                        $insertId += $totalAffectedRows - 1;
1489
                    }
1490
1491
                    $lastMessage = Message::notice(__('Inserted row id: %1$d'));
1492
                    $lastMessage->addParam($insertId);
1493
                    $lastMessages[] = $lastMessage;
1494
                }
1495
1496 12
                $this->dbi->freeResult($result);
1497
            }
1498
1499 12
            $warningMessages = $this->getWarningMessages();
1500
        }
1501
1502
        return [
1503 12
            $urlParams,
1504 12
            $totalAffectedRows,
1505 12
            $lastMessages,
1506 12
            $warningMessages,
1507 12
            $errorMessages,
1508 12
            $returnToSqlQuery,
1509
        ];
1510
    }
1511
1512
    /**
1513
     * get the warning messages array
1514
     *
1515
     * @return array
1516
     */
1517 16
    private function getWarningMessages()
1518
    {
1519 16
        $warningMessages = [];
1520 16
        foreach ($this->dbi->getWarnings() as $warning) {
1521 4
            $warningMessages[] = Message::sanitize(
1522 4
                $warning['Level'] . ': #' . $warning['Code'] . ' ' . $warning['Message']
1523
            );
1524
        }
1525
1526 16
        return $warningMessages;
1527
    }
1528
1529
    /**
1530
     * Column to display from the foreign table?
1531
     *
1532
     * @param string $whereComparison string that contain relation field value
1533
     * @param array  $map             all Relations to foreign tables for a given
1534
     *                                table or optionally a given column in a table
1535
     * @param string $relationField   relation field
1536
     *
1537
     * @return string display value from the foreign table
1538
     */
1539 4
    public function getDisplayValueForForeignTableColumn(
1540
        $whereComparison,
1541
        array $map,
1542
        $relationField
1543
    ) {
1544 4
        $foreigner = $this->relation->searchColumnInForeigners($map, $relationField);
1545
1546 4
        if (! is_array($foreigner)) {
1547
            return '';
1548
        }
1549
1550 4
        $displayField = $this->relation->getDisplayField(
1551 4
            $foreigner['foreign_db'],
1552 4
            $foreigner['foreign_table']
1553
        );
1554
        // Field to display from the foreign table?
1555 4
        if (is_string($displayField) && strlen($displayField) > 0) {
1556 4
            $dispsql = 'SELECT ' . Util::backquote($displayField)
1557 4
                . ' FROM ' . Util::backquote($foreigner['foreign_db'])
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquo...oreigner['foreign_db']) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

1557
                . ' FROM ' . /** @scrutinizer ignore-type */ Util::backquote($foreigner['foreign_db'])
Loading history...
1558 4
                . '.' . Util::backquote($foreigner['foreign_table'])
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquo...igner['foreign_table']) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

1558
                . '.' . /** @scrutinizer ignore-type */ Util::backquote($foreigner['foreign_table'])
Loading history...
1559 4
                . ' WHERE ' . Util::backquote($foreigner['foreign_field'])
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquo...igner['foreign_field']) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

1559
                . ' WHERE ' . /** @scrutinizer ignore-type */ Util::backquote($foreigner['foreign_field'])
Loading history...
1560 4
                . $whereComparison;
1561 4
            $dispresult = $this->dbi->tryQuery(
1562 4
                $dispsql,
1563 4
                DatabaseInterface::CONNECT_USER,
1564 4
                DatabaseInterface::QUERY_STORE
1565
            );
1566 4
            if ($dispresult && $this->dbi->numRows($dispresult) > 0) {
1567 4
                [$dispval] = $this->dbi->fetchRow($dispresult);
1568
            } else {
1569
                $dispval = '';
1570
            }
1571
1572 4
            if ($dispresult) {
1573 4
                $this->dbi->freeResult($dispresult);
1574
            }
1575
1576 4
            return $dispval;
1577
        }
1578
1579
        return '';
1580
    }
1581
1582
    /**
1583
     * Display option in the cell according to user choices
1584
     *
1585
     * @param array  $map                all Relations to foreign tables for a given
1586
     *                                   table or optionally a given column in a table
1587
     * @param string $relationField      relation field
1588
     * @param string $whereComparison    string that contain relation field value
1589
     * @param string $dispval            display value from the foreign table
1590
     * @param string $relationFieldValue relation field value
1591
     *
1592
     * @return string HTML <a> tag
1593
     */
1594 4
    public function getLinkForRelationalDisplayField(
1595
        array $map,
1596
        $relationField,
1597
        $whereComparison,
1598
        $dispval,
1599
        $relationFieldValue
1600
    ) {
1601 4
        $foreigner = $this->relation->searchColumnInForeigners($map, $relationField);
1602
1603 4
        if (! is_array($foreigner)) {
1604
            return '';
1605
        }
1606
1607 4
        if ($_SESSION['tmpval']['relational_display'] === 'K') {
1608
            // user chose "relational key" in the display options, so
1609
            // the title contains the display field
1610 4
            $title = ! empty($dispval)
1611 4
                ? ' title="' . htmlspecialchars($dispval) . '"'
1612 3
                : '';
1613
        } else {
1614 4
            $title = ' title="' . htmlspecialchars($relationFieldValue) . '"';
1615
        }
1616
1617 1
        $sqlQuery = 'SELECT * FROM '
1618 4
            . Util::backquote($foreigner['foreign_db'])
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquo...oreigner['foreign_db']) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

1618
            . /** @scrutinizer ignore-type */ Util::backquote($foreigner['foreign_db'])
Loading history...
1619 4
            . '.' . Util::backquote($foreigner['foreign_table'])
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquo...igner['foreign_table']) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

1619
            . '.' . /** @scrutinizer ignore-type */ Util::backquote($foreigner['foreign_table'])
Loading history...
1620 4
            . ' WHERE ' . Util::backquote($foreigner['foreign_field'])
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquo...igner['foreign_field']) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

1620
            . ' WHERE ' . /** @scrutinizer ignore-type */ Util::backquote($foreigner['foreign_field'])
Loading history...
1621 4
            . $whereComparison;
1622 1
        $urlParams = [
1623 4
            'db'    => $foreigner['foreign_db'],
1624 4
            'table' => $foreigner['foreign_table'],
1625 4
            'pos'   => '0',
1626 4
            'sql_signature' => Core::signSqlQuery($sqlQuery),
1627 4
            'sql_query' => $sqlQuery,
1628
        ];
1629 4
        $output = '<a href="' . Url::getFromRoute('/sql', $urlParams) . '"' . $title . '>';
1630
1631 4
        if ($_SESSION['tmpval']['relational_display'] === 'D') {
1632
            // user chose "relational display field" in the
1633
            // display options, so show display field in the cell
1634 4
            $output .= ! empty($dispval) ? htmlspecialchars($dispval) : '';
1635
        } else {
1636
            // otherwise display data in the cell
1637 4
            $output .= htmlspecialchars($relationFieldValue);
1638
        }
1639
1640 4
        $output .= '</a>';
1641
1642 4
        return $output;
1643
    }
1644
1645
    /**
1646
     * Transform edited values
1647
     *
1648
     * @param string $db             db name
1649
     * @param string $table          table name
1650
     * @param array  $transformation mimetypes for all columns of a table
1651
     *                               [field_name][field_key]
1652
     * @param array  $editedValues   transform columns list and new values
1653
     * @param string $file           file containing the transformation plugin
1654
     * @param string $columnName     column name
1655
     * @param array  $extraData      extra data array
1656
     * @param string $type           the type of transformation
1657
     *
1658
     * @return array
1659
     */
1660 4
    public function transformEditedValues(
1661
        $db,
1662
        $table,
1663
        array $transformation,
1664
        array &$editedValues,
1665
        $file,
1666
        $columnName,
1667
        array $extraData,
1668
        $type
1669
    ) {
1670 4
        $includeFile = 'libraries/classes/Plugins/Transformations/' . $file;
1671 4
        if (is_file($includeFile)) {
1672
            // $cfg['SaveCellsAtOnce'] = true; JS code sends an array
1673 4
            $whereClause = is_array($_POST['where_clause']) ? $_POST['where_clause'][0] : $_POST['where_clause'];
1674 1
            $urlParams = [
1675 4
                'db'            => $db,
1676 4
                'table'         => $table,
1677 4
                'where_clause_sign' => Core::signSqlQuery($whereClause),
1678 4
                'where_clause'  => $whereClause,
1679 4
                'transform_key' => $columnName,
1680
            ];
1681 4
            $transformOptions = $this->transformations->getOptions(
1682 4
                $transformation[$type . '_options'] ?? ''
1683
            );
1684 4
            $transformOptions['wrapper_link'] = Url::getCommon($urlParams);
1685 4
            $transformOptions['wrapper_params'] = $urlParams;
1686 4
            $className = $this->transformations->getClassName($includeFile);
1687 4
            if (class_exists($className)) {
1688
                /** @var TransformationsPlugin $transformationPlugin */
1689 4
                $transformationPlugin = new $className();
1690
1691 4
                foreach ($editedValues as $cellIndex => $currCellEditedValues) {
1692 4
                    if (! isset($currCellEditedValues[$columnName])) {
1693
                        continue;
1694
                    }
1695
1696 4
                    $extraData['transformations'][$cellIndex] = $transformationPlugin->applyTransformation(
1697 4
                        $currCellEditedValues[$columnName],
1698 1
                        $transformOptions
1699
                    );
1700 4
                    $editedValues[$cellIndex][$columnName] = $extraData['transformations'][$cellIndex];
1701
                }
1702
            }
1703
        }
1704
1705 4
        return $extraData;
1706
    }
1707
1708
    /**
1709
     * Get current value in multi edit mode
1710
     *
1711
     * @param array  $multiEditFuncs       multiple edit functions array
1712
     * @param array  $multiEditSalt        multiple edit array with encryption salt
1713
     * @param array  $gisFromTextFunctions array that contains gis from text functions
1714
     * @param string $currentValue         current value in the column
1715
     * @param array  $gisFromWkbFunctions  initially $val is $multi_edit_columns[$key]
1716
     * @param array  $funcOptionalParam    array('RAND','UNIX_TIMESTAMP')
1717
     * @param array  $funcNoParam          array of set of string
1718
     * @param string $key                  an md5 of the column name
1719
     *
1720
     * @return string
1721
     */
1722 8
    public function getCurrentValueAsAnArrayForMultipleEdit(
1723
        $multiEditFuncs,
1724
        $multiEditSalt,
1725
        $gisFromTextFunctions,
1726
        $currentValue,
1727
        $gisFromWkbFunctions,
1728
        $funcOptionalParam,
1729
        $funcNoParam,
1730
        $key
1731
    ) {
1732 8
        if (empty($multiEditFuncs[$key])) {
1733 8
            return $currentValue;
1734
        }
1735
1736 4
        if ($multiEditFuncs[$key] === 'PHP_PASSWORD_HASH') {
1737
            /**
1738
             * @see https://github.com/vimeo/psalm/issues/3350
1739
             *
1740
             * @psalm-suppress InvalidArgument
1741
             */
1742
            $hash = password_hash($currentValue, PASSWORD_DEFAULT);
1743
1744
            return "'" . $hash . "'";
1745
        }
1746
1747 4
        if ($multiEditFuncs[$key] === 'UUID') {
1748
            /* This way user will know what UUID new row has */
1749 4
            $uuid = $this->dbi->fetchValue('SELECT UUID()');
1750
1751 4
            return "'" . $uuid . "'";
0 ignored issues
show
Bug introduced by
Are you sure $uuid of type false|mixed can be used in concatenation? ( Ignorable by Annotation )

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

1751
            return "'" . /** @scrutinizer ignore-type */ $uuid . "'";
Loading history...
1752
        }
1753
1754
        if (
1755 4
            (in_array($multiEditFuncs[$key], $gisFromTextFunctions)
1756
            && substr($currentValue, 0, 3) == "'''")
1757 4
            || in_array($multiEditFuncs[$key], $gisFromWkbFunctions)
1758
        ) {
1759
            // Remove enclosing apostrophes
1760
            $currentValue = mb_substr($currentValue, 1, -1);
1761
            // Remove escaping apostrophes
1762
            $currentValue = str_replace("''", "'", $currentValue);
1763
            // Remove backslash-escaped apostrophes
1764
            $currentValue = str_replace("\'", "'", $currentValue);
1765
1766
            return $multiEditFuncs[$key] . '(' . $currentValue . ')';
1767
        }
1768
1769
        if (
1770 4
            ! in_array($multiEditFuncs[$key], $funcNoParam)
1771 4
            || ($currentValue != "''"
1772 4
            && in_array($multiEditFuncs[$key], $funcOptionalParam))
1773
        ) {
1774
            if (
1775 4
                (isset($multiEditSalt[$key])
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (IssetNode && $multiEdit...ncs[$key] === 'ENCRYPT', Probably Intended Meaning: IssetNode && ($multiEdit...cs[$key] === 'ENCRYPT')
Loading history...
1776 4
                && ($multiEditFuncs[$key] === 'AES_ENCRYPT'
1777 4
                || $multiEditFuncs[$key] === 'AES_DECRYPT'))
1778 4
                || (! empty($multiEditSalt[$key])
1779
                && ($multiEditFuncs[$key] === 'DES_ENCRYPT'
1780
                || $multiEditFuncs[$key] === 'DES_DECRYPT'
1781 4
                || $multiEditFuncs[$key] === 'ENCRYPT'))
1782
            ) {
1783 4
                return $multiEditFuncs[$key] . '(' . $currentValue . ",'"
1784 4
                    . $this->dbi->escapeString($multiEditSalt[$key]) . "')";
1785
            }
1786
1787 4
            return $multiEditFuncs[$key] . '(' . $currentValue . ')';
1788
        }
1789
1790 4
        return $multiEditFuncs[$key] . '()';
1791
    }
1792
1793
    /**
1794
     * Get query values array and query fields array for insert and update in multi edit
1795
     *
1796
     * @param array  $multiEditColumnsName     multiple edit columns name array
1797
     * @param array  $multiEditColumnsNull     multiple edit columns null array
1798
     * @param string $currentValue             current value in the column in loop
1799
     * @param array  $multiEditColumnsPrev     multiple edit previous columns array
1800
     * @param array  $multiEditFuncs           multiple edit functions array
1801
     * @param bool   $isInsert                 boolean value whether insert or not
1802
     * @param array  $queryValues              SET part of the sql query
1803
     * @param array  $queryFields              array of query fields
1804
     * @param string $currentValueAsAnArray    current value in the column
1805
     *                                             as an array
1806
     * @param array  $valueSets                array of valu sets
1807
     * @param string $key                      an md5 of the column name
1808
     * @param array  $multiEditColumnsNullPrev array of multiple edit columns
1809
     *                                             null previous
1810
     *
1811
     * @return array ($query_values, $query_fields)
1812
     */
1813 8
    public function getQueryValuesForInsertAndUpdateInMultipleEdit(
1814
        $multiEditColumnsName,
1815
        $multiEditColumnsNull,
1816
        $currentValue,
1817
        $multiEditColumnsPrev,
1818
        $multiEditFuncs,
1819
        $isInsert,
1820
        $queryValues,
1821
        $queryFields,
1822
        $currentValueAsAnArray,
1823
        $valueSets,
1824
        $key,
1825
        $multiEditColumnsNullPrev
1826
    ) {
1827
        //  i n s e r t
1828 8
        if ($isInsert) {
1829
            // no need to add column into the valuelist
1830 4
            if (strlen($currentValueAsAnArray) > 0) {
1831 4
                $queryValues[] = $currentValueAsAnArray;
1832
                // first inserted row so prepare the list of fields
1833 4
                if (empty($valueSets)) {
1834 4
                    $queryFields[] = Util::backquote(
1835 4
                        $multiEditColumnsName[$key]
1836
                    );
1837
                }
1838
            }
1839
        } elseif (
1840 8
            ! empty($multiEditColumnsNullPrev[$key])
1841 8
            && ! isset($multiEditColumnsNull[$key])
1842
        ) {
1843
            //  u p d a t e
1844
1845
            // field had the null checkbox before the update
1846
            // field no longer has the null checkbox
1847 4
            $queryValues[] = Util::backquote($multiEditColumnsName[$key])
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquo...iEditColumnsName[$key]) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

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

1847
            $queryValues[] = /** @scrutinizer ignore-type */ Util::backquote($multiEditColumnsName[$key])
Loading history...
1848 4
                . ' = ' . $currentValueAsAnArray;
1849
        } elseif (
1850 8
            ! (empty($multiEditFuncs[$key])
1851 8
            && isset($multiEditColumnsPrev[$key])
1852 4
            && (($currentValue === "'" . $this->dbi->escapeString($multiEditColumnsPrev[$key]) . "'")
1853 8
            || ($currentValue === '0x' . $multiEditColumnsPrev[$key])))
1854 8
            && ! empty($currentValue)
1855
        ) {
1856
            // avoid setting a field to NULL when it's already NULL
1857
            // (field had the null checkbox before the update
1858
            //  field still has the null checkbox)
1859
            if (
1860 8
                empty($multiEditColumnsNullPrev[$key])
1861 8
                || empty($multiEditColumnsNull[$key])
1862
            ) {
1863 8
                 $queryValues[] = Util::backquote($multiEditColumnsName[$key])
1864 8
                    . ' = ' . $currentValueAsAnArray;
1865
            }
1866
        }
1867
1868
        return [
1869 8
            $queryValues,
1870 8
            $queryFields,
1871
        ];
1872
    }
1873
1874
    /**
1875
     * Get the current column value in the form for different data types
1876
     *
1877
     * @param string|false $possiblyUploadedVal      uploaded file content
1878
     * @param string       $key                      an md5 of the column name
1879
     * @param array|null   $multiEditColumnsType     array of multi edit column types
1880
     * @param string       $currentValue             current column value in the form
1881
     * @param array|null   $multiEditAutoIncrement   multi edit auto increment
1882
     * @param int          $rownumber                index of where clause array
1883
     * @param array        $multiEditColumnsName     multi edit column names array
1884
     * @param array        $multiEditColumnsNull     multi edit columns null array
1885
     * @param array        $multiEditColumnsNullPrev multi edit columns previous null
1886
     * @param bool         $isInsert                 whether insert or not
1887
     * @param bool         $usingKey                 whether editing or new row
1888
     * @param string       $whereClause              where clause
1889
     * @param string       $table                    table name
1890
     * @param array        $multiEditFuncs           multiple edit functions array
1891
     *
1892
     * @return string  current column value in the form
1893
     */
1894 8
    public function getCurrentValueForDifferentTypes(
1895
        $possiblyUploadedVal,
1896
        $key,
1897
        ?array $multiEditColumnsType,
1898
        $currentValue,
1899
        ?array $multiEditAutoIncrement,
1900
        $rownumber,
1901
        $multiEditColumnsName,
1902
        $multiEditColumnsNull,
1903
        $multiEditColumnsNullPrev,
1904
        $isInsert,
1905
        $usingKey,
1906
        $whereClause,
1907
        $table,
1908
        $multiEditFuncs
1909
    ) {
1910
        // Fetch the current values of a row to use in case we have a protected field
1911
        if (
1912 8
            $isInsert
1913 8
            && $usingKey && isset($multiEditColumnsType)
1914 8
            && is_array($multiEditColumnsType) && ! empty($whereClause)
1915
        ) {
1916 4
            $protectedRow = $this->dbi->fetchSingleRow(
1917 4
                'SELECT * FROM ' . Util::backquote($table)
1918 4
                . ' WHERE ' . $whereClause . ';'
1919
            );
1920
        }
1921
1922 8
        if ($possiblyUploadedVal !== false) {
1923 4
            $currentValue = $possiblyUploadedVal;
1924 8
        } elseif (! empty($multiEditFuncs[$key])) {
1925
            $currentValue = "'" . $this->dbi->escapeString($currentValue)
1926
                . "'";
1927
        } else {
1928
            // c o l u m n    v a l u e    i n    t h e    f o r m
1929 8
            if (isset($multiEditColumnsType[$key])) {
1930 4
                $type = $multiEditColumnsType[$key];
1931
            } else {
1932 4
                $type = '';
1933
            }
1934
1935 8
            if ($type !== 'protected' && $type !== 'set' && strlen($currentValue) === 0) {
1936
                // best way to avoid problems in strict mode
1937
                // (works also in non-strict mode)
1938 4
                if (isset($multiEditAutoIncrement, $multiEditAutoIncrement[$key])) {
1939 4
                    $currentValue = 'NULL';
1940
                } else {
1941 4
                    $currentValue = "''";
1942
                }
1943 8
            } elseif ($type === 'set') {
1944 4
                if (! empty($_POST['fields']['multi_edit'][$rownumber][$key])) {
1945
                    $currentValue = implode(
1946
                        ',',
1947
                        $_POST['fields']['multi_edit'][$rownumber][$key]
1948
                    );
1949
                    $currentValue = "'"
1950
                        . $this->dbi->escapeString($currentValue) . "'";
1951
                } else {
1952 4
                     $currentValue = "''";
1953
                }
1954 8
            } elseif ($type === 'protected') {
1955
                // here we are in protected mode (asked in the config)
1956
                // so tbl_change has put this special value in the
1957
                // columns array, so we do not change the column value
1958
                // but we can still handle column upload
1959
1960
                // when in UPDATE mode, do not alter field's contents. When in INSERT
1961
                // mode, insert empty field because no values were submitted.
1962
                // If protected blobs where set, insert original fields content.
1963 4
                if (! empty($protectedRow[$multiEditColumnsName[$key]])) {
1964 1
                    $currentValue = '0x'
1965 4
                        . bin2hex($protectedRow[$multiEditColumnsName[$key]]);
1966
                } else {
1967 4
                    $currentValue = '';
1968
                }
1969 8
            } elseif ($type === 'hex') {
1970
                if (substr($currentValue, 0, 2) != '0x') {
1971
                    $currentValue = '0x' . $currentValue;
1972
                }
1973 8
            } elseif ($type === 'bit') {
1974 4
                $currentValue = (string) preg_replace('/[^01]/', '0', $currentValue);
1975 4
                $currentValue = "b'" . $this->dbi->escapeString($currentValue) . "'";
1976
            } elseif (
1977 8
                ! ($type === 'datetime' || $type === 'timestamp' || $type === 'date')
1978 4
                || ($currentValue !== 'CURRENT_TIMESTAMP'
1979 8
                    && $currentValue !== 'current_timestamp()')
1980
            ) {
1981 8
                $currentValue = "'" . $this->dbi->escapeString($currentValue)
1982 8
                    . "'";
1983
            }
1984
1985
            // Was the Null checkbox checked for this field?
1986
            // (if there is a value, we ignore the Null checkbox: this could
1987
            // be possible if Javascript is disabled in the browser)
1988
            if (
1989 8
                ! empty($multiEditColumnsNull[$key])
1990 8
                && ($currentValue == "''" || $currentValue == '')
1991
            ) {
1992 4
                $currentValue = 'NULL';
1993
            }
1994
1995
            // The Null checkbox was unchecked for this field
1996
            if (
1997 8
                empty($currentValue)
1998 8
                && ! empty($multiEditColumnsNullPrev[$key])
1999 8
                && ! isset($multiEditColumnsNull[$key])
2000
            ) {
2001 4
                $currentValue = "''";
2002
            }
2003
        }
2004
2005 8
        return $currentValue;
2006
    }
2007
2008
    /**
2009
     * Check whether inline edited value can be truncated or not,
2010
     * and add additional parameters for extra_data array  if needed
2011
     *
2012
     * @param string $db         Database name
2013
     * @param string $table      Table name
2014
     * @param string $columnName Column name
2015
     * @param array  $extraData  Extra data for ajax response
2016
     *
2017
     * @return void
2018
     */
2019 4
    public function verifyWhetherValueCanBeTruncatedAndAppendExtraData(
2020
        $db,
2021
        $table,
2022
        $columnName,
2023
        array &$extraData
2024
    ) {
2025 4
        $extraData['isNeedToRecheck'] = false;
2026
2027 4
        $sqlForRealValue = 'SELECT ' . Util::backquote($table) . '.'
2028 4
            . Util::backquote($columnName)
2029 4
            . ' FROM ' . Util::backquote($db) . '.'
2030 4
            . Util::backquote($table)
2031 4
            . ' WHERE ' . $_POST['where_clause'][0];
2032
2033 4
        $result = $this->dbi->tryQuery($sqlForRealValue);
2034 4
        $fieldsMeta = $this->dbi->getFieldsMeta($result) ?? [];
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type false; however, parameter $result of PhpMyAdmin\DatabaseInterface::getFieldsMeta() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

2034
        $fieldsMeta = $this->dbi->getFieldsMeta(/** @scrutinizer ignore-type */ $result) ?? [];
Loading history...
2035
        /** @var FieldMetadata $meta */
2036 4
        $meta = $fieldsMeta[0];
2037 4
        $row = $this->dbi->fetchRow($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type false; however, parameter $result of PhpMyAdmin\DatabaseInterface::fetchRow() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

2037
        $row = $this->dbi->fetchRow(/** @scrutinizer ignore-type */ $result);
Loading history...
2038
2039 4
        if ($row) {
2040 4
            $newValue = $row[0];
2041 4
            if ($meta->isTimeType()) {
2042 4
                $newValue = Util::addMicroseconds($newValue);
2043 4
            } elseif ($meta->isBinary()) {
2044
                $newValue = '0x' . bin2hex($newValue);
2045
            }
2046
2047 4
            $extraData['isNeedToRecheck'] = true;
2048 4
            $extraData['truncatableFieldValue'] = $newValue;
2049
        }
2050
2051 4
        $this->dbi->freeResult($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type false; however, parameter $result of PhpMyAdmin\DatabaseInterface::freeResult() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

2051
        $this->dbi->freeResult(/** @scrutinizer ignore-type */ $result);
Loading history...
2052 4
    }
2053
2054
    /**
2055
     * Function to get the columns of a table
2056
     *
2057
     * @param string $db    current db
2058
     * @param string $table current table
2059
     *
2060
     * @return array
2061
     */
2062 4
    public function getTableColumns($db, $table)
2063
    {
2064 4
        $this->dbi->selectDb($db);
2065
2066 4
        return array_values($this->dbi->getColumns($db, $table, null, true));
2067
    }
2068
2069
    /**
2070
     * Function to determine Insert/Edit rows
2071
     *
2072
     * @param string $whereClause where clause
2073
     * @param string $db          current database
2074
     * @param string $table       current table
2075
     *
2076
     * @return array
2077
     */
2078 4
    public function determineInsertOrEdit($whereClause, $db, $table): array
2079
    {
2080 4
        if (isset($_POST['where_clause'])) {
2081 4
            $whereClause = $_POST['where_clause'];
2082
        }
2083
2084 4
        if (isset($_SESSION['edit_next'])) {
2085 4
            $whereClause = $_SESSION['edit_next'];
2086 4
            unset($_SESSION['edit_next']);
2087 4
            $afterInsert = 'edit_next';
2088
        }
2089
2090 4
        if (isset($_POST['ShowFunctionFields'])) {
2091 4
            $GLOBALS['cfg']['ShowFunctionFields'] = $_POST['ShowFunctionFields'];
2092
        }
2093
2094 4
        if (isset($_POST['ShowFieldTypesInDataEditView'])) {
2095 4
            $GLOBALS['cfg']['ShowFieldTypesInDataEditView'] = $_POST['ShowFieldTypesInDataEditView'];
2096
        }
2097
2098 4
        if (isset($_POST['after_insert'])) {
2099 4
            $afterInsert = $_POST['after_insert'];
2100
        }
2101
2102 4
        if (isset($whereClause)) {
2103
            // we are editing
2104 4
            $insertMode = false;
2105 4
            $whereClauseArray = $this->getWhereClauseArray($whereClause);
2106 4
            [$whereClauses, $result, $rows, $foundUniqueKey] = $this->analyzeWhereClauses(
2107 4
                $whereClauseArray,
2108 4
                $table,
2109 4
                $db
2110
            );
2111
        } else {
2112
            // we are inserting
2113 4
            $insertMode = true;
2114 4
            $whereClause = null;
2115 4
            [$result, $rows] = $this->loadFirstRow($table, $db);
2116 4
            $whereClauses = null;
2117 4
            $whereClauseArray = [];
2118 4
            $foundUniqueKey = false;
2119
        }
2120
2121
        // Copying a row - fetched data will be inserted as a new row,
2122
        // therefore the where clause is needless.
2123
        if (
2124 4
            isset($_POST['default_action'])
2125 4
            && $_POST['default_action'] === 'insert'
2126
        ) {
2127 4
            $whereClause = $whereClauses = null;
2128
        }
2129
2130
        return [
2131 4
            $insertMode,
2132 4
            $whereClause,
2133 4
            $whereClauseArray,
2134 4
            $whereClauses,
2135 4
            $result,
2136 4
            $rows,
2137 4
            $foundUniqueKey,
2138 4
            $afterInsert ?? null,
2139
        ];
2140
    }
2141
2142
    /**
2143
     * Function to get comments for the table columns
2144
     *
2145
     * @param string $db    current database
2146
     * @param string $table current table
2147
     *
2148
     * @return array comments for columns
2149
     */
2150 4
    public function getCommentsMap($db, $table)
2151
    {
2152 4
        $commentsMap = [];
2153
2154 4
        if ($GLOBALS['cfg']['ShowPropertyComments']) {
2155 4
            $commentsMap = $this->relation->getComments($db, $table);
2156
        }
2157
2158 4
        return $commentsMap;
2159
    }
2160
2161
    /**
2162
     * Function to get URL parameters
2163
     *
2164
     * @param string $db    current database
2165
     * @param string $table current table
2166
     *
2167
     * @return array url parameters
2168
     */
2169 4
    public function getUrlParameters($db, $table)
2170
    {
2171 4
        global $goto;
2172
        /**
2173
         * @todo check if we could replace by "db_|tbl_" - please clarify!?
2174
         */
2175 1
        $urlParams = [
2176 4
            'db' => $db,
2177 4
            'sql_query' => $_POST['sql_query'],
2178
        ];
2179
2180 4
        if (strpos($goto, 'tbl_') === 0 || strpos($goto, 'index.php?route=/table') === 0) {
2181 4
            $urlParams['table'] = $table;
2182
        }
2183
2184 4
        return $urlParams;
2185
    }
2186
2187
    /**
2188
     * Function to get html for the gis editor div
2189
     *
2190
     * @return string
2191
     */
2192
    public function getHtmlForGisEditor()
2193
    {
2194
        return '<div id="gis_editor"></div>'
2195
            . '<div id="popup_background"></div>'
2196
            . '<br>';
2197
    }
2198
2199
    /**
2200
     * Function to get html for the ignore option in insert mode
2201
     *
2202
     * @param int  $rowId   row id
2203
     * @param bool $checked ignore option is checked or not
2204
     *
2205
     * @return string
2206
     */
2207 4
    public function getHtmlForIgnoreOption($rowId, $checked = true)
2208
    {
2209
        return '<input type="checkbox"'
2210 4
                . ($checked ? ' checked="checked"' : '')
2211 4
                . ' name="insert_ignore_' . $rowId . '"'
2212 4
                . ' id="insert_ignore_' . $rowId . '">'
2213 4
                . '<label for="insert_ignore_' . $rowId . '">'
2214 4
                . __('Ignore')
2215 4
                . '</label><br>' . "\n";
2216
    }
2217
2218
    /**
2219
     * Function to get html for the insert edit form header
2220
     *
2221
     * @param bool $hasBlobField whether has blob field
2222
     * @param bool $isUpload     whether is upload
2223
     *
2224
     * @return string
2225
     */
2226
    public function getHtmlForInsertEditFormHeader($hasBlobField, $isUpload)
2227
    {
2228
        $htmlOutput = '<form id="insertForm" class="lock-page ';
2229
        if ($hasBlobField && $isUpload) {
2230
            $htmlOutput .= 'disableAjax';
2231
        }
2232
2233
        $htmlOutput .= '" method="post" action="' . Url::getFromRoute('/table/replace') . '" name="insertForm" ';
2234
        if ($isUpload) {
2235
            $htmlOutput .= ' enctype="multipart/form-data"';
2236
        }
2237
2238
        $htmlOutput .= '>';
2239
2240
        return $htmlOutput;
2241
    }
2242
2243
    /**
2244
     * Function to get html for each insert/edit column
2245
     *
2246
     * @param array  $tableColumns       table columns
2247
     * @param int    $columnNumber       column index in table_columns
2248
     * @param array  $commentsMap        comments map
2249
     * @param bool   $timestampSeen      whether timestamp seen
2250
     * @param array  $currentResult      current result
2251
     * @param string $chgEvtHandler      javascript change event handler
2252
     * @param string $jsvkey             javascript validation key
2253
     * @param string $vkey               validation key
2254
     * @param bool   $insertMode         whether insert mode
2255
     * @param array  $currentRow         current row
2256
     * @param int    $oRows              row offset
2257
     * @param int    $tabindex           tab index
2258
     * @param int    $columnsCnt         columns count
2259
     * @param bool   $isUpload           whether upload
2260
     * @param array  $foreigners         foreigners
2261
     * @param int    $tabindexForValue   tab index offset for value
2262
     * @param string $table              table
2263
     * @param string $db                 database
2264
     * @param int    $rowId              row id
2265
     * @param int    $biggestMaxFileSize biggest max file size
2266
     * @param string $defaultCharEditing default char editing mode which is stored in the config.inc.php script
2267
     * @param string $textDir            text direction
2268
     * @param array  $repopulate         the data to be repopulated
2269
     * @param array  $columnMime         the mime information of column
2270
     * @param string $whereClause        the where clause
2271
     *
2272
     * @return string
2273
     */
2274 12
    private function getHtmlForInsertEditFormColumn(
2275
        array $tableColumns,
2276
        $columnNumber,
2277
        array $commentsMap,
2278
        $timestampSeen,
2279
        $currentResult,
2280
        $chgEvtHandler,
2281
        $jsvkey,
2282
        $vkey,
2283
        $insertMode,
2284
        array $currentRow,
2285
        &$oRows,
2286
        &$tabindex,
2287
        $columnsCnt,
2288
        $isUpload,
2289
        array $foreigners,
2290
        $tabindexForValue,
2291
        $table,
2292
        $db,
2293
        $rowId,
2294
        $biggestMaxFileSize,
2295
        $defaultCharEditing,
2296
        $textDir,
2297
        array $repopulate,
2298
        array $columnMime,
2299
        $whereClause
2300
    ) {
2301 12
        $column = $tableColumns[$columnNumber];
2302 12
        $readOnly = false;
2303
2304 12
        if (! isset($column['processed'])) {
2305 12
            $column = $this->analyzeTableColumnsArray($column, $commentsMap, $timestampSeen);
2306
        }
2307
2308 12
        $asIs = false;
2309 12
        if (! empty($repopulate) && ! empty($currentRow)) {
2310
            $currentRow[$column['Field']] = $repopulate[$column['Field_md5']];
2311
            $asIs = true;
2312
        }
2313
2314 12
        $extractedColumnspec = Util::extractColumnSpec($column['Type']);
2315
2316 12
        if ($column['len'] === -1) {
2317 12
            $column['len'] = $this->dbi->fieldLen($currentResult, $columnNumber);
0 ignored issues
show
Bug introduced by
$currentResult of type array is incompatible with the type object expected by parameter $result of PhpMyAdmin\DatabaseInterface::fieldLen(). ( Ignorable by Annotation )

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

2317
            $column['len'] = $this->dbi->fieldLen(/** @scrutinizer ignore-type */ $currentResult, $columnNumber);
Loading history...
2318
            // length is unknown for geometry fields,
2319
            // make enough space to edit very simple WKTs
2320 12
            if ($column['len'] === -1) {
2321 12
                $column['len'] = 30;
2322
            }
2323
        }
2324
2325
        //Call validation when the form submitted...
2326 3
        $onChangeClause = $chgEvtHandler
2327 12
            . "=\"return verificationsAfterFieldChange('"
2328 12
            . Sanitize::escapeJsString($column['Field_md5']) . "', '"
2329 12
            . Sanitize::escapeJsString($jsvkey) . "','" . $column['pma_type'] . "')\"";
2330
2331
        // Use an MD5 as an array index to avoid having special characters
2332
        // in the name attribute (see bug #1746964 )
2333 12
        $columnNameAppendix = $vkey . '[' . $column['Field_md5'] . ']';
2334
2335 12
        if ($column['Type'] === 'datetime' && ! isset($column['Default']) && $insertMode) {
2336 4
            $column['Default'] = date('Y-m-d H:i:s', time());
2337
        }
2338
2339
        // Get a list of GIS data types.
2340 12
        $gisDataTypes = Gis::getDataTypes();
2341
2342
        // Prepares the field value
2343 12
        $realNullValue = false;
2344 12
        $specialCharsEncoded = '';
2345 12
        if (! empty($currentRow)) {
2346
            // (we are editing)
2347
            [
2348
                $realNullValue,
2349
                $specialCharsEncoded,
2350
                $specialChars,
2351
                $data,
2352
                $backupField,
2353
            ] = $this->getSpecialCharsAndBackupFieldForExistingRow(
2354
                $currentRow,
2355
                $column,
2356
                $extractedColumnspec,
2357
                $realNullValue,
2358
                $gisDataTypes,
2359
                $columnNameAppendix,
2360
                $asIs
2361
            );
2362
        } else {
2363
            // (we are inserting)
2364
            // display default values
2365 12
            $tmp = $column;
2366 12
            if (isset($repopulate[$column['Field_md5']])) {
2367 4
                $tmp['Default'] = $repopulate[$column['Field_md5']];
2368
            }
2369
2370
            [
2371 3
                $realNullValue,
2372 3
                $data,
2373 3
                $specialChars,
2374 3
                $backupField,
2375 3
                $specialCharsEncoded,
2376 12
            ] = $this->getSpecialCharsAndBackupFieldForInsertingMode($tmp, $realNullValue);
2377 12
            unset($tmp);
2378
        }
2379
2380 12
        $idindex = ($oRows * $columnsCnt) + $columnNumber + 1;
2381 12
        $tabindex = $idindex;
2382
2383
        // Get a list of data types that are not yet supported.
2384 12
        $noSupportTypes = Util::unsupportedDatatypes();
0 ignored issues
show
Unused Code introduced by
The assignment to $noSupportTypes is dead and can be removed.
Loading history...
2385
2386
        // The function column
2387
        // -------------------
2388 12
        $foreignData = $this->relation->getForeignData($foreigners, $column['Field'], false, '', '');
2389 12
        $isColumnBinary = $this->isColumnBinary($column, $isUpload);
2390 12
        $functionOptions = '';
2391
2392 12
        if ($GLOBALS['cfg']['ShowFunctionFields']) {
2393 12
            $functionOptions = Generator::getFunctionsForField($column, $insertMode, $foreignData);
2394
        }
2395
2396
        // nullify code is needed by the js nullify() function to be able to generate calls to nullify() in jQuery
2397 12
        $nullifyCode = $this->getNullifyCodeForNullColumn($column, $foreigners, $foreignData);
2398
2399
        // The value column (depends on type)
2400
        // ----------------
2401
        // See bug #1667887 for the reason why we don't use the maxlength
2402
        // HTML attribute
2403
2404
        //add data attributes "no of decimals" and "data type"
2405 12
        $noDecimals = 0;
2406 12
        $type = current(explode('(', $column['pma_type']));
2407 12
        if (preg_match('/\(([^()]+)\)/', $column['pma_type'], $match)) {
2408 4
            $match[0] = trim($match[0], '()');
2409 4
            $noDecimals = $match[0];
2410
        }
2411
2412
        // Check input transformation of column
2413 12
        $transformedHtml = '';
2414 12
        if (! empty($columnMime['input_transformation'])) {
2415 4
            $file = $columnMime['input_transformation'];
2416 4
            $includeFile = 'libraries/classes/Plugins/Transformations/' . $file;
2417 4
            if (is_file($includeFile)) {
2418 4
                $className = $this->transformations->getClassName($includeFile);
2419 4
                if (class_exists($className)) {
2420 4
                    $transformationPlugin = new $className();
2421 4
                    $transformationOptions = $this->transformations->getOptions(
2422 4
                        $columnMime['input_transformation_options']
2423
                    );
2424 1
                    $urlParams = [
2425 4
                        'db'            => $db,
2426 4
                        'table'         => $table,
2427 4
                        'transform_key' => $column['Field'],
2428 4
                        'where_clause_sign' => Core::signSqlQuery($whereClause),
2429 4
                        'where_clause'  => $whereClause,
2430
                    ];
2431 4
                    $transformationOptions['wrapper_link'] = Url::getCommon($urlParams);
2432 4
                    $transformationOptions['wrapper_params'] = $urlParams;
2433 4
                    $currentValue = '';
2434 4
                    if (isset($currentRow[$column['Field']])) {
2435
                        $currentValue = $currentRow[$column['Field']];
2436
                    }
2437
2438 4
                    if (method_exists($transformationPlugin, 'getInputHtml')) {
2439 4
                        $transformedHtml = $transformationPlugin->getInputHtml(
2440 4
                            $column,
2441 1
                            $rowId,
2442 1
                            $columnNameAppendix,
2443 1
                            $transformationOptions,
2444 1
                            $currentValue,
2445 1
                            $textDir,
2446 1
                            $tabindex,
2447 1
                            $tabindexForValue,
2448 1
                            $idindex
2449
                        );
2450
                    }
2451
2452 4
                    if (method_exists($transformationPlugin, 'getScripts')) {
2453 4
                        $GLOBALS['plugin_scripts'] = array_merge(
2454 4
                            $GLOBALS['plugin_scripts'],
2455 4
                            $transformationPlugin->getScripts()
2456
                        );
2457
                    }
2458
                }
2459
            }
2460
        }
2461
2462 12
        $columnValue = '';
2463 12
        $foreignDropdown = '';
2464 12
        $dataType = '';
2465 12
        $textAreaRows = $GLOBALS['cfg']['TextareaRows'];
2466 12
        $textareaCols = $GLOBALS['cfg']['TextareaCols'];
2467 12
        $maxlength = '';
2468 12
        $enumSelectedValue = '';
2469 12
        $columnSetValues = [];
2470 12
        $setSelectSize = 0;
2471 12
        $isColumnProtectedBlob = false;
2472 12
        $blobValue = '';
2473 12
        $blobValueUnit = '';
2474 12
        $maxUploadSize = 0;
2475 12
        $selectOptionForUpload = '';
2476 12
        $inputFieldHtml = '';
2477 12
        if (empty($transformedHtml)) {
2478 12
            if (is_array($foreignData['disp_row'])) {
2479
                $foreignDropdown = $this->relation->foreignDropdown(
2480
                    $foreignData['disp_row'],
2481
                    $foreignData['foreign_field'],
2482
                    $foreignData['foreign_display'],
2483
                    $data,
2484
                    $GLOBALS['cfg']['ForeignKeyMaxLimit']
2485
                );
2486
            }
2487
2488 12
            $dataType = $this->dbi->types->getTypeClass($column['True_Type']);
2489
2490 12
            if ($column['is_char']) {
2491
                $textAreaRows = max($GLOBALS['cfg']['CharTextareaRows'], 7);
2492
                $textareaCols = $GLOBALS['cfg']['CharTextareaCols'];
2493
                $extractedColumnspec = Util::extractColumnSpec($column['Type']);
2494
                $maxlength = $extractedColumnspec['spec_in_brackets'];
2495 12
            } elseif ($GLOBALS['cfg']['LongtextDoubleTextarea'] && mb_strstr($column['pma_type'], 'longtext')) {
2496 8
                $textAreaRows = $GLOBALS['cfg']['TextareaRows'] * 2;
2497 8
                $textareaCols = $GLOBALS['cfg']['TextareaCols'] * 2;
2498
            }
2499
2500 12
            if ($column['pma_type'] === 'enum') {
2501
                if (! isset($column['values'])) {
2502
                    $column['values'] = $this->getColumnEnumValues($column, $extractedColumnspec);
2503
                }
2504
2505
                foreach ($column['values'] as $enumValue) {
2506
                    if (
2507
                        $data == $enumValue['plain'] || ($data == ''
2508
                            && (! isset($_POST['where_clause']) || $column['Null'] !== 'YES')
2509
                            && isset($column['Default']) && $enumValue['plain'] == $column['Default'])
2510
                    ) {
2511
                        $enumSelectedValue = $enumValue['plain'];
2512
                        break;
2513
                    }
2514
                }
2515 12
            } elseif ($column['pma_type'] === 'set') {
2516
                [$columnSetValues, $setSelectSize] = $this->getColumnSetValueAndSelectSize(
2517
                    $column,
2518
                    $extractedColumnspec
2519
                );
2520 12
            } elseif ($column['is_binary'] || $column['is_blob']) {
2521
                $isColumnProtectedBlob = ($GLOBALS['cfg']['ProtectBinary'] === 'blob' && $column['is_blob'])
2522
                    || ($GLOBALS['cfg']['ProtectBinary'] === 'all')
2523
                    || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' && ! $column['is_blob']);
2524
                if ($isColumnProtectedBlob && isset($data)) {
2525
                    $blobSize = Util::formatByteDown(mb_strlen(stripslashes($data)), 3, 1);
2526
                    if ($blobSize !== null) {
0 ignored issues
show
introduced by
The condition $blobSize !== null is always true.
Loading history...
2527
                        [$blobValue, $blobValueUnit] = $blobSize;
2528
                    }
2529
                }
2530
2531
                if ($isUpload && $column['is_blob']) {
2532
                    [$maxUploadSize] = $this->getMaxUploadSize($column, $biggestMaxFileSize);
2533
                }
2534
2535
                if (! empty($GLOBALS['cfg']['UploadDir'])) {
2536
                    $selectOptionForUpload = $this->getSelectOptionForUpload($vkey, $column);
2537
                }
2538
2539
                if (
2540
                    ! $isColumnProtectedBlob
2541
                    && ! ($column['is_blob'] || ($column['len'] > $GLOBALS['cfg']['LimitChars']))
2542
                ) {
2543
                    $inputFieldHtml = $this->getHtmlInput(
2544
                        $column,
2545
                        $columnNameAppendix,
2546
                        $specialChars,
2547
                        min(max($column['len'], 4), $GLOBALS['cfg']['LimitChars']),
2548
                        $onChangeClause,
2549
                        $tabindex,
2550
                        $tabindexForValue,
2551
                        $idindex,
2552
                        'HEX',
2553
                        $readOnly
2554
                    );
2555
                }
2556
            } else {
2557 12
                $columnValue = $this->getValueColumnForOtherDatatypes(
2558 12
                    $column,
2559 12
                    $defaultCharEditing,
2560 12
                    $backupField,
2561 12
                    $columnNameAppendix,
2562 12
                    $onChangeClause,
2563 12
                    $tabindex,
2564 12
                    $specialChars,
2565 12
                    $tabindexForValue,
2566 12
                    $idindex,
2567 12
                    $textDir,
2568 12
                    $specialCharsEncoded,
2569 12
                    $data,
2570 12
                    $extractedColumnspec,
2571 12
                    $readOnly
2572
                );
2573
            }
2574
        }
2575
2576 12
        return $this->template->render('table/insert/column_row', [
2577 12
            'db' => $db,
2578 12
            'table' => $table,
2579 12
            'column' => $column,
2580 12
            'row_id' => $rowId,
2581 12
            'show_field_types_in_data_edit_view' => $GLOBALS['cfg']['ShowFieldTypesInDataEditView'],
2582 12
            'show_function_fields' => $GLOBALS['cfg']['ShowFunctionFields'],
2583 12
            'is_column_binary' => $isColumnBinary,
2584 12
            'function_options' => $functionOptions,
2585 12
            'read_only' => $readOnly,
2586 12
            'nullify_code' => $nullifyCode,
2587 12
            'real_null_value' => $realNullValue,
2588 12
            'id_index' => $idindex,
2589 12
            'type' => $type,
2590 12
            'decimals' => $noDecimals,
2591 12
            'special_chars' => $specialChars,
2592 12
            'transformed_value' => $transformedHtml,
2593 12
            'value' => $columnValue,
2594 12
            'is_value_foreign_link' => $foreignData['foreign_link'] === true,
2595 12
            'backup_field' => $backupField,
2596 12
            'data' => $data,
2597 12
            'gis_data_types' => $gisDataTypes,
2598 12
            'foreign_dropdown' => $foreignDropdown,
2599 12
            'data_type' => $dataType,
2600 12
            'textarea_cols' => $textareaCols,
2601 12
            'textarea_rows' => $textAreaRows,
2602 12
            'text_dir' => $textDir,
2603 12
            'max_length' => $maxlength,
2604 12
            'longtext_double_textarea' => $GLOBALS['cfg']['LongtextDoubleTextarea'],
2605 12
            'enum_selected_value' => $enumSelectedValue,
2606 12
            'set_values' => $columnSetValues,
2607 12
            'set_select_size' => $setSelectSize,
2608 12
            'is_column_protected_blob' => $isColumnProtectedBlob,
2609 12
            'blob_value' => $blobValue,
2610 12
            'blob_value_unit' => $blobValueUnit,
2611 12
            'is_upload' => $isUpload,
2612 12
            'max_upload_size' => $maxUploadSize,
2613 12
            'select_option_for_upload' => $selectOptionForUpload,
2614 12
            'limit_chars' => $GLOBALS['cfg']['LimitChars'],
2615 12
            'input_field_html' => $inputFieldHtml,
2616
        ]);
2617
    }
2618
2619 12
    private function isColumnBinary(array $column, bool $isUpload): bool
2620
    {
2621 12
        global $cfg;
2622
2623 12
        if (! $cfg['ShowFunctionFields']) {
2624
            return false;
2625
        }
2626
2627 12
        return ($cfg['ProtectBinary'] === 'blob' && $column['is_blob'] && ! $isUpload)
2628 12
            || ($cfg['ProtectBinary'] === 'all' && $column['is_binary'])
2629 12
            || ($cfg['ProtectBinary'] === 'noblob' && $column['is_binary']);
2630
    }
2631
2632
    /**
2633
     * Function to get html for each insert/edit row
2634
     *
2635
     * @param array  $urlParams          url parameters
2636
     * @param array  $tableColumns       table columns
2637
     * @param array  $commentsMap        comments map
2638
     * @param bool   $timestampSeen      whether timestamp seen
2639
     * @param array  $currentResult      current result
2640
     * @param string $chgEvtHandler      javascript change event handler
2641
     * @param string $jsvkey             javascript validation key
2642
     * @param string $vkey               validation key
2643
     * @param bool   $insertMode         whether insert mode
2644
     * @param array  $currentRow         current row
2645
     * @param int    $oRows              row offset
2646
     * @param int    $tabindex           tab index
2647
     * @param int    $columnsCnt         columns count
2648
     * @param bool   $isUpload           whether upload
2649
     * @param array  $foreigners         foreigners
2650
     * @param int    $tabindexForValue   tab index offset for value
2651
     * @param string $table              table
2652
     * @param string $db                 database
2653
     * @param int    $rowId              row id
2654
     * @param int    $biggestMaxFileSize biggest max file size
2655
     * @param string $textDir            text direction
2656
     * @param array  $repopulate         the data to be repopulated
2657
     * @param array  $whereClauseArray   the array of where clauses
2658
     *
2659
     * @return string
2660
     */
2661 8
    public function getHtmlForInsertEditRow(
2662
        array $urlParams,
2663
        array $tableColumns,
2664
        array $commentsMap,
2665
        $timestampSeen,
2666
        $currentResult,
2667
        $chgEvtHandler,
2668
        $jsvkey,
2669
        $vkey,
2670
        $insertMode,
2671
        array $currentRow,
2672
        &$oRows,
2673
        &$tabindex,
2674
        $columnsCnt,
2675
        $isUpload,
2676
        array $foreigners,
2677
        $tabindexForValue,
2678
        $table,
2679
        $db,
2680
        $rowId,
2681
        $biggestMaxFileSize,
2682
        $textDir,
2683
        array $repopulate,
2684
        array $whereClauseArray
2685
    ) {
2686 8
        $htmlOutput = $this->getHeadAndFootOfInsertRowTable($urlParams)
2687 8
            . '<tbody>';
2688
2689
        //store the default value for CharEditing
2690 8
        $defaultCharEditing = $GLOBALS['cfg']['CharEditing'];
2691 8
        $mimeMap = $this->transformations->getMime($db, $table);
2692 8
        $whereClause = '';
2693 8
        if (isset($whereClauseArray[$rowId])) {
2694 8
            $whereClause = $whereClauseArray[$rowId];
2695
        }
2696
2697 8
        for ($columnNumber = 0; $columnNumber < $columnsCnt; $columnNumber++) {
2698 8
            $tableColumn = $tableColumns[$columnNumber];
2699 8
            $columnMime = [];
2700 8
            if (isset($mimeMap[$tableColumn['Field']])) {
2701
                $columnMime = $mimeMap[$tableColumn['Field']];
2702
            }
2703
2704 2
            $virtual = [
2705 6
                'VIRTUAL',
2706
                'PERSISTENT',
2707
                'VIRTUAL GENERATED',
2708
                'STORED GENERATED',
2709
            ];
2710 8
            if (in_array($tableColumn['Extra'], $virtual)) {
2711
                continue;
2712
            }
2713
2714 8
            $htmlOutput .= $this->getHtmlForInsertEditFormColumn(
2715 8
                $tableColumns,
2716 8
                $columnNumber,
2717 8
                $commentsMap,
2718 8
                $timestampSeen,
2719 8
                $currentResult,
2720 8
                $chgEvtHandler,
2721 8
                $jsvkey,
2722 8
                $vkey,
2723 8
                $insertMode,
2724 8
                $currentRow,
2725 8
                $oRows,
2726 8
                $tabindex,
2727 8
                $columnsCnt,
2728 8
                $isUpload,
2729 8
                $foreigners,
2730 8
                $tabindexForValue,
2731 8
                $table,
2732 8
                $db,
2733 8
                $rowId,
2734 8
                $biggestMaxFileSize,
2735 8
                $defaultCharEditing,
2736 8
                $textDir,
2737 8
                $repopulate,
2738 8
                $columnMime,
2739 8
                $whereClause
2740
            );
2741
        }
2742
2743 8
        $oRows++;
2744
2745 8
        return $htmlOutput . '  </tbody>'
2746 8
            . '</table></div><br>'
2747 8
            . '<div class="clearfloat"></div>';
2748
    }
2749
}
2750