Passed
Push — master ( 754437...3b285f )
by Maurício
10:47
created

libraries/classes/InsertEdit.php (1 issue)

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 const PASSWORD_DEFAULT;
14
use function array_fill;
15
use function array_flip;
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 sprintf;
45
use function str_replace;
46
use function stripcslashes;
47
use function stripslashes;
48
use function strlen;
49
use function strpos;
50
use function substr;
51
use function time;
52
use function trim;
53
54
/**
55
 * PhpMyAdmin\InsertEdit class
56
 */
57
class InsertEdit
58
{
59
    /**
60
     * DatabaseInterface instance
61
     *
62
     * @var DatabaseInterface
63
     */
64
    private $dbi;
65
66
    /** @var Relation */
67
    private $relation;
68
69
    /** @var Transformations */
70
    private $transformations;
71
72
    /** @var FileListing */
73
    private $fileListing;
74
75
    /** @var Template */
76
    public $template;
77
78
    /**
79
     * @param DatabaseInterface $dbi DatabaseInterface instance
80
     */
81 248
    public function __construct(DatabaseInterface $dbi)
82
    {
83 248
        $this->dbi = $dbi;
84 248
        $this->relation = new Relation($GLOBALS['dbi']);
85 248
        $this->transformations = new Transformations();
86 248
        $this->fileListing = new FileListing();
87 248
        $this->template = new Template();
88 248
    }
89
90
    /**
91
     * Retrieve form parameters for insert/edit form
92
     *
93
     * @param string     $db                 name of the database
94
     * @param string     $table              name of the table
95
     * @param array|null $where_clauses      where clauses
96
     * @param array      $where_clause_array array of where clauses
97
     * @param string     $err_url            error url
98
     *
99
     * @return array array of insert/edit form parameters
100
     */
101 4
    public function getFormParametersForInsertForm(
102
        $db,
103
        $table,
104
        ?array $where_clauses,
105
        array $where_clause_array,
106
        $err_url
107
    ) {
108
        $_form_params = [
109 4
            'db'        => $db,
110 4
            'table'     => $table,
111 4
            'goto'      => $GLOBALS['goto'],
112 4
            'err_url'   => $err_url,
113 4
            'sql_query' => $_POST['sql_query'],
114
        ];
115 4
        if (isset($where_clauses)) {
116 4
            foreach ($where_clause_array as $key_id => $where_clause) {
117 4
                $_form_params['where_clause[' . $key_id . ']'] = trim($where_clause);
118
            }
119
        }
120 4
        if (isset($_POST['clause_is_unique'])) {
121 4
            $_form_params['clause_is_unique'] = $_POST['clause_is_unique'];
122
        }
123
124 4
        return $_form_params;
125
    }
126
127
    /**
128
     * Creates array of where clauses
129
     *
130
     * @param array|string|null $where_clause where clause
131
     *
132
     * @return array whereClauseArray array of where clauses
133
     */
134 8
    private function getWhereClauseArray($where_clause)
135
    {
136 8
        if (! isset($where_clause)) {
137 4
            return [];
138
        }
139
140 8
        if (is_array($where_clause)) {
141 4
            return $where_clause;
142
        }
143
144 8
        return [0 => $where_clause];
145
    }
146
147
    /**
148
     * Analysing where clauses array
149
     *
150
     * @param array  $where_clause_array array of where clauses
151
     * @param string $table              name of the table
152
     * @param string $db                 name of the database
153
     *
154
     * @return array $where_clauses, $result, $rows, $found_unique_key
155
     */
156 8
    private function analyzeWhereClauses(
157
        array $where_clause_array,
158
        $table,
159
        $db
160
    ) {
161 8
        $rows               = [];
162 8
        $result             = [];
163 8
        $where_clauses      = [];
164 8
        $found_unique_key   = false;
165 8
        foreach ($where_clause_array as $key_id => $where_clause) {
166
            $local_query     = 'SELECT * FROM '
167 8
                . Util::backquote($db) . '.'
168 8
                . Util::backquote($table)
169 8
                . ' WHERE ' . $where_clause . ';';
170 8
            $result[$key_id] = $this->dbi->query(
171 8
                $local_query,
172 8
                DatabaseInterface::CONNECT_USER,
173 8
                DatabaseInterface::QUERY_STORE
174
            );
175 8
            $rows[$key_id] = $this->dbi->fetchAssoc($result[$key_id]);
176
177 8
            $where_clauses[$key_id] = str_replace('\\', '\\\\', $where_clause);
178 8
            $has_unique_condition = $this->showEmptyResultMessageOrSetUniqueCondition(
179 8
                $rows,
180 6
                $key_id,
181 6
                $where_clause_array,
182 6
                $local_query,
183 6
                $result
184
            );
185 8
            if (! $has_unique_condition) {
186 8
                continue;
187
            }
188
189
            $found_unique_key = true;
190
        }
191
192
        return [
193 8
            $where_clauses,
194 8
            $result,
195 8
            $rows,
196 8
            $found_unique_key,
197
        ];
198
    }
199
200
    /**
201
     * Show message for empty result or set the unique_condition
202
     *
203
     * @param array  $rows               MySQL returned rows
204
     * @param string $key_id             ID in current key
205
     * @param array  $where_clause_array array of where clauses
206
     * @param string $local_query        query performed
207
     * @param array  $result             MySQL result handle
208
     *
209
     * @return bool
210
     */
211 12
    private function showEmptyResultMessageOrSetUniqueCondition(
212
        array $rows,
213
        $key_id,
214
        array $where_clause_array,
215
        $local_query,
216
        array $result
217
    ) {
218 12
        $has_unique_condition = false;
219
220
        // No row returned
221 12
        if (! $rows[$key_id]) {
222 8
            unset($rows[$key_id], $where_clause_array[$key_id]);
223 8
            Response::getInstance()->addHTML(
224 8
                Generator::getMessage(
225 8
                    __('MySQL returned an empty result set (i.e. zero rows).'),
226 8
                    $local_query
227
                )
228
            );
229
            /**
230
             * @todo not sure what should be done at this point, but we must not
231
             * exit if we want the message to be displayed
232
             */
233
        } else {// end if (no row returned)
234 8
            $meta = $this->dbi->getFieldsMeta($result[$key_id]);
235
236 8
            [$unique_condition, $tmp_clause_is_unique] = Util::getUniqueCondition(
237 8
                $result[$key_id],
238 8
                count($meta),
239 8
                $meta,
240 8
                $rows[$key_id],
241 8
                true
242
            );
243
244 8
            if (! empty($unique_condition)) {
245 4
                $has_unique_condition = true;
246
            }
247 8
            unset($unique_condition, $tmp_clause_is_unique);
248
        }
249
250 12
        return $has_unique_condition;
251
    }
252
253
    /**
254
     * No primary key given, just load first row
255
     *
256
     * @param string $table name of the table
257
     * @param string $db    name of the database
258
     *
259
     * @return array containing $result and $rows arrays
260
     */
261 8
    private function loadFirstRow($table, $db)
262
    {
263 8
        $result = $this->dbi->query(
264 8
            'SELECT * FROM ' . Util::backquote($db)
265 8
            . '.' . Util::backquote($table) . ' LIMIT 1;',
266 8
            DatabaseInterface::CONNECT_USER,
267 8
            DatabaseInterface::QUERY_STORE
268
        );
269 8
        $rows = array_fill(0, $GLOBALS['cfg']['InsertRows'], false);
270
271
        return [
272 8
            $result,
273 8
            $rows,
274
        ];
275
    }
276
277
    /**
278
     * Add some url parameters
279
     *
280
     * @param array $url_params         containing $db and $table as url parameters
281
     * @param array $where_clause_array where clauses array
282
     *
283
     * @return array Add some url parameters to $url_params array and return it
284
     */
285 4
    public function urlParamsInEditMode(
286
        array $url_params,
287
        array $where_clause_array
288
    ): array {
289 4
        foreach ($where_clause_array as $where_clause) {
290 4
            $url_params['where_clause'] = trim($where_clause);
291
        }
292 4
        if (! empty($_POST['sql_query'])) {
293 4
            $url_params['sql_query'] = $_POST['sql_query'];
294
        }
295
296 4
        return $url_params;
297
    }
298
299
    /**
300
     * Show type information or function selectors in Insert/Edit
301
     *
302
     * @param string $which      function|type
303
     * @param array  $url_params containing url parameters
304
     * @param bool   $is_show    whether to show the element in $which
305
     *
306
     * @return string an HTML snippet
307
     */
308 16
    public function showTypeOrFunction($which, array $url_params, $is_show)
309
    {
310 16
        $params = [];
311
312 8
        switch ($which) {
313 16
            case 'function':
314 16
                $params['ShowFunctionFields'] = ($is_show ? 0 : 1);
315 16
                $params['ShowFieldTypesInDataEditView']
316 16
                = $GLOBALS['cfg']['ShowFieldTypesInDataEditView'];
317 16
                break;
318 16
            case 'type':
319 16
                $params['ShowFieldTypesInDataEditView'] = ($is_show ? 0 : 1);
320 16
                $params['ShowFunctionFields']
321 16
                = $GLOBALS['cfg']['ShowFunctionFields'];
322 16
                break;
323
        }
324
325 16
        $params['goto'] = Url::getFromRoute('/sql');
326 16
        $this_url_params = array_merge($url_params, $params);
327
328 16
        if (! $is_show) {
329 4
            return ' : <a href="' . Url::getFromRoute('/table/change') . '" data-post="'
330 4
                . Url::getCommon($this_url_params, '') . '">'
331 4
                . $this->showTypeOrFunctionLabel($which)
332 4
                . '</a>';
333
        }
334
335 16
        return '<th><a href="' . Url::getFromRoute('/table/change') . '" data-post="'
336 16
            . Url::getCommon($this_url_params, '')
337 16
            . '" title="' . __('Hide') . '">'
338 16
            . $this->showTypeOrFunctionLabel($which)
339 16
            . '</a></th>';
340
    }
341
342
    /**
343
     * Show type information or function selectors labels in Insert/Edit
344
     *
345
     * @param string $which function|type
346
     *
347
     * @return string|null an HTML snippet
348
     */
349 16
    private function showTypeOrFunctionLabel($which)
350
    {
351 8
        switch ($which) {
352 16
            case 'function':
353 16
                return __('Function');
354 16
            case 'type':
355 16
                return __('Type');
356
        }
357
358
        return null;
359
    }
360
361
     /**
362
      * Analyze the table column array
363
      *
364
      * @param array $column         description of column in given table
365
      * @param array $comments_map   comments for every column that has a comment
366
      * @param bool  $timestamp_seen whether a timestamp has been seen
367
      *
368
      * @return array                   description of column in given table
369
      */
370 16
    private function analyzeTableColumnsArray(
371
        array $column,
372
        array $comments_map,
373
        $timestamp_seen
374
    ) {
375 16
        $column['Field_html']    = htmlspecialchars($column['Field']);
376 16
        $column['Field_md5']     = md5($column['Field']);
377
        // True_Type contains only the type (stops at first bracket)
378 16
        $column['True_Type']     = preg_replace('@\(.*@s', '', $column['Type']);
379 16
        $column['len'] = preg_match('@float|double@', $column['Type']) ? 100 : -1;
380 16
        $column['Field_title']   = $this->getColumnTitle($column, $comments_map);
381 16
        $column['is_binary']     = $this->isColumn(
382 16
            $column,
383
            [
384 16
                'binary',
385
                'varbinary',
386
            ]
387
        );
388 16
        $column['is_blob']       = $this->isColumn(
389 16
            $column,
390
            [
391 16
                'blob',
392
                'tinyblob',
393
                'mediumblob',
394
                'longblob',
395
            ]
396
        );
397 16
        $column['is_char']       = $this->isColumn(
398 16
            $column,
399
            [
400 16
                'char',
401
                'varchar',
402
            ]
403
        );
404
405 16
        [$column['pma_type'], $column['wrap'], $column['first_timestamp']]
406 16
            = $this->getEnumSetAndTimestampColumns($column, $timestamp_seen);
407
408 16
        return $column;
409
    }
410
411
     /**
412
      * Retrieve the column title
413
      *
414
      * @param array $column       description of column in given table
415
      * @param array $comments_map comments for every column that has a comment
416
      *
417
      * @return string              column title
418
      */
419 20
    private function getColumnTitle(array $column, array $comments_map)
420
    {
421 20
        if (isset($comments_map[$column['Field']])) {
422
            return '<span style="border-bottom: 1px dashed black;" title="'
423 4
                . htmlspecialchars($comments_map[$column['Field']]) . '">'
424 4
                . $column['Field_html'] . '</span>';
425
        }
426
427 20
        return $column['Field_html'];
428
    }
429
430
     /**
431
      * check whether the column is of a certain type
432
      * the goal is to ensure that types such as "enum('one','two','binary',..)"
433
      * or "enum('one','two','varbinary',..)" are not categorized as binary
434
      *
435
      * @param array $column description of column in given table
436
      * @param array $types  the types to verify
437
      *
438
      * @return bool whether the column's type if one of the $types
439
      */
440 20
    public function isColumn(array $column, array $types)
441
    {
442 20
        foreach ($types as $one_type) {
443 20
            if (mb_stripos($column['Type'], $one_type) === 0) {
444 14
                return true;
445
            }
446
        }
447
448 20
        return false;
449
    }
450
451
    /**
452
     * Retrieve set, enum, timestamp table columns
453
     *
454
     * @param array $column         description of column in given table
455
     * @param bool  $timestamp_seen whether a timestamp has been seen
456
     *
457
     * @return array $column['pma_type'], $column['wrap'], $column['first_timestamp']
458
     */
459 20
    private function getEnumSetAndTimestampColumns(array $column, $timestamp_seen)
460
    {
461 20
        $column['first_timestamp'] = false;
462 20
        switch ($column['True_Type']) {
463 20
            case 'set':
464 4
                $column['pma_type'] = 'set';
465 4
                $column['wrap']  = '';
466 4
                break;
467 20
            case 'enum':
468 4
                $column['pma_type'] = 'enum';
469 4
                $column['wrap']  = '';
470 4
                break;
471 20
            case 'timestamp':
472 4
                if (! $timestamp_seen) {   // can only occur once per table
473 4
                    $column['first_timestamp'] = true;
474
                }
475 4
                $column['pma_type'] = $column['Type'];
476 4
                $column['wrap']  = ' nowrap';
477 4
                break;
478
479
            default:
480 20
                $column['pma_type'] = $column['Type'];
481 20
                $column['wrap']  = ' nowrap';
482 20
                break;
483
        }
484
485
        return [
486 20
            $column['pma_type'],
487 20
            $column['wrap'],
488 20
            $column['first_timestamp'],
489
        ];
490
    }
491
492
    /**
493
     * The function column
494
     * We don't want binary data to be destroyed
495
     * Note: from the MySQL manual: "BINARY doesn't affect how the column is
496
     *       stored or retrieved" so it does not mean that the contents is binary
497
     *
498
     * @param array  $column                description of column in given table
499
     * @param bool   $is_upload             upload or no
500
     * @param string $column_name_appendix  the name attribute
501
     * @param string $onChangeClause        onchange clause for fields
502
     * @param array  $no_support_types      list of datatypes that are not (yet)
503
     *                                      handled by PMA
504
     * @param int    $tabindex_for_function +3000
505
     * @param int    $tabindex              tab index
506
     * @param int    $idindex               id index
507
     * @param bool   $insert_mode           insert mode or edit mode
508
     * @param bool   $readOnly              is column read only or not
509
     * @param array  $foreignData           foreign key data
510
     *
511
     * @return string                           an html snippet
512
     */
513 16
    private function getFunctionColumn(
514
        array $column,
515
        $is_upload,
516
        $column_name_appendix,
517
        $onChangeClause,
518
        array $no_support_types,
519
        $tabindex_for_function,
520
        $tabindex,
521
        $idindex,
522
        $insert_mode,
523
        $readOnly,
524
        array $foreignData
525
    ): string {
526 16
        $html_output = '';
527 16
        if (($GLOBALS['cfg']['ProtectBinary'] === 'blob'
528 16
            && $column['is_blob'] && ! $is_upload)
529 16
            || ($GLOBALS['cfg']['ProtectBinary'] === 'all'
530 16
            && $column['is_binary'])
531 16
            || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob'
532 16
            && $column['is_binary'])
533
        ) {
534 4
            $html_output .= '<td class="text-center">' . __('Binary') . '</td>' . "\n";
535
        } elseif ($readOnly
536 16
            || mb_strstr($column['True_Type'], 'enum')
537 16
            || mb_strstr($column['True_Type'], 'set')
538 16
            || in_array($column['pma_type'], $no_support_types)
539
        ) {
540 4
            $html_output .= '<td class="text-center">--</td>' . "\n";
541
        } else {
542 16
            $html_output .= '<td>' . "\n";
543
544 16
            $html_output .= '<select name="funcs' . $column_name_appendix . '"'
545 16
                . ' ' . $onChangeClause
546 16
                . ' tabindex="' . ($tabindex + $tabindex_for_function) . '"'
547 16
                . ' id="field_' . $idindex . '_1">';
548 16
            $html_output .= Generator::getFunctionsForField(
549 16
                $column,
550 16
                $insert_mode,
551 16
                $foreignData
552 16
            ) . "\n";
553
554 16
            $html_output .= '</select>' . "\n";
555 16
            $html_output .= '</td>' . "\n";
556
        }
557
558 16
        return $html_output;
559
    }
