3 declare(strict_types
=1);
7 use PhpMyAdmin\ConfigStorage\Relation
;
8 use PhpMyAdmin\Dbal\ResultInterface
;
9 use PhpMyAdmin\Html\Generator
;
10 use PhpMyAdmin\Plugins\IOTransformationsPlugin
;
11 use PhpMyAdmin\Plugins\TransformationsPlugin
;
12 use PhpMyAdmin\Utils\Gis
;
15 use function array_fill
;
16 use function array_key_exists
;
17 use function array_merge
;
18 use function array_values
;
20 use function class_exists
;
24 use function htmlspecialchars
;
26 use function in_array
;
27 use function is_array
;
29 use function is_string
;
30 use function json_encode
;
32 use function mb_stripos
;
33 use function mb_strlen
;
34 use function mb_strstr
;
36 use function password_hash
;
37 use function preg_match
;
38 use function preg_replace
;
39 use function str_contains
;
40 use function str_ends_with
;
41 use function str_replace
;
42 use function str_starts_with
;
43 use function stripcslashes
;
44 use function stripslashes
;
50 use const PASSWORD_DEFAULT
;
54 private const FUNC_OPTIONAL_PARAM
= ['RAND', 'UNIX_TIMESTAMP'];
56 private const FUNC_NO_PARAM
= [
79 private int $rowOffset = 0;
80 private int $fieldIndex = 0;
82 public function __construct(
83 private DatabaseInterface
$dbi,
84 private Relation
$relation,
85 private Transformations
$transformations,
86 private FileListing
$fileListing,
87 private Template
$template,
92 * Retrieve form parameters for insert/edit form
94 * @param string $db name of the database
95 * @param string $table name of the table
96 * @param mixed[] $whereClauseArray
98 * @return array<string, string> array of insert/edit form parameters
100 public function getFormParametersForInsertForm(
103 array|
null $whereClauses,
104 array $whereClauseArray,
110 'goto' => $GLOBALS['goto'],
111 'err_url' => $errorUrl,
112 'sql_query' => $_POST['sql_query'] ??
'',
115 if ($formParams['sql_query'] === '' && isset($_GET['sql_query'], $_GET['sql_signature'])) {
116 if (Core
::checkSqlQuerySignature($_GET['sql_query'], $_GET['sql_signature'])) {
117 $formParams['sql_query'] = $_GET['sql_query'];
121 if (isset($whereClauses)) {
122 foreach ($whereClauseArray as $keyId => $whereClause) {
123 $formParams['where_clause[' . $keyId . ']'] = trim($whereClause);
127 if (isset($_POST['clause_is_unique'])) {
128 $formParams['clause_is_unique'] = $_POST['clause_is_unique'];
129 } elseif (isset($_GET['clause_is_unique'])) {
130 $formParams['clause_is_unique'] = $_GET['clause_is_unique'];
137 * Analysing where clauses array
139 * @param string[] $whereClauseArray array of where clauses
140 * @param string $table name of the table
141 * @param string $db name of the database
143 * @return array<int, string[]|ResultInterface[]|array<string, string|null>[]|bool>
144 * @phpstan-return array{string[], ResultInterface[], array<string|null>[], bool}
146 private function analyzeWhereClauses(
147 array $whereClauseArray,
154 $foundUniqueKey = false;
155 foreach ($whereClauseArray as $keyId => $whereClause) {
156 $localQuery = 'SELECT * FROM '
157 . Util
::backquote($db) . '.'
158 . Util
::backquote($table)
159 . ' WHERE ' . $whereClause . ';';
160 $result[$keyId] = $this->dbi
->query($localQuery);
161 $rows[$keyId] = $result[$keyId]->fetchAssoc();
163 $whereClauses[$keyId] = str_replace('\\', '\\\\', $whereClause);
164 $hasUniqueCondition = $this->showEmptyResultMessageOrSetUniqueCondition(
171 if (! $hasUniqueCondition) {
175 $foundUniqueKey = true;
178 return [$whereClauses, $result, $rows, $foundUniqueKey];
182 * Show message for empty result or set the unique_condition
184 * @param mixed[] $rows MySQL returned rows
185 * @param string|int $keyId ID in current key
186 * @param mixed[] $whereClauseArray array of where clauses
187 * @param string $localQuery query performed
188 * @param ResultInterface[] $result MySQL result handle
190 private function showEmptyResultMessageOrSetUniqueCondition(
193 array $whereClauseArray,
198 if (! $rows[$keyId]) {
199 unset($rows[$keyId], $whereClauseArray[$keyId]);
200 ResponseRenderer
::getInstance()->addHTML(
201 Generator
::getMessage(
202 __('MySQL returned an empty result set (i.e. zero rows).'),
207 * @todo not sure what should be done at this point, but we must not
208 * exit if we want the message to be displayed
214 $meta = $this->dbi
->getFieldsMeta($result[$keyId]);
216 $uniqueCondition = (new UniqueCondition($meta, $rows[$keyId], true))->getWhereClause();
218 return (bool) $uniqueCondition;
222 * No primary key given, just load first row
224 private function loadFirstRow(string $table, string $db): ResultInterface
226 return $this->dbi
->query(
227 'SELECT * FROM ' . Util
::backquote($db)
228 . '.' . Util
::backquote($table) . ' LIMIT 1;',
232 /** @return false[] */
233 private function getInsertRows(): array
235 // Can be a string on some old configuration storage settings
236 return array_fill(0, Config
::getInstance()->settings
['InsertRows'], false);
240 * Show type information or function selectors in Insert/Edit
242 * @param string $which function|type
243 * @param mixed[] $urlParams containing url parameters
244 * @param bool $isShow whether to show the element in $which
246 * @return string an HTML snippet
248 public function showTypeOrFunction(string $which, array $urlParams, bool $isShow): string
252 $config = Config
::getInstance();
255 $params['ShowFunctionFields'] = ($isShow ?
0 : 1);
256 $params['ShowFieldTypesInDataEditView'] = $config->settings
['ShowFieldTypesInDataEditView'];
259 $params['ShowFieldTypesInDataEditView'] = ($isShow ?
0 : 1);
260 $params['ShowFunctionFields'] = $config->settings
['ShowFunctionFields'];
264 $params['goto'] = Url
::getFromRoute('/sql');
265 $thisUrlParams = array_merge($urlParams, $params);
268 return ' : <a href="' . Url
::getFromRoute('/table/change') . '" data-post="'
269 . Url
::getCommon($thisUrlParams, '', false) . '">'
270 . $this->showTypeOrFunctionLabel($which)
274 return '<th><a href="' . Url
::getFromRoute('/table/change') . '" data-post="'
275 . Url
::getCommon($thisUrlParams, '', false)
276 . '" title="' . __('Hide') . '">'
277 . $this->showTypeOrFunctionLabel($which)
282 * Show type information or function selectors labels in Insert/Edit
284 * @param string $which function|type
286 * @return string an HTML snippet
288 private function showTypeOrFunctionLabel(string $which): string
290 return match ($which) {
291 'function' => __('Function'),
292 'type' => __('Type'),
298 * Retrieve the column title
300 * @param string $fieldName name of the column
301 * @param string[] $commentsMap comments for every column that has a comment
303 * @return string column title
305 private function getColumnTitle(string $fieldName, array $commentsMap): string
307 if (isset($commentsMap[$fieldName])) {
308 return '<span style="border-bottom: 1px dashed black;" title="'
309 . htmlspecialchars($commentsMap[$fieldName]) . '">'
310 . htmlspecialchars($fieldName) . '</span>';
313 return htmlspecialchars($fieldName);
317 * check whether the column is of a certain type
318 * the goal is to ensure that types such as "enum('one','two','binary',..)"
319 * or "enum('one','two','varbinary',..)" are not categorized as binary
321 * @param string $columnType column type as specified in the column definition
322 * @param string[] $types the types to verify
324 public function isColumn(string $columnType, array $types): bool
326 foreach ($types as $oneType) {
327 if (mb_stripos($columnType, $oneType) === 0) {
336 * Retrieve the nullify code for the null column
338 * @param InsertEditColumn $column description of column in given table
339 * @param mixed[] $foreigners keys into foreign fields
340 * @param mixed[] $foreignData data about the foreign keys
342 private function getNullifyCodeForNullColumn(
343 InsertEditColumn
$column,
347 $foreigner = $this->relation
->searchColumnInForeigners($foreigners, $column->field
);
348 if (mb_strstr($column->trueType
, 'enum')) {
349 $nullifyCode = mb_strlen($column->type
) > 20 ?
'1' : '2';
350 } elseif (mb_strstr($column->trueType
, 'set')) {
352 } elseif ($foreigner && $foreignData['foreign_link'] == false) {
353 // foreign key in a drop-down
355 } elseif ($foreigner && $foreignData['foreign_link'] == true) {
356 // foreign key with a browsing icon
366 * Get HTML textarea for insert form
368 * @param InsertEditColumn $column column information
369 * @param string $backupField hidden input field
370 * @param string $columnNameAppendix the name attribute
371 * @param string $onChangeClause onchange clause for fields
372 * @param string $textDir text direction
373 * @param string $specialCharsEncoded replaced char if the string starts
374 * with a \r\n pair (0x0d0a) add an extra \n
375 * @param string $dataType the html5 data-* attribute type
377 * @return string an html snippet
379 private function getTextarea(
380 InsertEditColumn
$column,
382 string $columnNameAppendix,
383 string $onChangeClause,
385 string $specialCharsEncoded,
389 $config = Config
::getInstance();
390 $textAreaRows = $config->settings
['TextareaRows'];
391 $textareaCols = $config->settings
['TextareaCols'];
393 if ($column->isChar
) {
395 * @todo clarify the meaning of the "textfield" class and explain
396 * why character columns have the "char" class instead
398 $theClass = 'char charField';
399 $textAreaRows = $config->settings
['CharTextareaRows'];
400 $textareaCols = $config->settings
['CharTextareaCols'];
401 $extractedColumnspec = Util
::extractColumnSpec($column->type
);
402 $maxlength = $extractedColumnspec['spec_in_brackets'];
403 } elseif ($config->settings
['LongtextDoubleTextarea'] && mb_strstr($column->pmaType
, 'longtext')) {
404 $textAreaRows = $config->settings
['TextareaRows'] * 2;
405 $textareaCols = $config->settings
['TextareaCols'] * 2;
408 return $backupField . "\n"
409 . '<textarea name="fields' . $columnNameAppendix . '"'
410 . ' class="' . $theClass . '"'
411 . (isset($maxlength) ?
' data-maxlength="' . $maxlength . '"' : '')
412 . ' rows="' . $textAreaRows . '"'
413 . ' cols="' . $textareaCols . '"'
414 . ' dir="' . $textDir . '"'
415 . ' id="field_' . $this->fieldIndex
. '_3"'
416 . ($onChangeClause !== '' ?
' onchange="' . htmlspecialchars($onChangeClause, ENT_COMPAT
) . '"' : '')
417 . ' tabindex="' . $this->fieldIndex
. '"'
418 . ' data-type="' . $dataType . '">'
419 . $specialCharsEncoded
424 * Get HTML input type
426 * @param InsertEditColumn $column description of column in given table
427 * @param string $columnNameAppendix the name attribute
428 * @param string $specialChars special characters
429 * @param int $fieldsize html field size
430 * @param string $onChangeClause onchange clause for fields
431 * @param string $dataType the html5 data-* attribute type
433 * @return string an html snippet
435 private function getHtmlInput(
436 InsertEditColumn
$column,
437 string $columnNameAppendix,
438 string $specialChars,
440 string $onChangeClause,
443 $theClass = 'textfield';
444 // verify True_Type which does not contain the parentheses and length
445 if ($column->trueType
=== 'date') {
446 $theClass .= ' datefield';
447 } elseif ($column->trueType
=== 'time') {
448 $theClass .= ' timefield';
449 } elseif ($column->trueType
=== 'datetime' ||
$column->trueType
=== 'timestamp') {
450 $theClass .= ' datetimefield';
454 $isInteger = in_array($column->trueType
, $this->dbi
->types
->getIntegerTypes());
456 $extractedColumnspec = Util
::extractColumnSpec($column->type
);
457 $isUnsigned = $extractedColumnspec['unsigned'];
458 $minMaxValues = $this->dbi
->types
->getIntegerRange($column->trueType
, ! $isUnsigned);
459 $inputMinMax = 'min="' . $minMaxValues[0] . '" '
460 . 'max="' . $minMaxValues[1] . '"';
464 // do not use the 'date' or 'time' types here; they have no effect on some
465 // browsers and create side effects (see bug #4218)
466 return '<input type="text"'
467 . ' name="fields' . $columnNameAppendix . '"'
468 . ' value="' . $specialChars . '" size="' . $fieldsize . '"'
470 ?
' data-maxlength="' . $fieldsize . '"'
472 . ($inputMinMax !== '' ?
' ' . $inputMinMax : '')
473 . ' data-type="' . $dataType . '"'
474 . ' class="' . $theClass . '" onchange="' . htmlspecialchars($onChangeClause, ENT_COMPAT
) . '"'
475 . ' tabindex="' . $this->fieldIndex
. '"'
476 . ($isInteger ?
' inputmode="numeric"' : '')
477 . ' id="field_' . $this->fieldIndex
. '_3">';
481 * Get HTML select option for upload
483 * @param string $vkey [multi_edit]['row_id']
484 * @param string $fieldHashMd5 array index as an MD5 to avoid having special characters
486 * @return string an HTML snippet
488 private function getSelectOptionForUpload(string $vkey, string $fieldHashMd5): string
490 $files = $this->fileListing
->getFileSelectOptions(
491 Util
::userDir((Config
::getInstance()->settings
['UploadDir'] ??
'')),
494 if ($files === false) {
495 return '<span style="color:red">' . __('Error') . '</span><br>' . "\n"
496 . __('The directory you set for upload work cannot be reached.') . "\n";
504 . '<i>' . __('Or') . '</i> '
505 . __('web server upload directory:') . '<br>' . "\n"
506 . '<select size="1" name="fields_uploadlocal'
507 . $vkey . '[' . $fieldHashMd5 . ']">' . "\n"
508 . '<option value="" selected="selected"></option>' . "\n"
510 . '</select>' . "\n";
514 * Retrieve the maximum upload file size
516 private function getMaxUploadSize(string $pmaType): string
518 // find maximum upload size, based on field type
520 * @todo with functions this is not so easy, as you can basically
521 * process any data with function like MD5
523 $maxFieldSize = match ($pmaType) {
526 'mediumblob' => 16777216,
527 'longblob' => 4294967296,// yeah, really
530 $thisFieldMaxSize = (int) Config
::getInstance()->get('max_upload_size'); // from PHP max
532 return Util
::getFormattedMaximumUploadSize(min($thisFieldMaxSize, $maxFieldSize)) . "\n";
536 * Get HTML for the Value column of other datatypes
537 * (here, "column" is used in the sense of HTML column in HTML table)
539 * @param InsertEditColumn $column description of column in given table
540 * @param string $defaultCharEditing default char editing mode which is stored
541 * in the config.inc.php script
542 * @param string $backupField hidden input field
543 * @param string $columnNameAppendix the name attribute
544 * @param string $onChangeClause onchange clause for fields
545 * @param string $specialChars special characters
546 * @param string $textDir text direction
547 * @param string $specialCharsEncoded replaced char if the string starts
548 * with a \r\n pair (0x0d0a) add an extra \n
549 * @param string $data data to edit
550 * @param mixed[] $extractedColumnspec associative array containing type,
551 * spec_in_brackets and possibly
552 * enum_set_values (another array)
554 * @return string an html snippet
556 private function getValueColumnForOtherDatatypes(
557 InsertEditColumn
$column,
558 string $defaultCharEditing,
560 string $columnNameAppendix,
561 string $onChangeClause,
562 string $specialChars,
564 string $specialCharsEncoded,
566 array $extractedColumnspec,
568 // HTML5 data-* attribute data-type
569 $dataType = $this->dbi
->types
->getTypeClass($column->trueType
);
570 $fieldsize = $this->getColumnSize($column, $extractedColumnspec['spec_in_brackets']);
572 $config = Config
::getInstance();
573 $isTextareaRequired = $column->isChar
574 && ($config->settings
['CharEditing'] === 'textarea' ||
str_contains($data, "\n"));
575 if ($isTextareaRequired) {
576 $config->settings
['CharEditing'] = $defaultCharEditing;
577 $htmlField = $this->getTextarea(
583 $specialCharsEncoded,
587 $htmlField = $this->getHtmlInput(
597 return $this->template
->render('table/insert/value_column_for_other_datatype', [
598 'html_field' => $htmlField,
599 'backup_field' => $backupField,
600 'is_textarea' => $isTextareaRequired,
601 'columnNameAppendix' => $columnNameAppendix,
609 * @param InsertEditColumn $column description of column in given table
610 * @param string $specInBrackets text in brackets inside column definition
612 * @return int field size
614 private function getColumnSize(InsertEditColumn
$column, string $specInBrackets): int
616 $config = Config
::getInstance();
617 if ($column->isChar
) {
618 $fieldsize = (int) $specInBrackets;
619 if ($fieldsize > $config->settings
['MaxSizeForInputField']) {
621 * This case happens for CHAR or VARCHAR columns which have
622 * a size larger than the maximum size for input field.
624 $config->settings
['CharEditing'] = 'textarea';
628 * This case happens for example for INT or DATE columns;
629 * in these situations, the value returned in $column['len']
632 $fieldsize = $column->length
;
636 max($fieldsize, $config->settings
['MinSizeForInputField']),
637 $config->settings
['MaxSizeForInputField'],
642 * get html for continue insertion form
644 * @param string $table name of the table
645 * @param string $db name of the database
646 * @param mixed[] $whereClauseArray
648 * @return string an html snippet
650 public function getContinueInsertionForm(
653 array $whereClauseArray,
656 return $this->template
->render('table/insert/continue_insertion_form', [
659 'where_clause_array' => $whereClauseArray,
660 'err_url' => $errorUrl,
661 'goto' => $GLOBALS['goto'],
662 'sql_query' => $_POST['sql_query'] ??
null,
663 'has_where_clause' => isset($_POST['where_clause']),
664 'insert_rows_default' => Config
::getInstance()->settings
['InsertRows'],
669 * @param string[]|string|null $whereClause
673 public static function isWhereClauseNumeric(array|
string|
null $whereClause): bool
675 if ($whereClause === null) {
679 if (! is_array($whereClause)) {
680 $whereClause = [$whereClause];
683 // If we have just numeric primary key, we can also edit next
684 // we are looking for `table_name`.`field_name` = numeric_value
685 foreach ($whereClause as $clause) {
686 // preg_match() returns 1 if there is a match
687 $isNumeric = preg_match('@^[\s]*`[^`]*`[\.]`[^`]*` = [0-9]+@', $clause) === 1;
697 * Get table head and table foot for insert row table
699 * @param mixed[] $urlParams url parameters
701 * @return string an html snippet
703 private function getHeadAndFootOfInsertRowTable(array $urlParams): string
708 $config = Config
::getInstance();
709 if ($config->settings
['ShowFieldTypesInDataEditView']) {
710 $type = $this->showTypeOrFunction('type', $urlParams, true);
713 if ($config->settings
['ShowFunctionFields']) {
714 $function = $this->showTypeOrFunction('function', $urlParams, true);
717 $template = new Template();
719 return $template->render('table/insert/get_head_and_foot_of_insert_row_table', [
721 'function' => $function,
726 * Prepares the field value and retrieve special chars, backup field and data array
728 * @param mixed[] $currentRow a row of the table
729 * @param InsertEditColumn $column description of column in given table
730 * @param mixed[] $extractedColumnspec associative array containing type,
731 * spec_in_brackets and possibly
732 * enum_set_values (another array)
733 * @param mixed[] $gisDataTypes list of GIS data types
734 * @param string $columnNameAppendix string to append to column name in input
735 * @param bool $asIs use the data as is, used in repopulating
737 * @return mixed[] $real_null_value, $data, $special_chars, $backup_field,
738 * $special_chars_encoded
739 * @psalm-return array{bool, string, string, string, string}
741 private function getSpecialCharsAndBackupFieldForExistingRow(
743 InsertEditColumn
$column,
744 array $extractedColumnspec,
746 string $columnNameAppendix,
749 $specialCharsEncoded = '';
751 $realNullValue = false;
753 if (! isset($currentRow[$column->field
])) {
754 $realNullValue = true;
755 $currentRow[$column->field
] = '';
758 } elseif ($column->trueType
=== 'bit') {
759 $specialChars = $asIs
760 ?
$currentRow[$column->field
]
761 : Util
::printableBitValue(
762 (int) $currentRow[$column->field
],
763 (int) $extractedColumnspec['spec_in_brackets'],
766 (str_starts_with($column->trueType
, 'timestamp')
767 ||
$column->trueType
=== 'datetime'
768 ||
$column->trueType
=== 'time')
769 && (str_contains($currentRow[$column->field
], '.'))
771 $currentRow[$column->field
] = $asIs
772 ?
$currentRow[$column->field
]
773 : Util
::addMicroseconds($currentRow[$column->field
]);
774 $specialChars = htmlspecialchars($currentRow[$column->field
], ENT_COMPAT
);
775 } elseif (in_array($column->trueType
, $gisDataTypes)) {
776 // Convert gis data to Well Know Text format
777 $currentRow[$column->field
] = $asIs
778 ?
$currentRow[$column->field
]
779 : Gis
::convertToWellKnownText($currentRow[$column->field
], true);
780 $specialChars = htmlspecialchars($currentRow[$column->field
], ENT_COMPAT
);
782 // special binary "characters"
783 if ($column->isBinary ||
($column->isBlob
&& Config
::getInstance()->settings
['ProtectBinary'] !== 'all')) {
784 $currentRow[$column->field
] = $asIs
785 ?
$currentRow[$column->field
]
786 : bin2hex($currentRow[$column->field
]);
789 $specialChars = htmlspecialchars($currentRow[$column->field
], ENT_COMPAT
);
791 //We need to duplicate the first \n or otherwise we will lose
792 //the first newline entered in a VARCHAR or TEXT column
793 $specialCharsEncoded = Util
::duplicateFirstNewline($specialChars);
795 $data = $currentRow[$column->field
];
798 /** @var string $defaultAction */
799 $defaultAction = $_POST['default_action'] ??
$_GET['default_action'] ??
'';
801 $defaultAction === 'insert'
802 && $column->key
=== 'PRI'
803 && str_contains($column->extra
, 'auto_increment')
805 // When copying row, it is useful to empty auto-increment column to prevent duplicate key error.
806 $data = $specialCharsEncoded = $specialChars = null;
809 // If a timestamp field value is not included in an update
810 // statement MySQL auto-update it to the current timestamp;
811 // however, things have changed since MySQL 4.1, so
812 // it's better to set a fields_prev in this situation
813 $backupField = '<input type="hidden" name="fields_prev'
814 . $columnNameAppendix . '" value="'
815 . htmlspecialchars($currentRow[$column->field
], ENT_COMPAT
) . '">';
817 return [$realNullValue, (string) $specialCharsEncoded, (string) $specialChars, (string) $data, $backupField];
821 * display default values
823 private function getSpecialCharsForInsertingMode(
824 string|
null $defaultValue,
827 if ($defaultValue === null) {
831 if ($trueType === 'bit') {
832 $specialChars = Util
::convertBitDefaultValue($defaultValue);
833 } elseif (str_starts_with($trueType, 'timestamp') ||
$trueType === 'datetime' ||
$trueType === 'time') {
834 $specialChars = Util
::addMicroseconds($defaultValue);
835 } elseif ($trueType === 'binary' ||
$trueType === 'varbinary') {
836 $specialChars = bin2hex($defaultValue);
837 } elseif (str_ends_with($trueType, 'text')) {
838 $textDefault = substr($defaultValue, 1, -1);
839 $specialChars = stripcslashes($textDefault !== '' ?
$textDefault : $defaultValue);
841 $specialChars = htmlspecialchars($defaultValue);
844 return $specialChars;
848 * set $_SESSION for edit_next
850 * @param string $oneWhereClause one where clause from where clauses array
852 public function setSessionForEditNext(string $oneWhereClause): void
854 $localQuery = 'SELECT * FROM ' . Util
::backquote($GLOBALS['db'])
855 . '.' . Util
::backquote($GLOBALS['table']) . ' WHERE '
856 . str_replace('` =', '` >', $oneWhereClause) . ' LIMIT 1;';
858 $res = $this->dbi
->query($localQuery);
859 $row = $res->fetchRow();
860 $meta = $this->dbi
->getFieldsMeta($res);
861 // must find a unique condition based on unique key,
862 // not a combination of all fields
863 $uniqueCondition = (new UniqueCondition($meta, $row, true))->getWhereClause();
864 if (! $uniqueCondition) {
868 $_SESSION['edit_next'] = $uniqueCondition;
872 * set $goto_include variable for different cases and retrieve like,
873 * if $GLOBALS['goto'] empty, if $goto_include previously not defined
874 * and new_insert, same_insert, edit_next
876 * @param string|false $gotoInclude store some script for include, otherwise it is
879 public function getGotoInclude(string|
false $gotoInclude): string
881 $validOptions = ['new_insert', 'same_insert', 'edit_next'];
882 if (isset($_POST['after_insert']) && in_array($_POST['after_insert'], $validOptions)) {
883 return '/table/change';
886 if (! empty($GLOBALS['goto'])) {
887 if (! preg_match('@^[a-z_]+\.php$@', $GLOBALS['goto'])) {
888 // this should NOT happen
889 //$GLOBALS['goto'] = false;
890 $gotoInclude = $GLOBALS['goto'] === 'index.php?route=/sql' ?
'/sql' : false;
892 $gotoInclude = $GLOBALS['goto'];
895 if ($GLOBALS['goto'] === 'index.php?route=/database/sql' && strlen($GLOBALS['table']) > 0) {
896 $GLOBALS['table'] = '';
900 if (! $gotoInclude) {
901 $gotoInclude = $GLOBALS['table'] === '' ?
'/database/sql' : '/table/sql';
908 * Defines the url to return in case of failure of the query
910 * @param mixed[] $urlParams url parameters
912 * @return string error url for query failure
914 public function getErrorUrl(array $urlParams): string
916 return $_POST['err_url'] ?? Url
::getFromRoute('/table/change', $urlParams);
920 * Executes the sql query and get the result, then move back to the calling page
922 * @param mixed[] $query built query from buildSqlQuery()
924 * @return mixed[] $total_affected_rows, $last_messages, $warning_messages, $error_messages
926 public function executeSqlQuery(array $query): array
928 $GLOBALS['sql_query'] = implode('; ', $query) . ';';
929 // to ensure that the query is displayed in case of
930 // "insert as new row" and then "insert another new row"
931 $GLOBALS['display_query'] = $GLOBALS['sql_query'];
933 $totalAffectedRows = 0;
935 $warningMessages = [];
938 foreach ($query as $singleQuery) {
939 if (isset($_POST['submit_type']) && $_POST['submit_type'] === 'showinsert') {
940 $lastMessages[] = Message
::notice(__('Showing SQL query'));
944 if (Config
::getInstance()->settings
['IgnoreMultiSubmitErrors']) {
945 $result = $this->dbi
->tryQuery($singleQuery);
947 $result = $this->dbi
->query($singleQuery);
951 $errorMessages[] = $this->dbi
->getError();
953 $totalAffectedRows +
= $this->dbi
->affectedRows();
955 $insertId = $this->dbi
->insertId();
956 if ($insertId !== 0) {
957 // insert_id is id of FIRST record inserted in one insert, so if we
958 // inserted multiple rows, we had to increment this
960 if ($totalAffectedRows > 0) {
961 $insertId +
= $totalAffectedRows - 1;
964 $lastMessage = Message
::notice(__('Inserted row id: %1$d'));
965 $lastMessage->addParam($insertId);
966 $lastMessages[] = $lastMessage;
970 $warningMessages = $this->getWarningMessages();
973 return [$totalAffectedRows, $lastMessages, $warningMessages, $errorMessages];
977 * get the warning messages array
981 private function getWarningMessages(): array
983 $warningMessages = [];
984 foreach ($this->dbi
->getWarnings() as $warning) {
985 $warningMessages[] = htmlspecialchars((string) $warning);
988 return $warningMessages;
992 * Column to display from the foreign table?
994 * @param string $whereComparison string that contain relation field value
995 * @param mixed[] $map all Relations to foreign tables for a given
996 * table or optionally a given column in a table
997 * @param string $relationField relation field
999 * @return string display value from the foreign table
1001 public function getDisplayValueForForeignTableColumn(
1002 string $whereComparison,
1004 string $relationField,
1006 $foreigner = $this->relation
->searchColumnInForeigners($map, $relationField);
1008 if (! is_array($foreigner)) {
1012 $displayField = $this->relation
->getDisplayField($foreigner['foreign_db'], $foreigner['foreign_table']);
1013 // Field to display from the foreign table?
1014 if ($displayField !== '') {
1015 $dispsql = 'SELECT ' . Util
::backquote($displayField)
1016 . ' FROM ' . Util
::backquote($foreigner['foreign_db'])
1017 . '.' . Util
::backquote($foreigner['foreign_table'])
1018 . ' WHERE ' . Util
::backquote($foreigner['foreign_field'])
1020 $dispresult = $this->dbi
->tryQuery($dispsql);
1021 if ($dispresult && $dispresult->numRows() > 0) {
1022 return (string) $dispresult->fetchValue();
1030 * Display option in the cell according to user choices
1032 * @param mixed[] $map all Relations to foreign tables for a given
1033 * table or optionally a given column in a table
1034 * @param string $relationField relation field
1035 * @param string $whereComparison string that contain relation field value
1036 * @param string $dispval display value from the foreign table
1037 * @param string $relationFieldValue relation field value
1039 * @return string HTML <a> tag
1041 public function getLinkForRelationalDisplayField(
1043 string $relationField,
1044 string $whereComparison,
1046 string $relationFieldValue,
1048 $foreigner = $this->relation
->searchColumnInForeigners($map, $relationField);
1050 if (! is_array($foreigner)) {
1054 if ($_SESSION['tmpval']['relational_display'] === 'K') {
1055 // user chose "relational key" in the display options, so
1056 // the title contains the display field
1057 $title = $dispval !== ''
1058 ?
' title="' . htmlspecialchars($dispval) . '"'
1061 $title = ' title="' . htmlspecialchars($relationFieldValue) . '"';
1064 $sqlQuery = 'SELECT * FROM '
1065 . Util
::backquote($foreigner['foreign_db'])
1066 . '.' . Util
::backquote($foreigner['foreign_table'])
1067 . ' WHERE ' . Util
::backquote($foreigner['foreign_field'])
1070 'db' => $foreigner['foreign_db'],
1071 'table' => $foreigner['foreign_table'],
1073 'sql_signature' => Core
::signSqlQuery($sqlQuery),
1074 'sql_query' => $sqlQuery,
1076 $output = '<a href="' . Url
::getFromRoute('/sql', $urlParams) . '"' . $title . '>';
1078 if ($_SESSION['tmpval']['relational_display'] === 'D') {
1079 // user chose "relational display field" in the
1080 // display options, so show display field in the cell
1081 $output .= htmlspecialchars($dispval);
1083 // otherwise display data in the cell
1084 $output .= htmlspecialchars($relationFieldValue);
1093 * Transform edited values
1095 * @param string $db db name
1096 * @param string $table table name
1097 * @param mixed[] $transformation mimetypes for all columns of a table
1098 * [field_name][field_key]
1099 * @param mixed[] $editedValues transform columns list and new values
1100 * @param string $file file containing the transformation plugin
1101 * @param string $columnName column name
1102 * @param mixed[] $extraData extra data array
1103 * @param string $type the type of transformation
1107 public function transformEditedValues(
1110 array $transformation,
1111 array &$editedValues,
1117 $includeFile = 'src/Plugins/Transformations/' . $file;
1118 if (is_file(ROOT_PATH
. $includeFile)) {
1119 // $cfg['SaveCellsAtOnce'] = true; JS code sends an array
1120 $whereClause = is_array($_POST['where_clause']) ?
$_POST['where_clause'][0] : $_POST['where_clause'];
1124 'where_clause_sign' => Core
::signSqlQuery($whereClause),
1125 'where_clause' => $whereClause,
1126 'transform_key' => $columnName,
1128 $transformOptions = $this->transformations
->getOptions($transformation[$type . '_options'] ??
'');
1129 $transformOptions['wrapper_link'] = Url
::getCommon($urlParams);
1130 $transformOptions['wrapper_params'] = $urlParams;
1131 $className = $this->transformations
->getClassName($includeFile);
1132 if (class_exists($className)) {
1133 /** @var TransformationsPlugin $transformationPlugin */
1134 $transformationPlugin = new $className();
1136 foreach ($editedValues as $cellIndex => $currCellEditedValues) {
1137 if (! isset($currCellEditedValues[$columnName])) {
1141 $extraData['transformations'][$cellIndex] = $transformationPlugin->applyTransformation(
1142 $currCellEditedValues[$columnName],
1145 $editedValues[$cellIndex][$columnName] = $extraData['transformations'][$cellIndex];
1154 * Get value part if a function was specified
1156 private function formatAsSqlFunction(
1157 EditField
$editField,
1159 if ($editField->function === 'PHP_PASSWORD_HASH') {
1160 $hash = password_hash($editField->value
, PASSWORD_DEFAULT
);
1162 return $this->dbi
->quoteString($hash);
1165 if ($editField->function === 'UUID') {
1166 /* This way user will know what UUID new row has */
1167 $uuid = (string) $this->dbi
->fetchValue('SELECT UUID()');
1169 return $this->dbi
->quoteString($uuid);
1173 in_array($editField->function, $this->getGisFromTextFunctions())
1174 ||
in_array($editField->function, $this->getGisFromWKBFunctions())
1176 preg_match('/^(\'?)(.*?)\1(?:,(\d+))?$/', $editField->value
, $matches);
1177 $escapedParams = $this->dbi
->quoteString($matches[2]) . (isset($matches[3]) ?
',' . $matches[3] : '');
1179 return $editField->function . '(' . $escapedParams . ')';
1183 ! in_array($editField->function, self
::FUNC_NO_PARAM
)
1184 ||
($editField->value
!== '' && in_array($editField->function, self
::FUNC_OPTIONAL_PARAM
))
1187 ($editField->salt
!== null
1188 && ($editField->function === 'AES_ENCRYPT'
1189 ||
$editField->function === 'AES_DECRYPT'
1190 ||
$editField->function === 'SHA2'))
1191 ||
($editField->salt
1192 && ($editField->function === 'DES_ENCRYPT'
1193 ||
$editField->function === 'DES_DECRYPT'
1194 ||
$editField->function === 'ENCRYPT'))
1196 return $editField->function . '(' . $this->dbi
->quoteString($editField->value
) . ','
1197 . $this->dbi
->quoteString($editField->salt
) . ')';
1200 return $editField->function . '(' . $this->dbi
->quoteString($editField->value
) . ')';
1203 return $editField->function . '()';
1207 * Get the field value formatted for use in a SQL statement.
1208 * Used in both INSERT and UPDATE statements.
1210 private function getValueFormattedAsSql(
1211 EditField
$editField,
1212 string $protectedValue = '',
1214 if ($editField->isUploaded
) {
1215 return $editField->value
;
1218 if ($editField->function !== '') {
1219 return $this->formatAsSqlFunction($editField);
1222 return $this->formatAsSqlValueBasedOnType($editField, $protectedValue);
1226 * Get query values array and query fields array for insert and update in multi edit
1228 * @param string|int $whereClause Either a positional index or string representing selected row
1230 public function getQueryValueForInsert(
1231 EditField
$editField,
1233 string|
int $whereClause,
1235 $protectedValue = '';
1236 if ($editField->type
=== 'protected' && $usingKey && $whereClause !== '') {
1237 // Fetch the current values of a row to use in case we have a protected field
1238 $protectedValue = $this->dbi
->fetchValue(
1239 'SELECT ' . Util
::backquote($editField->columnName
)
1240 . ' FROM ' . Util
::backquote($GLOBALS['table'])
1241 . ' WHERE ' . $whereClause,
1243 $protectedValue = is_string($protectedValue) ?
$protectedValue : '';
1246 return $this->getValueFormattedAsSql($editField, $protectedValue);
1250 * Get field-value pairs for update SQL.
1251 * During update, we build the SQL only with the fields that should be updated.
1253 public function getQueryValueForUpdate(EditField
$editField): string
1255 $currentValueFormattedAsSql = $this->getValueFormattedAsSql($editField);
1257 // avoid setting a field to NULL when it's already NULL
1258 // (field had the null checkbox before the update; field still has the null checkbox)
1259 if ($editField->wasPreviouslyNull
&& $editField->isNull
) {
1263 // A blob field that hasn't been changed will have no value
1264 if ($currentValueFormattedAsSql === '') {
1269 // Field had the null checkbox before the update; field no longer has the null checkbox
1270 $editField->wasPreviouslyNull ||
1271 // Field was marked as NULL (the value will be unchanged if it was an empty string)
1272 $editField->isNull ||
1273 // A function was applied to the field
1274 $editField->function !== '' ||
1275 // The value was changed
1276 $editField->value
!== $editField->previousValue
1278 return Util
::backquote($editField->columnName
) . ' = ' . $currentValueFormattedAsSql;
1285 * Get the current column value in the form for different data types
1287 private function formatAsSqlValueBasedOnType(
1288 EditField
$editField,
1289 string $protectedValue,
1291 if ($editField->type
=== 'protected') {
1292 // here we are in protected mode (asked in the config)
1293 // so tbl_change has put this special value in the
1294 // columns array, so we do not change the column value
1295 // but we can still handle column upload
1297 // when in UPDATE mode, do not alter field's contents. When in INSERT
1298 // mode, insert empty field because no values were submitted.
1299 // If protected blobs were set, insert original field's content.
1300 if ($protectedValue !== '') {
1301 return '0x' . bin2hex($protectedValue);
1304 if ($editField->isNull
) {
1308 // The Null checkbox was unchecked for this field
1309 if ($editField->wasPreviouslyNull
) {
1316 if ($editField->value
=== '') {
1317 // When the field is autoIncrement, the best way to avoid problems
1318 // in strict mode is to set the value to null (works also in non-strict mode)
1320 // If the value is empty and the null checkbox is checked, set it to null
1321 return $editField->autoIncrement ||
$editField->isNull ?
'NULL' : "''";
1324 if ($editField->type
=== 'hex') {
1325 if (! str_starts_with($editField->value
, '0x')) {
1326 return '0x' . $editField->value
;
1329 return $editField->value
;
1332 if ($editField->type
=== 'bit') {
1333 $currentValue = (string) preg_replace('/[^01]/', '0', $editField->value
);
1335 return 'b' . $this->dbi
->quoteString($currentValue);
1338 // For uuid type, generate uuid value
1339 // if empty value but not set null or value is uuid() function
1341 $editField->type
=== 'uuid'
1342 && ! $editField->isNull
1343 && in_array($editField->value
, ["''", '', "'uuid()'", 'uuid()'], true)
1349 ($editField->type
!== 'datetime' && $editField->type
!== 'timestamp' && $editField->type
!== 'date')
1350 ||
($editField->value
!== 'CURRENT_TIMESTAMP' && $editField->value
!== 'current_timestamp()')
1352 return $this->dbi
->quoteString($editField->value
);
1355 // If there is a value, we ignore the Null checkbox;
1356 // this could be possible if Javascript is disabled in the browser
1357 return $editField->value
;
1361 * Check whether inline edited value can be truncated or not,
1362 * and add additional parameters for extra_data array if needed
1364 * @param string $db Database name
1365 * @param string $table Table name
1366 * @param string $columnName Column name
1367 * @param mixed[] $extraData Extra data for ajax response
1369 public function verifyWhetherValueCanBeTruncatedAndAppendExtraData(
1375 $extraData['isNeedToRecheck'] = false;
1377 $sqlForRealValue = 'SELECT ' . Util
::backquote($table) . '.'
1378 . Util
::backquote($columnName)
1379 . ' FROM ' . Util
::backquote($db) . '.'
1380 . Util
::backquote($table)
1381 . ' WHERE ' . $_POST['where_clause'][0];
1383 $result = $this->dbi
->tryQuery($sqlForRealValue);
1389 $fieldsMeta = $this->dbi
->getFieldsMeta($result);
1390 $meta = $fieldsMeta[0];
1391 $newValue = $result->fetchValue();
1393 if ($newValue === false) {
1397 if ($newValue !== null) {
1398 if ($meta->isTimeType()) {
1399 $newValue = Util
::addMicroseconds($newValue);
1400 } elseif ($meta->isBinary()) {
1401 $newValue = '0x' . bin2hex($newValue);
1405 $extraData['isNeedToRecheck'] = true;
1406 $extraData['truncatableFieldValue'] = $newValue;
1410 * Function to get the columns of a table
1412 * @param string $db current db
1413 * @param string $table current table
1415 * @return list<ColumnFull>
1417 public function getTableColumns(string $db, string $table): array
1419 $this->dbi
->selectDb($db);
1421 return array_values($this->dbi
->getColumns($db, $table, true));
1425 * Function to determine Insert/Edit rows
1427 * @param string[]|string|null $whereClause where clause
1428 * @param string $db current database
1429 * @param string $table current table
1431 * @return array<int, bool|string[]|string|ResultInterface|ResultInterface[]|null>
1432 * @phpstan-return array{
1434 * string[]|string|null,
1437 * ResultInterface[]|ResultInterface,
1438 * array<string, string|null>[]|false[],
1443 public function determineInsertOrEdit(array|
string|
null $whereClause, string $db, string $table): array
1445 if (isset($_POST['where_clause'])) {
1446 $whereClause = $_POST['where_clause'];
1449 if (isset($_SESSION['edit_next'])) {
1450 $whereClause = $_SESSION['edit_next'];
1451 unset($_SESSION['edit_next']);
1452 $afterInsert = 'edit_next';
1455 $config = Config
::getInstance();
1456 if (isset($_POST['ShowFunctionFields'])) {
1457 $config->settings
['ShowFunctionFields'] = $_POST['ShowFunctionFields'];
1460 if (isset($_POST['ShowFieldTypesInDataEditView'])) {
1461 $config->settings
['ShowFieldTypesInDataEditView'] = $_POST['ShowFieldTypesInDataEditView'];
1464 if (isset($_POST['after_insert'])) {
1465 $afterInsert = $_POST['after_insert'];
1468 if (isset($whereClause)) {
1470 $insertMode = false;
1471 $whereClauseArray = (array) $whereClause;
1472 [$whereClauses, $result, $rows, $foundUniqueKey] = $this->analyzeWhereClauses(
1480 $whereClause = null;
1481 $result = $this->loadFirstRow($table, $db);
1482 $rows = $this->getInsertRows();
1483 $whereClauses = null;
1484 $whereClauseArray = [];
1485 $foundUniqueKey = false;
1488 /** @var string $defaultAction */
1489 $defaultAction = $_POST['default_action'] ??
$_GET['default_action'] ??
'';
1490 if ($defaultAction === 'insert') {
1491 // Copying a row - fetched data will be inserted as a new row, therefore the where clause is needless.
1492 $whereClause = $whereClauses = null;
1503 $afterInsert ??
null,
1508 * Function to get comments for the table columns
1510 * @param string $db current database
1511 * @param string $table current table
1513 * @return string[] comments for columns
1515 public function getCommentsMap(string $db, string $table): array
1517 if (Config
::getInstance()->settings
['ShowPropertyComments']) {
1518 return $this->relation
->getComments($db, $table);
1525 * Function to get html for the gis editor div
1527 public function getHtmlForGisEditor(): string
1529 return '<div id="gis_editor"></div><div id="popup_background"></div><br>';
1533 * Function to get html for the ignore option in insert mode
1535 * @param int $rowId row id
1536 * @param bool $checked ignore option is checked or not
1538 public function getHtmlForIgnoreOption(int $rowId, bool $checked = true): string
1540 return '<input type="checkbox"'
1541 . ($checked ?
' checked="checked"' : '')
1542 . ' name="insert_ignore_' . $rowId . '"'
1543 . ' id="insert_ignore_' . $rowId . '">'
1544 . '<label for="insert_ignore_' . $rowId . '">'
1546 . '</label><br>' . "\n";
1550 * Function to get html for the insert edit form header
1552 * @param bool $hasBlobField whether has blob field
1553 * @param bool $isUpload whether is upload
1555 public function getHtmlForInsertEditFormHeader(bool $hasBlobField, bool $isUpload): string
1557 $template = new Template();
1559 return $template->render('table/insert/get_html_for_insert_edit_form_header', [
1560 'has_blob_field' => $hasBlobField,
1561 'is_upload' => $isUpload,
1566 * Function to get html for each insert/edit column
1568 * @param ColumnFull $tableColumn column
1569 * @param int $columnNumber column index in table_columns
1570 * @param string[] $commentsMap comments map
1571 * @param int $columnLength length of the current column taken from field metadata
1572 * @param bool $insertMode whether insert mode
1573 * @param mixed[] $currentRow current row
1574 * @param int $columnsCnt columns count
1575 * @param bool $isUpload whether upload
1576 * @param mixed[] $foreigners foreigners
1577 * @param string $table table
1578 * @param string $db database
1579 * @param int $rowId row id
1580 * @param string $defaultCharEditing default char editing mode which is stored in the config.inc.php script
1581 * @param string $textDir text direction
1582 * @param mixed[] $repopulate the data to be repopulated
1583 * @param mixed[] $columnMime the mime information of column
1584 * @param string $whereClause the where clause
1586 private function getHtmlForInsertEditFormColumn(
1587 ColumnFull
$tableColumn,
1599 string $defaultCharEditing,
1603 string $whereClause,
1605 $column = new InsertEditColumn(
1606 $tableColumn->field
,
1608 $tableColumn->isNull
,
1610 $tableColumn->default,
1611 $tableColumn->extra
,
1613 $this->isColumn($tableColumn->type
, ['binary', 'varbinary']),
1614 $this->isColumn($tableColumn->type
, ['blob', 'tinyblob', 'mediumblob', 'longblob']),
1615 $this->isColumn($tableColumn->type
, ['char', 'varchar']),
1620 $fieldHashMd5 = $column->md5
;
1621 if ($repopulate !== [] && array_key_exists($fieldHashMd5, $currentRow)) {
1622 $currentRow[$column->field
] = $repopulate[$fieldHashMd5];
1626 $extractedColumnspec = Util
::extractColumnSpec($column->type
);
1628 //Call validation when the form submitted...
1629 $onChangeClause = 'return verificationsAfterFieldChange('
1630 . json_encode($fieldHashMd5) . ', '
1631 . json_encode((string) $rowId) . ',' . json_encode($column->pmaType
) . ')';
1633 $vkey = '[multi_edit][' . $rowId . ']';
1634 // Use an MD5 as an array index to avoid having special characters
1635 // in the name attribute (see bug #1746964 )
1636 $columnNameAppendix = $vkey . '[' . $fieldHashMd5 . ']';
1638 // Get a list of GIS data types.
1639 $gisDataTypes = Gis
::getDataTypes();
1641 // Prepares the field value
1642 if ($currentRow !== []) {
1646 $specialCharsEncoded,
1650 ] = $this->getSpecialCharsAndBackupFieldForExistingRow(
1653 $extractedColumnspec,
1655 $columnNameAppendix,
1659 // (we are inserting)
1660 // display default values
1661 $defaultValue = $column->default ??
null;
1662 if (isset($repopulate[$fieldHashMd5])) {
1663 $defaultValue = $repopulate[$fieldHashMd5];
1666 $realNullValue = $defaultValue === null;
1667 $data = (string) $defaultValue;
1668 $specialChars = $this->getSpecialCharsForInsertingMode($defaultValue, $column->trueType
);
1669 $specialCharsEncoded = Util
::duplicateFirstNewline($specialChars);
1673 $this->fieldIndex
= ($this->rowOffset
* $columnsCnt) +
$columnNumber +
1;
1675 // The function column
1676 // -------------------
1677 $foreignData = $this->relation
->getForeignData($foreigners, $column->field
, false, '', '');
1678 $isColumnBinary = $this->isColumnBinary($column, $isUpload);
1679 $functionOptions = '';
1681 $config = Config
::getInstance();
1682 if ($config->settings
['ShowFunctionFields']) {
1683 $defaultFunction = Generator
::getDefaultFunctionForField(
1685 $column->firstTimestamp
,
1693 $functionOptions = Generator
::getFunctionsForField($defaultFunction, $foreignData);
1696 // nullify code is needed by the js nullify() function to be able to generate calls to nullify() in jQuery
1697 $nullifyCode = $this->getNullifyCodeForNullColumn($column, $foreigners, $foreignData);
1699 // The value column (depends on type)
1701 // See bug #1667887 for the reason why we don't use the maxlength
1704 //add data attributes "no of decimals" and "data type"
1706 $type = current(explode('(', $column->pmaType
));
1707 if (preg_match('/\(([^()]+)\)/', $column->pmaType
, $match)) {
1708 $match[0] = trim($match[0], '()');
1709 $noDecimals = $match[0];
1712 // Check input transformation of column
1713 $transformedHtml = '';
1714 if (! empty($columnMime['input_transformation'])) {
1715 $file = $columnMime['input_transformation'];
1716 $includeFile = 'src/Plugins/Transformations/' . $file;
1717 if (is_file(ROOT_PATH
. $includeFile)) {
1718 $className = $this->transformations
->getClassName($includeFile);
1719 if (class_exists($className)) {
1720 $transformationPlugin = new $className();
1721 $transformationOptions = $this->transformations
->getOptions(
1722 $columnMime['input_transformation_options'],
1727 'transform_key' => $column->field
,
1728 'where_clause_sign' => Core
::signSqlQuery($whereClause),
1729 'where_clause' => $whereClause,
1731 $transformationOptions['wrapper_link'] = Url
::getCommon($urlParams);
1732 $transformationOptions['wrapper_params'] = $urlParams;
1734 if (isset($currentRow[$column->field
])) {
1735 $currentValue = $currentRow[$column->field
];
1738 if ($transformationPlugin instanceof IOTransformationsPlugin
) {
1739 $transformedHtml = $transformationPlugin->getInputHtml(
1740 $columnNameAppendix,
1741 $transformationOptions,
1747 $GLOBALS['plugin_scripts'] = array_merge(
1748 $GLOBALS['plugin_scripts'],
1749 $transformationPlugin->getScripts(),
1757 $foreignDropdown = '';
1759 $textAreaRows = $config->settings
['TextareaRows'];
1760 $textareaCols = $config->settings
['TextareaCols'];
1762 $enumSelectedValue = '';
1764 $columnSetValues = [];
1766 $isColumnProtectedBlob = false;
1768 $blobValueUnit = '';
1770 $selectOptionForUpload = '';
1771 $inputFieldHtml = '';
1772 if ($transformedHtml === '') {
1773 if (is_array($foreignData['disp_row'])) {
1774 $foreignDropdown = $this->relation
->foreignDropdown(
1775 $foreignData['disp_row'],
1776 $foreignData['foreign_field'],
1777 $foreignData['foreign_display'],
1779 $config->settings
['ForeignKeyMaxLimit'],
1783 $dataType = $this->dbi
->types
->getTypeClass($column->trueType
);
1785 if ($column->isChar
) {
1786 $textAreaRows = max($config->settings
['CharTextareaRows'], 7);
1787 $textareaCols = $config->settings
['CharTextareaCols'];
1788 $maxlength = $extractedColumnspec['spec_in_brackets'];
1789 } elseif ($config->settings
['LongtextDoubleTextarea'] && mb_strstr($column->pmaType
, 'longtext')) {
1790 $textAreaRows = $config->settings
['TextareaRows'] * 2;
1791 $textareaCols = $config->settings
['TextareaCols'] * 2;
1794 if ($column->pmaType
=== 'enum') {
1795 $enumValues = $extractedColumnspec['enum_set_values'];
1797 foreach ($enumValues as $enumValue) {
1799 $data == $enumValue ||
($data == ''
1800 && (! isset($_POST['where_clause']) ||
! $column->isNull
)
1801 && isset($column->default) && $enumValue == $column->default)
1803 $enumSelectedValue = $enumValue;
1807 } elseif ($column->pmaType
=== 'set') {
1808 $columnSetValues = $extractedColumnspec['enum_set_values'];
1809 $setSelectSize = min(4, count($extractedColumnspec['enum_set_values']));
1810 } elseif ($column->isBinary ||
$column->isBlob
) {
1811 $isColumnProtectedBlob = ($config->settings
['ProtectBinary'] === 'blob' && $column->isBlob
)
1812 ||
($config->settings
['ProtectBinary'] === 'all')
1813 ||
($config->settings
['ProtectBinary'] === 'noblob' && ! $column->isBlob
);
1814 if ($isColumnProtectedBlob) {
1815 [$blobValue, $blobValueUnit] = Util
::formatByteDown(mb_strlen(stripslashes($data)), 3, 1);
1818 if ($isUpload && $column->isBlob
) {
1819 $maxUploadSize = $this->getMaxUploadSize($column->pmaType
);
1822 if (! empty($config->settings
['UploadDir'])) {
1823 $selectOptionForUpload = $this->getSelectOptionForUpload($vkey, $fieldHashMd5);
1827 ! $isColumnProtectedBlob
1828 && ! ($column->isBlob ||
($column->length
> $config->settings
['LimitChars']))
1830 $inputFieldHtml = $this->getHtmlInput(
1832 $columnNameAppendix,
1834 min(max($column->length
, 4), $config->settings
['LimitChars']),
1840 $columnValue = $this->getValueColumnForOtherDatatypes(
1842 $defaultCharEditing,
1844 $columnNameAppendix,
1848 $specialCharsEncoded,
1850 $extractedColumnspec,
1855 return $this->template
->render('table/insert/column_row', [
1858 'column' => $column,
1860 'show_field_types_in_data_edit_view' => $config->settings
['ShowFieldTypesInDataEditView'],
1861 'show_function_fields' => $config->settings
['ShowFunctionFields'],
1862 'is_column_binary' => $isColumnBinary,
1863 'function_options' => $functionOptions,
1864 'nullify_code' => $nullifyCode,
1865 'real_null_value' => $realNullValue,
1866 'id_index' => $this->fieldIndex
,
1868 'decimals' => $noDecimals,
1869 'special_chars' => $specialChars,
1870 'transformed_value' => $transformedHtml,
1871 'value' => $columnValue,
1872 'is_value_foreign_link' => $foreignData['foreign_link'] === true,
1873 'backup_field' => $backupField,
1875 'gis_data_types' => $gisDataTypes,
1876 'foreign_dropdown' => $foreignDropdown,
1877 'data_type' => $dataType,
1878 'textarea_cols' => $textareaCols,
1879 'textarea_rows' => $textAreaRows,
1880 'text_dir' => $textDir,
1881 'max_length' => $maxlength,
1882 'longtext_double_textarea' => $config->settings
['LongtextDoubleTextarea'],
1883 'enum_selected_value' => $enumSelectedValue,
1884 'enum_values' => $enumValues,
1885 'set_values' => $columnSetValues,
1886 'set_select_size' => $setSelectSize,
1887 'is_column_protected_blob' => $isColumnProtectedBlob,
1888 'blob_value' => $blobValue,
1889 'blob_value_unit' => $blobValueUnit,
1890 'is_upload' => $isUpload,
1891 'max_upload_size' => $maxUploadSize,
1892 'select_option_for_upload' => $selectOptionForUpload,
1893 'limit_chars' => $config->settings
['LimitChars'],
1894 'input_field_html' => $inputFieldHtml,
1895 'field_title' => $this->getColumnTitle($column->field
, $commentsMap),
1899 private function isColumnBinary(InsertEditColumn
$column, bool $isUpload): bool
1901 $config = Config
::getInstance();
1902 if (! $config->settings
['ShowFunctionFields']) {
1906 return ($config->settings
['ProtectBinary'] === 'blob' && $column->isBlob
&& ! $isUpload)
1907 ||
($config->settings
['ProtectBinary'] === 'all' && $column->isBinary
)
1908 ||
($config->settings
['ProtectBinary'] === 'noblob' && $column->isBinary
);
1912 * Function to get html for each insert/edit row
1914 * @param mixed[] $urlParams url parameters
1915 * @param list<ColumnFull> $tableColumns table columns
1916 * @param string[] $commentsMap comments map
1917 * @param ResultInterface $currentResult current result
1918 * @param bool $insertMode whether insert mode
1919 * @param mixed[] $currentRow current row
1920 * @param bool $isUpload whether upload
1921 * @param mixed[] $foreigners foreigners
1922 * @param string $table table
1923 * @param string $db database
1924 * @param int $rowId row id
1925 * @param string $textDir text direction
1926 * @param mixed[] $repopulate the data to be repopulated
1927 * @param mixed[] $whereClauseArray the array of where clauses
1929 public function getHtmlForInsertEditRow(
1931 array $tableColumns,
1933 ResultInterface
$currentResult,
1943 array $whereClauseArray,
1945 $htmlOutput = $this->getHeadAndFootOfInsertRowTable($urlParams)
1948 //store the default value for CharEditing
1949 $defaultCharEditing = Config
::getInstance()->settings
['CharEditing'];
1950 $mimeMap = $this->transformations
->getMime($db, $table);
1952 if (isset($whereClauseArray[$rowId])) {
1953 $whereClause = $whereClauseArray[$rowId];
1956 $columnCount = count($tableColumns);
1957 for ($columnNumber = 0; $columnNumber < $columnCount; $columnNumber++
) {
1958 $tableColumn = $tableColumns[$columnNumber];
1960 if (isset($mimeMap[$tableColumn->field
])) {
1961 $columnMime = $mimeMap[$tableColumn->field
];
1964 $virtual = ['VIRTUAL', 'PERSISTENT', 'VIRTUAL GENERATED', 'STORED GENERATED'];
1965 if (in_array($tableColumn->extra
, $virtual)) {
1969 $htmlOutput .= $this->getHtmlForInsertEditFormColumn(
1973 $this->dbi
->getFieldsMeta($currentResult)[$columnNumber]->length
,
1982 $defaultCharEditing,
1992 return $htmlOutput . ' </tbody>'
1993 . '</table></div><br>'
1994 . '<div class="clearfloat"></div>';
1998 * Returns list of function names that accept WKB as text
2002 private function getGisFromTextFunctions(): array
2004 return $this->dbi
->getVersion() >= 50600 ?
2007 'ST_GeomCollFromText',
2011 'ST_MPointFromText',
2028 * Returns list of function names that accept WKB as binary
2032 private function getGisFromWKBFunctions(): array
2034 return $this->dbi
->getVersion() >= 50600 ?
2037 'ST_GeomCollFromWKB',