';
}
/**
* Show type information or function selectors labels in Insert/Edit
*
* @param string $which function|type
*
* @return string|null an HTML snippet
*/
private function showTypeOrFunctionLabel($which)
{
switch ($which) {
case 'function':
return __('Function');
case 'type':
return __('Type');
}
return null;
}
/**
* Analyze the table column array
*
* @param array $column description of column in given table
* @param array $commentsMap comments for every column that has a comment
* @param bool $timestampSeen whether a timestamp has been seen
*
* @return array description of column in given table
*/
private function analyzeTableColumnsArray(
array $column,
array $commentsMap,
$timestampSeen
) {
$column['Field_html'] = htmlspecialchars($column['Field']);
$column['Field_md5'] = md5($column['Field']);
// True_Type contains only the type (stops at first bracket)
$column['True_Type'] = preg_replace('@\(.*@s', '', $column['Type']);
$column['len'] = preg_match('@float|double@', $column['Type']) ? 100 : -1;
$column['Field_title'] = $this->getColumnTitle($column, $commentsMap);
$column['is_binary'] = $this->isColumn(
$column,
[
'binary',
'varbinary',
]
);
$column['is_blob'] = $this->isColumn(
$column,
[
'blob',
'tinyblob',
'mediumblob',
'longblob',
]
);
$column['is_char'] = $this->isColumn(
$column,
[
'char',
'varchar',
]
);
[
$column['pma_type'],
$column['wrap'],
$column['first_timestamp'],
] = $this->getEnumSetAndTimestampColumns($column, $timestampSeen);
return $column;
}
/**
* Retrieve the column title
*
* @param array $column description of column in given table
* @param array $commentsMap comments for every column that has a comment
*
* @return string column title
*/
private function getColumnTitle(array $column, array $commentsMap)
{
if (isset($commentsMap[$column['Field']])) {
return ''
. $column['Field_html'] . '';
}
return $column['Field_html'];
}
/**
* check whether the column is of a certain type
* the goal is to ensure that types such as "enum('one','two','binary',..)"
* or "enum('one','two','varbinary',..)" are not categorized as binary
*
* @param array $column description of column in given table
* @param array $types the types to verify
*
* @return bool whether the column's type if one of the $types
*/
public function isColumn(array $column, array $types)
{
foreach ($types as $oneType) {
if (mb_stripos($column['Type'], $oneType) === 0) {
return true;
}
}
return false;
}
/**
* Retrieve set, enum, timestamp table columns
*
* @param array $column description of column in given table
* @param bool $timestampSeen whether a timestamp has been seen
*
* @return array $column['pma_type'], $column['wrap'], $column['first_timestamp']
*/
private function getEnumSetAndTimestampColumns(array $column, $timestampSeen)
{
$column['first_timestamp'] = false;
switch ($column['True_Type']) {
case 'set':
$column['pma_type'] = 'set';
$column['wrap'] = '';
break;
case 'enum':
$column['pma_type'] = 'enum';
$column['wrap'] = '';
break;
case 'timestamp':
if (! $timestampSeen) { // can only occur once per table
$column['first_timestamp'] = true;
}
$column['pma_type'] = $column['Type'];
$column['wrap'] = ' text-nowrap';
break;
default:
$column['pma_type'] = $column['Type'];
$column['wrap'] = ' text-nowrap';
break;
}
return [
$column['pma_type'],
$column['wrap'],
$column['first_timestamp'],
];
}
/**
* Retrieve the nullify code for the null column
*
* @param array $column description of column in given table
* @param array $foreigners keys into foreign fields
* @param array $foreignData data about the foreign keys
*/
private function getNullifyCodeForNullColumn(
array $column,
array $foreigners,
array $foreignData
): string {
$foreigner = $this->relation->searchColumnInForeigners($foreigners, $column['Field']);
if (mb_strstr($column['True_Type'], 'enum')) {
if (mb_strlen((string) $column['Type']) > 20) {
$nullifyCode = '1';
} else {
$nullifyCode = '2';
}
} elseif (mb_strstr($column['True_Type'], 'set')) {
$nullifyCode = '3';
} elseif (
! empty($foreigners)
&& ! empty($foreigner)
&& $foreignData['foreign_link'] == false
) {
// foreign key in a drop-down
$nullifyCode = '4';
} elseif (
! empty($foreigners)
&& ! empty($foreigner)
&& $foreignData['foreign_link'] == true
) {
// foreign key with a browsing icon
$nullifyCode = '6';
} else {
$nullifyCode = '5';
}
return $nullifyCode;
}
/**
* Get HTML textarea for insert form
*
* @param array $column column information
* @param string $backupField hidden input field
* @param string $columnNameAppendix the name attribute
* @param string $onChangeClause onchange clause for fields
* @param int $tabindex tab index
* @param int $tabindexForValue offset for the values tabindex
* @param int $idindex id index
* @param string $textDir text direction
* @param string $specialCharsEncoded replaced char if the string starts
* with a \r\n pair (0x0d0a) add an extra \n
* @param string $dataType the html5 data-* attribute type
* @param bool $readOnly is column read only or not
*
* @return string an html snippet
*/
private function getTextarea(
array $column,
$backupField,
$columnNameAppendix,
$onChangeClause,
$tabindex,
$tabindexForValue,
$idindex,
$textDir,
$specialCharsEncoded,
$dataType,
$readOnly
) {
$theClass = '';
$textAreaRows = $GLOBALS['cfg']['TextareaRows'];
$textareaCols = $GLOBALS['cfg']['TextareaCols'];
if ($column['is_char']) {
/**
* @todo clarify the meaning of the "textfield" class and explain
* why character columns have the "char" class instead
*/
$theClass = 'char charField';
$textAreaRows = $GLOBALS['cfg']['CharTextareaRows'];
$textareaCols = $GLOBALS['cfg']['CharTextareaCols'];
$extractedColumnspec = Util::extractColumnSpec(
$column['Type']
);
$maxlength = $extractedColumnspec['spec_in_brackets'];
} elseif (
$GLOBALS['cfg']['LongtextDoubleTextarea']
&& mb_strstr($column['pma_type'], 'longtext')
) {
$textAreaRows = $GLOBALS['cfg']['TextareaRows'] * 2;
$textareaCols = $GLOBALS['cfg']['TextareaCols'] * 2;
}
return $backupField . "\n"
. '';
}
/**
* Get column values
*
* @param array $column description of column in given table
* @param array $extractedColumnspec associative array containing type,
* spec_in_brackets and possibly enum_set_values
* (another array)
*
* @return array column values as an associative array
*/
private function getColumnEnumValues(array $column, array $extractedColumnspec)
{
$column['values'] = [];
foreach ($extractedColumnspec['enum_set_values'] as $val) {
$column['values'][] = [
'plain' => $val,
'html' => htmlspecialchars($val),
];
}
return $column['values'];
}
/**
* Retrieve column 'set' value and select size
*
* @param array $column description of column in given table
* @param array $extractedColumnspec associative array containing type,
* spec_in_brackets and possibly enum_set_values
* (another array)
*
* @return array $column['values'], $column['select_size']
*/
private function getColumnSetValueAndSelectSize(
array $column,
array $extractedColumnspec
) {
if (! isset($column['values'])) {
$column['values'] = [];
foreach ($extractedColumnspec['enum_set_values'] as $val) {
$column['values'][] = [
'plain' => $val,
'html' => htmlspecialchars($val),
];
}
$column['select_size'] = min(4, count($column['values']));
}
return [
$column['values'],
$column['select_size'],
];
}
/**
* Get HTML input type
*
* @param array $column description of column in given table
* @param string $columnNameAppendix the name attribute
* @param string $specialChars special characters
* @param int $fieldsize html field size
* @param string $onChangeClause onchange clause for fields
* @param int $tabindex tab index
* @param int $tabindexForValue offset for the values tabindex
* @param int $idindex id index
* @param string $dataType the html5 data-* attribute type
* @param bool $readOnly is column read only or not
*
* @return string an html snippet
*/
private function getHtmlInput(
array $column,
$columnNameAppendix,
$specialChars,
$fieldsize,
$onChangeClause,
$tabindex,
$tabindexForValue,
$idindex,
$dataType,
$readOnly
) {
$inputType = 'text';
// do not use the 'date' or 'time' types here; they have no effect on some
// browsers and create side effects (see bug #4218)
$theClass = 'textfield';
// verify True_Type which does not contain the parentheses and length
if (! $readOnly) {
if ($column['True_Type'] === 'date') {
$theClass .= ' datefield';
} elseif ($column['True_Type'] === 'time') {
$theClass .= ' timefield';
} elseif (
$column['True_Type'] === 'datetime'
|| $column['True_Type'] === 'timestamp'
) {
$theClass .= ' datetimefield';
}
}
$inputMinMax = false;
if (in_array($column['True_Type'], $this->dbi->types->getIntegerTypes())) {
$extractedColumnspec = Util::extractColumnSpec(
$column['Type']
);
$isUnsigned = $extractedColumnspec['unsigned'];
$minMaxValues = $this->dbi->types->getIntegerRange(
$column['True_Type'],
! $isUnsigned
);
$inputMinMax = 'min="' . $minMaxValues[0] . '" '
. 'max="' . $minMaxValues[1] . '"';
$dataType = 'INT';
}
return '';
}
/**
* Get HTML select option for upload
*
* @param string $vkey [multi_edit]['row_id']
* @param array $column description of column in given table
*
* @return string|null an html snippet
*/
private function getSelectOptionForUpload($vkey, array $column)
{
$files = $this->fileListing->getFileSelectOptions(
Util::userDir($GLOBALS['cfg']['UploadDir'])
);
if ($files === false) {
return '' . __('Error') . ' ' . "\n"
. __('The directory you set for upload work cannot be reached.') . "\n";
}
if (! empty($files)) {
return " \n"
. '' . __('Or') . ' '
. __('web server upload directory:') . ' ' . "\n"
. '' . "\n";
}
return null;
}
/**
* Retrieve the maximum upload file size
*
* @param array $column description of column in given table
* @param int $biggestMaxFileSize biggest max file size for uploading
*
* @return array an html snippet and $biggest_max_file_size
*/
private function getMaxUploadSize(array $column, $biggestMaxFileSize)
{
// find maximum upload size, based on field type
/**
* @todo with functions this is not so easy, as you can basically
* process any data with function like MD5
*/
global $max_upload_size;
$maxFieldSizes = [
'tinyblob' => '256',
'blob' => '65536',
'mediumblob' => '16777216',
'longblob' => '4294967296',// yeah, really
];
$thisFieldMaxSize = $max_upload_size; // from PHP max
if ($thisFieldMaxSize > $maxFieldSizes[$column['pma_type']]) {
$thisFieldMaxSize = $maxFieldSizes[$column['pma_type']];
}
$htmlOutput = Util::getFormattedMaximumUploadSize(
$thisFieldMaxSize
) . "\n";
// do not generate here the MAX_FILE_SIZE, because we should
// put only one in the form to accommodate the biggest field
if ($thisFieldMaxSize > $biggestMaxFileSize) {
$biggestMaxFileSize = $thisFieldMaxSize;
}
return [
$htmlOutput,
$biggestMaxFileSize,
];
}
/**
* Get HTML for the Value column of other datatypes
* (here, "column" is used in the sense of HTML column in HTML table)
*
* @param array $column description of column in given table
* @param string $defaultCharEditing default char editing mode which is stored
* in the config.inc.php script
* @param string $backupField hidden input field
* @param string $columnNameAppendix the name attribute
* @param string $onChangeClause onchange clause for fields
* @param int $tabindex tab index
* @param string $specialChars special characters
* @param int $tabindexForValue offset for the values tabindex
* @param int $idindex id index
* @param string $textDir text direction
* @param string $specialCharsEncoded replaced char if the string starts
* with a \r\n pair (0x0d0a) add an extra \n
* @param string $data data to edit
* @param array $extractedColumnspec associative array containing type,
* spec_in_brackets and possibly
* enum_set_values (another array)
* @param bool $readOnly is column read only or not
*
* @return string an html snippet
*/
private function getValueColumnForOtherDatatypes(
array $column,
$defaultCharEditing,
$backupField,
$columnNameAppendix,
$onChangeClause,
$tabindex,
$specialChars,
$tabindexForValue,
$idindex,
$textDir,
$specialCharsEncoded,
$data,
array $extractedColumnspec,
$readOnly
) {
// HTML5 data-* attribute data-type
$dataType = $this->dbi->types->getTypeClass($column['True_Type']);
$fieldsize = $this->getColumnSize($column, $extractedColumnspec);
$htmlOutput = $backupField . "\n";
if (
$column['is_char']
&& ($GLOBALS['cfg']['CharEditing'] === 'textarea'
|| mb_strpos($data, "\n") !== false)
) {
$htmlOutput .= "\n";
$GLOBALS['cfg']['CharEditing'] = $defaultCharEditing;
$htmlOutput .= $this->getTextarea(
$column,
$backupField,
$columnNameAppendix,
$onChangeClause,
$tabindex,
$tabindexForValue,
$idindex,
$textDir,
$specialCharsEncoded,
$dataType,
$readOnly
);
} else {
$htmlOutput .= $this->getHtmlInput(
$column,
$columnNameAppendix,
$specialChars,
$fieldsize,
$onChangeClause,
$tabindex,
$tabindexForValue,
$idindex,
$dataType,
$readOnly
);
if (
preg_match('/(VIRTUAL|PERSISTENT|GENERATED)/', $column['Extra'])
&& strpos($column['Extra'], 'DEFAULT_GENERATED') === false
) {
$htmlOutput .= '';
}
if ($column['Extra'] === 'auto_increment') {
$htmlOutput .= '';
}
if (substr($column['pma_type'], 0, 9) === 'timestamp') {
$htmlOutput .= '';
}
if (substr($column['pma_type'], 0, 4) === 'date') {
$type = substr($column['pma_type'], 0, 8) === 'datetime' ? 'datetime' : 'date';
$htmlOutput .= '';
}
if ($column['True_Type'] === 'bit') {
$htmlOutput .= '';
}
}
return $htmlOutput;
}
/**
* Get the field size
*
* @param array $column description of column in given table
* @param array $extractedColumnspec associative array containing type,
* spec_in_brackets and possibly enum_set_values
* (another array)
*
* @return int field size
*/
private function getColumnSize(array $column, array $extractedColumnspec)
{
if ($column['is_char']) {
$fieldsize = $extractedColumnspec['spec_in_brackets'];
if ($fieldsize > $GLOBALS['cfg']['MaxSizeForInputField']) {
/**
* This case happens for CHAR or VARCHAR columns which have
* a size larger than the maximum size for input field.
*/
$GLOBALS['cfg']['CharEditing'] = 'textarea';
}
} else {
/**
* This case happens for example for INT or DATE columns;
* in these situations, the value returned in $column['len']
* seems appropriate.
*/
$fieldsize = $column['len'];
}
return min(
max($fieldsize, $GLOBALS['cfg']['MinSizeForInputField']),
$GLOBALS['cfg']['MaxSizeForInputField']
);
}
/**
* get html for continue insertion form
*
* @param string $table name of the table
* @param string $db name of the database
* @param array $whereClauseArray array of where clauses
* @param string $errorUrl error url
*
* @return string an html snippet
*/
public function getContinueInsertionForm(
$table,
$db,
array $whereClauseArray,
$errorUrl
) {
return $this->template->render('table/insert/continue_insertion_form', [
'db' => $db,
'table' => $table,
'where_clause_array' => $whereClauseArray,
'err_url' => $errorUrl,
'goto' => $GLOBALS['goto'],
'sql_query' => $_POST['sql_query'] ?? null,
'has_where_clause' => isset($_POST['where_clause']),
'insert_rows_default' => $GLOBALS['cfg']['InsertRows'],
]);
}
/**
* @param string[]|string|null $whereClause
*
* @psalm-pure
*/
public static function isWhereClauseNumeric($whereClause): bool
{
if (! isset($whereClause)) {
return false;
}
$isNumeric = false;
if (! is_array($whereClause)) {
$whereClause = [$whereClause];
}
// If we have just numeric primary key, we can also edit next
// we are looking for `table_name`.`field_name` = numeric_value
foreach ($whereClause as $clause) {
// preg_match() returns 1 if there is a match
$isNumeric = preg_match('@^[\s]*`[^`]*`[\.]`[^`]*` = [0-9]+@', $clause) === 1;
if ($isNumeric === true) {
break;
}
}
return $isNumeric;
}
/**
* Get table head and table foot for insert row table
*
* @param array $urlParams url parameters
*
* @return string an html snippet
*/
private function getHeadAndFootOfInsertRowTable(array $urlParams)
{
$htmlOutput = '