560
561
    /**
562
     * The null column
563
     *
564
     * @param array  $column               description of column in given table
565
     * @param string $column_name_appendix the name attribute
566
     * @param bool   $real_null_value      is column value null or not null
567
     * @param int    $tabindex             tab index
568
     * @param int    $tabindex_for_null    +6000
569
     * @param int    $idindex              id index
570
     * @param string $vkey                 [multi_edit]['row_id']
571
     * @param array  $foreigners           keys into foreign fields
572
     * @param array  $foreignData          data about the foreign keys
573
     * @param bool   $readOnly             is column read only or not
574
     *
575
     * @return string                       an html snippet
576
     */
577 16
    private function getNullColumn(
578
        array $column,
579
        $column_name_appendix,
580
        $real_null_value,
581
        $tabindex,
582
        $tabindex_for_null,
583
        $idindex,
584
        $vkey,
585
        array $foreigners,
586
        array $foreignData,
587
        $readOnly
588
    ) {
589 16
        if ($column['Null'] != 'YES' || $readOnly) {
590 16
            return "<td></td>\n";
591
        }
592 4
        $html_output = '';
593 4
        $html_output .= '<td>' . "\n";
594
        $html_output .= '<input type="hidden" name="fields_null_prev'
595 4
            . $column_name_appendix . '"';
596 4
        if ($real_null_value && ! $column['first_timestamp']) {
597 4
            $html_output .= ' value="on"';
598
        }
599 4
        $html_output .= '>' . "\n";
600
601
        $html_output .= '<input type="checkbox" class="checkbox_null" tabindex="'
602 4
            . ($tabindex + $tabindex_for_null) . '"'
603 4
            . ' name="fields_null' . $column_name_appendix . '"';
604 4
        if ($real_null_value) {
605 4
            $html_output .= ' checked="checked"';
606
        }
607 4
        $html_output .= ' id="field_' . $idindex . '_2">';
608
609
        // nullify_code is needed by the js nullify() function
610 4
        $nullify_code = $this->getNullifyCodeForNullColumn(
611 4
            $column,
612 3
            $foreigners,
613 3
            $foreignData
614
        );
615
        // to be able to generate calls to nullify() in jQuery
616
        $html_output .= '<input type="hidden" class="nullify_code" name="nullify_code'
617 4
            . $column_name_appendix . '" value="' . $nullify_code . '">';
618
        $html_output .= '<input type="hidden" class="hashed_field" name="hashed_field'
619 4
            . $column_name_appendix . '" value="' . $column['Field_md5'] . '">';
620
        $html_output .= '<input type="hidden" class="multi_edit" name="multi_edit'
621 4
            . $column_name_appendix . '" value="' . Sanitize::escapeJsString($vkey) . '">';
622 4
        $html_output .= '</td>' . "\n";
623
624 4
        return $html_output;
625
    }
626
627
    /**
628
     * Retrieve the nullify code for the null column
629
     *
630
     * @param array $column      description of column in given table
631
     * @param array $foreigners  keys into foreign fields
632
     * @param array $foreignData data about the foreign keys
633
     */
634 8
    private function getNullifyCodeForNullColumn(
635
        array $column,
636
        array $foreigners,
637
        array $foreignData
638
    ): string {
639 8
        $foreigner = $this->relation->searchColumnInForeigners($foreigners, $column['Field']);
640 8
        if (mb_strstr($column['True_Type'], 'enum')) {
641 8
            if (mb_strlen((string) $column['Type']) > 20) {
642 4
                $nullify_code = '1';
643
            } else {
644 8
                $nullify_code = '2';
645
            }
646 4
        } elseif (mb_strstr($column['True_Type'], 'set')) {
647 4
            $nullify_code = '3';
648 4
        } elseif (! empty($foreigners)
649 4
            && ! empty($foreigner)
650 4
            && $foreignData['foreign_link'] == false
651
        ) {
652
            // foreign key in a drop-down
653 4
            $nullify_code = '4';
654
        } elseif (! empty($foreigners)
655
            && ! empty($foreigner)
656
            && $foreignData['foreign_link'] == true
657
        ) {
658
            // foreign key with a browsing icon
659
            $nullify_code = '6';
660
        } else {
661
            $nullify_code = '5';
662
        }
663
664 8
        return $nullify_code;
665
    }
666
667
    /**
668
     * Get the HTML elements for value column in insert form
669
     * (here, "column" is used in the sense of HTML column in HTML table)
670
     *
671
     * @param array  $column                description of column in given table
672
     * @param string $backup_field          hidden input field
673
     * @param string $column_name_appendix  the name attribute
674
     * @param string $onChangeClause        onchange clause for fields
675
     * @param int    $tabindex              tab index
676
     * @param int    $tabindex_for_value    offset for the values tabindex
677
     * @param int    $idindex               id index
678
     * @param string $data                  description of the column field
679
     * @param string $special_chars         special characters
680
     * @param array  $foreignData           data about the foreign keys
681
     * @param array  $paramTableDbArray     array containing $table and $db
682
     * @param int    $rownumber             the row number
683
     * @param array  $titles                An HTML IMG tag for a particular icon from
684
     *                                      a theme, which may be an actual file or
685
     *                                      an icon from a sprite
686
     * @param string $text_dir              text direction
687
     * @param string $special_chars_encoded replaced char if the string starts
688
     *                                      with a \r\n pair (0x0d0a) add an extra \n
689
     * @param string $vkey                  [multi_edit]['row_id']
690
     * @param bool   $is_upload             is upload or not
691
     * @param int    $biggest_max_file_size 0 integer
692
     * @param string $default_char_editing  default char editing mode which is stored
693
     *                                      in the config.inc.php script
694
     * @param array  $no_support_types      list of datatypes that are not (yet)
695
     *                                      handled by PMA
696
     * @param array  $gis_data_types        list of GIS data types
697
     * @param array  $extracted_columnspec  associative array containing type,
698
     *                                      spec_in_brackets and possibly
699
     *                                      enum_set_values (another array)
700
     * @param bool   $readOnly              is column read only or not
701
     *
702
     * @return string an html snippet
703
     */
704 12
    private function getValueColumn(
705
        array $column,
706
        $backup_field,
707
        $column_name_appendix,
708
        $onChangeClause,
709
        $tabindex,
710
        $tabindex_for_value,
711
        $idindex,
712
        $data,
713
        $special_chars,
714
        array $foreignData,
715
        array $paramTableDbArray,
716
        $rownumber,
717
        array $titles,
718
        $text_dir,
719
        $special_chars_encoded,
720
        $vkey,
721
        $is_upload,
722
        $biggest_max_file_size,
723
        $default_char_editing,
724
        array $no_support_types,
725
        array $gis_data_types,
726
        array $extracted_columnspec,
727
        $readOnly
728
    ) {
729
        // HTML5 data-* attribute data-type
730 12
        $data_type = $this->dbi->types->getTypeClass($column['True_Type']);
731 12
        $html_output = '';
732
733 12
        if ($foreignData['foreign_link'] == true) {
734
            $html_output .= $this->getForeignLink(
735
                $column,
736
                $backup_field,
737
                $column_name_appendix,
738
                $onChangeClause,
739
                $tabindex,
740
                $tabindex_for_value,
741
                $idindex,
742
                $data,
743
                $paramTableDbArray,
744
                $rownumber,
745
                $titles,
746
                $readOnly
747
            );
748 12
        } elseif (is_array($foreignData['disp_row'])) {
749
            $html_output .= $this->dispRowForeignData(
750
                $column,
751
                $backup_field,
752
                $column_name_appendix,
753
                $onChangeClause,
754
                $tabindex,
755
                $tabindex_for_value,
756
                $idindex,
757
                $data,
758
                $foreignData,
759
                $readOnly
760
            );
761
        } elseif ((
762 12
                $GLOBALS['cfg']['LongtextDoubleTextarea']
763 8
                && mb_strstr($column['pma_type'], 'longtext'))
764 12
            || mb_strstr($column['pma_type'], 'json')
765
        ) {
766 8
            $html_output .= $this->getTextarea(
767 8
                $column,
768 6
                $backup_field,
769 6
                $column_name_appendix,
770 6
                $onChangeClause,
771 6
                $tabindex,
772 6
                $tabindex_for_value,
773 6
                $idindex,
774 6
                $text_dir,
775 6
                $special_chars_encoded,
776 6
                $data_type,
777 6
                $readOnly
778
            );
779 4
        } elseif (mb_strstr($column['pma_type'], 'text')) {
780
            $html_output .= $this->getTextarea(
781
                $column,
782
                $backup_field,
783
                $column_name_appendix,
784
                $onChangeClause,
785
                $tabindex,
786
                $tabindex_for_value,
787
                $idindex,
788
                $text_dir,
789
                $special_chars_encoded,
790
                $data_type,
791
                $readOnly
792
            );
793
            $html_output .= "\n";
794
            if (mb_strlen($special_chars) > 32000) {
795
                $html_output .= "</td>\n";
796
                $html_output .= '<td>' . __(
797
                    'Because of its length,<br> this column might not be editable.'
798
                );
799
            }
800 4
        } elseif ($column['pma_type'] == 'enum') {
801
            $html_output .= $this->getPmaTypeEnum(
802
                $column,
803
                $backup_field,
804
                $column_name_appendix,
805
                $extracted_columnspec,
806
                $onChangeClause,
807
                $tabindex,
808
                $tabindex_for_value,
809
                $idindex,
810
                $data,
811
                $readOnly
812
            );
813 4
        } elseif ($column['pma_type'] == 'set') {
814
            $html_output .= $this->getPmaTypeSet(
815
                $column,
816
                $extracted_columnspec,
817
                $backup_field,
818
                $column_name_appendix,
819
                $onChangeClause,
820
                $tabindex,
821
                $tabindex_for_value,
822
                $idindex,
823
                $data,
824
                $readOnly
825
            );
826 4
        } elseif ($column['is_binary'] || $column['is_blob']) {
827
            $html_output .= $this->getBinaryAndBlobColumn(
828
                $column,
829
                $data,
830
                $special_chars,
831
                $biggest_max_file_size,
832
                $backup_field,
833
                $column_name_appendix,
834
                $onChangeClause,
835
                $tabindex,
836
                $tabindex_for_value,
837
                $idindex,
838
                $text_dir,
839
                $special_chars_encoded,
840
                $vkey,
841
                $is_upload,
842
                $readOnly
843
            );
844 4
        } elseif (! in_array($column['pma_type'], $no_support_types)) {
845 4
            $html_output .= $this->getValueColumnForOtherDatatypes(
846 4
                $column,
847 3
                $default_char_editing,
848 3
                $backup_field,
849 3
                $column_name_appendix,
850 3
                $onChangeClause,
851 3
                $tabindex,
852 3
                $special_chars,
853 3
                $tabindex_for_value,
854 3
                $idindex,
855 3
                $text_dir,
856 3
                $special_chars_encoded,
857 3
                $data,
858 3
                $extracted_columnspec,
859 3
                $readOnly
860
            );
861
        }
862
863 12
        if (in_array($column['pma_type'], $gis_data_types)) {
864
            $html_output .= $this->getHtmlForGisDataTypes();
865
        }
866
867 12
        return $html_output;
868
    }
869
870
    /**
871
     * Get HTML for foreign link in insert form
872
     *
873
     * @param array  $column               description of column in given table
874
     * @param string $backup_field         hidden input field
875
     * @param string $column_name_appendix the name attribute
876
     * @param string $onChangeClause       onchange clause for fields
877
     * @param int    $tabindex             tab index
878
     * @param int    $tabindex_for_value   offset for the values tabindex
879
     * @param int    $idindex              id index
880
     * @param string $data                 data to edit
881
     * @param array  $paramTableDbArray    array containing $table and $db
882
     * @param int    $rownumber            the row number
883
     * @param array  $titles               An HTML IMG tag for a particular icon from
884
     *                                     a theme, which may be an actual file or
885
     *                                     an icon from a sprite
886
     * @param bool   $readOnly             is column read only or not
887
     *
888
     * @return string                       an html snippet
889
     */
890 4
    private function getForeignLink(
891
        array $column,
892
        $backup_field,
893
        $column_name_appendix,
894
        $onChangeClause,
895
        $tabindex,
896
        $tabindex_for_value,
897
        $idindex,
898
        $data,
899
        array $paramTableDbArray,
900
        $rownumber,
901
        array $titles,
902
        $readOnly
903
    ) {
904 4
        [$table, $db] = $paramTableDbArray;
905 4
        $html_output = '';
906 4
        $html_output .= $backup_field . "\n";
907
908
        $html_output .= '<input type="hidden" name="fields_type'
909 4
            . $column_name_appendix . '" value="foreign">';
910
911 4
        $html_output .= '<input type="text" name="fields' . $column_name_appendix . '" '
912 4
            . 'class="textfield" '
913 4
            . $onChangeClause . ' '
914 4
            . ($readOnly ? 'readonly="readonly" ' : '')
915 4
            . 'tabindex="' . ($tabindex + $tabindex_for_value) . '" '
916 4
            . 'id="field_' . $idindex . '_3" '
917 4
            . 'value="' . htmlspecialchars($data) . '">';
918
919
        $html_output .= '<a class="ajax browse_foreign" href="'
920 4
            . Url::getFromRoute('/browse-foreigners')
921 4
            . '" data-post="'
922 4
            . Url::getCommon(
923
                [
924 4
                    'db' => $db,
925 4
                    'table' => $table,
926 4
                    'field' => $column['Field'],
927 4
                    'rownumber' => $rownumber,
928 4
                    'data'      => $data,
929
                ],
930 4
                ''
931 4
            ) . '">'
932 4
            . str_replace("'", "\'", $titles['Browse']) . '</a>';
933
934 4
        return $html_output;
935
    }
936
937
    /**
938
     * Get HTML to display foreign data
939
     *
940
     * @param array  $column               description of column in given table
941
     * @param string $backup_field         hidden input field
942
     * @param string $column_name_appendix the name attribute
943
     * @param string $onChangeClause       onchange clause for fields
944
     * @param int    $tabindex             tab index
945
     * @param int    $tabindex_for_value   offset for the values tabindex
946
     * @param int    $idindex              id index
947
     * @param string $data                 data to edit
948
     * @param array  $foreignData          data about the foreign keys
949
     * @param bool   $readOnly             is display read only or not
950
     *
951
     * @return string                       an html snippet
952
     */
953 8
    private function dispRowForeignData(
954
        $column,
955
        $backup_field,
956
        $column_name_appendix,
957
        $onChangeClause,
958
        $tabindex,
959
        $tabindex_for_value,
960
        $idindex,
961
        $data,
962
        array $foreignData,
963
        $readOnly
964
    ) {
965 8
        $html_output = '';
966 8
        $html_output .= $backup_field . "\n";
967
        $html_output .= '<input type="hidden"'
968 8
            . ' name="fields_type' . $column_name_appendix . '"';
969 8
        if ($column['is_binary']) {
970 4
            $html_output .= ' value="hex">';
971
        } else {
972 4
            $html_output .= ' value="foreign">';
973
        }
974
975 8
        $html_output .= '<select name="fields' . $column_name_appendix . '"'
976 8
            . ' ' . $onChangeClause
977 8
            . ' class="textfield"'
978 8
            . ($readOnly ? ' disabled' : '')
979 8
            . ' tabindex="' . ($tabindex + $tabindex_for_value) . '"'
980 8
            . ' id="field_' . $idindex . '_3">';
981 8
        $html_output .= $this->relation->foreignDropdown(
982 8
            $foreignData['disp_row'],
983 8
            $foreignData['foreign_field'],
984 8
            $foreignData['foreign_display'],
985 6
            $data,
986 8
            $GLOBALS['cfg']['ForeignKeyMaxLimit']
987
        );
988 8
        $html_output .= '</select>';
989
990
        //Add hidden input, as disabled <select> input does not included in POST.
991 8
        if ($readOnly) {
992
            $html_output .= '<input name="fields' . $column_name_appendix . '"'
993
                . ' type="hidden" value="' . htmlspecialchars($data) . '">';
994
        }
995
996 8
        return $html_output;
997
    }
998
999
    /**
1000
     * Get HTML textarea for insert form
1001
     *
1002
     * @param array  $column                column information
1003
     * @param string $backup_field          hidden input field
1004
     * @param string $column_name_appendix  the name attribute
1005
     * @param string $onChangeClause        onchange clause for fields
1006
     * @param int    $tabindex              tab index
1007
     * @param int    $tabindex_for_value    offset for the values tabindex
1008
     * @param int    $idindex               id index
1009
     * @param string $text_dir              text direction
1010
     * @param string $special_chars_encoded replaced char if the string starts
1011
     *                                      with a \r\n pair (0x0d0a) add an extra \n
1012
     * @param string $data_type             the html5 data-* attribute type
1013
     * @param bool   $readOnly              is column read only or not
1014
     *
1015
     * @return string                       an html snippet
1016
     */
