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
;
49 use const PASSWORD_DEFAULT
;
53 private const FUNC_OPTIONAL_PARAM
= ['RAND', 'UNIX_TIMESTAMP'];
55 private const FUNC_NO_PARAM
= [
78 private int $rowOffset = 0;
79 private int $fieldIndex = 0;
81 public function __construct(
82 private DatabaseInterface
$dbi,
83 private Relation
$relation,
84 private Transformations
$transformations,
85 private FileListing
$fileListing,
86 private Template
$template,
91 * Retrieve form parameters for insert/edit form
93 * @param string $db name of the database
94 * @param string $table name of the table
95 * @param mixed[] $whereClauseArray
97 * @return array<string, string> array of insert/edit form parameters
99 public function getFormParametersForInsertForm(
102 array|
null $whereClauses,
103 array $whereClauseArray,
109 'goto' => $GLOBALS['goto'],
110 'err_url' => $errorUrl,
111 'sql_query' => $_POST['sql_query'] ??
'',
114 if ($formParams['sql_query'] === '' && isset($_GET['sql_query'], $_GET['sql_signature'])) {
115 if (Core
::checkSqlQuerySignature($_GET['sql_query'], $_GET['sql_signature'])) {
116 $formParams['sql_query'] = $_GET['sql_query'];
120 if (isset($whereClauses)) {
121 foreach ($whereClauseArray as $keyId => $whereClause) {
122 $formParams['where_clause[' . $keyId . ']'] = trim($whereClause);
126 if (isset($_POST['clause_is_unique'])) {
127 $formParams['clause_is_unique'] = $_POST['clause_is_unique'];
128 } elseif (isset($_GET['clause_is_unique'])) {
129 $formParams['clause_is_unique'] = $_GET['clause_is_unique'];
136 * Analysing where clauses array
138 * @param string[] $whereClauseArray array of where clauses
139 * @param string $table name of the table
140 * @param string $db name of the database
142 * @return array<int, string[]|ResultInterface[]|array<string, string|null>[]|bool>
143 * @phpstan-return array{string[], ResultInterface[], array<string|null>[], bool}
145 private function analyzeWhereClauses(
146 array $whereClauseArray,
153 $foundUniqueKey = false;
154 foreach ($whereClauseArray as $keyId => $whereClause) {
155 $localQuery = 'SELECT * FROM '
156 . Util
::backquote($db) . '.'
157 . Util
::backquote($table)
158 . ' WHERE ' . $whereClause . ';';
159 $result[$keyId] = $this->dbi
->query($localQuery);
160 $rows[$keyId] = $result[$keyId]->fetchAssoc();
162 $whereClauses[$keyId] = str_replace('\\', '\\\\', $whereClause);
163 $hasUniqueCondition = $this->showEmptyResultMessageOrSetUniqueCondition(
170 if (! $hasUniqueCondition) {
174 $foundUniqueKey = true;
177 return [$whereClauses, $result, $rows, $foundUniqueKey];
181 * Show message for empty result or set the unique_condition
183 * @param mixed[] $rows MySQL returned rows
184 * @param string|int $keyId ID in current key
185 * @param mixed[] $whereClauseArray array of where clauses
186 * @param string $localQuery query performed
187 * @param ResultInterface[] $result MySQL result handle
189 private function showEmptyResultMessageOrSetUniqueCondition(
192 array $whereClauseArray,
197 if (! $rows[$keyId]) {
198 unset($rows[$keyId], $whereClauseArray[$keyId]);
199 ResponseRenderer
::getInstance()->addHTML(
200 Generator
::getMessage(
201 __('MySQL returned an empty result set (i.e. zero rows).'),
206 * @todo not sure what should be done at this point, but we must not
207 * exit if we want the message to be displayed
213 $meta = $this->dbi
->getFieldsMeta($result[$keyId]);
215 $uniqueCondition = (new UniqueCondition($meta, $rows[$keyId], true))->getWhereClause();
217 return (bool) $uniqueCondition;
221 * No primary key given, just load first row
223 private function loadFirstRow(string $table, string $db): ResultInterface
225 return $this->dbi
->query(
226 'SELECT * FROM ' . Util
::backquote($db)
227 . '.' . Util
::backquote($table) . ' LIMIT 1;',
231 /** @return false[] */
232 private function getInsertRows(): array
234 // Can be a string on some old configuration storage settings
235 return array_fill(0, Config
::getInstance()->settings
['InsertRows'], false);
239 * Show type information or function selectors in Insert/Edit
241 * @param string $which function|type
242 * @param mixed[] $urlParams containing url parameters
243 * @param bool $isShow whether to show the element in $which
245 * @return string an HTML snippet
247 public function showTypeOrFunction(string $which, array $urlParams, bool $isShow): string
251 $config = Config
::getInstance();
254 $params['ShowFunctionFields'] = ($isShow ?
0 : 1);
255 $params['ShowFieldTypesInDataEditView'] = $config->settings
['ShowFieldTypesInDataEditView'];
258 $params['ShowFieldTypesInDataEditView'] = ($isShow ?
0 : 1);
259 $params['ShowFunctionFields'] = $config->settings
['ShowFunctionFields'];
263 $params['goto'] = Url
::getFromRoute('/sql');
264 $thisUrlParams = array_merge($urlParams, $params);
267 return ' : <a href="' . Url
::getFromRoute('/table/change') . '" data-post="'
268 . Url
::getCommon($thisUrlParams, '', false) . '">'
269 . $this->showTypeOrFunctionLabel($which)
273 return '<th><a href="' . Url
::getFromRoute('/table/change') . '" data-post="'
274 . Url
::getCommon($thisUrlParams, '', false)
275 . '" title="' . __('Hide') . '">'
276 . $this->showTypeOrFunctionLabel($which)
281 * Show type information or function selectors labels in Insert/Edit
283 * @param string $which function|type
285 * @return string an HTML snippet
287 private function showTypeOrFunctionLabel(string $which): string
289 return match ($which) {
290 'function' => __('Function'),
291 'type' => __('Type'),
297 * Retrieve the column title
299 * @param string $fieldName name of the column
300 * @param string[] $commentsMap comments for every column that has a comment
302 * @return string column title
304 private function getColumnTitle(string $fieldName, array $commentsMap): string
306 if (isset($commentsMap[$fieldName])) {
307 return '<span style="border-bottom: 1px dashed black;" title="'
308 . htmlspecialchars($commentsMap[$fieldName]) . '">'
309 . htmlspecialchars($fieldName) . '</span>';
312 return htmlspecialchars($fieldName);
316 * check whether the column is of a certain type
317 * the goal is to ensure that types such as "enum('one','two','binary',..)"
318 * or "enum('one','two','varbinary',..)" are not categorized as binary
320 * @param string $columnType column type as specified in the column definition
321 * @param string[] $types the types to verify
323 public function isColumn(string $columnType, array $types): bool
325 foreach ($types as $oneType) {
326 if (mb_stripos($columnType, $oneType) === 0) {
335 * Retrieve the nullify code for the null column
337 * @param InsertEditColumn $column description of column in given table
338 * @param mixed[] $foreigners keys into foreign fields
339 * @param mixed[] $foreignData data about the foreign keys
341 private function getNullifyCodeForNullColumn(
342 InsertEditColumn
$column,
346 $foreigner = $this->relation
->searchColumnInForeigners($foreigners, $column->field
);
347 if (mb_strstr($column->trueType
, 'enum')) {
348 $nullifyCode = mb_strlen($column->type
) > 20 ?
'1' : '2';
349 } elseif (mb_strstr($column->trueType
, 'set')) {
351 } elseif ($foreigner && $foreignData['foreign_link'] == false) {
352 // foreign key in a drop-down
354 } elseif ($foreigner && $foreignData['foreign_link'] == true) {
355 // foreign key with a browsing icon
365 * Get HTML textarea for insert form
367 * @param InsertEditColumn $column column information
368 * @param string $backupField hidden input field
369 * @param string $columnNameAppendix the name attribute
370 * @param string $onChangeClause onchange clause for fields
371 * @param string $textDir text direction
372 * @param string $specialCharsEncoded replaced char if the string starts
373 * with a \r\n pair (0x0d0a) add an extra \n
374 * @param string $dataType the html5 data-* attribute type
376 * @return string an html snippet
378 private function getTextarea(
379 InsertEditColumn
$column,
381 string $columnNameAppendix,
382 string $onChangeClause,
384 string $specialCharsEncoded,
388 $config = Config
::getInstance();
389 $textAreaRows = $config->settings
['TextareaRows'];
390 $textareaCols = $config->settings
['TextareaCols'];
392 if ($column->isChar
) {
394 * @todo clarify the meaning of the "textfield" class and explain
395 * why character columns have the "char" class instead
397 $theClass = 'char charField';
398 $textAreaRows = $config->settings
['CharTextareaRows'];
399 $textareaCols = $config->settings
['CharTextareaCols'];
400 $extractedColumnspec = Util
::extractColumnSpec($column->type
);
401 $maxlength = $extractedColumnspec['spec_in_brackets'];
402 } elseif ($config->settings
['LongtextDoubleTextarea'] && mb_strstr($column->pmaType
, 'longtext')) {
403 $textAreaRows = $config->settings
['TextareaRows'] * 2;
404 $textareaCols = $config->settings
['TextareaCols'] * 2;
407 return $backupField . "\n"
408 . '<textarea name="fields' . $columnNameAppendix . '"'
409 . ' class="' . $theClass . '"'
410 . (isset($maxlength) ?
' data-maxlength="' . $maxlength . '"' : '')
411 . ' rows="' . $textAreaRows . '"'
412 . ' cols="' . $textareaCols . '"'
413 . ' dir="' . $textDir . '"'
414 . ' id="field_' . $this->fieldIndex
. '_3"'
415 . ($onChangeClause !== '' ?
' onchange="' . htmlspecialchars($onChangeClause, ENT_COMPAT
) . '"' : '')
416 . ' tabindex="' . $this->fieldIndex
. '"'
417 . ' data-type="' . $dataType . '">'
418 . $specialCharsEncoded
423 * Get HTML input type
425 * @param InsertEditColumn $column description of column in given table
426 * @param string $columnNameAppendix the name attribute
427 * @param string $specialChars special characters
428 * @param int $fieldsize html field size
429 * @param string $onChangeClause onchange clause for fields
430 * @param string $dataType the html5 data-* attribute type
432 * @return string an html snippet
434 private function getHtmlInput(
435 InsertEditColumn
$column,
436 string $columnNameAppendix,
437 string $specialChars,
439 string $onChangeClause,
442 $theClass = 'textfield';
443 // verify True_Type which does not contain the parentheses and length
444 if ($column->trueType
=== 'date') {
445 $theClass .= ' datefield';
446 } elseif ($column->trueType
=== 'time') {
447 $theClass .= ' timefield';
448 } elseif ($column->trueType
=== 'datetime' ||
$column->trueType
=== 'timestamp') {
449 $theClass .= ' datetimefield';
453 $isInteger = in_array($column->trueType
, $this->dbi
->types
->getIntegerTypes(), true);
455 $extractedColumnspec = Util
::extractColumnSpec($column->type
);
456 $isUnsigned = $extractedColumnspec['unsigned'];
457 $minMaxValues = $this->dbi
->types
->getIntegerRange($column->trueType
, ! $isUnsigned);
458 $inputMinMax = 'min="' . $minMaxValues[0] . '" '
459 . 'max="' . $minMaxValues[1] . '"';
463 // do not use the 'date' or 'time' types here; they have no effect on some
464 // browsers and create side effects (see bug #4218)
465 return '<input type="text"'
466 . ' name="fields' . $columnNameAppendix . '"'
467 . ' value="' . $specialChars . '" size="' . $fieldsize . '"'
469 ?
' data-maxlength="' . $fieldsize . '"'
471 . ($inputMinMax !== '' ?
' ' . $inputMinMax : '')
472 . ' data-type="' . $dataType . '"'
473 . ' class="' . $theClass . '" onchange="' . htmlspecialchars($onChangeClause, ENT_COMPAT
) . '"'
474 . ' tabindex="' . $this->fieldIndex
. '"'
475 . ($isInteger ?
' inputmode="numeric"' : '')
476 . ' id="field_' . $this->fieldIndex
. '_3">';
480 * Get HTML select option for upload
482 * @param string $vkey [multi_edit]['row_id']
483 * @param string $fieldHashMd5 array index as an MD5 to avoid having special characters
485 * @return string an HTML snippet
487 private function getSelectOptionForUpload(string $vkey, string $fieldHashMd5): string
489 $files = $this->fileListing
->getFileSelectOptions(
490 Util
::userDir((Config
::getInstance()->settings
['UploadDir'] ??
'')),
493 if ($files === false) {
494 return '<span style="color:red">' . __('Error') . '</span><br>' . "\n"
495 . __('The directory you set for upload work cannot be reached.') . "\n";
503 . '<i>' . __('Or') . '</i> '
504 . __('web server upload directory:') . '<br>' . "\n"
505 . '<select size="1" name="fields_uploadlocal'
506 . $vkey . '[' . $fieldHashMd5 . ']">' . "\n"
507 . '<option value="" selected="selected"></option>' . "\n"
509 . '</select>' . "\n";
513 * Retrieve the maximum upload file size
515 private function getMaxUploadSize(string $pmaType): string
517 // find maximum upload size, based on field type
519 * @todo with functions this is not so easy, as you can basically
520 * process any data with function like MD5
522 $maxFieldSize = match ($pmaType) {
525 'mediumblob' => 16777216,
526 'longblob' => 4294967296,// yeah, really
529 $thisFieldMaxSize = (int) Config
::getInstance()->get('max_upload_size'); // from PHP max
531 return Util
::getFormattedMaximumUploadSize(min($thisFieldMaxSize, $maxFieldSize)) . "\n";
535 * Get HTML for the Value column of other datatypes
536 * (here, "column" is used in the sense of HTML column in HTML table)
538 * @param InsertEditColumn $column description of column in given table
539 * @param string $defaultCharEditing default char editing mode which is stored
540 * in the config.inc.php script
541 * @param string $backupField hidden input field
542 * @param string $columnNameAppendix the name attribute
543 * @param string $onChangeClause onchange clause for fields
544 * @param string $specialChars special characters
545 * @param string $textDir text direction
546 * @param string $specialCharsEncoded replaced char if the string starts
547 * with a \r\n pair (0x0d0a) add an extra \n
548 * @param string $data data to edit
549 * @param mixed[] $extractedColumnspec associative array containing type,
550 * spec_in_brackets and possibly
551 * enum_set_values (another array)
553 * @return string an html snippet
555 private function getValueColumnForOtherDatatypes(
556 InsertEditColumn
$column,
557 string $defaultCharEditing,
559 string $columnNameAppendix,
560 string $onChangeClause,
561 string $specialChars,
563 string $specialCharsEncoded,
565 array $extractedColumnspec,
567 // HTML5 data-* attribute data-type
568 $dataType = $this->dbi
->types
->getTypeClass($column->trueType
);
569 $fieldsize = $this->getColumnSize($column, $extractedColumnspec['spec_in_brackets']);
571 $config = Config
::getInstance();
572 $isTextareaRequired = $column->isChar
573 && ($config->settings
['CharEditing'] === 'textarea' ||
str_contains($data, "\n"));
574 if ($isTextareaRequired) {
575 $config->settings
['CharEditing'] = $defaultCharEditing;
576 $htmlField = $this->getTextarea(
582 $specialCharsEncoded,
586 $htmlField = $this->getHtmlInput(
596 return $this->template
->render('table/insert/value_column_for_other_datatype', [
597 'html_field' => $htmlField,
598 'backup_field' => $backupField,
599 'is_textarea' => $isTextareaRequired,
600 'columnNameAppendix' => $columnNameAppendix,
608 * @param InsertEditColumn $column description of column in given table
609 * @param string $specInBrackets text in brackets inside column definition
611 * @return int field size
613 private function getColumnSize(InsertEditColumn
$column, string $specInBrackets): int
615 $config = Config
::getInstance();
616 if ($column->isChar
) {
617 $fieldsize = (int) $specInBrackets;
618 if ($fieldsize > $config->settings
['MaxSizeForInputField']) {
620 * This case happens for CHAR or VARCHAR columns which have
621 * a size larger than the maximum size for input field.
623 $config->settings
['CharEditing'] = 'textarea';
627 * This case happens for example for INT or DATE columns;
628 * in these situations, the value returned in $column['len']
631 $fieldsize = $column->length
;
635 max($fieldsize, $config->settings
['MinSizeForInputField']),
636 $config->settings
['MaxSizeForInputField'],
641 * get html for continue insertion form
643 * @param string $table name of the table
644 * @param string $db name of the database
645 * @param mixed[] $whereClauseArray
647 * @return string an html snippet
649 public function getContinueInsertionForm(
652 array $whereClauseArray,
655 return $this->template
->render('table/insert/continue_insertion_form', [
658 'where_clause_array' => $whereClauseArray,
659 'err_url' => $errorUrl,
660 'goto' => $GLOBALS['goto'],
661 'sql_query' => $_POST['sql_query'] ??
null,
662 'has_where_clause' => isset($_POST['where_clause']),
663 'insert_rows_default' => Config
::getInstance()->settings
['InsertRows'],
668 * @param string[]|string|null $whereClause
672 public static function isWhereClauseNumeric(array|
string|
null $whereClause): bool
674 if ($whereClause === null) {
678 if (! is_array($whereClause)) {
679 $whereClause = [$whereClause];
682 // If we have just numeric primary key, we can also edit next
683 // we are looking for `table_name`.`field_name` = numeric_value
684 foreach ($whereClause as $clause) {
685 // preg_match() returns 1 if there is a match
686 $isNumeric = preg_match('@^[\s]*`[^`]*`[\.]`[^`]*` = [0-9]+@', $clause) === 1;
696 * Get table head and table foot for insert row table
698 * @param mixed[] $urlParams url parameters
700 * @return string an html snippet
702 private function getHeadAndFootOfInsertRowTable(array $urlParams): string
707 $config = Config
::getInstance();
708 if ($config->settings
['ShowFieldTypesInDataEditView']) {
709 $type = $this->showTypeOrFunction('type', $urlParams, true);
712 if ($config->settings
['ShowFunctionFields']) {
713 $function = $this->showTypeOrFunction('function', $urlParams, true);
716 $template = new Template();
718 return $template->render('table/insert/get_head_and_foot_of_insert_row_table', [
720 'function' => $function,
725 * Prepares the field value and retrieve special chars, backup field and data array
727 * @param mixed[] $currentRow a row of the table
728 * @param InsertEditColumn $column description of column in given table
729 * @param mixed[] $extractedColumnspec associative array containing type,
730 * spec_in_brackets and possibly
731 * enum_set_values (another array)
732 * @param string $columnNameAppendix string to append to column name in input
733 * @param bool $asIs use the data as is, used in repopulating
735 * @return mixed[] $real_null_value, $data, $special_chars, $backup_field,
736 * $special_chars_encoded
737 * @psalm-return array{bool, string, string, string, string}
739 private function getSpecialCharsAndBackupFieldForExistingRow(
741 InsertEditColumn
$column,
742 array $extractedColumnspec,
743 string $columnNameAppendix,
746 $specialCharsEncoded = '';
748 $realNullValue = false;
750 if (! isset($currentRow[$column->field
])) {
751 $realNullValue = true;
752 $currentRow[$column->field
] = '';
755 } elseif ($column->trueType
=== 'bit') {
756 $specialChars = $asIs
757 ?
$currentRow[$column->field
]
758 : Util
::printableBitValue(
759 (int) $currentRow[$column->field
],
760 (int) $extractedColumnspec['spec_in_brackets'],
763 (str_starts_with($column->trueType
, 'timestamp')
764 ||
$column->trueType
=== 'datetime'
765 ||
$column->trueType
=== 'time')
766 && (str_contains($currentRow[$column->field
], '.'))
768 $currentRow[$column->field
] = $asIs
769 ?
$currentRow[$column->field
]
770 : Util
::addMicroseconds($currentRow[$column->field
]);
771 $specialChars = htmlspecialchars($currentRow[$column->field
], ENT_COMPAT
);
772 } elseif (in_array($column->trueType
, Gis
::getDataTypes(), true)) {
773 // Convert gis data to Well Know Text format
774 $currentRow[$column->field
] = $asIs
775 ?
$currentRow[$column->field
]
776 : Gis
::convertToWellKnownText($currentRow[$column->field
], true);
777 $specialChars = htmlspecialchars($currentRow[$column->field
], ENT_COMPAT
);
779 // special binary "characters"
780 if ($column->isBinary ||
($column->isBlob
&& Config
::getInstance()->settings
['ProtectBinary'] !== 'all')) {
781 $currentRow[$column->field
] = $asIs
782 ?
$currentRow[$column->field
]
783 : bin2hex($currentRow[$column->field
]);
786 $specialChars = htmlspecialchars($currentRow[$column->field
], ENT_COMPAT
);
788 //We need to duplicate the first \n or otherwise we will lose
789 //the first newline entered in a VARCHAR or TEXT column
790 $specialCharsEncoded = Util
::duplicateFirstNewline($specialChars);
792 $data = $currentRow[$column->field
];
795 /** @var string $defaultAction */
796 $defaultAction = $_POST['default_action'] ??
$_GET['default_action'] ??
'';
798 $defaultAction === 'insert'
799 && $column->key
=== 'PRI'
800 && str_contains($column->extra
, 'auto_increment')
802 // When copying row, it is useful to empty auto-increment column to prevent duplicate key error.
803 $data = $specialCharsEncoded = $specialChars = null;
806 // If a timestamp field value is not included in an update
807 // statement MySQL auto-update it to the current timestamp;
808 // however, things have changed since MySQL 4.1, so
809 // it's better to set a fields_prev in this situation
810 $backupField = '<input type="hidden" name="fields_prev'
811 . $columnNameAppendix . '" value="'
812 . htmlspecialchars($currentRow[$column->field
], ENT_COMPAT
) . '">';
814 return [$realNullValue, (string) $specialCharsEncoded, (string) $specialChars, (string) $data, $backupField];
818 * display default values
820 private function getSpecialCharsForInsertingMode(
821 string|
null $defaultValue,
824 if ($defaultValue === null) {
828 if ($trueType === 'bit') {
829 $specialChars = Util
::convertBitDefaultValue($defaultValue);
830 } elseif (str_starts_with($trueType, 'timestamp') ||
$trueType === 'datetime' ||
$trueType === 'time') {
831 $specialChars = Util
::addMicroseconds($defaultValue);
832 } elseif ($trueType === 'binary' ||
$trueType === 'varbinary') {
833 $specialChars = bin2hex($defaultValue);
834 } elseif (str_ends_with($trueType, 'text')) {
835 $textDefault = substr($defaultValue, 1, -1);
836 $specialChars = stripcslashes($textDefault !== '' ?
$textDefault : $defaultValue);
838 $specialChars = htmlspecialchars($defaultValue);
841 return $specialChars;
845 * set $_SESSION for edit_next
847 * @param string $oneWhereClause one where clause from where clauses array
849 public function setSessionForEditNext(string $oneWhereClause): void
851 $localQuery = 'SELECT * FROM ' . Util
::backquote(Current
::$database)
852 . '.' . Util
::backquote(Current
::$table) . ' WHERE '
853 . str_replace('` =', '` >', $oneWhereClause) . ' LIMIT 1;';
855 $res = $this->dbi
->query($localQuery);
856 $row = $res->fetchRow();
857 $meta = $this->dbi
->getFieldsMeta($res);
858 // must find a unique condition based on unique key,
859 // not a combination of all fields
860 $uniqueCondition = (new UniqueCondition($meta, $row, true))->getWhereClause();
861 if ($uniqueCondition === '') {
865 $_SESSION['edit_next'] = $uniqueCondition;
869 * set $goto_include variable for different cases and retrieve like,
870 * if $GLOBALS['goto'] empty, if $goto_include previously not defined
871 * and new_insert, same_insert, edit_next
873 * @param string|false $gotoInclude store some script for include, otherwise it is
876 public function getGotoInclude(string|
false $gotoInclude): string
878 $validOptions = ['new_insert', 'same_insert', 'edit_next'];
879 if (isset($_POST['after_insert']) && in_array($_POST['after_insert'], $validOptions, true)) {
880 return '/table/change';
883 if (! empty($GLOBALS['goto'])) {
884 if (! preg_match('@^[a-z_]+\.php$@', $GLOBALS['goto'])) {
885 // this should NOT happen
886 //$GLOBALS['goto'] = false;
887 $gotoInclude = $GLOBALS['goto'] === 'index.php?route=/sql' ?
'/sql' : false;
889 $gotoInclude = $GLOBALS['goto'];
892 if ($GLOBALS['goto'] === 'index.php?route=/database/sql' && Current
::$table !== '') {
893 Current
::$table = '';
897 if (! $gotoInclude) {
898 $gotoInclude = Current
::$table === '' ?
'/database/sql' : '/table/sql';
905 * Defines the url to return in case of failure of the query
907 * @param mixed[] $urlParams url parameters
909 * @return string error url for query failure
911 public function getErrorUrl(array $urlParams): string
913 return $_POST['err_url'] ?? Url
::getFromRoute('/table/change', $urlParams);
917 * Executes the sql query and get the result, then move back to the calling page
919 * @param mixed[] $query built query from buildSqlQuery()
921 * @return mixed[] $total_affected_rows, $last_messages, $warning_messages, $error_messages
923 public function executeSqlQuery(array $query): array
925 $GLOBALS['sql_query'] = implode('; ', $query) . ';';
926 // to ensure that the query is displayed in case of
927 // "insert as new row" and then "insert another new row"
928 $GLOBALS['display_query'] = $GLOBALS['sql_query'];
930 $totalAffectedRows = 0;
932 $warningMessages = [];
935 foreach ($query as $singleQuery) {
936 if (isset($_POST['submit_type']) && $_POST['submit_type'] === 'showinsert') {
937 $lastMessages[] = Message
::notice(__('Showing SQL query'));
941 if (Config
::getInstance()->settings
['IgnoreMultiSubmitErrors']) {
942 $result = $this->dbi
->tryQuery($singleQuery);
944 $result = $this->dbi
->query($singleQuery);
948 $errorMessages[] = $this->dbi
->getError();
950 $totalAffectedRows +
= $this->dbi
->affectedRows();
952 $insertId = $this->dbi
->insertId();
953 if ($insertId !== 0) {
954 // insert_id is id of FIRST record inserted in one insert, so if we
955 // inserted multiple rows, we had to increment this
957 if ($totalAffectedRows > 0) {
958 $insertId +
= $totalAffectedRows - 1;
961 $lastMessage = Message
::notice(__('Inserted row id: %1$d'));
962 $lastMessage->addParam($insertId);
963 $lastMessages[] = $lastMessage;
967 $warningMessages = $this->getWarningMessages();
970 return [$totalAffectedRows, $lastMessages, $warningMessages, $errorMessages];
974 * get the warning messages array
978 private function getWarningMessages(): array
980 $warningMessages = [];
981 foreach ($this->dbi
->getWarnings() as $warning) {
982 $warningMessages[] = htmlspecialchars((string) $warning);
985 return $warningMessages;
989 * Column to display from the foreign table?
991 * @param string $whereComparison string that contain relation field value
992 * @param mixed[] $map all Relations to foreign tables for a given
993 * table or optionally a given column in a table
994 * @param string $relationField relation field
996 * @return string display value from the foreign table
998 public function getDisplayValueForForeignTableColumn(
999 string $whereComparison,
1001 string $relationField,
1003 $foreigner = $this->relation
->searchColumnInForeigners($map, $relationField);
1005 if (! is_array($foreigner)) {
1009 $displayField = $this->relation
->getDisplayField($foreigner['foreign_db'], $foreigner['foreign_table']);
1010 // Field to display from the foreign table?
1011 if ($displayField !== '') {
1012 $dispsql = 'SELECT ' . Util
::backquote($displayField)
1013 . ' FROM ' . Util
::backquote($foreigner['foreign_db'])
1014 . '.' . Util
::backquote($foreigner['foreign_table'])
1015 . ' WHERE ' . Util
::backquote($foreigner['foreign_field'])
1017 $dispresult = $this->dbi
->tryQuery($dispsql);
1018 if ($dispresult && $dispresult->numRows() > 0) {
1019 return (string) $dispresult->fetchValue();
1027 * Display option in the cell according to user choices
1029 * @param mixed[] $map all Relations to foreign tables for a given
1030 * table or optionally a given column in a table
1031 * @param string $relationField relation field
1032 * @param string $whereComparison string that contain relation field value
1033 * @param string $dispval display value from the foreign table
1034 * @param string $relationFieldValue relation field value
1036 * @return string HTML <a> tag
1038 public function getLinkForRelationalDisplayField(
1040 string $relationField,
1041 string $whereComparison,
1043 string $relationFieldValue,
1045 $foreigner = $this->relation
->searchColumnInForeigners($map, $relationField);
1047 if (! is_array($foreigner)) {
1051 if ($_SESSION['tmpval']['relational_display'] === 'K') {
1052 // user chose "relational key" in the display options, so
1053 // the title contains the display field
1054 $title = $dispval !== ''
1055 ?
' title="' . htmlspecialchars($dispval) . '"'
1058 $title = ' title="' . htmlspecialchars($relationFieldValue) . '"';
1061 $sqlQuery = 'SELECT * FROM '
1062 . Util
::backquote($foreigner['foreign_db'])
1063 . '.' . Util
::backquote($foreigner['foreign_table'])
1064 . ' WHERE ' . Util
::backquote($foreigner['foreign_field'])
1067 'db' => $foreigner['foreign_db'],
1068 'table' => $foreigner['foreign_table'],
1070 'sql_signature' => Core
::signSqlQuery($sqlQuery),
1071 'sql_query' => $sqlQuery,
1073 $output = '<a href="' . Url
::getFromRoute('/sql', $urlParams) . '"' . $title . '>';
1075 if ($_SESSION['tmpval']['relational_display'] === 'D') {
1076 // user chose "relational display field" in the
1077 // display options, so show display field in the cell
1078 $output .= htmlspecialchars($dispval);
1080 // otherwise display data in the cell
1081 $output .= htmlspecialchars($relationFieldValue);
1090 * Transform edited values
1092 * @param string $db db name
1093 * @param string $table table name
1094 * @param mixed[] $transformation mimetypes for all columns of a table
1095 * [field_name][field_key]
1096 * @param mixed[] $editedValues transform columns list and new values
1097 * @param string $file file containing the transformation plugin
1098 * @param string $columnName column name
1099 * @param mixed[] $extraData extra data array
1100 * @param string $type the type of transformation
1104 public function transformEditedValues(
1107 array $transformation,
1108 array &$editedValues,
1114 $includeFile = 'src/Plugins/Transformations/' . $file;
1115 if (is_file(ROOT_PATH
. $includeFile)) {
1116 // $cfg['SaveCellsAtOnce'] = true; JS code sends an array
1117 $whereClause = is_array($_POST['where_clause']) ?
$_POST['where_clause'][0] : $_POST['where_clause'];
1121 'where_clause_sign' => Core
::signSqlQuery($whereClause),
1122 'where_clause' => $whereClause,
1123 'transform_key' => $columnName,
1125 $transformOptions = $this->transformations
->getOptions($transformation[$type . '_options'] ??
'');
1126 $transformOptions['wrapper_link'] = Url
::getCommon($urlParams);
1127 $transformOptions['wrapper_params'] = $urlParams;
1128 $className = $this->transformations
->getClassName($includeFile);
1129 if (class_exists($className)) {
1130 /** @var TransformationsPlugin $transformationPlugin */
1131 $transformationPlugin = new $className();
1133 foreach ($editedValues as $cellIndex => $currCellEditedValues) {
1134 if (! isset($currCellEditedValues[$columnName])) {
1138 $extraData['transformations'][$cellIndex] = $transformationPlugin->applyTransformation(
1139 $currCellEditedValues[$columnName],
1142 $editedValues[$cellIndex][$columnName] = $extraData['transformations'][$cellIndex];
1151 * Get value part if a function was specified
1153 private function formatAsSqlFunction(
1154 EditField
$editField,
1156 if ($editField->function === 'PHP_PASSWORD_HASH') {
1157 $hash = password_hash($editField->value
, PASSWORD_DEFAULT
);
1159 return $this->dbi
->quoteString($hash);
1162 if ($editField->function === 'UUID') {
1163 /* This way user will know what UUID new row has */
1164 $uuid = (string) $this->dbi
->fetchValue('SELECT UUID()');
1166 return $this->dbi
->quoteString($uuid);
1170 in_array($editField->function, $this->getGisFromTextFunctions(), true)
1171 ||
in_array($editField->function, $this->getGisFromWKBFunctions(), true)
1173 preg_match('/^(\'?)(.*?)\1(?:,(\d+))?$/', $editField->value
, $matches);
1174 $escapedParams = $this->dbi
->quoteString($matches[2]) . (isset($matches[3]) ?
',' . $matches[3] : '');
1176 return $editField->function . '(' . $escapedParams . ')';
1180 ! in_array($editField->function, self
::FUNC_NO_PARAM
, true)
1181 ||
($editField->value
!== '' && in_array($editField->function, self
::FUNC_OPTIONAL_PARAM
, true))
1184 ($editField->salt
!== null
1185 && ($editField->function === 'AES_ENCRYPT'
1186 ||
$editField->function === 'AES_DECRYPT'
1187 ||
$editField->function === 'SHA2'))
1188 ||
($editField->salt
1189 && ($editField->function === 'DES_ENCRYPT'
1190 ||
$editField->function === 'DES_DECRYPT'
1191 ||
$editField->function === 'ENCRYPT'))
1193 return $editField->function . '(' . $this->dbi
->quoteString($editField->value
) . ','
1194 . $this->dbi
->quoteString($editField->salt
) . ')';
1197 return $editField->function . '(' . $this->dbi
->quoteString($editField->value
) . ')';
1200 return $editField->function . '()';
1204 * Get the field value formatted for use in a SQL statement.
1205 * Used in both INSERT and UPDATE statements.
1207 private function getValueFormattedAsSql(
1208 EditField
$editField,
1209 string $protectedValue = '',
1211 if ($editField->isUploaded
) {
1212 return $editField->value
;
1215 if ($editField->function !== '') {
1216 return $this->formatAsSqlFunction($editField);
1219 return $this->formatAsSqlValueBasedOnType($editField, $protectedValue);
1223 * Get query values array and query fields array for insert and update in multi edit
1225 * @param string|int $whereClause Either a positional index or string representing selected row
1227 public function getQueryValueForInsert(
1228 EditField
$editField,
1230 string|
int $whereClause,
1232 $protectedValue = '';
1233 if ($editField->type
=== 'protected' && $usingKey && $whereClause !== '') {
1234 // Fetch the current values of a row to use in case we have a protected field
1235 $protectedValue = $this->dbi
->fetchValue(
1236 'SELECT ' . Util
::backquote($editField->columnName
)
1237 . ' FROM ' . Util
::backquote(Current
::$table)
1238 . ' WHERE ' . $whereClause,
1240 $protectedValue = is_string($protectedValue) ?
$protectedValue : '';
1243 return $this->getValueFormattedAsSql($editField, $protectedValue);
1247 * Get field-value pairs for update SQL.
1248 * During update, we build the SQL only with the fields that should be updated.
1250 public function getQueryValueForUpdate(EditField
$editField): string
1252 $currentValueFormattedAsSql = $this->getValueFormattedAsSql($editField);
1254 // avoid setting a field to NULL when it's already NULL
1255 // (field had the null checkbox before the update; field still has the null checkbox)
1256 if ($editField->wasPreviouslyNull
&& $editField->isNull
) {
1260 // A blob field that hasn't been changed will have no value
1261 if ($currentValueFormattedAsSql === '') {
1266 // Field had the null checkbox before the update; field no longer has the null checkbox
1267 $editField->wasPreviouslyNull ||
1268 // Field was marked as NULL (the value will be unchanged if it was an empty string)
1269 $editField->isNull ||
1270 // A function was applied to the field
1271 $editField->function !== '' ||
1272 // The value was changed
1273 $editField->value
!== $editField->previousValue
1275 return Util
::backquote($editField->columnName
) . ' = ' . $currentValueFormattedAsSql;
1282 * Get the current column value in the form for different data types
1284 private function formatAsSqlValueBasedOnType(
1285 EditField
$editField,
1286 string $protectedValue,
1288 if ($editField->type
=== 'protected') {
1289 // here we are in protected mode (asked in the config)
1290 // so tbl_change has put this special value in the
1291 // columns array, so we do not change the column value
1292 // but we can still handle column upload
1294 // when in UPDATE mode, do not alter field's contents. When in INSERT
1295 // mode, insert empty field because no values were submitted.
1296 // If protected blobs were set, insert original field's content.
1297 if ($protectedValue !== '') {
1298 return '0x' . bin2hex($protectedValue);
1301 if ($editField->isNull
) {
1305 // The Null checkbox was unchecked for this field
1306 if ($editField->wasPreviouslyNull
) {
1313 if ($editField->value
=== '') {
1314 // When the field is autoIncrement, the best way to avoid problems
1315 // in strict mode is to set the value to null (works also in non-strict mode)
1317 // If the value is empty and the null checkbox is checked, set it to null
1318 return $editField->autoIncrement ||
$editField->isNull ?
'NULL' : "''";
1321 if ($editField->type
=== 'hex') {
1322 if (! str_starts_with($editField->value
, '0x')) {
1323 return '0x' . $editField->value
;
1326 return $editField->value
;
1329 if ($editField->type
=== 'bit') {
1330 $currentValue = (string) preg_replace('/[^01]/', '0', $editField->value
);
1332 return 'b' . $this->dbi
->quoteString($currentValue);
1335 // For uuid type, generate uuid value
1336 // if empty value but not set null or value is uuid() function
1338 $editField->type
=== 'uuid'
1339 && ! $editField->isNull
1340 && in_array($editField->value
, ["''", '', "'uuid()'", 'uuid()'], true)
1346 ($editField->type
!== 'datetime' && $editField->type
!== 'timestamp' && $editField->type
!== 'date')
1347 ||
($editField->value
!== 'CURRENT_TIMESTAMP' && $editField->value
!== 'current_timestamp()')
1349 return $this->dbi
->quoteString($editField->value
);
1352 // If there is a value, we ignore the Null checkbox;
1353 // this could be possible if Javascript is disabled in the browser
1354 return $editField->value
;
1358 * Check whether inline edited value can be truncated or not,
1359 * and add additional parameters for extra_data array if needed
1361 * @param string $db Database name
1362 * @param string $table Table name
1363 * @param string $columnName Column name
1364 * @param mixed[] $extraData Extra data for ajax response
1366 public function verifyWhetherValueCanBeTruncatedAndAppendExtraData(
1372 $extraData['isNeedToRecheck'] = false;
1374 $sqlForRealValue = 'SELECT ' . Util
::backquote($table) . '.'
1375 . Util
::backquote($columnName)
1376 . ' FROM ' . Util
::backquote($db) . '.'
1377 . Util
::backquote($table)
1378 . ' WHERE ' . $_POST['where_clause'][0];
1380 $result = $this->dbi
->tryQuery($sqlForRealValue);
1386 $fieldsMeta = $this->dbi
->getFieldsMeta($result);
1387 $meta = $fieldsMeta[0];
1388 $newValue = $result->fetchValue();
1390 if ($newValue === false) {
1394 if ($newValue !== null) {
1395 if ($meta->isTimeType()) {
1396 $newValue = Util
::addMicroseconds($newValue);
1397 } elseif ($meta->isBinary()) {
1398 $newValue = '0x' . bin2hex($newValue);
1402 $extraData['isNeedToRecheck'] = true;
1403 $extraData['truncatableFieldValue'] = $newValue;
1407 * Function to get the columns of a table
1409 * @param string $db current db
1410 * @param string $table current table
1412 * @return list<ColumnFull>
1414 public function getTableColumns(string $db, string $table): array
1416 $this->dbi
->selectDb($db);
1418 return array_values($this->dbi
->getColumns($db, $table, true));
1422 * Function to determine Insert/Edit rows
1424 * @param string[]|string|null $whereClause where clause
1425 * @param string $db current database
1426 * @param string $table current table
1428 * @return array<int, bool|string[]|string|ResultInterface|ResultInterface[]|null>
1429 * @phpstan-return array{
1431 * string[]|string|null,
1434 * ResultInterface[]|ResultInterface,
1435 * array<string, string|null>[]|false[],
1440 public function determineInsertOrEdit(array|
string|
null $whereClause, string $db, string $table): array
1442 if (isset($_POST['where_clause'])) {
1443 $whereClause = $_POST['where_clause'];
1446 if (isset($_SESSION['edit_next'])) {
1447 $whereClause = $_SESSION['edit_next'];
1448 unset($_SESSION['edit_next']);
1449 $afterInsert = 'edit_next';
1452 $config = Config
::getInstance();
1453 if (isset($_POST['ShowFunctionFields'])) {
1454 $config->settings
['ShowFunctionFields'] = $_POST['ShowFunctionFields'];
1457 if (isset($_POST['ShowFieldTypesInDataEditView'])) {
1458 $config->settings
['ShowFieldTypesInDataEditView'] = $_POST['ShowFieldTypesInDataEditView'];
1461 if (isset($_POST['after_insert'])) {
1462 $afterInsert = $_POST['after_insert'];
1465 if (isset($whereClause)) {
1467 $insertMode = false;
1468 $whereClauseArray = (array) $whereClause;
1469 [$whereClauses, $result, $rows, $foundUniqueKey] = $this->analyzeWhereClauses(
1477 $whereClause = null;
1478 $result = $this->loadFirstRow($table, $db);
1479 $rows = $this->getInsertRows();
1480 $whereClauses = null;
1481 $whereClauseArray = [];
1482 $foundUniqueKey = false;
1485 /** @var string $defaultAction */
1486 $defaultAction = $_POST['default_action'] ??
$_GET['default_action'] ??
'';
1487 if ($defaultAction === 'insert') {
1488 // Copying a row - fetched data will be inserted as a new row, therefore the where clause is needless.
1489 $whereClause = $whereClauses = null;
1500 $afterInsert ??
null,
1505 * Function to get comments for the table columns
1507 * @param string $db current database
1508 * @param string $table current table
1510 * @return string[] comments for columns
1512 public function getCommentsMap(string $db, string $table): array
1514 if (Config
::getInstance()->settings
['ShowPropertyComments']) {
1515 return $this->relation
->getComments($db, $table);
1522 * Function to get html for the gis editor div
1524 public function getHtmlForGisEditor(): string
1526 return '<div id="gis_editor"></div><div id="popup_background"></div><br>';
1530 * Function to get html for the ignore option in insert mode
1532 * @param int $rowId row id
1533 * @param bool $checked ignore option is checked or not
1535 public function getHtmlForIgnoreOption(int $rowId, bool $checked = true): string
1537 return '<input type="checkbox"'
1538 . ($checked ?
' checked="checked"' : '')
1539 . ' name="insert_ignore_' . $rowId . '"'
1540 . ' id="insert_ignore_' . $rowId . '">'
1541 . '<label for="insert_ignore_' . $rowId . '">'
1543 . '</label><br>' . "\n";
1547 * Function to get html for the insert edit form header
1549 * @param bool $hasBlobField whether has blob field
1550 * @param bool $isUpload whether is upload
1552 public function getHtmlForInsertEditFormHeader(bool $hasBlobField, bool $isUpload): string
1554 $template = new Template();
1556 return $template->render('table/insert/get_html_for_insert_edit_form_header', [
1557 'has_blob_field' => $hasBlobField,
1558 'is_upload' => $isUpload,
1563 * Function to get html for each insert/edit column
1565 * @param ColumnFull $tableColumn column
1566 * @param int $columnNumber column index in table_columns
1567 * @param string[] $commentsMap comments map
1568 * @param int $columnLength length of the current column taken from field metadata
1569 * @param bool $insertMode whether insert mode
1570 * @param mixed[] $currentRow current row
1571 * @param int $columnsCnt columns count
1572 * @param bool $isUpload whether upload
1573 * @param mixed[] $foreigners foreigners
1574 * @param string $table table
1575 * @param string $db database
1576 * @param int $rowId row id
1577 * @param string $defaultCharEditing default char editing mode which is stored in the config.inc.php script
1578 * @param string $textDir text direction
1579 * @param mixed[] $repopulate the data to be repopulated
1580 * @param mixed[] $columnMime the mime information of column
1581 * @param string $whereClause the where clause
1583 private function getHtmlForInsertEditFormColumn(
1584 ColumnFull
$tableColumn,
1596 string $defaultCharEditing,
1600 string $whereClause,
1602 $column = new InsertEditColumn(
1603 $tableColumn->field
,
1605 $tableColumn->isNull
,
1607 $tableColumn->default,
1608 $tableColumn->extra
,
1610 $this->isColumn($tableColumn->type
, ['binary', 'varbinary']),
1611 $this->isColumn($tableColumn->type
, ['blob', 'tinyblob', 'mediumblob', 'longblob']),
1612 $this->isColumn($tableColumn->type
, ['char', 'varchar']),
1617 $fieldHashMd5 = $column->md5
;
1618 if ($repopulate !== [] && array_key_exists($fieldHashMd5, $currentRow)) {
1619 $currentRow[$column->field
] = $repopulate[$fieldHashMd5];
1623 $extractedColumnspec = Util
::extractColumnSpec($column->type
);
1625 //Call validation when the form submitted...
1626 $onChangeClause = 'return verificationsAfterFieldChange('
1627 . json_encode($fieldHashMd5) . ', '
1628 . json_encode((string) $rowId) . ',' . json_encode($column->pmaType
) . ')';
1630 $vkey = '[multi_edit][' . $rowId . ']';
1631 // Use an MD5 as an array index to avoid having special characters
1632 // in the name attribute (see bug #1746964 )
1633 $columnNameAppendix = $vkey . '[' . $fieldHashMd5 . ']';
1635 // Prepares the field value
1636 if ($currentRow !== []) {
1640 $specialCharsEncoded,
1644 ] = $this->getSpecialCharsAndBackupFieldForExistingRow(
1647 $extractedColumnspec,
1648 $columnNameAppendix,
1652 // (we are inserting)
1653 // display default values
1654 $defaultValue = $column->default ??
null;
1655 if (isset($repopulate[$fieldHashMd5])) {
1656 $defaultValue = $repopulate[$fieldHashMd5];
1659 $realNullValue = $defaultValue === null;
1660 $data = (string) $defaultValue;
1661 $specialChars = $this->getSpecialCharsForInsertingMode($defaultValue, $column->trueType
);
1662 $specialCharsEncoded = Util
::duplicateFirstNewline($specialChars);
1666 $this->fieldIndex
= ($this->rowOffset
* $columnsCnt) +
$columnNumber +
1;
1668 // The function column
1669 // -------------------
1670 $foreignData = $this->relation
->getForeignData($foreigners, $column->field
, false, '', '');
1671 $isColumnBinary = $this->isColumnBinary($column, $isUpload);
1672 $functionOptions = '';
1674 $config = Config
::getInstance();
1675 if ($config->settings
['ShowFunctionFields']) {
1676 $defaultFunction = Generator
::getDefaultFunctionForField(
1678 $column->firstTimestamp
,
1686 $functionOptions = Generator
::getFunctionsForField($defaultFunction, $foreignData);
1689 // nullify code is needed by the js nullify() function to be able to generate calls to nullify() in jQuery
1690 $nullifyCode = $this->getNullifyCodeForNullColumn($column, $foreigners, $foreignData);
1692 // The value column (depends on type)
1694 // See bug #1667887 for the reason why we don't use the maxlength
1697 //add data attributes "no of decimals" and "data type"
1699 $type = current(explode('(', $column->pmaType
));
1700 if (preg_match('/\(([^()]+)\)/', $column->pmaType
, $match)) {
1701 $match[0] = trim($match[0], '()');
1702 $noDecimals = $match[0];
1705 // Check input transformation of column
1706 $transformedHtml = '';
1707 if (! empty($columnMime['input_transformation'])) {
1708 $file = $columnMime['input_transformation'];
1709 $includeFile = 'src/Plugins/Transformations/' . $file;
1710 if (is_file(ROOT_PATH
. $includeFile)) {
1711 $className = $this->transformations
->getClassName($includeFile);
1712 if (class_exists($className)) {
1713 $transformationPlugin = new $className();
1714 $transformationOptions = $this->transformations
->getOptions(
1715 $columnMime['input_transformation_options'],
1720 'transform_key' => $column->field
,
1721 'where_clause_sign' => Core
::signSqlQuery($whereClause),
1722 'where_clause' => $whereClause,
1724 $transformationOptions['wrapper_link'] = Url
::getCommon($urlParams);
1725 $transformationOptions['wrapper_params'] = $urlParams;
1727 if (isset($currentRow[$column->field
])) {
1728 $currentValue = $currentRow[$column->field
];
1731 if ($transformationPlugin instanceof IOTransformationsPlugin
) {
1732 $transformedHtml = $transformationPlugin->getInputHtml(
1733 $columnNameAppendix,
1734 $transformationOptions,
1740 $GLOBALS['plugin_scripts'] = array_merge(
1741 $GLOBALS['plugin_scripts'],
1742 $transformationPlugin->getScripts(),
1750 $foreignDropdown = '';
1752 $textAreaRows = $config->settings
['TextareaRows'];
1753 $textareaCols = $config->settings
['TextareaCols'];
1755 $enumSelectedValue = '';
1757 $columnSetValues = [];
1759 $isColumnProtectedBlob = false;
1761 $blobValueUnit = '';
1763 $selectOptionForUpload = '';
1764 $inputFieldHtml = '';
1765 if ($transformedHtml === '') {
1766 if (is_array($foreignData['disp_row'])) {
1767 $foreignDropdown = $this->relation
->foreignDropdown(
1768 $foreignData['disp_row'],
1769 $foreignData['foreign_field'],
1770 $foreignData['foreign_display'],
1772 $config->settings
['ForeignKeyMaxLimit'],
1776 $dataType = $this->dbi
->types
->getTypeClass($column->trueType
);
1778 if ($column->isChar
) {
1779 $textAreaRows = max($config->settings
['CharTextareaRows'], 7);
1780 $textareaCols = $config->settings
['CharTextareaCols'];
1781 $maxlength = $extractedColumnspec['spec_in_brackets'];
1782 } elseif ($config->settings
['LongtextDoubleTextarea'] && mb_strstr($column->pmaType
, 'longtext')) {
1783 $textAreaRows = $config->settings
['TextareaRows'] * 2;
1784 $textareaCols = $config->settings
['TextareaCols'] * 2;
1787 if ($column->pmaType
=== 'enum') {
1788 $enumValues = $extractedColumnspec['enum_set_values'];
1790 foreach ($enumValues as $enumValue) {
1792 $data == $enumValue ||
($data == ''
1793 && (! isset($_POST['where_clause']) ||
! $column->isNull
)
1794 && isset($column->default) && $enumValue == $column->default)
1796 $enumSelectedValue = $enumValue;
1800 } elseif ($column->pmaType
=== 'set') {
1801 $columnSetValues = $extractedColumnspec['enum_set_values'];
1802 $setSelectSize = min(4, count($extractedColumnspec['enum_set_values']));
1803 } elseif ($column->isBinary ||
$column->isBlob
) {
1804 $isColumnProtectedBlob = ($config->settings
['ProtectBinary'] === 'blob' && $column->isBlob
)
1805 ||
($config->settings
['ProtectBinary'] === 'all')
1806 ||
($config->settings
['ProtectBinary'] === 'noblob' && ! $column->isBlob
);
1807 if ($isColumnProtectedBlob) {
1808 [$blobValue, $blobValueUnit] = Util
::formatByteDown(mb_strlen(stripslashes($data)), 3, 1);
1811 if ($isUpload && $column->isBlob
) {
1812 $maxUploadSize = $this->getMaxUploadSize($column->pmaType
);
1815 if (! empty($config->settings
['UploadDir'])) {
1816 $selectOptionForUpload = $this->getSelectOptionForUpload($vkey, $fieldHashMd5);
1820 ! $isColumnProtectedBlob
1821 && ! ($column->isBlob ||
($column->length
> $config->settings
['LimitChars']))
1823 $inputFieldHtml = $this->getHtmlInput(
1825 $columnNameAppendix,
1827 min(max($column->length
, 4), $config->settings
['LimitChars']),
1833 $columnValue = $this->getValueColumnForOtherDatatypes(
1835 $defaultCharEditing,
1837 $columnNameAppendix,
1841 $specialCharsEncoded,
1843 $extractedColumnspec,
1848 return $this->template
->render('table/insert/column_row', [
1851 'column' => $column,
1853 'show_field_types_in_data_edit_view' => $config->settings
['ShowFieldTypesInDataEditView'],
1854 'show_function_fields' => $config->settings
['ShowFunctionFields'],
1855 'is_column_binary' => $isColumnBinary,
1856 'function_options' => $functionOptions,
1857 'nullify_code' => $nullifyCode,
1858 'real_null_value' => $realNullValue,
1859 'id_index' => $this->fieldIndex
,
1861 'decimals' => $noDecimals,
1862 'special_chars' => $specialChars,
1863 'transformed_value' => $transformedHtml,
1864 'value' => $columnValue,
1865 'is_value_foreign_link' => $foreignData['foreign_link'] === true,
1866 'backup_field' => $backupField,
1868 'gis_data_types' => Gis
::getDataTypes(),
1869 'foreign_dropdown' => $foreignDropdown,
1870 'data_type' => $dataType,
1871 'textarea_cols' => $textareaCols,
1872 'textarea_rows' => $textAreaRows,
1873 'text_dir' => $textDir,
1874 'max_length' => $maxlength,
1875 'longtext_double_textarea' => $config->settings
['LongtextDoubleTextarea'],
1876 'enum_selected_value' => $enumSelectedValue,
1877 'enum_values' => $enumValues,
1878 'set_values' => $columnSetValues,
1879 'set_select_size' => $setSelectSize,
1880 'is_column_protected_blob' => $isColumnProtectedBlob,
1881 'blob_value' => $blobValue,
1882 'blob_value_unit' => $blobValueUnit,
1883 'is_upload' => $isUpload,
1884 'max_upload_size' => $maxUploadSize,
1885 'select_option_for_upload' => $selectOptionForUpload,
1886 'limit_chars' => $config->settings
['LimitChars'],
1887 'input_field_html' => $inputFieldHtml,
1888 'field_title' => $this->getColumnTitle($column->field
, $commentsMap),
1892 private function isColumnBinary(InsertEditColumn
$column, bool $isUpload): bool
1894 $config = Config
::getInstance();
1895 if (! $config->settings
['ShowFunctionFields']) {
1899 return ($config->settings
['ProtectBinary'] === 'blob' && $column->isBlob
&& ! $isUpload)
1900 ||
($config->settings
['ProtectBinary'] === 'all' && $column->isBinary
)
1901 ||
($config->settings
['ProtectBinary'] === 'noblob' && $column->isBinary
);
1905 * Function to get html for each insert/edit row
1907 * @param mixed[] $urlParams url parameters
1908 * @param list<ColumnFull> $tableColumns table columns
1909 * @param string[] $commentsMap comments map
1910 * @param ResultInterface $currentResult current result
1911 * @param bool $insertMode whether insert mode
1912 * @param mixed[] $currentRow current row
1913 * @param bool $isUpload whether upload
1914 * @param mixed[] $foreigners foreigners
1915 * @param string $table table
1916 * @param string $db database
1917 * @param int $rowId row id
1918 * @param string $textDir text direction
1919 * @param mixed[] $repopulate the data to be repopulated
1920 * @param mixed[] $whereClauseArray the array of where clauses
1922 public function getHtmlForInsertEditRow(
1924 array $tableColumns,
1926 ResultInterface
$currentResult,
1936 array $whereClauseArray,
1938 $htmlOutput = $this->getHeadAndFootOfInsertRowTable($urlParams)
1941 //store the default value for CharEditing
1942 $defaultCharEditing = Config
::getInstance()->settings
['CharEditing'];
1943 $mimeMap = $this->transformations
->getMime($db, $table);
1945 if (isset($whereClauseArray[$rowId])) {
1946 $whereClause = $whereClauseArray[$rowId];
1949 $columnCount = count($tableColumns);
1950 for ($columnNumber = 0; $columnNumber < $columnCount; $columnNumber++
) {
1951 $tableColumn = $tableColumns[$columnNumber];
1953 if (isset($mimeMap[$tableColumn->field
])) {
1954 $columnMime = $mimeMap[$tableColumn->field
];
1957 $virtual = ['VIRTUAL', 'PERSISTENT', 'VIRTUAL GENERATED', 'STORED GENERATED'];
1958 if (in_array($tableColumn->extra
, $virtual, true)) {
1962 $htmlOutput .= $this->getHtmlForInsertEditFormColumn(
1966 $this->dbi
->getFieldsMeta($currentResult)[$columnNumber]->length
,
1975 $defaultCharEditing,
1985 return $htmlOutput . ' </tbody>'
1986 . '</table></div><br>'
1987 . '<div class="clearfloat"></div>';
1991 * Returns list of function names that accept WKB as text
1995 private function getGisFromTextFunctions(): array
1997 return $this->dbi
->getVersion() >= 50600 ?
2000 'ST_GeomCollFromText',
2004 'ST_MPointFromText',
2021 * Returns list of function names that accept WKB as binary
2025 private function getGisFromWKBFunctions(): array
2027 return $this->dbi
->getVersion() >= 50600 ?
2030 'ST_GeomCollFromWKB',