1017 20
    private function getTextarea(
1018
        array $column,
1019
        $backup_field,
1020
        $column_name_appendix,
1021
        $onChangeClause,
1022
        $tabindex,
1023
        $tabindex_for_value,
1024
        $idindex,
1025
        $text_dir,
1026
        $special_chars_encoded,
1027
        $data_type,
1028
        $readOnly
1029
    ) {
1030 20
        $the_class = '';
1031 20
        $textAreaRows = $GLOBALS['cfg']['TextareaRows'];
1032 20
        $textareaCols = $GLOBALS['cfg']['TextareaCols'];
1033
1034 20
        if ($column['is_char']) {
1035
            /**
1036
             * @todo clarify the meaning of the "textfield" class and explain
1037
             *       why character columns have the "char" class instead
1038
             */
1039 12
            $the_class = 'char charField';
1040 12
            $textAreaRows = max($GLOBALS['cfg']['CharTextareaRows'], 7);
1041 12
            $textareaCols = $GLOBALS['cfg']['CharTextareaCols'];
1042 12
            $extracted_columnspec = Util::extractColumnSpec(
1043 12
                $column['Type']
1044
            );
1045 12
            $maxlength = $extracted_columnspec['spec_in_brackets'];
1046 12
        } elseif ($GLOBALS['cfg']['LongtextDoubleTextarea']
1047 12
            && mb_strstr($column['pma_type'], 'longtext')
1048
        ) {
1049 8
            $textAreaRows = $GLOBALS['cfg']['TextareaRows'] * 2;
1050 8
            $textareaCols = $GLOBALS['cfg']['TextareaCols'] * 2;
1051
        }
1052
1053 20
        return $backup_field . "\n"
1054 20
            . '<textarea name="fields' . $column_name_appendix . '"'
1055 20
            . ' class="' . $the_class . '"'
1056 20
            . ($readOnly ? ' readonly="readonly"' : '')
1057 20
            . (isset($maxlength) ? ' data-maxlength="' . $maxlength . '"' : '')
1058 20
            . ' rows="' . $textAreaRows . '"'
1059 20
            . ' cols="' . $textareaCols . '"'
1060 20
            . ' dir="' . $text_dir . '"'
1061 20
            . ' id="field_' . $idindex . '_3"'
1062 20
            . (! empty($onChangeClause) ? ' ' . $onChangeClause : '')
1063 20
            . ' tabindex="' . ($tabindex + $tabindex_for_value) . '"'
1064 20
            . ' data-type="' . $data_type . '">'
1065 20
            . $special_chars_encoded
1066 20
            . '</textarea>';
1067
    }
1068
1069
    /**
1070
     * Get HTML for enum type
1071
     *
1072
     * @param array  $column               description of column in given table
1073
     * @param string $backup_field         hidden input field
1074
     * @param string $column_name_appendix the name attribute
1075
     * @param array  $extracted_columnspec associative array containing type,
1076
     *                                     spec_in_brackets and possibly
1077
     *                                     enum_set_values (another array)
1078
     * @param string $onChangeClause       onchange clause for fields
1079
     * @param int    $tabindex             tab index
1080
     * @param int    $tabindex_for_value   offset for the values tabindex
1081
     * @param int    $idindex              id index
1082
     * @param mixed  $data                 data to edit
1083
     * @param bool   $readOnly             is column read only or not
1084
     *
1085
     * @return string an html snippet
1086
     */
1087 4
    private function getPmaTypeEnum(
1088
        array $column,
1089
        $backup_field,
1090
        $column_name_appendix,
1091
        array $extracted_columnspec,
1092
        $onChangeClause,
1093
        $tabindex,
1094
        $tabindex_for_value,
1095
        $idindex,
1096
        $data,
1097
        $readOnly
1098
    ) {
1099 4
        $html_output = '';
1100 4
        if (! isset($column['values'])) {
1101
            $column['values'] = $this->getColumnEnumValues(
1102
                $column,
1103
                $extracted_columnspec
1104
            );
1105
        }
1106 4
        $column_enum_values = $column['values'];
1107
        $html_output .= '<input type="hidden" name="fields_type'
1108 4
            . $column_name_appendix . '" value="enum">';
1109 4
        $html_output .= "\n" . '            ' . $backup_field . "\n";
1110 4
        if (mb_strlen($column['Type']) > 20) {
1111 4
            $html_output .= $this->getDropDownDependingOnLength(
1112 4
                $column,
1113 3
                $column_name_appendix,
1114 3
                $onChangeClause,
1115 3
                $tabindex,
1116 3
                $tabindex_for_value,
1117 3
                $idindex,
1118 3
                $data,
1119 3
                $column_enum_values,
1120 3
                $readOnly
1121
            );
1122
        } else {
1123 4
            $html_output .= $this->getRadioButtonDependingOnLength(
1124 4
                $column_name_appendix,
1125 3
                $onChangeClause,
1126 3
                $tabindex,
1127 3
                $column,
1128 3
                $tabindex_for_value,
1129 3
                $idindex,
1130 3
                $data,
1131 3
                $column_enum_values,
1132 3
                $readOnly
1133
            );
1134
        }
1135
1136 4
        return $html_output;
1137
    }
1138
1139
    /**
1140
     * Get column values
1141
     *
1142
     * @param array $column               description of column in given table
1143
     * @param array $extracted_columnspec associative array containing type,
1144
     *                                    spec_in_brackets and possibly enum_set_values
1145
     *                                    (another array)
1146
     *
1147
     * @return array column values as an associative array
1148
     */
1149 4
    private function getColumnEnumValues(array $column, array $extracted_columnspec)
1150
    {
1151 4
        $column['values'] = [];
1152 4
        foreach ($extracted_columnspec['enum_set_values'] as $val) {
1153 4
            $column['values'][] = [
1154 4
                'plain' => $val,
1155 4
                'html'  => htmlspecialchars($val),
1156
            ];
1157
        }
1158
1159 4
        return $column['values'];
1160
    }
1161
1162
    /**
1163
     * Get HTML drop down for more than 20 string length
1164
     *
1165
     * @param array  $column               description of column in given table
1166
     * @param string $column_name_appendix the name attribute
1167
     * @param string $onChangeClause       onchange clause for fields
1168
     * @param int    $tabindex             tab index
1169
     * @param int    $tabindex_for_value   offset for the values tabindex
1170
     * @param int    $idindex              id index
1171
     * @param string $data                 data to edit
1172
     * @param array  $column_enum_values   $column['values']
1173
     * @param bool   $readOnly             is column read only or not
1174
     *
1175
     * @return string                       an html snippet
1176
     */
1177 8
    private function getDropDownDependingOnLength(
1178
        array $column,
1179
        $column_name_appendix,
1180
        $onChangeClause,
1181
        $tabindex,
1182
        $tabindex_for_value,
1183
        $idindex,
1184
        $data,
1185
        array $column_enum_values,
1186
        $readOnly
1187
    ) {
1188 8
        $html_output = '<select name="fields' . $column_name_appendix . '"'
1189 8
            . ' ' . $onChangeClause
1190 8
            . ' class="textfield"'
1191 8
            . ' tabindex="' . ($tabindex + $tabindex_for_value) . '"'
1192 8
            . ($readOnly ? ' disabled' : '')
1193 8
            . ' id="field_' . $idindex . '_3">';
1194 8
        $html_output .= '<option value="">&nbsp;</option>' . "\n";
1195
1196 8
        $selected_html = '';
1197 8
        foreach ($column_enum_values as $enum_value) {
1198 8
            $html_output .= '<option value="' . $enum_value['html'] . '"';
1199 8
            if ($data == $enum_value['plain']
1200 8
                || ($data == ''
1201 8
                && (! isset($_POST['where_clause']) || $column['Null'] != 'YES')
1202 8
                && isset($column['Default'])
1203 8
                && $enum_value['plain'] == $column['Default'])
1204
            ) {
1205 4
                $html_output .= ' selected="selected"';
1206 4
                $selected_html = $enum_value['html'];
1207
            }
1208 8
            $html_output .= '>' . $enum_value['html'] . '</option>' . "\n";
1209
        }
1210 8
        $html_output .= '</select>';
1211
1212
        //Add hidden input, as disabled <select> input does not included in POST.
1213 8
        if ($readOnly) {
1214
            $html_output .= '<input name="fields' . $column_name_appendix . '"'
1215
                . ' type="hidden" value="' . $selected_html . '">';
1216
        }
1217
1218 8
        return $html_output;
1219
    }
1220
1221
    /**
1222
     * Get HTML radio button for less than 20 string length
1223
     *
1224
     * @param string $column_name_appendix the name attribute
1225
     * @param string $onChangeClause       onchange clause for fields
1226
     * @param int    $tabindex             tab index
1227
     * @param array  $column               description of column in given table
1228
     * @param int    $tabindex_for_value   offset for the values tabindex
1229
     * @param int    $idindex              id index
1230
     * @param string $data                 data to edit
1231
     * @param array  $column_enum_values   $column['values']
1232
     * @param bool   $readOnly             is column read only or not
1233
     *
1234
     * @return string                       an html snippet
1235
     */
1236 8
    private function getRadioButtonDependingOnLength(
1237
        $column_name_appendix,
1238
        $onChangeClause,
1239
        $tabindex,
1240
        array $column,
1241
        $tabindex_for_value,
1242
        $idindex,
1243
        $data,
1244
        array $column_enum_values,
1245
        $readOnly
1246
    ) {
1247 8
        $j = 0;
1248 8
        $html_output = '';
1249 8
        foreach ($column_enum_values as $enum_value) {
1250
            $html_output .= '            '
1251 8
                . '<input type="radio" name="fields' . $column_name_appendix . '"'
1252 8
                . ' class="textfield"'
1253 8
                . ' value="' . $enum_value['html'] . '"'
1254 8
                . ' id="field_' . $idindex . '_3_' . $j . '"'
1255 8
                . ' ' . $onChangeClause;
1256 8
            if ($data == $enum_value['plain']
1257 8
                || ($data == ''
1258 8
                && (! isset($_POST['where_clause']) || $column['Null'] != 'YES')
1259 8
                && isset($column['Default'])
1260 8
                && $enum_value['plain'] == $column['Default'])
1261
            ) {
1262 4
                $html_output .= ' checked="checked"';
1263 8
            } elseif ($readOnly) {
1264
                $html_output .= ' disabled';
1265
            }
1266 8
            $html_output .= ' tabindex="' . ($tabindex + $tabindex_for_value) . '">';
1267 8
            $html_output .= '<label for="field_' . $idindex . '_3_' . $j . '">'
1268 8
                . $enum_value['html'] . '</label>' . "\n";
1269 8
            $j++;
1270
        }
1271
1272 8
        return $html_output;
1273
    }
1274
1275
    /**
1276
     * Get the HTML for 'set' pma type
1277
     *
1278
     * @param array  $column               description of column in given table
1279
     * @param array  $extracted_columnspec associative array containing type,
1280
     *                                     spec_in_brackets and possibly
1281
     *                                     enum_set_values (another array)
1282
     * @param string $backup_field         hidden input field
1283
     * @param string $column_name_appendix the name attribute
1284
     * @param string $onChangeClause       onchange clause for fields
1285
     * @param int    $tabindex             tab index
1286
     * @param int    $tabindex_for_value   offset for the values tabindex
1287
     * @param int    $idindex              id index
1288
     * @param string $data                 description of the column field
1289
     * @param bool   $readOnly             is column read only or not
1290
     *
1291
     * @return string                       an html snippet
1292
     */
1293 4
    private function getPmaTypeSet(
1294
        array $column,
1295
        array $extracted_columnspec,
1296
        $backup_field,
1297
        $column_name_appendix,
1298
        $onChangeClause,
1299
        $tabindex,
1300
        $tabindex_for_value,
1301
        $idindex,
1302
        $data,
1303
        $readOnly
1304
    ) {
1305 4
        [$column_set_values, $select_size] = $this->getColumnSetValueAndSelectSize(
1306 4
            $column,
1307 3
            $extracted_columnspec
1308
        );
1309 4
        $vset = array_flip(explode(',', $data));
1310 4
        $html_output = $backup_field . "\n";
1311
        $html_output .= '<input type="hidden" name="fields_type'
1312 4
            . $column_name_appendix . '" value="set">';
1313 4
        $html_output .= '<select name="fields' . $column_name_appendix . '[]"'
1314 4
            . ' class="textfield"'
1315 4
            . ($readOnly ? ' disabled' : '')
1316 4
            . ' size="' . $select_size . '"'
1317 4
            . ' multiple="multiple"'
1318 4
            . ' ' . $onChangeClause
1319 4
            . ' tabindex="' . ($tabindex + $tabindex_for_value) . '"'
1320 4
            . ' id="field_' . $idindex . '_3">';
1321
1322 4
        $selected_html = '';
1323 4
        foreach ($column_set_values as $column_set_value) {
1324 4
            $html_output .= '<option value="' . $column_set_value['html'] . '"';
1325 4
            if (isset($vset[$column_set_value['plain']])) {
1326 4
                $html_output .= ' selected="selected"';
1327 4
                $selected_html = $column_set_value['html'];
1328
            }
1329 4
            $html_output .= '>' . $column_set_value['html'] . '</option>' . "\n";
1330
        }
1331 4
        $html_output .= '</select>';
1332
1333
        //Add hidden input, as disabled <select> input does not included in POST.
1334 4
        if ($readOnly) {
1335
            $html_output .= '<input name="fields' . $column_name_appendix . '[]"'
1336
                . ' type="hidden" value="' . $selected_html . '">';
1337
        }
1338
1339 4
        return $html_output;
1340
    }
1341
1342
    /**
1343
     * Retrieve column 'set' value and select size
1344
     *
1345
     * @param array $column               description of column in given table
1346
     * @param array $extracted_columnspec associative array containing type,
1347
     *                                    spec_in_brackets and possibly enum_set_values
1348
     *                                    (another array)
1349
     *
1350
     * @return array $column['values'], $column['select_size']
1351
     */
1352 8
    private function getColumnSetValueAndSelectSize(
1353
        array $column,
1354
        array $extracted_columnspec
1355
    ) {
1356 8
        if (! isset($column['values'])) {
1357 4
            $column['values'] = [];
1358 4
            foreach ($extracted_columnspec['enum_set_values'] as $val) {
1359 4
                $column['values'][] = [
1360 4
                    'plain' => $val,
1361 4
                    'html'  => htmlspecialchars($val),
1362
                ];
1363
            }
1364 4
            $column['select_size'] = min(4, count($column['values']));
1365
        }
1366
1367
        return [
1368 8
            $column['values'],
1369 8
            $column['select_size'],
1370
        ];
1371
    }
1372
1373
    /**
1374
     * Get HTML for binary and blob column
1375
     *
1376
     * @param array       $column                description of column in given table
1377
     * @param string|null $data                  data to edit
1378
     * @param string      $special_chars         special characters
1379
     * @param int         $biggest_max_file_size biggest max file size for uploading
1380
     * @param string      $backup_field          hidden input field
1381
     * @param string      $column_name_appendix  the name attribute
1382
     * @param string      $onChangeClause        onchange clause for fields
1383
     * @param int         $tabindex              tab index
1384
     * @param int         $tabindex_for_value    offset for the values tabindex
1385
     * @param int         $idindex               id index
1386
     * @param string      $text_dir              text direction
1387
     * @param string      $special_chars_encoded replaced char if the string starts
1388
     *                                           with a \r\n pair (0x0d0a) add an
1389
     *                                           extra \n
1390
     * @param string      $vkey                  [multi_edit]['row_id']
1391
     * @param bool        $is_upload             is upload or not
1392
     * @param bool        $readOnly              is column read only or not
1393
     *
1394
     * @return string                           an html snippet
1395
     */
1396 4
    private function getBinaryAndBlobColumn(
1397
        array $column,
1398
        ?string $data,
1399
        $special_chars,
1400
        $biggest_max_file_size,
1401
        $backup_field,
1402
        $column_name_appendix,
1403
        $onChangeClause,
1404
        $tabindex,
1405
        $tabindex_for_value,
1406
        $idindex,
1407
        $text_dir,
1408
        $special_chars_encoded,
1409
        $vkey,
1410
        $is_upload,
1411
        $readOnly
1412
    ) {
1413 4
        $html_output = '';
1414
        // Add field type : Protected or Hexadecimal
1415
        $fields_type_html = '<input type="hidden" name="fields_type'
1416 4
            . $column_name_appendix . '" value="%s">';
1417
        // Default value : hex
1418 4
        $fields_type_val = 'hex';
1419 4
        if (($GLOBALS['cfg']['ProtectBinary'] === 'blob' && $column['is_blob'])
1420 4
            || ($GLOBALS['cfg']['ProtectBinary'] === 'all')
1421 4
            || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' && ! $column['is_blob'])
1422
        ) {
1423 4
            $html_output .= __('Binary - do not edit');
1424 4
            if (isset($data)) {
1425 4
                $data_size = Util::formatByteDown(
1426 4
                    mb_strlen(stripslashes($data)),
1427 4
                    3,
1428 4
                    1
1429
                );
1430 4
                $html_output .= ' (' . $data_size[0] . ' ' . $data_size[1] . ')';
1431 4
                unset($data_size);
1432
            }
1433 4
            $fields_type_val = 'protected';
1434
            $html_output .= '<input type="hidden" name="fields'
1435 4
                . $column_name_appendix . '" value="">';
1436 4
        } elseif ($column['is_blob']
1437 4
            || ($column['len'] > $GLOBALS['cfg']['LimitChars'])
1438
        ) {
1439 4
            $html_output .= "\n" . $this->getTextarea(
1440 4
                $column,
1441 4
                $backup_field,
1442 4
                $column_name_appendix,
1443 4
                $onChangeClause,
1444 4
                $tabindex,
1445 4
                $tabindex_for_value,
1446 4
                $idindex,
1447 4
                $text_dir,
1448 4
                $special_chars_encoded,
1449 4
                'HEX',
1450 4
                $readOnly
1451
            );
1452
        } else {
1453
            // field size should be at least 4 and max $GLOBALS['cfg']['LimitChars']
1454 4
            $fieldsize = min(max($column['len'], 4), $GLOBALS['cfg']['LimitChars']);
1455 4
            $html_output .= "\n" . $backup_field . "\n" . $this->getHtmlInput(
1456 4
                $column,
1457 3
                $column_name_appendix,
1458 3
                $special_chars,
1459 3
                $fieldsize,
1460 3
                $onChangeClause,
1461 3
                $tabindex,
1462 3
                $tabindex_for_value,
1463 3
                $idindex,
1464 4
                'HEX',
1465 3
                $readOnly
1466
            );
1467
        }
1468 4
        $html_output .= sprintf($fields_type_html, $fields_type_val);
1469
1470 4
        if ($is_upload && $column['is_blob'] && ! $readOnly) {
1471
            // We don't want to prevent users from using
1472
            // browser's default drag-drop feature on some page(s),
1473
            // so we add noDragDrop class to the input
1474
            $html_output .= '<br>'
1475
                . '<input type="file"'
1476 4
                . ' name="fields_upload' . $vkey . '[' . $column['Field_md5'] . ']"'
1477 4
                . ' class="textfield noDragDrop" id="field_' . $idindex . '_3" size="10"'
1478 4
                . ' ' . $onChangeClause . '>&nbsp;';
1479 4
            [$html_out] = $this->getMaxUploadSize(
1480 4
                $column,
1481 3
                $biggest_max_file_size
1482
            );
1483 4
            $html_output .= $html_out;
1484
        }
1485
1486 4
        if (! empty($GLOBALS['cfg']['UploadDir']) && ! $readOnly) {
1487
            $html_output .= $this->getSelectOptionForUpload($vkey, $column);
1488
        }
1489
1490 4
        return $html_output;
1491
    }
1492
1493
    /**
1494
     * Get HTML input type
1495
     *
1496
     * @param array  $column               description of column in given table
1497
     * @param string $column_name_appendix the name attribute
1498
     * @param string $special_chars        special characters
1499
     * @param int    $fieldsize            html field size
1500
     * @param string $onChangeClause       onchange clause for fields
1501
     * @param int    $tabindex             tab index
1502
     * @param int    $tabindex_for_value   offset for the values tabindex
1503
     * @param int    $idindex              id index
1504
     * @param string $data_type            the html5 data-* attribute type
1505
     * @param bool   $readOnly             is column read only or not
1506
     *
1507
     * @return string                       an html snippet
1508
     */
1509 16
    private function getHtmlInput(
1510
        array $column,
1511
        $column_name_appendix,
1512
        $special_chars,
1513
        $fieldsize,
1514
        $onChangeClause,
1515
        $tabindex,
1516
        $tabindex_for_value,
1517
        $idindex,
1518
        $data_type,
1519
        $readOnly
1520
    ) {
1521 16
        $input_type = 'text';
1522
        // do not use the 'date' or 'time' types here; they have no effect on some
1523
        // browsers and create side effects (see bug #4218)
1524
1525 16
        $the_class = 'textfield';
1526
        // verify True_Type which does not contain the parentheses and length
1527 16
        if (! $readOnly) {
1528 16
            if ($column['True_Type'] === 'date') {
1529 4
                $the_class .= ' datefield';
1530 16
            } elseif ($column['True_Type'] === 'time') {
1531
                $the_class .= ' timefield';
1532 16
            } elseif ($column['True_Type'] === 'datetime'
1533 16
                || $column['True_Type'] === 'timestamp'
1534
            ) {
1535 12
                $the_class .= ' datetimefield';
1536
            }
1537
        }
1538 16
        $input_min_max = false;
1539 16
        if (in_array($column['True_Type'], $this->dbi->types->getIntegerTypes())) {
1540
            $extracted_columnspec = Util::extractColumnSpec(
1541
                $column['Type']
1542
            );
1543
            $is_unsigned = $extracted_columnspec['unsigned'];
1544
            $min_max_values = $this->dbi->types->getIntegerRange(
1545
                $column['True_Type'],
1546
                ! $is_unsigned
1547
            );
1548
            $input_min_max = 'min="' . $min_max_values[0] . '" '
1549
                . 'max="' . $min_max_values[1] . '"';
1550
            $data_type = 'INT';
1551
        }
1552
1553 16
        return '<input type="' . $input_type . '"'
1554 16
            . ' name="fields' . $column_name_appendix . '"'
1555 16
            . ' value="' . $special_chars . '" size="' . $fieldsize . '"'
1556 16
            . (isset($column['is_char']) && $column['is_char']
1557
            ? ' data-maxlength="' . $fieldsize . '"'
1558 16
            : '')
1559 16
            . ($readOnly ? ' readonly="readonly"' : '')
1560 16
            . ($input_min_max !== false ? ' ' . $input_min_max : '')
1561 16
            . ' data-type="' . $data_type . '"'
1562 16
            . ($input_type === 'time' ? ' step="1"' : '')
1563 16
            . ' class="' . $the_class . '" ' . $onChangeClause
1564 16
            . ' tabindex="' . ($tabindex + $tabindex_for_value) . '"'
1565 16
            . ' id="field_' . $idindex . '_3">';
1566
    }
1567
1568
    /**
1569
     * Get HTML select option for upload
1570
     *
1571
     * @param string $vkey   [multi_edit]['row_id']
1572
     * @param array  $column description of column in given table
1573
     *
1574
     * @return string|null an html snippet
1575
     */
1576
    private function getSelectOptionForUpload($vkey, array $column)
1577
    {
1578
        $files = $this->fileListing->getFileSelectOptions(
1579
            Util::userDir($GLOBALS['cfg']['UploadDir'])
1580
        );
1581
1582
        if ($files === false) {
1583
            return '<span style="color:red">' . __('Error') . '</span><br>' . "\n"
1584
                . __('The directory you set for upload work cannot be reached.') . "\n";
1585
        }
1586
1587
        if (! empty($files)) {
1588
            return "<br>\n"
1589
                . '<i>' . __('Or') . '</i> '
1590
                . __('web server upload directory:') . '<br>' . "\n"
1591
                . '<select size="1" name="fields_uploadlocal'
1592
                . $vkey . '[' . $column['Field_md5'] . ']">' . "\n"
1593
                . '<option value="" selected="selected"></option>' . "\n"
1594
                . $files
1595
                . '</select>' . "\n";
1596
        }
1597
1598
        return null;
1599
    }
1600
1601
    /**
1602
     * Retrieve the maximum upload file size
1603
     *
1604
     * @param array $column                description of column in given table
1605
     * @param int   $biggest_max_file_size biggest max file size for uploading
1606
     *
1607
     * @return array an html snippet and $biggest_max_file_size
1608
     */
1609 8
    private function getMaxUploadSize(array $column, $biggest_max_file_size)
1610
    {
1611
        // find maximum upload size, based on field type
1612
        /**
1613
         * @todo with functions this is not so easy, as you can basically
1614
         * process any data with function like MD5
1615
         */
1616 8
        global $max_upload_size;
1617
        $max_field_sizes = [
1618 8
            'tinyblob'   =>        '256',
1619
            'blob'       =>      '65536',
1620
            'mediumblob' =>   '16777216',
1621
            'longblob'   => '4294967296',// yeah, really
1622
        ];
1623
1624 8
        $this_field_max_size = $max_upload_size; // from PHP max
1625 8
        if ($this_field_max_size > $max_field_sizes[$column['pma_type']]) {
1626 4
            $this_field_max_size = $max_field_sizes[$column['pma_type']];
1627
        }
1628
        $html_output
1629 8
            = Util::getFormattedMaximumUploadSize(
1630 8
                $this_field_max_size
1631 8
            ) . "\n";
1632
        // do not generate here the MAX_FILE_SIZE, because we should
1633
        // put only one in the form to accommodate the biggest field
1634 8
        if ($this_field_max_size > $biggest_max_file_size) {
1635 8
            $biggest_max_file_size = $this_field_max_size;
1636
        }
1637
1638
        return [
1639 8
            $html_output,
1640 8
            $biggest_max_file_size,
1641
        ];
1642
    }
1643
1644
    /**
1645
     * Get HTML for the Value column of other datatypes
1646
     * (here, "column" is used in the sense of HTML column in HTML table)
1647
     *
1648
     * @param array  $column                description of column in given table
1649
     * @param string $default_char_editing  default char editing mode which is stored
1650
     *                                      in the config.inc.php script
1651
     * @param string $backup_field          hidden input field
1652
     * @param string $column_name_appendix  the name attribute
1653
     * @param string $onChangeClause        onchange clause for fields
1654
     * @param int    $tabindex              tab index
1655
     * @param string $special_chars         special characters
1656
     * @param int    $tabindex_for_value    offset for the values tabindex
1657
     * @param int    $idindex               id index
1658
     * @param string $text_dir              text direction
1659
     * @param string $special_chars_encoded replaced char if the string starts
1660
     *                                      with a \r\n pair (0x0d0a) add an extra \n
1661
     * @param string $data                  data to edit
1662
     * @param array  $extracted_columnspec  associative array containing type,
1663
     *                                      spec_in_brackets and possibly
1664
     *                                      enum_set_values (another array)
1665
     * @param bool   $readOnly              is column read only or not
1666
     *
1667
     * @return string an html snippet
1668
     */
1669 8
    private function getValueColumnForOtherDatatypes(
1670
        array $column,
1671
        $default_char_editing,
1672
        $backup_field,
1673
        $column_name_appendix,
1674
        $onChangeClause,
1675
        $tabindex,
1676
        $special_chars,
1677
        $tabindex_for_value,
1678
        $idindex,
1679
        $text_dir,
1680
        $special_chars_encoded,
1681
        $data,
1682
        array $extracted_columnspec,
1683
        $readOnly
1684
    ) {
1685
        // HTML5 data-* attribute data-type
1686 8
        $data_type = $this->dbi->types->getTypeClass($column['True_Type']);
1687 8
        $fieldsize = $this->getColumnSize($column, $extracted_columnspec);
1688 8
        $html_output = $backup_field . "\n";
1689 8
        if ($column['is_char']
1690 4
            && ($GLOBALS['cfg']['CharEditing'] == 'textarea'
1691 8
            || mb_strpos($data, "\n") !== false)
1692
        ) {
1693 4
            $html_output .= "\n";
1694 4
            $GLOBALS['cfg']['CharEditing'] = $default_char_editing;
1695 4
            $html_output .= $this->getTextarea(
1696 4
                $column,
1697 4
                $backup_field,
1698 4
                $column_name_appendix,
1699 4
                $onChangeClause,
1700 4
                $tabindex,
1701 4
                $tabindex_for_value,
1702 4
                $idindex,
1703 4
                $text_dir,
1704 4
                $special_chars_encoded,
1705 4
                $data_type,
1706 4
                $readOnly
1707
            );
1708
        } else {
1709 8
            $html_output .= $this->getHtmlInput(
1710 8
                $column,
1711 8
                $column_name_appendix,
1712 8
                $special_chars,
1713 8
                $fieldsize,
1714 8
                $onChangeClause,
1715 8
                $tabindex,
1716 8
                $tabindex_for_value,
1717 8
                $idindex,
1718 8
                $data_type,
1719 8
                $readOnly
1720
            );
1721
1722 8
            if (preg_match('/(VIRTUAL|PERSISTENT|GENERATED)/', $column['Extra'])
1723 8
                && strpos($column['Extra'], 'DEFAULT_GENERATED') === false
1724
            ) {
1725
                $html_output .= '<input type="hidden" name="virtual'
1726
                    . $column_name_appendix . '" value="1">';
1727
            }
1728 8
            if ($column['Extra'] == 'auto_increment') {
1729
                $html_output .= '<input type="hidden" name="auto_increment'
1730 4
                    . $column_name_appendix . '" value="1">';
1731
            }
1732 8
            if (substr($column['pma_type'], 0, 9) == 'timestamp') {
1733
                $html_output .= '<input type="hidden" name="fields_type'
1734 4
                    . $column_name_appendix . '" value="timestamp">';
1735
            }
1736 8
            if (substr($column['pma_type'], 0, 8) == 'datetime') {
1737
                $html_output .= '<input type="hidden" name="fields_type'
1738 8
                    . $column_name_appendix . '" value="datetime">';
1739
            }
1740 8
            if ($column['True_Type'] == 'bit') {
1741
                $html_output .= '<input type="hidden" name="fields_type'
1742
                    . $column_name_appendix . '" value="bit">';
1743
            }
1744
        }
1745
1746 8
        return $html_output;
1747
    }
1748
1749
    /**
1750
     * Get the field size
1751
     *
1752
     * @param array $column               description of column in given table
1753
     * @param array $extracted_columnspec associative array containing type,
1754
     *                                    spec_in_brackets and possibly enum_set_values
1755
     *                                    (another array)
1756
     *
1757
     * @return int field size
1758
     */
1759 12
    private function getColumnSize(array $column, array $extracted_columnspec)
1760
    {
1761 12
        if ($column['is_char']) {
1762 8
            $fieldsize = $extracted_columnspec['spec_in_brackets'];
1763 8
            if ($fieldsize > $GLOBALS['cfg']['MaxSizeForInputField']) {
1764
                /**
1765
                 * This case happens for CHAR or VARCHAR columns which have
1766
                 * a size larger than the maximum size for input field.
1767
                 */
1768 8
                $GLOBALS['cfg']['CharEditing'] = 'textarea';
1769
            }
1770
        } else {
1771
            /**
1772
             * This case happens for example for INT or DATE columns;
1773
             * in these situations, the value returned in $column['len']
1774
             * seems appropriate.
1775
             */
1776 12
            $fieldsize = $column['len'];
1777
        }
1778
1779 12
        return min(
1780 12
            max($fieldsize, $GLOBALS['cfg']['MinSizeForInputField']),
1781 12
            $GLOBALS['cfg']['MaxSizeForInputField']
1782
        );
1783
    }
1784
1785
    /**
1786
     * Get HTML for gis data types
1787
     *
1788
     * @return string an html snippet
1789
     */
1790 4
    private function getHtmlForGisDataTypes()
1791
    {
1792 4
        $edit_str = Generator::getIcon('b_edit', __('Edit/Insert'));
1793
1794
        return '<span class="open_gis_editor">'
1795 4
            . Generator::linkOrButton(
1796 4
                '#',
1797 4
                $edit_str,
1798 4
                [],
1799 4
                '_blank'
1800
            )
1801 4
            . '</span>';
1802
    }
1803
1804
    /**
1805
     * get html for continue insertion form
1806
     *
1807
     * @param string $table              name of the table
1808
     * @param string $db                 name of the database
1809
     * @param array  $where_clause_array array of where clauses
1810
     * @param string $err_url            error url
1811
     *
1812
     * @return string                   an html snippet
1813
     */
1814 4
    public function getContinueInsertionForm(
1815
        $table,
1816
        $db,
1817
        array $where_clause_array,
1818
        $err_url
1819
    ) {
1820 4
        return $this->template->render('table/insert/continue_insertion_form', [
1821 4
            'db' => $db,
1822 4
            'table' => $table,
1823 4
            'where_clause_array' => $where_clause_array,
1824 4
            'err_url' => $err_url,
1825 4
            'goto' => $GLOBALS['goto'],
1826 4
            'sql_query' => $_POST['sql_query'] ?? null,
1827 4
            'has_where_clause' => isset($_POST['where_clause']),
1828 4
            'insert_rows_default' => $GLOBALS['cfg']['InsertRows'],
1829
        ]);
1830
    }
1831
1832
    /**
1833
     * Get action panel
1834
     *
1835
     * @param array|null $where_clause       where clause
1836
     * @param string     $after_insert       insert mode, e.g. new_insert, same_insert
1837
     * @param int        $tabindex           tab index
1838
     * @param int        $tabindex_for_value offset for the values tabindex
1839
     * @param bool       $found_unique_key   boolean variable for unique key
1840
     *
1841
     * @return string an html snippet
1842
     */
1843 4
    public function getActionsPanel(
1844
        $where_clause,
1845
        $after_insert,
1846
        $tabindex,
1847
        $tabindex_for_value,
1848
        $found_unique_key
1849
    ) {
1850
        $html_output = '<fieldset id="actions_panel">'
1851
            . '<table cellpadding="5" cellspacing="0" class="tdblock w-100">'
1852
            . '<tr>'
1853
            . '<td class="nowrap vmiddle">'
1854 4
            . $this->getSubmitTypeDropDown($where_clause, $tabindex, $tabindex_for_value)
1855 4
            . "\n";
1856
1857
        $html_output .= '</td>'
1858
            . '<td class="vmiddle">'
1859
            . '&nbsp;&nbsp;&nbsp;<strong>'
1860 4
            . __('and then') . '</strong>&nbsp;&nbsp;&nbsp;'
1861 4
            . '</td>'
1862 4
            . '<td class="nowrap vmiddle">'
1863 4
            . $this->getAfterInsertDropDown(
1864 4
                $where_clause,
1865 3
                $after_insert,
1866 3
                $found_unique_key
1867
            )
1868 4
            . '</td>'
1869 4
            . '</tr>';
1870
        $html_output .= '<tr>'
1871 4
            . $this->getSubmitAndResetButtonForActionsPanel($tabindex, $tabindex_for_value)
1872 4
            . '</tr>'
1873 4
            . '</table>'
1874 4
            . '</fieldset>';
1875
1876 4
        return $html_output;
1877
    }
1878
1879
    /**
1880
     * Get a HTML drop down for submit types
1881
     *
1882
     * @param array|null $where_clause       where clause
1883
     * @param int        $tabindex           tab index
1884
     * @param int        $tabindex_for_value offset for the values tabindex
1885
     *
1886
     * @return string                       an html snippet
1887
     */
1888 8
    private function getSubmitTypeDropDown(
1889
        $where_clause,
1890
        $tabindex,
1891
        $tabindex_for_value
1892
    ) {
1893
        $html_output = '<select name="submit_type" class="control_at_footer" tabindex="'
1894 8
            . ($tabindex + $tabindex_for_value + 1) . '">';
1895 8
        if (isset($where_clause)) {
1896 4
            $html_output .= '<option value="save">' . __('Save') . '</option>';
1897
        }
1898
        $html_output .= '<option value="insert">'
1899 8
            . __('Insert as new row')
1900 8
            . '</option>'
1901 8
            . '<option value="insertignore">'
1902 8
            . __('Insert as new row and ignore errors')
1903 8
            . '</option>'
1904 8
            . '<option value="showinsert">'
1905 8
            . __('Show insert query')
1906 8
            . '</option>'
1907 8
            . '</select>';
1908
1909 8
        return $html_output;
1910
    }
1911
1912
    /**
1913
     * Get HTML drop down for after insert
1914
     *
1915
     * @param array|null $where_clause     where clause
1916
     * @param string     $after_insert     insert mode, e.g. new_insert, same_insert
1917
     * @param bool       $found_unique_key boolean variable for unique key
1918
     *
1919
     * @return string                   an html snippet
1920
     */
1921 8
    private function getAfterInsertDropDown($where_clause, $after_insert, $found_unique_key)
1922
    {
1923
        $html_output = '<select name="after_insert" class="control_at_footer">'
1924
            . '<option value="back" '
1925 8
            . ($after_insert == 'back' ? 'selected="selected"' : '') . '>'
1926 8
            . __('Go back to previous page') . '</option>'
1927 8
            . '<option value="new_insert" '
1928 8
            . ($after_insert == 'new_insert' ? 'selected="selected"' : '') . '>'
1929 8
            . __('Insert another new row') . '</option>';
1930
1931 8
        if (isset($where_clause)) {
1932
            $html_output .= '<option value="same_insert" '
1933 4
                . ($after_insert == 'same_insert' ? 'selected="selected"' : '') . '>'
1934 4
                . __('Go back to this page') . '</option>';
1935
1936
            // If we have just numeric primary key, we can also edit next
1937
            // in 2.8.2, we were looking for `field_name` = numeric_value
1938
            //if (preg_match('@^[\s]*`[^`]*` = [0-9]+@', $where_clause)) {
1939
            // in 2.9.0, we are looking for `table_name`.`field_name` = numeric_value
1940 4
            $is_numeric = false;
1941 4
            if (! is_array($where_clause)) {
1942 4
                $where_clause = [$where_clause];
1943
            }
1944 4
            for ($i = 0, $nb = count($where_clause); $i < $nb; $i++) {
1945
                // preg_match() returns 1 if there is a match
1946 4
                $is_numeric = (preg_match(
1947 4
                    '@^[\s]*`[^`]*`[\.]`[^`]*` = [0-9]+@',
1948 4
                    $where_clause[$i]
1949 4
                ) == 1);
1950 4
                if ($is_numeric === true) {
1951 4
                    break;
1952
                }
1953
            }
1954 4
            if ($found_unique_key && $is_numeric) {
1955
                $html_output .= '<option value="edit_next" '
1956 4
                    . ($after_insert == 'edit_next' ? 'selected="selected"' : '') . '>'
1957 4
                    . __('Edit next row') . '</option>';
1958
            }
1959
        }
1960
1961 8
        return $html_output . '</select>';
1962
    }
1963
1964
    /**
1965
     * get Submit button and Reset button for action panel
1966
     *
1967
     * @param int $tabindex           tab index
1968
     * @param int $tabindex_for_value offset for the values tabindex
1969
     *
1970
     * @return string an html snippet
1971
     */
1972 8
    private function getSubmitAndResetButtonForActionsPanel($tabindex, $tabindex_for_value)
1973
    {
1974
        return '<td>'
1975 8
            . Generator::showHint(
1976 8
                __(
1977
                    'Use TAB key to move from value to value,'
1978 8
                    . ' or CTRL+arrows to move anywhere.'
1979
                )
1980
            )
1981 8
            . '</td>'
1982 8
            . '<td colspan="3" class="right vmiddle">'
1983 8
            . '<input type="button" class="btn btn-secondary preview_sql" value="' . __('Preview SQL') . '"'
1984 8
            . ' tabindex="' . ($tabindex + $tabindex_for_value + 6) . '">'
1985 8
            . '<input type="reset" class="btn btn-secondary control_at_footer" value="' . __('Reset') . '"'
1986 8
            . ' tabindex="' . ($tabindex + $tabindex_for_value + 7) . '">'
1987 8
            . '<input type="submit" class="btn btn-primary control_at_footer" value="' . __('Go') . '"'
1988 8
            . ' tabindex="' . ($tabindex + $tabindex_for_value + 8) . '" id="buttonYes">'
1989 8
            . '</td>';
1990
    }
1991
1992
    /**
1993
     * Get table head and table foot for insert row table
1994
     *
1995
     * @param array $url_params url parameters
1996
     *
1997
     * @return string           an html snippet
1998
     */
1999 12
    private function getHeadAndFootOfInsertRowTable(array $url_params)
2000
    {
2001
        $html_output = '<div class="responsivetable">'
2002
            . '<table class="insertRowTable topmargin">'
2003
            . '<thead>'
2004
            . '<tr>'
2005 12
            . '<th>' . __('Column') . '</th>';
2006
2007 12
        if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) {
2008 12
            $html_output .= $this->showTypeOrFunction('type', $url_params, true);
2009
        }
2010 12
        if ($GLOBALS['cfg']['ShowFunctionFields']) {
2011 12
            $html_output .= $this->showTypeOrFunction('function', $url_params, true);
2012
        }
2013
2014 12
        $html_output .= '<th>' . __('Null') . '</th>'
2015 12
            . '<th class="fillPage">' . __('Value') . '</th>'
2016 12
            . '</tr>'
2017 12
            . '</thead>'
2018 12
            . ' <tfoot>'
2019 12
            . '<tr>'
2020 12
            . '<th colspan="5" class="tblFooters right">'
2021 12
            . '<input class="btn btn-primary" type="submit" value="' . __('Go') . '">'
2022 12
            . '</th>'
2023 12
            . '</tr>'
2024 12
            . '</tfoot>';
2025
2026 12
        return $html_output;
2027
    }
2028
2029
    /**
2030
     * Prepares the field value and retrieve special chars, backup field and data array
2031
     *
2032
     * @param array  $current_row          a row of the table
2033
     * @param array  $column               description of column in given table
2034
     * @param array  $extracted_columnspec associative array containing type,
2035
     *                                     spec_in_brackets and possibly
2036
     *                                     enum_set_values (another array)
2037
     * @param bool   $real_null_value      whether column value null or not null
2038
     * @param array  $gis_data_types       list of GIS data types
2039
     * @param string $column_name_appendix string to append to column name in input
2040
     * @param bool   $as_is                use the data as is, used in repopulating
2041
     *
2042
     * @return array $real_null_value, $data, $special_chars, $backup_field,
2043
     *               $special_chars_encoded
2044
     */
2045 4
    private function getSpecialCharsAndBackupFieldForExistingRow(
2046
        array $current_row,
2047
        array $column,
2048
        array $extracted_columnspec,
2049
        $real_null_value,
2050
        array $gis_data_types,
2051
        $column_name_appendix,
2052
        $as_is
2053
    ) {
2054 4
        $special_chars_encoded = '';
2055 4
        $data = null;
2056
        // (we are editing)
2057 4
        if (! isset($current_row[$column['Field']])) {
2058 4
            $real_null_value = true;
2059 4
            $current_row[$column['Field']] = '';
2060 4
            $special_chars = '';
2061 4
            $data = $current_row[$column['Field']];
2062 4
        } elseif ($column['True_Type'] == 'bit') {
2063 4
            $special_chars = $as_is
2064 4
                ? $current_row[$column['Field']]
2065 4
                : Util::printableBitValue(
2066 4
                    (int) $current_row[$column['Field']],
2067 4
                    (int) $extracted_columnspec['spec_in_brackets']
2068
                );
2069 4
        } elseif ((substr($column['True_Type'], 0, 9) == 'timestamp'
2070 4
            || $column['True_Type'] == 'datetime'
2071 4
            || $column['True_Type'] == 'time')
2072 4
            && (mb_strpos($current_row[$column['Field']], '.') !== false)
2073
        ) {
2074
            $current_row[$column['Field']] = $as_is
2075
                ? $current_row[$column['Field']]
2076
                : Util::addMicroseconds(
2077
                    $current_row[$column['Field']]
2078
                );
2079
            $special_chars = htmlspecialchars($current_row[$column['Field']]);
2080 4
        } elseif (in_array($column['True_Type'], $gis_data_types)) {
2081
            // Convert gis data to Well Know Text format
2082 4
            $current_row[$column['Field']] = $as_is
2083
                ? $current_row[$column['Field']]
2084 4
                : Util::asWKT(
2085 4
                    $current_row[$column['Field']],
2086 4
                    true
2087
                );
2088 4
            $special_chars = htmlspecialchars($current_row[$column['Field']]);
2089
        } else {
2090
            // special binary "characters"
2091 4
            if ($column['is_binary']
2092 4
                || ($column['is_blob'] && $GLOBALS['cfg']['ProtectBinary'] !== 'all')
2093
            ) {
2094 4
                $current_row[$column['Field']] = $as_is
2095
                    ? $current_row[$column['Field']]
2096 4
                    : bin2hex(
2097 4
                        $current_row[$column['Field']]
2098
                    );
2099
            } // end if
2100 4
            $special_chars = htmlspecialchars($current_row[$column['Field']]);
2101
2102
            //We need to duplicate the first \n or otherwise we will lose
2103
            //the first newline entered in a VARCHAR or TEXT column
2104
            $special_chars_encoded
2105 4
                = Util::duplicateFirstNewline($special_chars);
2106
2107 4
            $data = $current_row[$column['Field']];
2108
        } // end if... else...
2109
2110
        //when copying row, it is useful to empty auto-increment column
2111
        // to prevent duplicate key error
2112 4
        if (isset($_POST['default_action'])
2113 4
            && $_POST['default_action'] === 'insert'
2114
        ) {
2115 4
            if ($column['Key'] === 'PRI'
2116 4
                && mb_strpos($column['Extra'], 'auto_increment') !== false
2117
            ) {
2118 4
                $data = $special_chars_encoded = $special_chars = null;
2119
            }
2120
        }
2121
        // If a timestamp field value is not included in an update
2122
        // statement MySQL auto-update it to the current timestamp;
2123
        // however, things have changed since MySQL 4.1, so
2124
        // it's better to set a fields_prev in this situation
2125
        $backup_field = '<input type="hidden" name="fields_prev'
2126 4
            . $column_name_appendix . '" value="'
2127 4
            . htmlspecialchars($current_row[$column['Field']]) . '">';
2128
2129
        return [
2130 4
            $real_null_value,
2131 4
            $special_chars_encoded,
2132 4
            $special_chars,
2133 4
            $data,
2134 4
            $backup_field,
2135
        ];
2136
    }
2137
2138
    /**
2139
     * display default values
2140
     *
2141
     * @param array $column          description of column in given table
2142
     * @param bool  $real_null_value whether column value null or not null
2143
     *
2144
     * @return array $real_null_value, $data, $special_chars,
2145
     *               $backup_field, $special_chars_encoded
2146
     */
2147 16
    private function getSpecialCharsAndBackupFieldForInsertingMode(
2148
        array $column,
2149
        $real_null_value
2150
    ) {
2151 16
        if (! isset($column['Default'])) {
2152 12
            $column['Default']    = '';
2153 12
            $real_null_value          = true;
2154 12
            $data                     = '';
2155
        } else {
2156 8
            $data                     = $column['Default'];
2157
        }
2158
2159 16
        $trueType = $column['True_Type'];
2160
2161 16
        if ($trueType == 'bit') {
2162 4
            $special_chars = Util::convertBitDefaultValue(
2163 4
                $column['Default']
2164
            );
2165 16
        } elseif (substr($trueType, 0, 9) == 'timestamp'
2166 16
            || $trueType == 'datetime'
2167 16
            || $trueType == 'time'
2168
        ) {
2169 4
            $special_chars = Util::addMicroseconds($column['Default']);
2170 16
        } elseif ($trueType == 'binary' || $trueType == 'varbinary') {
2171
            $special_chars = bin2hex($column['Default']);
2172 16
        } elseif (substr($trueType, -4) === 'text') {
2173 8
            $textDefault = substr($column['Default'], 1, -1);
2174 8
            $special_chars = stripcslashes($textDefault !== false ? $textDefault : $column['Default']);
2175
        } else {
2176 8
            $special_chars = htmlspecialchars($column['Default']);
2177
        }
2178 16
        $backup_field = '';
2179 16
        $special_chars_encoded = Util::duplicateFirstNewline(
2180 16
            $special_chars
2181
        );
2182
2183
        return [
2184 16
            $real_null_value,
2185 16
            $data,
2186 16
            $special_chars,
2187 16
            $backup_field,
2188 16
            $special_chars_encoded,
2189
        ];
2190
    }
2191
2192
    /**
2193
     * Prepares the update/insert of a row
2194
     *
2195
     * @return array $loop_array, $using_key, $is_insert, $is_insertignore
2196
     */
2197 4
    public function getParamsForUpdateOrInsert()
2198
    {
2199 4
        if (isset($_POST['where_clause'])) {
2200
            // we were editing something => use the WHERE clause
2201 4
            $loop_array = is_array($_POST['where_clause'])
2202
                ? $_POST['where_clause']
2203 4
                : [$_POST['where_clause']];
2204 4
            $using_key  = true;
2205 4
            $is_insert  = isset($_POST['submit_type'])
2206 4
                          && ($_POST['submit_type'] == 'insert'
2207 4
                          || $_POST['submit_type'] == 'showinsert'
2208 4
                          || $_POST['submit_type'] == 'insertignore');
2209
        } else {
2210
            // new row => use indexes
2211 4
            $loop_array = [];
2212 4
            if (! empty($_POST['fields'])) {
2213 4
                foreach ($_POST['fields']['multi_edit'] as $key => $dummy) {
2214 4
                    $loop_array[] = $key;
2215
                }
2216
            }
2217 4
            $using_key  = false;
2218 4
            $is_insert  = true;
2219
        }
2220 4
        $is_insertignore  = isset($_POST['submit_type'])
2221 4
            && $_POST['submit_type'] == 'insertignore';
2222
2223
        return [
2224 4
            $loop_array,
2225 4
            $using_key,
2226 4
            $is_insert,
2227 4
            $is_insertignore,
2228
        ];
2229
    }
2230
2231
    /**
2232
     * Check wether insert row mode and if so include tbl_changen script and set
2233
     * global variables.
2234
     *
2235
     * @return void
2236
     */
2237 4
    public function isInsertRow()
2238
    {
2239 4
        global $containerBuilder;
2240
2241 4
        if (! isset($_POST['insert_rows'])
2242 4
            || ! is_numeric($_POST['insert_rows'])
2243 4
            || $_POST['insert_rows'] == $GLOBALS['cfg']['InsertRows']
2244
        ) {
2245
            return;
2246
        }
2247
2248 4
        $GLOBALS['cfg']['InsertRows'] = $_POST['insert_rows'];
2249 4
        $response = Response::getInstance();
2250 4
        $header = $response->getHeader();
2251 4
        $scripts = $header->getScripts();
2252 4
        $scripts->addFile('vendor/jquery/additional-methods.js');
2253 4
        $scripts->addFile('table/change.js');
2254 4
        if (! defined('TESTSUITE')) {
2255
            /** @var ChangeController $controller */
2256
            $controller = $containerBuilder->get(ChangeController::class);
2257
            $controller->index();
2258
            exit;
2259
        }
2260 4
    }
2261
2262
    /**
2263
     * set $_SESSION for edit_next
2264
     *
2265
     * @param string $one_where_clause one where clause from where clauses array
2266
     *
2267
     * @return void
2268
     */
2269 4
    public function setSessionForEditNext($one_where_clause)
2270
    {
2271 4
        $local_query = 'SELECT * FROM ' . Util::backquote($GLOBALS['db'])
2272 4
            . '.' . Util::backquote($GLOBALS['table']) . ' WHERE '
2273 4
            . str_replace('` =', '` >', $one_where_clause) . ' LIMIT 1;';
2274
2275 4
        $res = $this->dbi->query($local_query);
2276 4
        $row = $this->dbi->fetchRow($res);
2277 4
        $meta = $this->dbi->getFieldsMeta($res);
2278
        // must find a unique condition based on unique key,
2279
        // not a combination of all fields
2280 4
        [$unique_condition, $clause_is_unique] = Util::getUniqueCondition(
2281 4
            $res,
2282 4
            count($meta),
2283 4
            $meta,
2284 4
            $row ?? [],
2285 4
            true
2286
        );
2287 4
        if (! empty($unique_condition)) {
2288 4
            $_SESSION['edit_next'] = $unique_condition;
2289
        }
2290 4
        unset($unique_condition, $clause_is_unique);
2291 4
    }
2292
2293
    /**
2294
     * set $goto_include variable for different cases and retrieve like,
2295
     * if $GLOBALS['goto'] empty, if $goto_include previously not defined
2296
     * and new_insert, same_insert, edit_next
2297
     *
2298
     * @param string|false $goto_include store some script for include, otherwise it is
2299
     *                                   boolean false
2300
     *
2301
     * @return string|false
2302
     */
2303 4
    public function getGotoInclude($goto_include)
2304
    {
2305
        $valid_options = [
2306 4
            'new_insert',
2307
            'same_insert',
2308
            'edit_next',
2309
        ];
2310 4
        if (isset($_POST['after_insert'])
2311 4
            && in_array($_POST['after_insert'], $valid_options)
2312
        ) {
2313 4
            $goto_include = '/table/change';
2314 4
        } elseif (! empty($GLOBALS['goto'])) {
2315 4
            if (! preg_match('@^[a-z_]+\.php$@', $GLOBALS['goto'])) {
2316
                // this should NOT happen
2317
                //$GLOBALS['goto'] = false;
2318 4
                if ($GLOBALS['goto'] === 'index.php?route=/sql') {
2319
                    $goto_include = '/sql';
2320
                } else {
2321 4
                    $goto_include = false;
2322
                }
2323
            } else {
2324
                $goto_include = $GLOBALS['goto'];
2325
            }
2326 4
            if ($GLOBALS['goto'] == 'index.php?route=/database/sql' && strlen($GLOBALS['table']) > 0) {
2327 4
                $GLOBALS['table'] = '';
2328
            }
2329
        }
2330 4
        if (! $goto_include) {
2331 4
            if (strlen($GLOBALS['table']) === 0) {
2332 4
                $goto_include = '/database/sql';
2333
            } else {
2334 4
                $goto_include = '/table/sql';
2335
            }
2336
        }
2337
2338 4
        return $goto_include;
2339
    }
2340
2341
    /**
2342
     * Defines the url to return in case of failure of the query
2343
     *
2344
     * @param array $url_params url parameters
2345
     *
2346
     * @return string           error url for query failure
2347
     */
2348 4
    public function getErrorUrl(array $url_params)
2349
    {
2350 4
        if (isset($_POST['err_url'])) {
2351 4
            return $_POST['err_url'];
2352
        }
2353
2354 4
        return Url::getFromRoute('/table/change', $url_params);
2355
    }
2356
2357
    /**
2358
     * Builds the sql query
2359
     *
2360
     * @param bool  $is_insertignore $_POST['submit_type'] == 'insertignore'
2361
     * @param array $query_fields    column names array
2362
     * @param array $value_sets      array of query values
2363
     *
2364
     * @return array of query
2365
     */
2366 4
    public function buildSqlQuery($is_insertignore, array $query_fields, array $value_sets)
2367
    {
2368 4
        if ($is_insertignore) {
2369 4
            $insert_command = 'INSERT IGNORE ';
2370
        } else {
2371 4
            $insert_command = 'INSERT ';
2372
        }
2373
2374
        return [
2375 4
            $insert_command . 'INTO '
2376 4
            . Util::backquote($GLOBALS['table'])
2377 4
            . ' (' . implode(', ', $query_fields) . ') VALUES ('
2378 4
            . implode('), (', $value_sets) . ')',
2379
        ];
2380
    }
2381
2382
    /**
2383
     * Executes the sql query and get the result, then move back to the calling page
2384
     *
2385
     * @param array $url_params url parameters array
2386
     * @param array $query      built query from buildSqlQuery()
2387
     *
2388
     * @return array $url_params, $total_affected_rows, $last_messages
2389
     *               $warning_messages, $error_messages, $return_to_sql_query
2390
     */
2391 8
    public function executeSqlQuery(array $url_params, array $query)
2392
    {
2393 8
        $return_to_sql_query = '';
2394 8
        if (! empty($GLOBALS['sql_query'])) {
2395 8
            $url_params['sql_query'] = $GLOBALS['sql_query'];
2396 8
            $return_to_sql_query = $GLOBALS['sql_query'];
2397
        }
2398 8
        $GLOBALS['sql_query'] = implode('; ', $query) . ';';
2399
        // to ensure that the query is displayed in case of
2400
        // "insert as new row" and then "insert another new row"
2401 8
        $GLOBALS['display_query'] = $GLOBALS['sql_query'];
2402
2403 8
        $total_affected_rows = 0;
2404 8
        $last_messages = [];
2405 8
        $warning_messages = [];
2406 8
        $error_messages = [];
2407
2408 8
        foreach ($query as $single_query) {
2409 8
            if ($_POST['submit_type'] == 'showinsert') {
2410
                $last_messages[] = Message::notice(__('Showing SQL query'));
2411
                continue;
2412
            }
2413 8
            if ($GLOBALS['cfg']['IgnoreMultiSubmitErrors']) {
2414 4
                $result = $this->dbi->tryQuery($single_query);
2415
            } else {
2416 4
                $result = $this->dbi->query($single_query);
2417
            }
2418 8
            if (! $result) {
2419 8
                $error_messages[] = $this->dbi->getError();
2420
            } else {
2421 8
                $tmp = @$this->dbi->affectedRows();
2422
2423 8
                if ($tmp) {
2424 8
                    $total_affected_rows += $tmp;
2425
                }
2426 8
                unset($tmp);
2427
2428 8
                $insert_id = $this->dbi->insertId();
2429 8
                if ($insert_id != 0) {
2430
                    // insert_id is id of FIRST record inserted in one insert, so if we
2431
                    // inserted multiple rows, we had to increment this
2432
2433 8
                    if ($total_affected_rows > 0) {
2434 8
                        $insert_id += $total_affected_rows - 1;
2435
                    }
2436 8
                    $last_message = Message::notice(__('Inserted row id: %1$d'));
2437 8
                    $last_message->addParam($insert_id);
2438 8
                    $last_messages[] = $last_message;
2439
                }
2440 8
                $this->dbi->freeResult($result);
2441
            }
2442 8
            $warning_messages = $this->getWarningMessages();
2443
        }
2444
2445
        return [
2446 8
            $url_params,
2447 8
            $total_affected_rows,
2448 8
            $last_messages,
2449 8
            $warning_messages,
2450 8
            $error_messages,
2451 8
            $return_to_sql_query,
2452
        ];
2453
    }
2454
2455
    /**
2456
     * get the warning messages array
2457
     *
2458
     * @return array
2459
     */
2460 12
    private function getWarningMessages()
2461
    {
2462 12
        $warning_essages = [];
2463 12
        foreach ($this->dbi->getWarnings() as $warning) {
2464 4
            $warning_essages[] = Message::sanitize(
2465 4
                $warning['Level'] . ': #' . $warning['Code'] . ' ' . $warning['Message']
2466
            );
2467
        }
2468
2469 12
        return $warning_essages;
2470
    }
2471
2472
    /**
2473
     * Column to display from the foreign table?
2474
     *
2475
     * @param string $where_comparison string that contain relation field value
2476
     * @param array  $map              all Relations to foreign tables for a given
2477
     *                                 table or optionally a given column in a table
2478
     * @param string $relation_field   relation field
2479
     *
2480
     * @return string display value from the foreign table
2481
     */
2482 4
    public function getDisplayValueForForeignTableColumn(
2483
        $where_comparison,
2484
        array $map,
2485
        $relation_field
2486
    ) {
2487 4
        $foreigner = $this->relation->searchColumnInForeigners($map, $relation_field);
2488
2489 4
        if (! is_array($foreigner)) {
2490
            return '';
2491
        }
2492
2493 4
        $display_field = $this->relation->getDisplayField(
2494 4
            $foreigner['foreign_db'],
2495 4
            $foreigner['foreign_table']
2496
        );
2497
        // Field to display from the foreign table?
2498 4
        if (is_string($display_field) && strlen($display_field) > 0) {
2499 4
            $dispsql = 'SELECT ' . Util::backquote($display_field)
2500 4
                . ' FROM ' . Util::backquote($foreigner['foreign_db'])
2501 4
                . '.' . Util::backquote($foreigner['foreign_table'])
2502 4
                . ' WHERE ' . Util::backquote($foreigner['foreign_field'])
2503 4
                . $where_comparison;
2504 4
            $dispresult = $this->dbi->tryQuery(
2505 4
                $dispsql,
2506 4
                DatabaseInterface::CONNECT_USER,
2507 4
                DatabaseInterface::QUERY_STORE
2508
            );
2509 4
            if ($dispresult && $this->dbi->numRows($dispresult) > 0) {
2510 4
                [$dispval] = $this->dbi->fetchRow($dispresult);
2511
            } else {
2512
                $dispval = '';
2513
            }
2514 4
            if ($dispresult) {
2515 4
                $this->dbi->freeResult($dispresult);
2516
            }
2517
2518 4
            return $dispval;
2519
        }
2520
2521
        return '';
2522
    }
2523
2524
    /**
2525
     * Display option in the cell according to user choices
2526
     *
2527
     * @param array  $map                  all Relations to foreign tables for a given
2528
     *                                     table or optionally a given column in a table
2529
     * @param string $relation_field       relation field
2530
     * @param string $where_comparison     string that contain relation field value
2531
     * @param string $dispval              display value from the foreign table
2532
     * @param string $relation_field_value relation field value
2533
     *
2534
     * @return string HTML <a> tag
2535
     */
2536 4
    public function getLinkForRelationalDisplayField(
2537
        array $map,
2538
        $relation_field,
2539
        $where_comparison,
2540
        $dispval,
2541
        $relation_field_value
2542
    ) {
2543 4
        $foreigner = $this->relation->searchColumnInForeigners($map, $relation_field);
2544
2545 4
        if (! is_array($foreigner)) {
2546
            return '';
2547
        }
2548
2549 4
        if ($_SESSION['tmpval']['relational_display'] == 'K') {
2550
            // user chose "relational key" in the display options, so
2551
            // the title contains the display field
2552 4
            $title = ! empty($dispval)
2553 4
                ? ' title="' . htmlspecialchars($dispval) . '"'
2554 4
                : '';
2555
        } else {
2556 4
            $title = ' title="' . htmlspecialchars($relation_field_value) . '"';
2557
        }
2558
        $sqlQuery = 'SELECT * FROM '
2559 4
            . Util::backquote($foreigner['foreign_db'])
2560 4
            . '.' . Util::backquote($foreigner['foreign_table'])
2561 4
            . ' WHERE ' . Util::backquote($foreigner['foreign_field'])
2562 4
            . $where_comparison;
2563
        $_url_params = [
2564 4
            'db'    => $foreigner['foreign_db'],
2565 4
            'table' => $foreigner['foreign_table'],
2566 4
            'pos'   => '0',
2567 4
            'sql_signature' => Core::signSqlQuery($sqlQuery),
2568 4
            'sql_query' => $sqlQuery,
2569
        ];
2570 4
        $output = '<a href="' . Url::getFromRoute('/sql', $_url_params) . '"' . $title . '>';
2571
2572 4
        if ($_SESSION['tmpval']['relational_display'] == 'D') {
2573
            // user chose "relational display field" in the
2574
            // display options, so show display field in the cell
2575 4
            $output .= ! empty($dispval) ? htmlspecialchars($dispval) : '';
2576
        } else {
2577
            // otherwise display data in the cell
2578 4
            $output .= htmlspecialchars($relation_field_value);
2579
        }
2580 4
        $output .= '</a>';
2581
2582 4
        return $output;
2583
    }
2584
2585
    /**
2586
     * Transform edited values
2587
     *
2588
     * @param string $db             db name
2589
     * @param string $table          table name
2590
     * @param array  $transformation mimetypes for all columns of a table
2591
     *                               [field_name][field_key]
2592
     * @param array  $edited_values  transform columns list and new values
2593
     * @param string $file           file containing the transformation plugin
2594
     * @param string $column_name    column name
2595
     * @param array  $extra_data     extra data array
2596
     * @param string $type           the type of transformation
2597
     *
2598
     * @return array
2599
     */
2600 4
    public function transformEditedValues(
2601
        $db,
2602
        $table,
2603
        array $transformation,
2604
        array &$edited_values,
2605
        $file,
2606
        $column_name,
2607
        array $extra_data,
2608
        $type
2609
    ) {
2610 4
        $include_file = 'libraries/classes/Plugins/Transformations/' . $file;
2611 4
        if (is_file($include_file)) {
2612
            $_url_params = [
2613 4
                'db'            => $db,
2614 4
                'table'         => $table,
2615 4
                'where_clause'  => $_POST['where_clause'],
2616 4
                'transform_key' => $column_name,
2617
            ];
2618 4
            $transform_options = $this->transformations->getOptions(
2619 4
                $transformation[$type . '_options'] ?? ''
2620
            );
2621 4
            $transform_options['wrapper_link'] = Url::getCommon($_url_params);
2622 4
            $transform_options['wrapper_params'] = $_url_params;
2623 4
            $class_name = $this->transformations->getClassName($include_file);
2624 4
            if (class_exists($class_name)) {
2625
                /** @var TransformationsPlugin $transformation_plugin */
2626 4
                $transformation_plugin = new $class_name();
2627
2628 4
                foreach ($edited_values as $cell_index => $curr_cell_edited_values) {
2629 4
                    if (! isset($curr_cell_edited_values[$column_name])) {
2630
                        continue;
2631
                    }
2632
2633 4
                    $edited_values[$cell_index][$column_name]
2634 4
                        = $extra_data['transformations'][$cell_index]
2635 4
                            = $transformation_plugin->applyTransformation(
2636 4
                                $curr_cell_edited_values[$column_name],
2637 3
                                $transform_options
2638
                            );
2639
                }   // end of loop for each transformation cell
2640
            }
2641
        }
2642
2643 4
        return $extra_data;
2644
    }
2645
2646
    /**
2647
     * Get current value in multi edit mode
2648
     *
2649
     * @param array  $multi_edit_funcs        multiple edit functions array
2650
     * @param array  $multi_edit_salt         multiple edit array with encryption salt
2651
     * @param array  $gis_from_text_functions array that contains gis from text functions
2652
     * @param string $current_value           current value in the column
2653
     * @param array  $gis_from_wkb_functions  initially $val is $multi_edit_columns[$key]
2654
     * @param array  $func_optional_param     array('RAND','UNIX_TIMESTAMP')
2655
     * @param array  $func_no_param           array of set of string
2656
     * @param string $key                     an md5 of the column name
2657
     *
2658
     * @return string
2659
     */
2660 4
    public function getCurrentValueAsAnArrayForMultipleEdit(
2661
        $multi_edit_funcs,
2662
        $multi_edit_salt,
2663
        $gis_from_text_functions,
2664
        $current_value,
2665
        $gis_from_wkb_functions,
2666
        $func_optional_param,
2667
        $func_no_param,
2668
        $key
2669
    ) {
2670 4
        if (empty($multi_edit_funcs[$key])) {
2671 4
            return $current_value;
2672
        }
2673
2674 4
        if ($multi_edit_funcs[$key] === 'PHP_PASSWORD_HASH') {
2675
            /**
2676
             * @see https://github.com/vimeo/psalm/issues/3350
2677
             *
2678
             * @psalm-suppress InvalidArgument
2679
             */
2680
            $hash = password_hash($current_value, PASSWORD_DEFAULT);
2681
2682
            return "'" . $hash . "'";
2683
        }
2684
2685 4
        if ($multi_edit_funcs[$key] === 'UUID') {
2686
            /* This way user will know what UUID new row has */
2687 4
            $uuid = $this->dbi->fetchValue('SELECT UUID()');
2688
2689 4
            return "'" . $uuid . "'";
2690
        }
2691
2692 4
        if ((in_array($multi_edit_funcs[$key], $gis_from_text_functions)
2693
            && substr($current_value, 0, 3) == "'''")
2694 4
            || in_array($multi_edit_funcs[$key], $gis_from_wkb_functions)
2695
        ) {
2696
            // Remove enclosing apostrophes
2697
            $current_value = mb_substr($current_value, 1, -1);
2698
            // Remove escaping apostrophes
2699
            $current_value = str_replace("''", "'", $current_value);
2700
2701
            return $multi_edit_funcs[$key] . '(' . $current_value . ')';
2702
        }
2703
2704 4
        if (! in_array($multi_edit_funcs[$key], $func_no_param)
2705 4
            || ($current_value != "''"
2706 4
            && in_array($multi_edit_funcs[$key], $func_optional_param))
2707
        ) {
2708 4
            if ((isset($multi_edit_salt[$key])
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: (IssetNode && $multi_edi...uncs[$key] == 'ENCRYPT', Probably Intended Meaning: IssetNode && ($multi_edi...ncs[$key] == 'ENCRYPT')
Loading history...
2709 4
                && ($multi_edit_funcs[$key] == 'AES_ENCRYPT'
2710 4
                || $multi_edit_funcs[$key] == 'AES_DECRYPT'))
2711 4
                || (! empty($multi_edit_salt[$key])
2712
                && ($multi_edit_funcs[$key] == 'DES_ENCRYPT'
2713
                || $multi_edit_funcs[$key] == 'DES_DECRYPT'
2714 4
                || $multi_edit_funcs[$key] == 'ENCRYPT'))
2715
            ) {
2716 4
                return $multi_edit_funcs[$key] . '(' . $current_value . ",'"
2717 4
                    . $this->dbi->escapeString($multi_edit_salt[$key]) . "')";
2718
            }
2719
2720 4
            return $multi_edit_funcs[$key] . '(' . $current_value . ')';
2721
        }
2722
2723 4
        return $multi_edit_funcs[$key] . '()';
2724
    }
2725
2726
    /**
2727
     * Get query values array and query fields array for insert and update in multi edit
2728
     *
2729
     * @param array  $multi_edit_columns_name      multiple edit columns name array
2730
     * @param array  $multi_edit_columns_null      multiple edit columns null array
2731
     * @param string $current_value                current value in the column in loop
2732
     * @param array  $multi_edit_columns_prev      multiple edit previous columns array
2733
     * @param array  $multi_edit_funcs             multiple edit functions array
2734
     * @param bool   $is_insert                    boolean value whether insert or not
2735
     * @param array  $query_values                 SET part of the sql query
2736
     * @param array  $query_fields                 array of query fields
2737
     * @param string $current_value_as_an_array    current value in the column
2738
     *                                             as an array
2739
     * @param array  $value_sets                   array of valu sets
2740
     * @param string $key                          an md5 of the column name
2741
     * @param array  $multi_edit_columns_null_prev array of multiple edit columns
2742
     *                                             null previous
2743
     *
2744
     * @return array ($query_values, $query_fields)
2745
     */
2746 4
    public function getQueryValuesForInsertAndUpdateInMultipleEdit(
2747
        $multi_edit_columns_name,
2748
        $multi_edit_columns_null,
2749
        $current_value,
2750
        $multi_edit_columns_prev,
2751
        $multi_edit_funcs,
2752
        $is_insert,
2753
        $query_values,
2754
        $query_fields,
2755
        $current_value_as_an_array,
2756
        $value_sets,
2757
        $key,
2758
        $multi_edit_columns_null_prev
2759
    ) {
2760
        //  i n s e r t
2761 4
        if ($is_insert) {
2762
            // no need to add column into the valuelist
2763 4
            if (strlen($current_value_as_an_array) > 0) {
2764 4
                $query_values[] = $current_value_as_an_array;
2765
                // first inserted row so prepare the list of fields
2766 4
                if (empty($value_sets)) {
2767 4
                    $query_fields[] = Util::backquote(
2768 4
                        $multi_edit_columns_name[$key]
2769
                    );
2770
                }
2771
            }
2772 4
        } elseif (! empty($multi_edit_columns_null_prev[$key])
2773 4
            && ! isset($multi_edit_columns_null[$key])
2774
        ) {
2775
            //  u p d a t e
2776
2777
            // field had the null checkbox before the update
2778
            // field no longer has the null checkbox
2779 4
            $query_values[]
2780 4
                = Util::backquote($multi_edit_columns_name[$key])
2781 4
                . ' = ' . $current_value_as_an_array;
2782 4
        } elseif (! (empty($multi_edit_funcs[$key])
2783 4
            && isset($multi_edit_columns_prev[$key])
2784 4
            && (($current_value === "'" . $this->dbi->escapeString($multi_edit_columns_prev[$key]) . "'")
2785 4
            || ($current_value === '0x' . $multi_edit_columns_prev[$key])))
2786 4
            && ! empty($current_value)
2787
        ) {
2788
            // avoid setting a field to NULL when it's already NULL
2789
            // (field had the null checkbox before the update
2790
            //  field still has the null checkbox)
2791 4
            if (empty($multi_edit_columns_null_prev[$key])
2792 4
                || empty($multi_edit_columns_null[$key])
2793
            ) {
2794 4
                 $query_values[]
2795 4
                     = Util::backquote($multi_edit_columns_name[$key])
2796 4
                    . ' = ' . $current_value_as_an_array;
2797
            }
2798
        }
2799
2800
        return [
2801 4
            $query_values,
2802 4
            $query_fields,
2803
        ];
2804
    }
2805
2806
    /**
2807
     * Get the current column value in the form for different data types
2808
     *
2809
     * @param string|false $possibly_uploaded_val        uploaded file content
2810
     * @param string       $key                          an md5 of the column name
2811
     * @param array|null   $multi_edit_columns_type      array of multi edit column types
2812
     * @param string       $current_value                current column value in the form
2813
     * @param array|null   $multi_edit_auto_increment    multi edit auto increment
2814
     * @param int          $rownumber                    index of where clause array
2815
     * @param array        $multi_edit_columns_name      multi edit column names array
2816
     * @param array        $multi_edit_columns_null      multi edit columns null array
2817
     * @param array        $multi_edit_columns_null_prev multi edit columns previous null
2818
     * @param bool         $is_insert                    whether insert or not
2819
     * @param bool         $using_key                    whether editing or new row
2820
     * @param string       $where_clause                 where clause
2821
     * @param string       $table                        table name
2822
     * @param array        $multi_edit_funcs             multiple edit functions array
2823
     *
2824
     * @return string  current column value in the form
2825
     */
2826 4
    public function getCurrentValueForDifferentTypes(
2827
        $possibly_uploaded_val,
2828
        $key,
2829
        ?array $multi_edit_columns_type,
2830
        $current_value,
2831
        ?array $multi_edit_auto_increment,
2832
        $rownumber,
2833
        $multi_edit_columns_name,
2834
        $multi_edit_columns_null,
2835
        $multi_edit_columns_null_prev,
2836
        $is_insert,
2837
        $using_key,
2838
        $where_clause,
2839
        $table,
2840
        $multi_edit_funcs
2841
    ) {
2842
        // Fetch the current values of a row to use in case we have a protected field
2843 4
        if ($is_insert
2844 4
            && $using_key && isset($multi_edit_columns_type)
2845 4
            && is_array($multi_edit_columns_type) && ! empty($where_clause)
2846
        ) {
2847 4
            $protected_row = $this->dbi->fetchSingleRow(
2848 4
                'SELECT * FROM ' . Util::backquote($table)
2849 4
                . ' WHERE ' . $where_clause . ';'
2850
            );
2851
        }
2852
2853 4
        if ($possibly_uploaded_val !== false) {
2854 4
            $current_value = $possibly_uploaded_val;
2855 4
        } elseif (! empty($multi_edit_funcs[$key])) {
2856
            $current_value = "'" . $this->dbi->escapeString($current_value)
2857
                . "'";
2858
        } else {
2859
            // c o l u m n    v a l u e    i n    t h e    f o r m
2860 4
            if (isset($multi_edit_columns_type[$key])) {
2861 4
                $type = $multi_edit_columns_type[$key];
2862
            } else {
2863
                $type = '';
2864
            }
2865
2866 4
            if ($type != 'protected' && $type != 'set' && strlen($current_value) === 0) {
2867
                // best way to avoid problems in strict mode
2868
                // (works also in non-strict mode)
2869 4
                if (isset($multi_edit_auto_increment, $multi_edit_auto_increment[$key])) {
2870 4
                    $current_value = 'NULL';
2871
                } else {
2872 4
                    $current_value = "''";
2873
                }
2874 4
            } elseif ($type == 'set') {
2875 4
                if (! empty($_POST['fields']['multi_edit'][$rownumber][$key])) {
2876
                    $current_value = implode(
2877
                        ',',
2878
                        $_POST['fields']['multi_edit'][$rownumber][$key]
2879
                    );
2880
                    $current_value = "'"
2881
                        . $this->dbi->escapeString($current_value) . "'";
2882
                } else {
2883 4
                     $current_value = "''";
2884
                }
2885 4
            } elseif ($type == 'protected') {
2886
                // here we are in protected mode (asked in the config)
2887
                // so tbl_change has put this special value in the
2888
                // columns array, so we do not change the column value
2889
                // but we can still handle column upload
2890
2891
                // when in UPDATE mode, do not alter field's contents. When in INSERT
2892
                // mode, insert empty field because no values were submitted.
2893
                // If protected blobs where set, insert original fields content.
2894 4
                if (! empty($protected_row[$multi_edit_columns_name[$key]])) {
2895
                    $current_value = '0x'
2896 4
                        . bin2hex($protected_row[$multi_edit_columns_name[$key]]);
2897
                } else {
2898 4
                    $current_value = '';
2899
                }
2900 4
            } elseif ($type === 'hex') {
2901
                if (substr($current_value, 0, 2) != '0x') {
2902
                    $current_value = '0x' . $current_value;
2903
                }
2904 4
            } elseif ($type == 'bit') {
2905 4
                $current_value = preg_replace('/[^01]/', '0', $current_value);
2906 4
                $current_value = "b'" . $this->dbi->escapeString($current_value)
2907 4
                    . "'";
2908 4
            } elseif (! ($type == 'datetime' || $type == 'timestamp')
2909
                || ($current_value != 'CURRENT_TIMESTAMP'
2910 4
                    && $current_value != 'current_timestamp()')
2911
            ) {
2912 4
                $current_value = "'" . $this->dbi->escapeString($current_value)
2913 4
                    . "'";
2914
            }
2915
2916
            // Was the Null checkbox checked for this field?
2917
            // (if there is a value, we ignore the Null checkbox: this could
2918
            // be possible if Javascript is disabled in the browser)
2919 4
            if (! empty($multi_edit_columns_null[$key])
2920 4
                && ($current_value == "''" || $current_value == '')
2921
            ) {
2922 4
                $current_value = 'NULL';
2923
            }
2924
2925
            // The Null checkbox was unchecked for this field
2926 4
            if (empty($current_value)
2927 4
                && ! empty($multi_edit_columns_null_prev[$key])
2928 4
                && ! isset($multi_edit_columns_null[$key])
2929
            ) {
2930 4
                $current_value = "''";
2931
            }
2932
        }  // end else (column value in the form)
2933
2934 4
        return $current_value;
2935
    }
2936
2937
    /**
2938
     * Check whether inline edited value can be truncated or not,
2939
     * and add additional parameters for extra_data array  if needed
2940
     *
2941
     * @param string $db          Database name
2942
     * @param string $table       Table name
2943
     * @param string $column_name Column name
2944
     * @param array  $extra_data  Extra data for ajax response
2945
     *
2946
     * @return void
2947
     */
2948 4
    public function verifyWhetherValueCanBeTruncatedAndAppendExtraData(
2949
        $db,
2950
        $table,
2951
        $column_name,
2952
        array &$extra_data
2953
    ) {
2954 4
        $extra_data['isNeedToRecheck'] = false;
2955
2956 4
        $sql_for_real_value = 'SELECT ' . Util::backquote($table) . '.'
2957 4
            . Util::backquote($column_name)
2958 4
            . ' FROM ' . Util::backquote($db) . '.'
2959 4
            . Util::backquote($table)
2960 4
            . ' WHERE ' . $_POST['where_clause'][0];
2961
2962 4
        $result = $this->dbi->tryQuery($sql_for_real_value);
2963 4
        $fields_meta = $this->dbi->getFieldsMeta($result);
2964 4
        $meta = $fields_meta[0];
2965 4
        $row = $this->dbi->fetchRow($result);
2966
2967 4
        if ($row) {
2968 4
            $new_value = $row[0];
2969 4
            if ((substr($meta->type, 0, 9) == 'timestamp')
2970 4
                || ($meta->type == 'datetime')
2971 4
                || ($meta->type == 'time')
2972
            ) {
2973 4
                $new_value = Util::addMicroseconds($new_value);
2974 4
            } elseif (mb_strpos($meta->flags, 'binary') !== false) {
2975
                $new_value = '0x' . bin2hex($new_value);
2976
            }
2977 4
            $extra_data['isNeedToRecheck'] = true;
2978 4
            $extra_data['truncatableFieldValue'] = $new_value;
2979
        }
2980 4
        $this->dbi->freeResult($result);
2981 4
    }
2982
2983
    /**
2984
     * Function to get the columns of a table
2985
     *
2986
     * @param string $db    current db
2987
     * @param string $table current table
2988
     *
2989
     * @return array
2990
     */
2991 4
    public function getTableColumns($db, $table)
2992
    {
2993 4
        $this->dbi->selectDb($db);
2994
2995 4
        return array_values($this->dbi->getColumns($db, $table, null, true));
2996
    }
2997
2998
    /**
2999
     * Function to determine Insert/Edit rows
3000
     *
3001
     * @param string $where_clause where clause
3002
     * @param string $db           current database
3003
     * @param string $table        current table
3004
     *
3005
     * @return array
3006
     */
3007 4
    public function determineInsertOrEdit($where_clause, $db, $table): array
3008
    {
3009 4
        if (isset($_POST['where_clause'])) {
3010 4
            $where_clause = $_POST['where_clause'];
3011
        }
3012 4
        if (isset($_SESSION['edit_next'])) {
3013 4
            $where_clause = $_SESSION['edit_next'];
3014 4
            unset($_SESSION['edit_next']);
3015 4
            $after_insert = 'edit_next';
3016
        }
3017 4
        if (isset($_POST['ShowFunctionFields'])) {
3018 4
            $GLOBALS['cfg']['ShowFunctionFields'] = $_POST['ShowFunctionFields'];
3019
        }
3020 4
        if (isset($_POST['ShowFieldTypesInDataEditView'])) {
3021 4
            $GLOBALS['cfg']['ShowFieldTypesInDataEditView']
3022 4
                = $_POST['ShowFieldTypesInDataEditView'];
3023
        }
3024 4
        if (isset($_POST['after_insert'])) {
3025 4
            $after_insert = $_POST['after_insert'];
3026
        }
3027
3028 4
        if (isset($where_clause)) {
3029
            // we are editing
3030 4
            $insert_mode = false;
3031 4
            $where_clause_array = $this->getWhereClauseArray($where_clause);
3032
            [$where_clauses, $result, $rows, $found_unique_key]
3033 4
                = $this->analyzeWhereClauses(
3034 4
                    $where_clause_array,
3035 4
                    $table,
3036 4
                    $db
3037
                );
3038
        } else {
3039
            // we are inserting
3040 4
            $insert_mode = true;
3041 4
            $where_clause = null;
3042 4
            [$result, $rows] = $this->loadFirstRow($table, $db);
3043 4
            $where_clauses = null;
3044 4
            $where_clause_array = [];
3045 4
            $found_unique_key = false;
3046
        }
3047
3048
        // Copying a row - fetched data will be inserted as a new row,
3049
        // therefore the where clause is needless.
3050 4
        if (isset($_POST['default_action'])
3051 4
            && $_POST['default_action'] === 'insert'
3052
        ) {
3053 4
            $where_clause = $where_clauses = null;
3054
        }
3055
3056
        return [
3057 4
            $insert_mode,
3058 4
            $where_clause,
3059 4
            $where_clause_array,
3060 4
            $where_clauses,
3061 4
            $result,
3062 4
            $rows,
3063 4
            $found_unique_key,
3064 4
            $after_insert ?? null,
3065
        ];
3066
    }
3067
3068
    /**
3069
     * Function to get comments for the table columns
3070
     *
3071
     * @param string $db    current database
3072
     * @param string $table current table
3073
     *
3074
     * @return array comments for columns
3075
     */
3076 4
    public function getCommentsMap($db, $table)
3077
    {
3078 4
        $comments_map = [];
3079
3080 4
        if ($GLOBALS['cfg']['ShowPropertyComments']) {
3081 4
            $comments_map = $this->relation->getComments($db, $table);
3082
        }
3083
3084 4
        return $comments_map;
3085
    }
3086
3087
    /**
3088
     * Function to get URL parameters
3089
     *
3090
     * @param string $db    current database
3091
     * @param string $table current table
3092
     *
3093
     * @return array url parameters
3094
     */
3095 4
    public function getUrlParameters($db, $table)
3096
    {
3097 4
        global $goto;
3098
        /**
3099
         * @todo check if we could replace by "db_|tbl_" - please clarify!?
3100
         */
3101
        $url_params = [
3102 4
            'db' => $db,
3103 4
            'sql_query' => $_POST['sql_query'],
3104
        ];
3105
3106 4
        if (strpos($goto, 'tbl_') === 0 || strpos($goto, 'index.php?route=/table') === 0) {
3107 4
            $url_params['table'] = $table;
3108
        }
3109
3110 4
        return $url_params;
3111
    }
3112
3113
    /**
3114
     * Function to get html for the gis editor div
3115
     *
3116
     * @return string
3117
     */
3118
    public function getHtmlForGisEditor()
3119
    {
3120
        return '<div id="gis_editor"></div>'
3121
            . '<div id="popup_background"></div>'
3122
            . '<br>';
3123
    }
3124
3125
    /**
3126
     * Function to get html for the ignore option in insert mode
3127
     *
3128
     * @param int  $row_id  row id
3129
     * @param bool $checked ignore option is checked or not
3130
     *
3131
     * @return string
3132
     */
3133 4
    public function getHtmlForIgnoreOption($row_id, $checked = true)
3134
    {
3135
        return '<input type="checkbox"'
3136 4
                . ($checked ? ' checked="checked"' : '')
3137 4
                . ' name="insert_ignore_' . $row_id . '"'
3138 4
                . ' id="insert_ignore_' . $row_id . '">'
3139 4
                . '<label for="insert_ignore_' . $row_id . '">'
3140 4
                . __('Ignore')
3141 4
                . '</label><br>' . "\n";
3142
    }
3143
3144
    /**
3145
     * Function to get html for the function option
3146
     *
3147
     * @param array  $column               column
3148
     * @param string $column_name_appendix column name appendix
3149
     */
3150 12
    private function getHtmlForFunctionOption(array $column, $column_name_appendix): string
3151
    {
3152
        return '<tr class="noclick">'
3153
            . '<td '
3154
            . 'class="text-center">'
3155 12
            . $column['Field_title']
3156 12
            . '<input type="hidden" name="fields_name' . $column_name_appendix
3157 12
            . '" value="' . $column['Field_html'] . '">'
3158 12
            . '</td>';
3159
    }
3160
3161
    /**
3162
     * Function to get html for the column type
3163
     *
3164
     * @param array $column column
3165
     *
3166
     * @return string
3167
     */
3168 12
    private function getHtmlForInsertEditColumnType(array $column)
3169
    {
3170 12
        return '<td class="text-center' . $column['wrap'] . '">'
3171 12
            . '<span class="column_type" dir="ltr">' . $column['pma_type'] . '</span>'
3172 12
            . '</td>';
3173
    }
3174
3175
    /**
3176
     * Function to get html for the insert edit form header
3177
     *
3178
     * @param bool $has_blob_field whether has blob field
3179
     * @param bool $is_upload      whether is upload
3180
     *
3181
     * @return string
3182
     */
3183
    public function getHtmlForInsertEditFormHeader($has_blob_field, $is_upload)
3184
    {
3185
        $html_output = '<form id="insertForm" class="lock-page ';
3186
        if ($has_blob_field && $is_upload) {
3187
            $html_output .= 'disableAjax';
3188
        }
3189
        $html_output .= '" method="post" action="' . Url::getFromRoute('/table/replace') . '" name="insertForm" ';
3190
        if ($is_upload) {
3191
            $html_output .= ' enctype="multipart/form-data"';
3192
        }
3193
        $html_output .= '>';
3194
3195
        return $html_output;
3196
    }
3197
3198
    /**
3199
     * Function to get html for each insert/edit column
3200
     *
3201
     * @param array  $table_columns         table columns
3202
     * @param int    $column_number         column index in table_columns
3203
     * @param array  $comments_map          comments map
3204
     * @param bool   $timestamp_seen        whether timestamp seen
3205
     * @param array  $current_result        current result
3206
     * @param string $chg_evt_handler       javascript change event handler
3207
     * @param string $jsvkey                javascript validation key
3208
     * @param string $vkey                  validation key
3209
     * @param bool   $insert_mode           whether insert mode
3210
     * @param array  $current_row           current row
3211
     * @param int    $o_rows                row offset
3212
     * @param int    $tabindex              tab index
3213
     * @param int    $columns_cnt           columns count
3214
     * @param bool   $is_upload             whether upload
3215
     * @param int    $tabindex_for_function tab index offset for function
3216
     * @param array  $foreigners            foreigners
3217
     * @param int    $tabindex_for_null     tab index offset for null
3218
     * @param int    $tabindex_for_value    tab index offset for value
3219
     * @param string $table                 table
3220
     * @param string $db                    database
3221
     * @param int    $row_id                row id
3222
     * @param array  $titles                titles
3223
     * @param int    $biggest_max_file_size biggest max file size
3224
     * @param string $default_char_editing  default char editing mode which is stored
3225
     *                                      in the config.inc.php script
3226
     * @param string $text_dir              text direction
3227
     * @param array  $repopulate            the data to be repopulated
3228
     * @param array  $column_mime           the mime information of column
3229
     * @param string $where_clause          the where clause
3230
     *
3231
     * @return string
3232
     */
3233 12
    private function getHtmlForInsertEditFormColumn(
3234
        array $table_columns,
3235
        $column_number,
3236
        array $comments_map,
3237
        $timestamp_seen,
3238
        $current_result,
3239
        $chg_evt_handler,
3240
        $jsvkey,
3241
        $vkey,
3242
        $insert_mode,
3243
        array $current_row,
3244
        &$o_rows,
3245
        &$tabindex,
3246
        $columns_cnt,
3247
        $is_upload,
3248
        $tabindex_for_function,
3249
        array $foreigners,
3250
        $tabindex_for_null,
3251
        $tabindex_for_value,
3252
        $table,
3253
        $db,
3254
        $row_id,
3255
        array $titles,
3256
        $biggest_max_file_size,
3257
        $default_char_editing,
3258
        $text_dir,
3259
        array $repopulate,
3260
        array $column_mime,
3261
        $where_clause
3262
    ) {
3263 12
        $column = $table_columns[$column_number];
3264 12
        $readOnly = false;
3265
3266 12
        if (! isset($column['processed'])) {
3267 12
            $column = $this->analyzeTableColumnsArray(
3268 12
                $column,
3269 12
                $comments_map,
3270 12
                $timestamp_seen
3271
            );
3272
        }
3273 12
        $as_is = false;
3274 12
        if (! empty($repopulate) && ! empty($current_row)) {
3275
            $current_row[$column['Field']] = $repopulate[$column['Field_md5']];
3276
            $as_is = true;
3277
        }
3278
3279
        $extracted_columnspec
3280 12
            = Util::extractColumnSpec($column['Type']);
3281
3282 12
        if ($column['len'] === -1) {
3283 12
            $column['len'] = $this->dbi->fieldLen(
3284 12
                $current_result,
3285 9
                $column_number
3286
            );
3287
            // length is unknown for geometry fields,
3288
            // make enough space to edit very simple WKTs
3289 12
            if ($column['len'] === -1) {
3290 12
                $column['len'] = 30;
3291
            }
3292
        }
3293
        //Call validation when the form submitted...
3294
        $onChangeClause = $chg_evt_handler
3295 12
            . "=\"return verificationsAfterFieldChange('"
3296 12
            . Sanitize::escapeJsString($column['Field_md5']) . "', '"
3297 12
            . Sanitize::escapeJsString($jsvkey) . "','" . $column['pma_type'] . "')\"";
3298
3299
        // Use an MD5 as an array index to avoid having special characters
3300
        // in the name attribute (see bug #1746964 )
3301 12
        $column_name_appendix = $vkey . '[' . $column['Field_md5'] . ']';
3302
3303 12
        if ($column['Type'] === 'datetime'
3304 12
            && ! isset($column['Default'])
3305 12
            && $column['Default'] !== null
3306 12
            && $insert_mode
3307
        ) {
3308
            $column['Default'] = date('Y-m-d H:i:s', time());
3309
        }
3310
3311 12
        $html_output = $this->getHtmlForFunctionOption(
3312 12
            $column,
3313 12
            $column_name_appendix
3314
        );
3315
3316 12
        if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) {
3317 12
            $html_output .= $this->getHtmlForInsertEditColumnType($column);
3318
        } //End if
3319
3320
        // Get a list of GIS data types.
3321 12
        $gis_data_types = Util::getGISDatatypes();
3322
3323
        // Prepares the field value
3324 12
        $real_null_value = false;
3325 12
        $special_chars_encoded = '';
3326 12
        if (! empty($current_row)) {
3327
            // (we are editing)
3328
            [
3329
                $real_null_value,
3330
                $special_chars_encoded,
3331
                $special_chars,
3332
                $data,
3333
                $backup_field,
3334
            ]
3335
                = $this->getSpecialCharsAndBackupFieldForExistingRow(
3336
                    $current_row,
3337
                    $column,
3338
                    $extracted_columnspec,
3339
                    $real_null_value,
3340
                    $gis_data_types,
3341
                    $column_name_appendix,
3342
                    $as_is
3343
                );
3344
        } else {
3345
            // (we are inserting)
3346
            // display default values
3347 12
            $tmp = $column;
3348 12
            if (isset($repopulate[$column['Field_md5']])) {
3349 4
                $tmp['Default'] = $repopulate[$column['Field_md5']];
3350
            }
3351
            [
3352
                $real_null_value,
3353
                $data,
3354
                $special_chars,
3355
                $backup_field,
3356
                $special_chars_encoded,
3357
            ]
3358 12
                = $this->getSpecialCharsAndBackupFieldForInsertingMode(
3359 12
                    $tmp,
3360 12
                    $real_null_value
3361
                );
3362 12
            unset($tmp);
3363
        }
3364
3365 12
        $idindex = ($o_rows * $columns_cnt) + $column_number + 1;
3366 12
        $tabindex = $idindex;
3367
3368
        // Get a list of data types that are not yet supported.
3369 12
        $no_support_types = Util::unsupportedDatatypes();
3370
3371
        // The function column
3372
        // -------------------
3373 12
        $foreignData = $this->relation->getForeignData(
3374 12
            $foreigners,
3375 12
            $column['Field'],
3376 12
            false,
3377 12
            '',
3378 12
            ''
3379
        );
3380 12
        if ($GLOBALS['cfg']['ShowFunctionFields']) {
3381 12
            $html_output .= $this->getFunctionColumn(
3382 12
                $column,
3383 12
                $is_upload,
3384 12
                $column_name_appendix,
3385 12
                $onChangeClause,
3386 12
                $no_support_types,
3387 12
                $tabindex_for_function,
3388 12
                $tabindex,
3389 12
                $idindex,
3390 12
                $insert_mode,
3391 12
                $readOnly,
3392 12
                $foreignData
3393
            );
3394
        }
3395
3396
        // The null column
3397
        // ---------------
3398 12
        $html_output .= $this->getNullColumn(
3399 12
            $column,
3400 12
            $column_name_appendix,
3401 12
            $real_null_value,
3402 12
            $tabindex,
3403 12
            $tabindex_for_null,
3404 12
            $idindex,
3405 12
            $vkey,
3406 12
            $foreigners,
3407 12
            $foreignData,
3408 12
            $readOnly
3409
        );
3410
3411
        // The value column (depends on type)
3412
        // ----------------
3413
        // See bug #1667887 for the reason why we don't use the maxlength
3414
        // HTML attribute
3415
3416
        //add data attributes "no of decimals" and "data type"
3417 12
        $no_decimals = 0;
3418 12
        $type = current(explode('(', $column['pma_type']));
3419 12
        if (preg_match('/\(([^()]+)\)/', $column['pma_type'], $match)) {
3420 4
            $match[0] = trim($match[0], '()');
3421 4
            $no_decimals = $match[0];
3422
        }
3423 12
        $html_output .= '<td data-type="' . $type . '" data-decimals="'
3424 12
            . $no_decimals . '">' . "\n";
3425
        // Will be used by js/table/change.js to set the default value
3426
        // for the "Continue insertion" feature
3427
        $html_output .= '<span class="default_value hide">'
3428 12
            . $special_chars . '</span>';
3429
3430
        // Check input transformation of column
3431 12
        $transformed_html = '';
3432 12
        if (! empty($column_mime['input_transformation'])) {
3433 4
            $file = $column_mime['input_transformation'];
3434 4
            $include_file = 'libraries/classes/Plugins/Transformations/' . $file;
3435 4
            if (is_file($include_file)) {
3436 4
                $class_name = $this->transformations->getClassName($include_file);
3437 4
                if (class_exists($class_name)) {
3438 4
                    $transformation_plugin = new $class_name();
3439 4
                    $transformation_options = $this->transformations->getOptions(
3440 4
                        $column_mime['input_transformation_options']
3441
                    );
3442
                    $_url_params = [
3443 4
                        'db'            => $db,
3444 4
                        'table'         => $table,
3445 4
                        'transform_key' => $column['Field'],
3446 4
                        'where_clause'  => $where_clause,
3447
                    ];
3448 4
                    $transformation_options['wrapper_link'] = Url::getCommon($_url_params);
3449 4
                    $transformation_options['wrapper_params'] = $_url_params;
3450 4
                    $current_value = '';
3451 4
                    if (isset($current_row[$column['Field']])) {
3452
                        $current_value = $current_row[$column['Field']];
3453
                    }
3454 4
                    if (method_exists($transformation_plugin, 'getInputHtml')) {
3455 4
                        $transformed_html = $transformation_plugin->getInputHtml(
3456 4
                            $column,
3457 3
                            $row_id,
3458 3
                            $column_name_appendix,
3459 3
                            $transformation_options,
3460 3
                            $current_value,
3461 3
                            $text_dir,
3462 3
                            $tabindex,
3463 3
                            $tabindex_for_value,
3464 3
                            $idindex
3465
                        );
3466
                    }
3467 4
                    if (method_exists($transformation_plugin, 'getScripts')) {
3468 4
                        $GLOBALS['plugin_scripts'] = array_merge(
3469 4
                            $GLOBALS['plugin_scripts'],
3470 4
                            $transformation_plugin->getScripts()
3471
                        );
3472
                    }
3473
                }
3474
            }
3475
        }
3476 12
        if (! empty($transformed_html)) {
3477 4
            $html_output .= $transformed_html;
3478
        } else {
3479 12
            $html_output .= $this->getValueColumn(
3480 12
                $column,
3481 12
                $backup_field,
3482 12
                $column_name_appendix,
3483 12
                $onChangeClause,
3484 12
                $tabindex,
3485 12
                $tabindex_for_value,
3486 12
                $idindex,
3487 12
                $data,
3488 12
                $special_chars,
3489 12
                $foreignData,
3490
                [
3491 12
                    $table,
3492 12
                    $db,
3493
                ],
3494 12
                $row_id,
3495 12
                $titles,
3496 12
                $text_dir,
3497 12
                $special_chars_encoded,
3498 12
                $vkey,
3499 12
                $is_upload,
3500 12
                $biggest_max_file_size,
3501 12
                $default_char_editing,
3502 12
                $no_support_types,
3503 12
                $gis_data_types,
3504 12
                $extracted_columnspec,
3505 12
                $readOnly
3506
            );
3507
        }
3508
3509 12
        return $html_output;
3510
    }
3511
3512
    /**
3513
     * Function to get html for each insert/edit row
3514
     *
3515
     * @param array  $url_params            url parameters
3516
     * @param array  $table_columns         table columns
3517
     * @param array  $comments_map          comments map
3518
     * @param bool   $timestamp_seen        whether timestamp seen
3519
     * @param array  $current_result        current result
3520
     * @param string $chg_evt_handler       javascript change event handler
3521
     * @param string $jsvkey                javascript validation key
3522
     * @param string $vkey                  validation key
3523
     * @param bool   $insert_mode           whether insert mode
3524
     * @param array  $current_row           current row
3525
     * @param int    $o_rows                row offset
3526
     * @param int    $tabindex              tab index
3527
     * @param int    $columns_cnt           columns count
3528
     * @param bool   $is_upload             whether upload
3529
     * @param int    $tabindex_for_function tab index offset for function
3530
     * @param array  $foreigners            foreigners
3531
     * @param int    $tabindex_for_null     tab index offset for null
3532
     * @param int    $tabindex_for_value    tab index offset for value
3533
     * @param string $table                 table
3534
     * @param string $db                    database
3535
     * @param int    $row_id                row id
3536
     * @param array  $titles                titles
3537
     * @param int    $biggest_max_file_size biggest max file size
3538
     * @param string $text_dir              text direction
3539
     * @param array  $repopulate            the data to be repopulated
3540
     * @param array  $where_clause_array    the array of where clauses
3541
     *
3542
     * @return string
3543
     */
3544 8
    public function getHtmlForInsertEditRow(
3545
        array $url_params,
3546
        array $table_columns,
3547
        array $comments_map,
3548
        $timestamp_seen,
3549
        $current_result,
3550
        $chg_evt_handler,
3551
        $jsvkey,
3552
        $vkey,
3553
        $insert_mode,
3554
        array $current_row,
3555
        &$o_rows,
3556
        &$tabindex,
3557
        $columns_cnt,
3558
        $is_upload,
3559
        $tabindex_for_function,
3560
        array $foreigners,
3561
        $tabindex_for_null,
3562
        $tabindex_for_value,
3563
        $table,
3564
        $db,
3565
        $row_id,
3566
        array $titles,
3567
        $biggest_max_file_size,
3568
        $text_dir,
3569
        array $repopulate,
3570
        array $where_clause_array
3571
    ) {
3572 8
        $html_output = $this->getHeadAndFootOfInsertRowTable($url_params)
3573 8
            . '<tbody>';
3574
3575
        //store the default value for CharEditing
3576 8
        $default_char_editing = $GLOBALS['cfg']['CharEditing'];
3577 8
        $mime_map = $this->transformations->getMime($db, $table);
3578 8
        $where_clause = '';
3579 8
        if (isset($where_clause_array[$row_id])) {
3580 8
            $where_clause = $where_clause_array[$row_id];
3581
        }
3582 8
        for ($column_number = 0; $column_number < $columns_cnt; $column_number++) {
3583 8
            $table_column = $table_columns[$column_number];
3584 8
            $column_mime = [];
3585 8
            if (isset($mime_map[$table_column['Field']])) {
3586
                $column_mime = $mime_map[$table_column['Field']];
3587
            }
3588
3589
            $virtual = [
3590 8
                'VIRTUAL',
3591
                'PERSISTENT',
3592
                'VIRTUAL GENERATED',
3593
                'STORED GENERATED',
3594
            ];
3595 8
            if (in_array($table_column['Extra'], $virtual)) {
3596
                continue;
3597
            }
3598
3599 8
            $html_output .= $this->getHtmlForInsertEditFormColumn(
3600 8
                $table_columns,
3601 8
                $column_number,
3602 8
                $comments_map,
3603 8
                $timestamp_seen,
3604 8
                $current_result,
3605 8
                $chg_evt_handler,
3606 8
                $jsvkey,
3607 8
                $vkey,
3608 8
                $insert_mode,
3609 8
                $current_row,
3610 8
                $o_rows,
3611 8
                $tabindex,
3612 8
                $columns_cnt,
3613 8
                $is_upload,
3614 8
                $tabindex_for_function,
3615 8
                $foreigners,
3616 8
                $tabindex_for_null,
3617 8
                $tabindex_for_value,
3618 8
                $table,
3619 8
                $db,
3620 8
                $row_id,
3621 8
                $titles,
3622 8
                $biggest_max_file_size,
3623 8
                $default_char_editing,
3624 8
                $text_dir,
3625 8
                $repopulate,
3626 8
                $column_mime,
3627 8
                $where_clause
3628
            );
3629
        } // end for
3630 8
        $o_rows++;
3631
3632 8
        return $html_output . '  </tbody>'
3633 8
            . '</table></div><br>'
3634 8
            . '<div class="clearfloat"></div>';
3635
    }
3636
}
3637