Update website

This commit is contained in:
Guilhem Lavaux 2025-03-24 09:27:39 +01:00
parent a0b0d3dae7
commit ae7ef6ad45
3151 changed files with 566766 additions and 48 deletions

View file

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Controllers\AbstractController as Controller;
use PhpMyAdmin\Response;
use PhpMyAdmin\Template;
abstract class AbstractController extends Controller
{
/** @var string */
protected $db;
/** @var string */
protected $table;
/**
* @param Response $response
* @param string $db Database name
* @param string $table Table name
*/
public function __construct($response, Template $template, $db, $table)
{
parent::__construct($response, $template);
$this->db = $db;
$this->table = $table;
}
}

View file

@ -0,0 +1,190 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Config;
use PhpMyAdmin\CreateAddField;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Message;
use PhpMyAdmin\Relation;
use PhpMyAdmin\Response;
use PhpMyAdmin\Table\ColumnsDefinition;
use PhpMyAdmin\Template;
use PhpMyAdmin\Transformations;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function intval;
use function is_array;
use function min;
use function strlen;
/**
* Displays add field form and handles it.
*/
class AddFieldController extends AbstractController
{
/** @var Transformations */
private $transformations;
/** @var Config */
private $config;
/** @var Relation */
private $relation;
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
* @param DatabaseInterface $dbi
*/
public function __construct(
$response,
Template $template,
$db,
$table,
Transformations $transformations,
Config $config,
Relation $relation,
$dbi
) {
parent::__construct($response, $template, $db, $table);
$this->transformations = $transformations;
$this->config = $config;
$this->relation = $relation;
$this->dbi = $dbi;
}
public function index(): void
{
global $err_url, $message, $action, $active_page, $sql_query;
global $num_fields, $regenerate, $result, $db, $table;
$this->addScriptFiles(['table/structure.js']);
// Check parameters
Util::checkParameters(['db', 'table']);
$cfg = $this->config->settings;
/**
* Defines the url to return to in case of error in a sql statement
*/
$err_url = Url::getFromRoute('/table/sql', [
'db' => $db,
'table' => $table,
]);
// check number of fields to be created
if (isset($_POST['submit_num_fields'])) {
if (isset($_POST['orig_after_field'])) {
$_POST['after_field'] = $_POST['orig_after_field'];
}
if (isset($_POST['orig_field_where'])) {
$_POST['field_where'] = $_POST['orig_field_where'];
}
$num_fields = min(
intval($_POST['orig_num_fields']) + intval($_POST['added_fields']),
4096
);
$regenerate = true;
} elseif (isset($_POST['num_fields']) && intval($_POST['num_fields']) > 0) {
$num_fields = min(4096, intval($_POST['num_fields']));
} else {
$num_fields = 1;
}
if (isset($_POST['do_save_data'])) {
// avoid an incorrect calling of PMA_updateColumns() via
// /table/structure below
unset($_POST['do_save_data']);
$createAddField = new CreateAddField($this->dbi);
[$result, $sql_query] = $createAddField->tryColumnCreationQuery($db, $table, $err_url);
if ($result !== true) {
$error_message_html = Generator::mysqlDie(
'',
'',
false,
$err_url,
false
);
$this->response->addHTML($error_message_html ?? '');
$this->response->setRequestStatus(false);
return;
}
// Update comment table for mime types [MIME]
if (isset($_POST['field_mimetype'])
&& is_array($_POST['field_mimetype'])
&& $cfg['BrowseMIME']
) {
foreach ($_POST['field_mimetype'] as $fieldindex => $mimetype) {
if (! isset($_POST['field_name'][$fieldindex])
|| strlen($_POST['field_name'][$fieldindex]) <= 0
) {
continue;
}
$this->transformations->setMime(
$db,
$table,
$_POST['field_name'][$fieldindex],
$mimetype,
$_POST['field_transformation'][$fieldindex],
$_POST['field_transformation_options'][$fieldindex],
$_POST['field_input_transformation'][$fieldindex],
$_POST['field_input_transformation_options'][$fieldindex]
);
}
}
// Go back to the structure sub-page
$message = Message::success(
__('Table %1$s has been altered successfully.')
);
$message->addParam($table);
$this->response->addJSON(
'message',
Generator::getMessage($message, $sql_query, 'success')
);
return;
}
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
$active_page = Url::getFromRoute('/table/structure');
/**
* Display the form
*/
$action = Url::getFromRoute('/table/add-field');
$this->addScriptFiles(['vendor/jquery/jquery.uitablefilter.js', 'indexes.js']);
$templateData = ColumnsDefinition::displayForm(
$this->transformations,
$this->relation,
$this->dbi,
$action,
$num_fields,
$regenerate
);
$this->render('columns_definitions/column_definitions_form', $templateData);
}
}

View file

@ -0,0 +1,324 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Config\PageSettings;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\InsertEdit;
use PhpMyAdmin\Relation;
use PhpMyAdmin\Response;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use function array_fill;
use function count;
use function is_array;
use function mb_strpos;
use function strlen;
/**
* Displays form for editing and inserting new table rows.
*/
class ChangeController extends AbstractController
{
/** @var InsertEdit */
private $insertEdit;
/** @var Relation */
private $relation;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
*/
public function __construct(
$response,
Template $template,
$db,
$table,
InsertEdit $insertEdit,
Relation $relation
) {
parent::__construct($response, $template, $db, $table);
$this->insertEdit = $insertEdit;
$this->relation = $relation;
}
public function index(): void
{
global $cfg, $is_upload, $db, $table, $text_dir, $disp_message, $url_params;
global $err_url, $where_clause, $unsaved_values, $insert_mode, $where_clause_array, $where_clauses;
global $result, $rows, $found_unique_key, $after_insert, $comments_map, $table_columns;
global $chg_evt_handler, $timestamp_seen, $columns_cnt, $tabindex, $tabindex_for_function;
global $tabindex_for_null, $tabindex_for_value, $o_rows, $biggest_max_file_size, $has_blob_field;
global $jsvkey, $vkey, $current_result, $repopulate, $checked;
$pageSettings = new PageSettings('Edit');
$this->response->addHTML($pageSettings->getErrorHTML());
$this->response->addHTML($pageSettings->getHTML());
DbTableExists::check();
/**
* Determine whether Insert or Edit and set global variables
*/
[
$insert_mode,
$where_clause,
$where_clause_array,
$where_clauses,
$result,
$rows,
$found_unique_key,
$after_insert,
] = $this->insertEdit->determineInsertOrEdit(
$where_clause ?? null,
$db,
$table
);
// Increase number of rows if unsaved rows are more
if (! empty($unsaved_values) && count($rows) < count($unsaved_values)) {
$rows = array_fill(0, count($unsaved_values), false);
}
/**
* Defines the url to return to in case of error in a sql statement
* (at this point, $GLOBALS['goto'] will be set but could be empty)
*/
if (empty($GLOBALS['goto'])) {
if (strlen($table) > 0) {
// avoid a problem (see bug #2202709)
$GLOBALS['goto'] = Url::getFromRoute('/table/sql');
} else {
$GLOBALS['goto'] = Url::getFromRoute('/database/sql');
}
}
$_url_params = $this->insertEdit->getUrlParameters($db, $table);
$err_url = $GLOBALS['goto'] . Url::getCommon(
$_url_params,
mb_strpos($GLOBALS['goto'], '?') === false ? '?' : '&'
);
unset($_url_params);
$comments_map = $this->insertEdit->getCommentsMap($db, $table);
/**
* START REGULAR OUTPUT
*/
$this->addScriptFiles([
'makegrid.js',
'vendor/stickyfill.min.js',
'sql.js',
'table/change.js',
'vendor/jquery/additional-methods.js',
'gis_data_editor.js',
]);
/**
* Displays the query submitted and its result
*
* $disp_message come from /table/replace
*/
if (! empty($disp_message)) {
$this->response->addHTML(Generator::getMessage($disp_message, null));
}
$table_columns = $this->insertEdit->getTableColumns($db, $table);
// retrieve keys into foreign fields, if any
$foreigners = $this->relation->getForeigners($db, $table);
// Retrieve form parameters for insert/edit form
$_form_params = $this->insertEdit->getFormParametersForInsertForm(
$db,
$table,
$where_clauses,
$where_clause_array,
$err_url
);
/**
* Displays the form
*/
// autocomplete feature of IE kills the "onchange" event handler and it
// must be replaced by the "onpropertychange" one in this case
$chg_evt_handler = 'onchange';
// Had to put the URI because when hosted on an https server,
// some browsers send wrongly this form to the http server.
$html_output = '';
// Set if we passed the first timestamp field
$timestamp_seen = false;
$columns_cnt = count($table_columns);
$tabindex = 0;
$tabindex_for_function = +3000;
$tabindex_for_null = +6000;
$tabindex_for_value = 0;
$o_rows = 0;
$biggest_max_file_size = 0;
$url_params['db'] = $db;
$url_params['table'] = $table;
$url_params = $this->insertEdit->urlParamsInEditMode(
$url_params,
$where_clause_array
);
$has_blob_field = false;
foreach ($table_columns as $column) {
if ($this->insertEdit->isColumn(
$column,
[
'blob',
'tinyblob',
'mediumblob',
'longblob',
]
)) {
$has_blob_field = true;
break;
}
}
//Insert/Edit form
//If table has blob fields we have to disable ajax.
$html_output .= $this->insertEdit->getHtmlForInsertEditFormHeader($has_blob_field, $is_upload);
$html_output .= Url::getHiddenInputs($_form_params);
// user can toggle the display of Function column and column types
// (currently does not work for multi-edits)
if (! $cfg['ShowFunctionFields'] || ! $cfg['ShowFieldTypesInDataEditView']) {
$html_output .= __('Show');
}
if (! $cfg['ShowFunctionFields']) {
$html_output .= $this->insertEdit->showTypeOrFunction('function', $url_params, false);
}
if (! $cfg['ShowFieldTypesInDataEditView']) {
$html_output .= $this->insertEdit->showTypeOrFunction('type', $url_params, false);
}
$GLOBALS['plugin_scripts'] = [];
foreach ($rows as $row_id => $current_row) {
if (empty($current_row)) {
$current_row = [];
}
$jsvkey = $row_id;
$vkey = '[multi_edit][' . $jsvkey . ']';
$current_result = (isset($result) && is_array($result) && isset($result[$row_id])
? $result[$row_id]
: $result);
$repopulate = [];
$checked = true;
if (isset($unsaved_values[$row_id])) {
$repopulate = $unsaved_values[$row_id];
$checked = false;
}
if ($insert_mode && $row_id > 0) {
$html_output .= $this->insertEdit->getHtmlForIgnoreOption($row_id, $checked);
}
$html_output .= $this->insertEdit->getHtmlForInsertEditRow(
$url_params,
$table_columns,
$comments_map,
$timestamp_seen,
$current_result,
$chg_evt_handler,
$jsvkey,
$vkey,
$insert_mode,
$current_row,
$o_rows,
$tabindex,
$columns_cnt,
$is_upload,
$tabindex_for_function,
$foreigners,
$tabindex_for_null,
$tabindex_for_value,
$table,
$db,
$row_id,
$biggest_max_file_size,
$text_dir,
$repopulate,
$where_clause_array
);
}
$this->addScriptFiles($GLOBALS['plugin_scripts']);
unset($unsaved_values, $checked, $repopulate, $GLOBALS['plugin_scripts']);
if (! isset($after_insert)) {
$after_insert = 'back';
}
//action panel
$html_output .= $this->insertEdit->getActionsPanel(
$where_clause,
$after_insert,
$tabindex,
$tabindex_for_value,
$found_unique_key
);
if ($biggest_max_file_size > 0) {
$html_output .= '<input type="hidden" name="MAX_FILE_SIZE" value="' . $biggest_max_file_size . '">' . "\n";
}
$html_output .= '</form>';
$html_output .= $this->insertEdit->getHtmlForGisEditor();
// end Insert/Edit form
if ($insert_mode) {
//Continue insertion form
$html_output .= $this->insertEdit->getContinueInsertionForm(
$table,
$db,
$where_clause_array,
$err_url
);
}
$this->response->addHTML($html_output);
}
public function rows(): void
{
global $active_page, $where_clause;
if (isset($_POST['goto']) && (! isset($_POST['rows_to_delete']) || ! is_array($_POST['rows_to_delete']))) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('No row selected.'));
return;
}
// As we got the rows to be edited from the
// 'rows_to_delete' checkbox, we use the index of it as the
// indicating WHERE clause. Then we build the array which is used
// for the /table/change script.
$where_clause = [];
if (isset($_POST['rows_to_delete']) && is_array($_POST['rows_to_delete'])) {
foreach ($_POST['rows_to_delete'] as $i => $i_where_clause) {
$where_clause[] = $i_where_clause;
}
}
$active_page = Url::getFromRoute('/table/change');
$this->index();
}
}

View file

@ -0,0 +1,237 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Message;
use PhpMyAdmin\Response;
use PhpMyAdmin\SqlParser\Components\Limit;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\SelectStatement;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function array_keys;
use function htmlspecialchars;
use function in_array;
use function json_encode;
use function min;
use function strlen;
/**
* Handles creation of the chart.
*/
class ChartController extends AbstractController
{
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
* @param DatabaseInterface $dbi
*/
public function __construct($response, Template $template, $db, $table, $dbi)
{
parent::__construct($response, $template, $db, $table);
$this->dbi = $dbi;
}
public function index(): void
{
global $db, $table, $cfg, $sql_query, $err_url;
if (isset($_REQUEST['pos'], $_REQUEST['session_max_rows']) && $this->response->isAjax()
) {
$this->ajax();
return;
}
// Throw error if no sql query is set
if (! isset($sql_query) || $sql_query == '') {
$this->response->setRequestStatus(false);
$this->response->addHTML(
Message::error(__('No SQL query was set to fetch data.'))
);
return;
}
$this->addScriptFiles([
'chart.js',
'table/chart.js',
'vendor/jqplot/jquery.jqplot.js',
'vendor/jqplot/plugins/jqplot.barRenderer.js',
'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js',
'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js',
'vendor/jqplot/plugins/jqplot.categoryAxisRenderer.js',
'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js',
'vendor/jqplot/plugins/jqplot.pointLabels.js',
'vendor/jqplot/plugins/jqplot.pieRenderer.js',
'vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js',
'vendor/jqplot/plugins/jqplot.highlighter.js',
]);
$url_params = [];
/**
* Runs common work
*/
if (strlen($table) > 0) {
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
$url_params['goto'] = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$url_params['back'] = Url::getFromRoute('/table/sql');
$this->dbi->selectDb($db);
} elseif (strlen($db) > 0) {
$url_params['goto'] = Util::getScriptNameForOption(
$cfg['DefaultTabDatabase'],
'database'
);
$url_params['back'] = Url::getFromRoute('/sql');
Util::checkParameters(['db']);
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
$err_url .= Url::getCommon(['db' => $db], '&');
if (! $this->hasDatabase()) {
return;
}
} else {
$url_params['goto'] = Util::getScriptNameForOption(
$cfg['DefaultTabServer'],
'server'
);
$url_params['back'] = Url::getFromRoute('/sql');
$err_url = Url::getFromRoute('/');
if ($this->dbi->isSuperUser()) {
$this->dbi->selectDb('mysql');
}
}
$data = [];
$result = $this->dbi->tryQuery($sql_query);
$fields_meta = $this->dbi->getFieldsMeta($result);
while ($row = $this->dbi->fetchAssoc($result)) {
$data[] = $row;
}
$keys = array_keys($data[0]);
$numeric_types = [
'int',
'real',
];
$numeric_column_count = 0;
foreach ($keys as $idx => $key) {
if (! in_array($fields_meta[$idx]->type, $numeric_types)) {
continue;
}
$numeric_column_count++;
}
if ($numeric_column_count == 0) {
$this->response->setRequestStatus(false);
$this->response->addJSON(
'message',
__('No numeric columns present in the table to plot.')
);
return;
}
$url_params['db'] = $db;
$url_params['reload'] = 1;
/**
* Displays the page
*/
$this->render('table/chart/tbl_chart', [
'url_params' => $url_params,
'keys' => $keys,
'fields_meta' => $fields_meta,
'numeric_types' => $numeric_types,
'numeric_column_count' => $numeric_column_count,
'sql_query' => $sql_query,
]);
}
/**
* Handle ajax request
*/
public function ajax(): void
{
global $db, $table, $sql_query, $url_params, $err_url, $cfg;
if (strlen($table) > 0 && strlen($db) > 0) {
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
}
$parser = new Parser($sql_query);
/**
* @var SelectStatement $statement
*/
$statement = $parser->statements[0];
if (empty($statement->limit)) {
$statement->limit = new Limit(
$_REQUEST['session_max_rows'],
$_REQUEST['pos']
);
} else {
$start = $statement->limit->offset + $_REQUEST['pos'];
$rows = min(
$_REQUEST['session_max_rows'],
$statement->limit->rowCount - $_REQUEST['pos']
);
$statement->limit = new Limit($rows, $start);
}
$sql_with_limit = $statement->build();
$data = [];
$result = $this->dbi->tryQuery($sql_with_limit);
while ($row = $this->dbi->fetchAssoc($result)) {
$data[] = $row;
}
if (empty($data)) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('No data to display'));
return;
}
$sanitized_data = [];
foreach ($data as $data_row_number => $data_row) {
$tmp_row = [];
foreach ($data_row as $data_column => $data_value) {
$escaped_value = $data_value === null ? null : htmlspecialchars($data_value);
$tmp_row[htmlspecialchars($data_column)] = $escaped_value;
}
$sanitized_data[] = $tmp_row;
}
$this->response->setRequestStatus(true);
$this->response->addJSON('message', null);
$this->response->addJSON('chartData', json_encode($sanitized_data));
}
}

View file

@ -0,0 +1,183 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Config;
use PhpMyAdmin\Core;
use PhpMyAdmin\CreateAddField;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Relation;
use PhpMyAdmin\Response;
use PhpMyAdmin\Table\ColumnsDefinition;
use PhpMyAdmin\Template;
use PhpMyAdmin\Transformations;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function htmlspecialchars;
use function is_array;
use function mb_strtolower;
use function sprintf;
use function strlen;
/**
* Displays table create form and handles it.
*/
class CreateController extends AbstractController
{
/** @var Transformations */
private $transformations;
/** @var Config */
private $config;
/** @var Relation */
private $relation;
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
* @param DatabaseInterface $dbi
*/
public function __construct(
$response,
Template $template,
$db,
$table,
Transformations $transformations,
Config $config,
Relation $relation,
$dbi
) {
parent::__construct($response, $template, $db, $table);
$this->transformations = $transformations;
$this->config = $config;
$this->relation = $relation;
$this->dbi = $dbi;
}
public function index(): void
{
global $num_fields, $action, $sql_query, $result, $db, $table;
Util::checkParameters(['db']);
$cfg = $this->config->settings;
/* Check if database name is empty */
if (strlen($db) === 0) {
Generator::mysqlDie(
__('The database name is empty!'),
'',
false,
'index.php'
);
}
/**
* Selects the database to work with
*/
if (! $this->dbi->selectDb($db)) {
Generator::mysqlDie(
sprintf(__('\'%s\' database does not exist.'), htmlspecialchars($db)),
'',
false,
'index.php'
);
}
if ($this->dbi->getColumns($db, $table)) {
// table exists already
Generator::mysqlDie(
sprintf(__('Table %s already exists!'), htmlspecialchars($table)),
'',
false,
Url::getFromRoute('/database/structure', ['db' => $db])
);
}
$createAddField = new CreateAddField($this->dbi);
$num_fields = $createAddField->getNumberOfFieldsFromRequest();
$action = Url::getFromRoute('/table/create');
/**
* The form used to define the structure of the table has been submitted
*/
if (isset($_POST['do_save_data'])) {
// lower_case_table_names=1 `DB` becomes `db`
if ($this->dbi->getLowerCaseNames() === '1') {
$db = mb_strtolower(
$db
);
$table = mb_strtolower(
$table
);
}
$sql_query = $createAddField->getTableCreationQuery($db, $table);
// If there is a request for SQL previewing.
if (isset($_POST['preview_sql'])) {
Core::previewSQL($sql_query);
return;
}
// Executes the query
$result = $this->dbi->tryQuery($sql_query);
if ($result) {
// Update comment table for mime types [MIME]
if (isset($_POST['field_mimetype'])
&& is_array($_POST['field_mimetype'])
&& $cfg['BrowseMIME']
) {
foreach ($_POST['field_mimetype'] as $fieldindex => $mimetype) {
if (! isset($_POST['field_name'][$fieldindex])
|| strlen($_POST['field_name'][$fieldindex]) <= 0
) {
continue;
}
$this->transformations->setMime(
$db,
$table,
$_POST['field_name'][$fieldindex],
$mimetype,
$_POST['field_transformation'][$fieldindex],
$_POST['field_transformation_options'][$fieldindex],
$_POST['field_input_transformation'][$fieldindex],
$_POST['field_input_transformation_options'][$fieldindex]
);
}
}
} else {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', $this->dbi->getError());
}
return;
}
// This global variable needs to be reset for the header class to function properly
$table = '';
$this->addScriptFiles(['vendor/jquery/jquery.uitablefilter.js', 'indexes.js']);
$templateData = ColumnsDefinition::displayForm(
$this->transformations,
$this->relation,
$this->dbi,
$action,
$num_fields
);
$this->render('columns_definitions/column_definitions_form', $templateData);
}
}

View file

@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Operations;
use PhpMyAdmin\Relation;
use PhpMyAdmin\RelationCleanup;
use PhpMyAdmin\Response;
use PhpMyAdmin\Sql;
use PhpMyAdmin\Template;
use PhpMyAdmin\Transformations;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function is_array;
use function sprintf;
class DeleteController extends AbstractController
{
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
* @param DatabaseInterface $dbi
*/
public function __construct($response, Template $template, $db, $table, $dbi)
{
parent::__construct($response, $template, $db, $table);
$this->dbi = $dbi;
}
public function rows(): void
{
global $db, $goto, $sql_query, $table, $disp_message, $disp_query, $PMA_Theme, $active_page;
$mult_btn = $_POST['mult_btn'] ?? '';
$original_sql_query = $_POST['original_sql_query'] ?? '';
$selected = $_POST['selected'] ?? [];
$relation = new Relation($this->dbi);
$sql = new Sql(
$this->dbi,
$relation,
new RelationCleanup($this->dbi, $relation),
new Operations($this->dbi, $relation),
new Transformations(),
$this->template
);
if ($mult_btn === __('Yes')) {
$default_fk_check_value = Util::handleDisableFKCheckInit();
$sql_query = '';
foreach ($selected as $row) {
$query = sprintf(
'DELETE FROM %s WHERE %s LIMIT 1;',
Util::backquote($table),
$row
);
$sql_query .= $query . "\n";
$this->dbi->selectDb($db);
$this->dbi->query($query);
}
if (! empty($_REQUEST['pos'])) {
$_REQUEST['pos'] = $sql->calculatePosForLastPage(
$db,
$table,
$_REQUEST['pos']
);
}
Util::handleDisableFKCheckCleanup($default_fk_check_value);
$disp_message = __('Your SQL query has been executed successfully.');
$disp_query = $sql_query;
}
$_url_params = $GLOBALS['url_params'];
$_url_params['goto'] = Url::getFromRoute('/table/sql');
if (isset($original_sql_query)) {
$sql_query = $original_sql_query;
}
$active_page = Url::getFromRoute('/sql');
$this->response->addHTML($sql->executeQueryAndSendQueryResponse(
null,
false,
$db,
$table,
null,
null,
null,
null,
null,
$goto,
$PMA_Theme->getImgPath(),
null,
null,
$sql_query,
null
));
}
public function confirm(): void
{
global $db, $table, $sql_query, $url_params, $err_url, $cfg;
$selected = $_POST['rows_to_delete'] ?? null;
if (! isset($selected) || ! is_array($selected)) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('No row selected.'));
return;
}
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
$this->render('table/delete/confirm', [
'db' => $db,
'table' => $table,
'selected' => $selected,
'sql_query' => $sql_query,
'is_foreign_key_check' => Util::isForeignKeyCheck(),
]);
}
}

View file

@ -0,0 +1,175 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Config\PageSettings;
use PhpMyAdmin\Export\Options;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Message;
use PhpMyAdmin\Plugins;
use PhpMyAdmin\Response;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\SelectStatement;
use PhpMyAdmin\SqlParser\Utils\Query;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function array_merge;
use function implode;
use function is_array;
class ExportController extends AbstractController
{
/** @var Options */
private $export;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
*/
public function __construct(
$response,
Template $template,
$db,
$table,
Options $export
) {
parent::__construct($response, $template, $db, $table);
$this->export = $export;
}
public function index(): void
{
global $db, $url_params, $table, $replaces, $cfg, $err_url;
global $sql_query, $where_clause, $num_tables, $unlim_num_rows;
$pageSettings = new PageSettings('Export');
$pageSettingsErrorHtml = $pageSettings->getErrorHTML();
$pageSettingsHtml = $pageSettings->getHTML();
$this->addScriptFiles(['export.js']);
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
$url_params['goto'] = Url::getFromRoute('/table/export');
$url_params['back'] = Url::getFromRoute('/table/export');
$message = '';
// When we have some query, we need to remove LIMIT from that and possibly
// generate WHERE clause (if we are asked to export specific rows)
if (! empty($sql_query)) {
$parser = new Parser($sql_query);
if (! empty($parser->statements[0])
&& ($parser->statements[0] instanceof SelectStatement)
) {
// Checking if the WHERE clause has to be replaced.
if (! empty($where_clause) && is_array($where_clause)) {
$replaces[] = [
'WHERE',
'WHERE (' . implode(') OR (', $where_clause) . ')',
];
}
// Preparing to remove the LIMIT clause.
$replaces[] = [
'LIMIT',
'',
];
// Replacing the clauses.
$sql_query = Query::replaceClauses(
$parser->statements[0],
$parser->list,
$replaces
);
}
$message = Generator::getMessage(Message::success());
}
if (! isset($sql_query)) {
$sql_query = '';
}
if (! isset($num_tables)) {
$num_tables = 0;
}
if (! isset($unlim_num_rows)) {
$unlim_num_rows = 0;
}
$GLOBALS['single_table'] = $_POST['single_table'] ?? $_GET['single_table'] ?? $GLOBALS['single_table'] ?? null;
$exportList = Plugins::getExport('table', isset($GLOBALS['single_table']));
if (empty($exportList)) {
$this->response->addHTML(Message::error(
__('Could not load export plugins, please check your installation!')
)->getDisplay());
return;
}
$exportType = 'table';
$isReturnBackFromRawExport = isset($_POST['export_type']) && $_POST['export_type'] === 'raw';
if (isset($_POST['raw_query']) || $isReturnBackFromRawExport) {
$exportType = 'raw';
}
$options = $this->export->getOptions(
$exportType,
$db,
$table,
$sql_query,
$num_tables,
$unlim_num_rows,
$exportList
);
$this->render('table/export/index', array_merge($options, [
'export_type' => $exportType,
'page_settings_error_html' => $pageSettingsErrorHtml,
'page_settings_html' => $pageSettingsHtml,
'message' => $message,
]));
}
public function rows(): void
{
global $active_page, $single_table, $where_clause;
if (isset($_POST['goto']) && (! isset($_POST['rows_to_delete']) || ! is_array($_POST['rows_to_delete']))) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('No row selected.'));
return;
}
// Needed to allow SQL export
$single_table = true;
// As we got the rows to be exported from the
// 'rows_to_delete' checkbox, we use the index of it as the
// indicating WHERE clause. Then we build the array which is used
// for the /table/change script.
$where_clause = [];
if (isset($_POST['rows_to_delete']) && is_array($_POST['rows_to_delete'])) {
foreach ($_POST['rows_to_delete'] as $i => $i_where_clause) {
$where_clause[] = $i_where_clause;
}
}
$active_page = Url::getFromRoute('/table/export');
$this->index();
}
}

View file

@ -0,0 +1,390 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Response;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function array_key_exists;
use function count;
use function is_array;
use function mb_strtolower;
use function preg_match;
use function preg_replace;
use function str_ireplace;
use function str_replace;
use function strncasecmp;
use function strpos;
/**
* Handles find and replace tab.
*
* Displays find and replace form, allows previewing and do the replacing.
*/
class FindReplaceController extends AbstractController
{
/** @var array */
private $columnNames;
/** @var array */
private $columnTypes;
/** @var string */
private $connectionCharSet;
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name
* @param string $table Table name
* @param DatabaseInterface $dbi
*/
public function __construct($response, Template $template, $db, $table, $dbi)
{
parent::__construct($response, $template, $db, $table);
$this->dbi = $dbi;
$this->columnNames = [];
$this->columnTypes = [];
$this->loadTableInfo();
$this->connectionCharSet = $this->dbi->fetchValue(
'SELECT @@character_set_connection'
);
}
public function index(): void
{
global $db, $table, $url_params, $cfg, $err_url;
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
if (isset($_POST['find'])) {
$this->findAction();
return;
}
$this->addScriptFiles(['table/find_replace.js']);
if (isset($_POST['replace'])) {
$this->replaceAction();
}
// Displays the find and replace form
$this->displaySelectionFormAction();
}
/**
* Gets all the columns of a table along with their types.
*/
private function loadTableInfo(): void
{
// Gets the list and number of columns
$columns = $this->dbi->getColumns(
$this->db,
$this->table,
null,
true
);
foreach ($columns as $row) {
// set column name
$this->columnNames[] = $row['Field'];
$type = (string) $row['Type'];
// reformat mysql query output
if (strncasecmp($type, 'set', 3) == 0
|| strncasecmp($type, 'enum', 4) == 0
) {
$type = str_replace(',', ', ', $type);
} else {
// strip the "BINARY" attribute, except if we find "BINARY(" because
// this would be a BINARY or VARBINARY column type
if (! preg_match('@BINARY[\(]@i', $type)) {
$type = str_ireplace('BINARY', '', $type);
}
$type = str_ireplace('ZEROFILL', '', $type);
$type = str_ireplace('UNSIGNED', '', $type);
$type = mb_strtolower($type);
}
if (empty($type)) {
$type = '&nbsp;';
}
$this->columnTypes[] = $type;
}
}
/**
* Display selection form action
*/
public function displaySelectionFormAction(): void
{
global $goto;
if (! isset($goto)) {
$goto = Util::getScriptNameForOption(
$GLOBALS['cfg']['DefaultTabTable'],
'table'
);
}
$column_names = $this->columnNames;
$column_types = $this->columnTypes;
$types = [];
$num_cols = count($column_names);
for ($i = 0; $i < $num_cols; $i++) {
$types[$column_names[$i]] = preg_replace(
'@\\(.*@s',
'',
$column_types[$i]
);
}
$this->render('table/find_replace/index', [
'db' => $this->db,
'table' => $this->table,
'goto' => $goto,
'column_names' => $column_names,
'types' => $types,
'sql_types' => $this->dbi->types,
]);
}
public function findAction(): void
{
$useRegex = array_key_exists('useRegex', $_POST)
&& $_POST['useRegex'] === 'on';
$preview = $this->getReplacePreview(
$_POST['columnIndex'],
$_POST['find'],
$_POST['replaceWith'],
$useRegex,
$this->connectionCharSet
);
$this->response->addJSON('preview', $preview);
}
public function replaceAction(): void
{
$this->replace(
$_POST['columnIndex'],
$_POST['findString'],
$_POST['replaceWith'],
$_POST['useRegex'],
$this->connectionCharSet
);
$this->response->addHTML(
Generator::getMessage(
__('Your SQL query has been executed successfully.'),
null,
'success'
)
);
}
/**
* Returns HTML for previewing strings found and their replacements
*
* @param int $columnIndex index of the column
* @param string $find string to find in the column
* @param string $replaceWith string to replace with
* @param bool $useRegex to use Regex replace or not
* @param string $charSet character set of the connection
*
* @return string HTML for previewing strings found and their replacements
*/
public function getReplacePreview(
$columnIndex,
$find,
$replaceWith,
$useRegex,
$charSet
) {
$column = $this->columnNames[$columnIndex];
if ($useRegex) {
$result = $this->getRegexReplaceRows(
$columnIndex,
$find,
$replaceWith,
$charSet
);
} else {
$sql_query = 'SELECT '
. Util::backquote($column) . ','
. ' REPLACE('
. Util::backquote($column) . ", '" . $find . "', '"
. $replaceWith
. "'),"
. ' COUNT(*)'
. ' FROM ' . Util::backquote($this->db)
. '.' . Util::backquote($this->table)
. ' WHERE ' . Util::backquote($column)
. " LIKE '%" . $find . "%' COLLATE " . $charSet . '_bin'; // here we
// change the collation of the 2nd operand to a case sensitive
// binary collation to make sure that the comparison
// is case sensitive
$sql_query .= ' GROUP BY ' . Util::backquote($column)
. ' ORDER BY ' . Util::backquote($column) . ' ASC';
$result = $this->dbi->fetchResult($sql_query, 0);
}
return $this->template->render('table/find_replace/replace_preview', [
'db' => $this->db,
'table' => $this->table,
'column_index' => $columnIndex,
'find' => $find,
'replace_with' => $replaceWith,
'use_regex' => $useRegex,
'result' => $result,
]);
}
/**
* Finds and returns Regex pattern and their replacements
*
* @param int $columnIndex index of the column
* @param string $find string to find in the column
* @param string $replaceWith string to replace with
* @param string $charSet character set of the connection
*
* @return array|bool Array containing original values, replaced values and count
*/
private function getRegexReplaceRows(
$columnIndex,
$find,
$replaceWith,
$charSet
) {
$column = $this->columnNames[$columnIndex];
$sql_query = 'SELECT '
. Util::backquote($column) . ','
. ' 1,' // to add an extra column that will have replaced value
. ' COUNT(*)'
. ' FROM ' . Util::backquote($this->db)
. '.' . Util::backquote($this->table)
. ' WHERE ' . Util::backquote($column)
. " RLIKE '" . $this->dbi->escapeString($find) . "' COLLATE "
. $charSet . '_bin'; // here we
// change the collation of the 2nd operand to a case sensitive
// binary collation to make sure that the comparison is case sensitive
$sql_query .= ' GROUP BY ' . Util::backquote($column)
. ' ORDER BY ' . Util::backquote($column) . ' ASC';
$result = $this->dbi->fetchResult($sql_query, 0);
if (is_array($result)) {
/* Iterate over possible delimiters to get one */
$delimiters = [
'/',
'@',
'#',
'~',
'!',
'$',
'%',
'^',
'&',
'_',
];
$found = false;
for ($i = 0, $l = count($delimiters); $i < $l; $i++) {
if (strpos($find, $delimiters[$i]) === false) {
$found = true;
break;
}
}
if (! $found) {
return false;
}
$find = $delimiters[$i] . $find . $delimiters[$i];
foreach ($result as $index => $row) {
$result[$index][1] = preg_replace(
$find,
$replaceWith,
$row[0]
);
}
}
return $result;
}
/**
* Replaces a given string in a column with a give replacement
*
* @param int $columnIndex index of the column
* @param string $find string to find in the column
* @param string $replaceWith string to replace with
* @param bool $useRegex to use Regex replace or not
* @param string $charSet character set of the connection
*
* @return void
*/
public function replace(
$columnIndex,
$find,
$replaceWith,
$useRegex,
$charSet
) {
$column = $this->columnNames[$columnIndex];
if ($useRegex) {
$toReplace = $this->getRegexReplaceRows(
$columnIndex,
$find,
$replaceWith,
$charSet
);
$sql_query = 'UPDATE ' . Util::backquote($this->table)
. ' SET ' . Util::backquote($column) . ' = CASE';
if (is_array($toReplace)) {
foreach ($toReplace as $row) {
$sql_query .= "\n WHEN " . Util::backquote($column)
. " = '" . $this->dbi->escapeString($row[0])
. "' THEN '" . $this->dbi->escapeString($row[1]) . "'";
}
}
$sql_query .= ' END'
. ' WHERE ' . Util::backquote($column)
. " RLIKE '" . $this->dbi->escapeString($find) . "' COLLATE "
. $charSet . '_bin'; // here we
// change the collation of the 2nd operand to a case sensitive
// binary collation to make sure that the comparison
// is case sensitive
} else {
$sql_query = 'UPDATE ' . Util::backquote($this->table)
. ' SET ' . Util::backquote($column) . ' ='
. ' REPLACE('
. Util::backquote($column) . ", '" . $find . "', '"
. $replaceWith
. "')"
. ' WHERE ' . Util::backquote($column)
. " LIKE '%" . $find . "%' COLLATE " . $charSet . '_bin'; // here we
// change the collation of the 2nd operand to a case sensitive
// binary collation to make sure that the comparison
// is case sensitive
}
$this->dbi->query(
$sql_query,
DatabaseInterface::CONNECT_USER,
DatabaseInterface::QUERY_STORE
);
$GLOBALS['sql_query'] = $sql_query;
}
}

View file

@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Core;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Mime;
use PhpMyAdmin\Response;
use PhpMyAdmin\Template;
use PhpMyAdmin\Util;
use function htmlspecialchars;
use function ini_set;
use function sprintf;
use function strlen;
/**
* Provides download to a given field defined in parameters.
*/
class GetFieldController extends AbstractController
{
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
* @param DatabaseInterface $dbi
*/
public function __construct($response, Template $template, $db, $table, $dbi)
{
parent::__construct($response, $template, $db, $table);
$this->dbi = $dbi;
}
public function index(): void
{
global $db, $table;
$this->response->disable();
/* Check parameters */
Util::checkParameters([
'db',
'table',
]);
/* Select database */
if (! $this->dbi->selectDb($db)) {
Generator::mysqlDie(
sprintf(__('\'%s\' database does not exist.'), htmlspecialchars($db)),
'',
false
);
}
/* Check if table exists */
if (! $this->dbi->getColumns($db, $table)) {
Generator::mysqlDie(__('Invalid table name'));
}
if (! isset($_GET['where_clause'])
|| ! isset($_GET['where_clause_sign'])
|| ! Core::checkSqlQuerySignature($_GET['where_clause'], $_GET['where_clause_sign'])
) {
/* l10n: In case a SQL query did not pass a security check */
Core::fatalError(__('There is an issue with your request.'));
return;
}
/* Grab data */
$sql = 'SELECT ' . Util::backquote($_GET['transform_key'])
. ' FROM ' . Util::backquote($table)
. ' WHERE ' . $_GET['where_clause'] . ';';
$result = $this->dbi->fetchValue($sql);
/* Check return code */
if ($result === false) {
Generator::mysqlDie(
__('MySQL returned an empty result set (i.e. zero rows).'),
$sql
);
}
/* Avoid corrupting data */
ini_set('url_rewriter.tags', '');
Core::downloadHeader(
$table . '-' . $_GET['transform_key'] . '.bin',
Mime::detect($result),
strlen($result)
);
echo $result;
}
}

View file

@ -0,0 +1,202 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Core;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Gis\GisVisualization;
use PhpMyAdmin\Message;
use PhpMyAdmin\Response;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function array_merge;
/**
* Handles creation of the GIS visualizations.
*/
final class GisVisualizationController extends AbstractController
{
/** @var GisVisualization */
private $visualization;
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
* @param DatabaseInterface $dbi
*/
public function __construct($response, Template $template, $db, $table, $dbi)
{
parent::__construct($response, $template, $db, $table);
$this->dbi = $dbi;
}
public function index(): void
{
global $cfg, $url_params, $PMA_Theme, $db, $err_url;
Util::checkParameters(['db']);
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
$err_url .= Url::getCommon(['db' => $db], '&');
if (! $this->hasDatabase()) {
return;
}
// SQL query for retrieving GIS data
$sqlQuery = '';
if (isset($_GET['sql_query'], $_GET['sql_signature'])) {
if (Core::checkSqlQuerySignature($_GET['sql_query'], $_GET['sql_signature'])) {
$sqlQuery = $_GET['sql_query'];
}
} elseif (isset($_POST['sql_query'])) {
$sqlQuery = $_POST['sql_query'];
}
// Throw error if no sql query is set
if ($sqlQuery == '') {
$this->response->setRequestStatus(false);
$this->response->addHTML(
Message::error(__('No SQL query was set to fetch data.'))
);
return;
}
// Execute the query and return the result
$result = $this->dbi->tryQuery($sqlQuery);
// Get the meta data of results
$meta = $this->dbi->getFieldsMeta($result);
// Find the candidate fields for label column and spatial column
$labelCandidates = [];
$spatialCandidates = [];
foreach ($meta as $column_meta) {
if ($column_meta->type === 'geometry') {
$spatialCandidates[] = $column_meta->name;
} else {
$labelCandidates[] = $column_meta->name;
}
}
// Get settings if any posted
$visualizationSettings = [];
if (Core::isValid($_POST['visualizationSettings'], 'array')) {
$visualizationSettings = $_POST['visualizationSettings'];
}
// Check mysql version
$visualizationSettings['mysqlVersion'] = $this->dbi->getVersion();
$visualizationSettings['isMariaDB'] = $this->dbi->isMariaDB();
if (! isset($visualizationSettings['labelColumn'])
&& isset($labelCandidates[0])
) {
$visualizationSettings['labelColumn'] = '';
}
// If spatial column is not set, use first geometric column as spatial column
if (! isset($visualizationSettings['spatialColumn'])) {
$visualizationSettings['spatialColumn'] = $spatialCandidates[0];
}
// Convert geometric columns from bytes to text.
$pos = $_GET['pos'] ?? $_SESSION['tmpval']['pos'];
if (isset($_GET['session_max_rows'])) {
$rows = $_GET['session_max_rows'];
} else {
if ($_SESSION['tmpval']['max_rows'] !== 'all') {
$rows = $_SESSION['tmpval']['max_rows'];
} else {
$rows = $GLOBALS['cfg']['MaxRows'];
}
}
$this->visualization = GisVisualization::get(
$sqlQuery,
$visualizationSettings,
$rows,
$pos
);
if (isset($_GET['saveToFile'])) {
$this->saveToFile($visualizationSettings['spatialColumn'], $_GET['fileFormat']);
return;
}
$this->addScriptFiles([
'vendor/openlayers/OpenLayers.js',
'vendor/jquery/jquery.svg.js',
'table/gis_visualization.js',
]);
// If all the rows contain SRID, use OpenStreetMaps on the initial loading.
if (! isset($_POST['displayVisualization'])) {
if ($this->visualization->hasSrid()) {
$visualizationSettings['choice'] = 'useBaseLayer';
} else {
unset($visualizationSettings['choice']);
}
}
$this->visualization->setUserSpecifiedSettings($visualizationSettings);
if ($visualizationSettings != null) {
foreach ($this->visualization->getSettings() as $setting => $val) {
if (isset($visualizationSettings[$setting])) {
continue;
}
$visualizationSettings[$setting] = $val;
}
}
/**
* Displays the page
*/
$url_params['goto'] = Util::getScriptNameForOption(
$cfg['DefaultTabDatabase'],
'database'
);
$url_params['back'] = Url::getFromRoute('/sql');
$url_params['sql_query'] = $sqlQuery;
$url_params['sql_signature'] = Core::signSqlQuery($sqlQuery);
$downloadUrl = Url::getFromRoute('/table/gis-visualization', array_merge(
$url_params,
[
'saveToFile' => true,
'session_max_rows' => $rows,
'pos' => $pos,
]
));
$html = $this->template->render('table/gis_visualization/gis_visualization', [
'url_params' => $url_params,
'download_url' => $downloadUrl,
'label_candidates' => $labelCandidates,
'spatial_candidates' => $spatialCandidates,
'visualization_settings' => $visualizationSettings,
'sql_query' => $sqlQuery,
'visualization' => $this->visualization->toImage('svg'),
'draw_ol' => $this->visualization->asOl(),
'theme_image_path' => $PMA_Theme->getImgPath(),
]);
$this->response->addHTML($html);
}
/**
* @param string $filename File name
* @param string $format Save format
*/
private function saveToFile(string $filename, string $format): void
{
$this->response->disable();
$this->visualization->toFile($filename, $format);
}
}

View file

@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Charsets;
use PhpMyAdmin\Charsets\Charset;
use PhpMyAdmin\Config\PageSettings;
use PhpMyAdmin\Core;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Encoding;
use PhpMyAdmin\Import;
use PhpMyAdmin\Import\Ajax;
use PhpMyAdmin\Message;
use PhpMyAdmin\Plugins;
use PhpMyAdmin\Response;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function intval;
final class ImportController extends AbstractController
{
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
* @param DatabaseInterface $dbi
*/
public function __construct($response, Template $template, $db, $table, $dbi)
{
parent::__construct($response, $template, $db, $table);
$this->dbi = $dbi;
}
public function index(): void
{
global $db, $max_upload_size, $table, $url_params, $SESSION_KEY, $cfg, $PMA_Theme, $err_url;
$pageSettings = new PageSettings('Import');
$pageSettingsErrorHtml = $pageSettings->getErrorHTML();
$pageSettingsHtml = $pageSettings->getHTML();
$this->addScriptFiles(['import.js']);
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
$url_params['goto'] = Url::getFromRoute('/table/import');
$url_params['back'] = Url::getFromRoute('/table/import');
[$SESSION_KEY, $uploadId] = Ajax::uploadProgressSetup();
$importList = Plugins::getImport('table');
if (empty($importList)) {
$this->response->addHTML(Message::error(__(
'Could not load import plugins, please check your installation!'
))->getDisplay());
return;
}
$offset = null;
if (Core::isValid($_REQUEST['offset'], 'numeric')) {
$offset = intval($_REQUEST['offset']);
}
$timeoutPassed = $_REQUEST['timeout_passed'] ?? null;
$localImportFile = $_REQUEST['local_import_file'] ?? null;
$compressions = Import::getCompressions();
$allCharsets = Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
$charsets = [];
/** @var Charset $charset */
foreach ($allCharsets as $charset) {
$charsets[] = [
'name' => $charset->getName(),
'description' => $charset->getDescription(),
];
}
$idKey = $_SESSION[$SESSION_KEY]['handler']::getIdKey();
$hiddenInputs = [
$idKey => $uploadId,
'import_type' => 'table',
'db' => $db,
'table' => $table,
];
$this->render('table/import/index', [
'page_settings_error_html' => $pageSettingsErrorHtml,
'page_settings_html' => $pageSettingsHtml,
'upload_id' => $uploadId,
'handler' => $_SESSION[$SESSION_KEY]['handler'],
'theme_image_path' => $PMA_Theme->getImgPath(),
'hidden_inputs' => $hiddenInputs,
'db' => $db,
'table' => $table,
'max_upload_size' => $max_upload_size,
'import_list' => $importList,
'local_import_file' => $localImportFile,
'is_upload' => $GLOBALS['is_upload'],
'upload_dir' => $cfg['UploadDir'] ?? null,
'timeout_passed_global' => $GLOBALS['timeout_passed'] ?? null,
'compressions' => $compressions,
'is_encoding_supported' => Encoding::isSupported(),
'encodings' => Encoding::listEncodings(),
'import_charset' => $cfg['Import']['charset'] ?? null,
'timeout_passed' => $timeoutPassed,
'offset' => $offset,
'can_convert_kanji' => Encoding::canConvertKanji(),
'charsets' => $charsets,
'is_foreign_key_check' => Util::isForeignKeyCheck(),
'user_upload_dir' => Util::userDir($cfg['UploadDir'] ?? ''),
'local_files' => Import::getLocalFiles($importList),
]);
}
}

View file

@ -0,0 +1,278 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Index;
use PhpMyAdmin\Message;
use PhpMyAdmin\Query\Compatibility;
use PhpMyAdmin\Query\Generator as QueryGenerator;
use PhpMyAdmin\Response;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function count;
use function is_array;
use function json_decode;
/**
* Displays index edit/creation form and handles it.
*/
class IndexesController extends AbstractController
{
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
* @param DatabaseInterface $dbi
*/
public function __construct($response, Template $template, $db, $table, $dbi)
{
parent::__construct($response, $template, $db, $table);
$this->dbi = $dbi;
}
public function index(): void
{
global $db, $table, $url_params, $cfg, $err_url;
if (! isset($_POST['create_edit_table'])) {
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
}
if (isset($_POST['index'])) {
if (is_array($_POST['index'])) {
// coming already from form
$index = new Index($_POST['index']);
} else {
$index = $this->dbi->getTable($this->db, $this->table)->getIndex($_POST['index']);
}
} else {
$index = new Index();
}
if (isset($_POST['do_save_data'])) {
$this->doSaveData($index, false);
return;
}
$this->displayForm($index);
}
public function indexRename(): void
{
global $db, $table, $url_params, $cfg, $err_url;
if (! isset($_POST['create_edit_table'])) {
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
}
if (isset($_POST['index'])) {
if (is_array($_POST['index'])) {
// coming already from form
$index = new Index($_POST['index']);
} else {
$index = $this->dbi->getTable($this->db, $this->table)->getIndex($_POST['index']);
}
} else {
$index = new Index();
}
if (isset($_POST['do_save_data'])) {
$this->doSaveData($index, true);
return;
}
$this->displayRenameForm($index);
}
/**
* Display the rename form to rename an index
*
* @param Index $index An Index instance.
*/
public function displayRenameForm(Index $index): void
{
$this->dbi->selectDb($GLOBALS['db']);
$formParams = [
'db' => $this->db,
'table' => $this->table,
];
if (isset($_POST['old_index'])) {
$formParams['old_index'] = $_POST['old_index'];
} elseif (isset($_POST['index'])) {
$formParams['old_index'] = $_POST['index'];
}
$this->addScriptFiles(['indexes.js']);
$this->render('table/index_rename_form', [
'index' => $index,
'form_params' => $formParams,
]);
}
/**
* Display the form to edit/create an index
*
* @param Index $index An Index instance.
*/
public function displayForm(Index $index): void
{
$this->dbi->selectDb($GLOBALS['db']);
$add_fields = 0;
if (isset($_POST['index']) && is_array($_POST['index'])) {
// coming already from form
if (isset($_POST['index']['columns']['names'])) {
$add_fields = count($_POST['index']['columns']['names'])
- $index->getColumnCount();
}
if (isset($_POST['add_fields'])) {
$add_fields += $_POST['added_fields'];
}
} elseif (isset($_POST['create_index'])) {
$add_fields = $_POST['added_fields'];
}
// Get fields and stores their name/type
if (isset($_POST['create_edit_table'])) {
$fields = json_decode($_POST['columns'], true);
$index_params = [
'Non_unique' => $_POST['index']['Index_choice'] === 'UNIQUE'
? '0' : '1',
];
$index->set($index_params);
$add_fields = count($fields);
} else {
$fields = $this->dbi->getTable($this->db, $this->table)
->getNameAndTypeOfTheColumns();
}
$form_params = [
'db' => $this->db,
'table' => $this->table,
];
if (isset($_POST['create_index'])) {
$form_params['create_index'] = 1;
} elseif (isset($_POST['old_index'])) {
$form_params['old_index'] = $_POST['old_index'];
} elseif (isset($_POST['index'])) {
$form_params['old_index'] = $_POST['index'];
}
$this->addScriptFiles(['indexes.js']);
$this->render('table/index_form', [
'fields' => $fields,
'index' => $index,
'form_params' => $form_params,
'add_fields' => $add_fields,
'create_edit_table' => isset($_POST['create_edit_table']),
'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'],
]);
}
/**
* Process the data from the edit/create index form,
* run the query to build the new index
* and moves back to /table/sql
*
* @param Index $index An Index instance.
* @param bool $renameMode Rename the Index mode
*/
public function doSaveData(Index $index, bool $renameMode): void
{
global $containerBuilder;
$error = false;
$sql_query = '';
if ($renameMode && Compatibility::isCompatibleRenameIndex($this->dbi->getVersion())) {
$oldIndexName = $_POST['old_index'];
if ($oldIndexName === 'PRIMARY') {
if ($index->getName() === '') {
$index->setName('PRIMARY');
} elseif ($index->getName() !== 'PRIMARY') {
$error = Message::error(
__('The name of the primary key must be "PRIMARY"!')
);
}
}
$sql_query = QueryGenerator::getSqlQueryForIndexRename(
$this->db,
$this->table,
$oldIndexName,
$index->getName()
);
} else {
$sql_query = $this->dbi->getTable($this->db, $this->table)
->getSqlQueryForIndexCreateOrEdit($index, $error);
}
// If there is a request for SQL previewing.
if (isset($_POST['preview_sql'])) {
$this->response->addJSON(
'sql_data',
$this->template->render('preview_sql', ['query_data' => $sql_query])
);
} elseif (! $error) {
$this->dbi->query($sql_query);
$response = Response::getInstance();
if ($response->isAjax()) {
$message = Message::success(
__('Table %1$s has been altered successfully.')
);
$message->addParam($this->table);
$this->response->addJSON(
'message',
Generator::getMessage($message, $sql_query, 'success')
);
$indexes = Index::getFromTable($this->table, $this->db);
$indexesDuplicates = Index::findDuplicates($this->table, $this->db);
$this->response->addJSON(
'index_table',
$this->template->render('indexes', [
'url_params' => [
'db' => $this->db,
'table' => $this->table,
],
'indexes' => $indexes,
'indexes_duplicates' => $indexesDuplicates,
])
);
} else {
/** @var StructureController $controller */
$controller = $containerBuilder->get(StructureController::class);
$controller->index();
}
} else {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', $error);
}
}
}

View file

@ -0,0 +1,213 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Response;
use PhpMyAdmin\Table\Maintenance;
use PhpMyAdmin\Template;
use function count;
use function is_array;
final class MaintenanceController extends AbstractController
{
/** @var Maintenance */
private $model;
/**
* @param Response $response
* @param string $db
* @param string $table
*/
public function __construct(
$response,
Template $template,
$db,
$table,
Maintenance $model
) {
parent::__construct($response, $template, $db, $table);
$this->model = $model;
}
public function analyze(): void
{
global $cfg;
/** @var string[] $selected */
$selected = $_POST['selected_tbl'] ?? [];
if (empty($selected) || ! is_array($selected)) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('No table selected.'));
return;
}
if ($cfg['DisableMultiTableMaintenance'] && count($selected) > 1) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('Maintenance operations on multiple tables are disabled.'));
return;
}
[$rows, $query] = $this->model->getAnalyzeTableRows($this->db, $selected);
$message = Generator::getMessage(
__('Your SQL query has been executed successfully.'),
$query,
'success'
);
$this->render('table/maintenance/analyze', [
'message' => $message,
'rows' => $rows,
]);
}
public function check(): void
{
global $cfg;
/** @var string[] $selected */
$selected = $_POST['selected_tbl'] ?? [];
if (empty($selected) || ! is_array($selected)) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('No table selected.'));
return;
}
if ($cfg['DisableMultiTableMaintenance'] && count($selected) > 1) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('Maintenance operations on multiple tables are disabled.'));
return;
}
[$rows, $query] = $this->model->getCheckTableRows($this->db, $selected);
$message = Generator::getMessage(
__('Your SQL query has been executed successfully.'),
$query,
'success'
);
$indexesProblems = $this->model->getIndexesProblems($this->db, $selected);
$this->render('table/maintenance/check', [
'message' => $message,
'rows' => $rows,
'indexes_problems' => $indexesProblems,
]);
}
public function checksum(): void
{
global $cfg;
/** @var string[] $selected */
$selected = $_POST['selected_tbl'] ?? [];
if (empty($selected) || ! is_array($selected)) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('No table selected.'));
return;
}
if ($cfg['DisableMultiTableMaintenance'] && count($selected) > 1) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('Maintenance operations on multiple tables are disabled.'));
return;
}
[$rows, $query, $warnings] = $this->model->getChecksumTableRows($this->db, $selected);
$message = Generator::getMessage(
__('Your SQL query has been executed successfully.'),
$query,
'success'
);
$this->render('table/maintenance/checksum', [
'message' => $message,
'rows' => $rows,
'warnings' => $warnings,
]);
}
public function optimize(): void
{
global $cfg;
/** @var string[] $selected */
$selected = $_POST['selected_tbl'] ?? [];
if (empty($selected) || ! is_array($selected)) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('No table selected.'));
return;
}
if ($cfg['DisableMultiTableMaintenance'] && count($selected) > 1) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('Maintenance operations on multiple tables are disabled.'));
return;
}
[$rows, $query] = $this->model->getOptimizeTableRows($this->db, $selected);
$message = Generator::getMessage(
__('Your SQL query has been executed successfully.'),
$query,
'success'
);
$this->render('table/maintenance/optimize', [
'message' => $message,
'rows' => $rows,
]);
}
public function repair(): void
{
global $cfg;
/** @var string[] $selected */
$selected = $_POST['selected_tbl'] ?? [];
if (empty($selected) || ! is_array($selected)) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('No table selected.'));
return;
}
if ($cfg['DisableMultiTableMaintenance'] && count($selected) > 1) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', __('Maintenance operations on multiple tables are disabled.'));
return;
}
[$rows, $query] = $this->model->getRepairTableRows($this->db, $selected);
$message = Generator::getMessage(
__('Your SQL query has been executed successfully.'),
$query,
'success'
);
$this->render('table/maintenance/repair', [
'message' => $message,
'rows' => $rows,
]);
}
}

View file

@ -0,0 +1,496 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Charsets;
use PhpMyAdmin\CheckUserPrivileges;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Index;
use PhpMyAdmin\Message;
use PhpMyAdmin\Operations;
use PhpMyAdmin\Partition;
use PhpMyAdmin\Query\Utilities;
use PhpMyAdmin\Relation;
use PhpMyAdmin\Response;
use PhpMyAdmin\StorageEngine;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function count;
use function implode;
use function mb_strstr;
use function mb_strtolower;
use function mb_strtoupper;
use function preg_replace;
use function strlen;
class OperationsController extends AbstractController
{
/** @var Operations */
private $operations;
/** @var CheckUserPrivileges */
private $checkUserPrivileges;
/** @var Relation */
private $relation;
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
* @param DatabaseInterface $dbi
*/
public function __construct(
$response,
Template $template,
$db,
$table,
Operations $operations,
CheckUserPrivileges $checkUserPrivileges,
Relation $relation,
$dbi
) {
parent::__construct($response, $template, $db, $table);
$this->operations = $operations;
$this->checkUserPrivileges = $checkUserPrivileges;
$this->relation = $relation;
$this->dbi = $dbi;
}
public function index(): void
{
global $url_params, $reread_info, $tbl_is_view, $tbl_storage_engine;
global $show_comment, $tbl_collation, $table_info_num_rows, $row_format, $auto_increment, $create_options;
global $table_alters, $warning_messages, $lowerCaseNames, $db, $table, $reload, $result;
global $new_tbl_storage_engine, $sql_query, $message_to_show, $columns, $hideOrderTable, $indexes;
global $notNull, $comment, $err_url, $cfg;
$this->checkUserPrivileges->getPrivileges();
// lower_case_table_names=1 `DB` becomes `db`
$lowerCaseNames = $this->dbi->getLowerCaseNames() === '1';
if ($lowerCaseNames) {
$table = mb_strtolower($table);
}
$pma_table = $this->dbi->getTable($db, $table);
$this->addScriptFiles(['table/operations.js']);
Util::checkParameters(['db', 'table']);
$isSystemSchema = Utilities::isSystemSchema($db);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
$url_params['goto'] = $url_params['back'] = Url::getFromRoute('/table/operations');
/**
* Gets relation settings
*/
$cfgRelation = $this->relation->getRelationsParam();
// reselect current db (needed in some cases probably due to
// the calling of PhpMyAdmin\Relation)
$this->dbi->selectDb($db);
$reread_info = $pma_table->getStatusInfo(null, false);
$GLOBALS['showtable'] = $pma_table->getStatusInfo(null, (isset($reread_info) && $reread_info));
if ($pma_table->isView()) {
$tbl_is_view = true;
$tbl_storage_engine = __('View');
$show_comment = null;
} else {
$tbl_is_view = false;
$tbl_storage_engine = $pma_table->getStorageEngine();
$show_comment = $pma_table->getComment();
}
$tbl_collation = $pma_table->getCollation();
$table_info_num_rows = $pma_table->getNumRows();
$row_format = $pma_table->getRowFormat();
$auto_increment = $pma_table->getAutoIncrement();
$create_options = $pma_table->getCreateOptions();
// set initial value of these variables, based on the current table engine
if ($pma_table->isEngine('ARIA')) {
// the value for transactional can be implicit
// (no create option found, in this case it means 1)
// or explicit (option found with a value of 0 or 1)
// ($create_options['transactional'] may have been set by Table class,
// from the $create_options)
$create_options['transactional'] = ($create_options['transactional'] ?? '') == '0'
? '0'
: '1';
$create_options['page_checksum'] = $create_options['page_checksum'] ?? '';
}
$pma_table = $this->dbi->getTable(
$db,
$table
);
$reread_info = false;
$table_alters = [];
/**
* If the table has to be moved to some other database
*/
if (isset($_POST['submit_move']) || isset($_POST['submit_copy'])) {
$message = $this->operations->moveOrCopyTable($db, $table);
if (! $this->response->isAjax()) {
return;
}
$this->response->addJSON('message', $message);
if ($message->isSuccess()) {
$this->response->addJSON('db', $db);
return;
}
$this->response->setRequestStatus(false);
return;
}
/**
* Updates table comment, type and options if required
*/
if (isset($_POST['submitoptions'])) {
$_message = '';
$warning_messages = [];
if (isset($_POST['new_name'])) {
// lower_case_table_names=1 `DB` becomes `db`
if ($lowerCaseNames) {
$_POST['new_name'] = mb_strtolower(
$_POST['new_name']
);
}
// Get original names before rename operation
$oldTable = $pma_table->getName();
$oldDb = $pma_table->getDbName();
if ($pma_table->rename($_POST['new_name'])) {
if (isset($_POST['adjust_privileges'])
&& ! empty($_POST['adjust_privileges'])
) {
$this->operations->adjustPrivilegesRenameOrMoveTable(
$oldDb,
$oldTable,
$_POST['db'],
$_POST['new_name']
);
}
// Reselect the original DB
$db = $oldDb;
$this->dbi->selectDb($oldDb);
$_message .= $pma_table->getLastMessage();
$result = true;
$table = $pma_table->getName();
$reread_info = true;
$reload = true;
} else {
$_message .= $pma_table->getLastError();
$result = false;
}
}
if (! empty($_POST['new_tbl_storage_engine'])
&& mb_strtoupper($_POST['new_tbl_storage_engine']) !== $tbl_storage_engine
) {
$new_tbl_storage_engine = mb_strtoupper($_POST['new_tbl_storage_engine']);
if ($pma_table->isEngine('ARIA')) {
$create_options['transactional'] = ($create_options['transactional'] ?? '') == '0'
? '0'
: '1';
$create_options['page_checksum'] = $create_options['page_checksum'] ?? '';
}
} else {
$new_tbl_storage_engine = '';
}
$row_format = $create_options['row_format'] ?? $pma_table->getRowFormat();
$table_alters = $this->operations->getTableAltersArray(
$pma_table,
$create_options['pack_keys'],
(empty($create_options['checksum']) ? '0' : '1'),
($create_options['page_checksum'] ?? ''),
(empty($create_options['delay_key_write']) ? '0' : '1'),
$row_format,
$new_tbl_storage_engine,
(isset($create_options['transactional']) && $create_options['transactional'] == '0' ? '0' : '1'),
$tbl_collation
);
if (count($table_alters) > 0) {
$sql_query = 'ALTER TABLE '
. Util::backquote($table);
$sql_query .= "\r\n" . implode("\r\n", $table_alters);
$sql_query .= ';';
$result = (bool) $this->dbi->query($sql_query);
$reread_info = true;
unset($table_alters);
$warning_messages = $this->operations->getWarningMessagesArray();
}
if (isset($_POST['tbl_collation'], $_POST['change_all_collations'])
&& ! empty($_POST['tbl_collation'])
&& ! empty($_POST['change_all_collations'])
) {
$this->operations->changeAllColumnsCollation(
$db,
$table,
$_POST['tbl_collation']
);
}
if (isset($_POST['tbl_collation']) && empty($_POST['tbl_collation'])) {
if ($this->response->isAjax()) {
$this->response->setRequestStatus(false);
$this->response->addJSON(
'message',
Message::error(__('No collation provided.'))
);
return;
}
}
}
/**
* Reordering the table has been requested by the user
*/
if (isset($_POST['submitorderby']) && ! empty($_POST['order_field'])) {
[$sql_query, $result] = $this->operations->getQueryAndResultForReorderingTable();
}
/**
* A partition operation has been requested by the user
*/
if (isset($_POST['submit_partition'])
&& ! empty($_POST['partition_operation'])
) {
[$sql_query, $result] = $this->operations->getQueryAndResultForPartition();
}
if ($reread_info) {
// to avoid showing the old value (for example the AUTO_INCREMENT) after
// a change, clear the cache
$this->dbi->getCache()->clearTableCache();
$this->dbi->selectDb($db);
$GLOBALS['showtable'] = $pma_table->getStatusInfo(null, true);
if ($pma_table->isView()) {
$tbl_is_view = true;
$tbl_storage_engine = __('View');
$show_comment = null;
} else {
$tbl_is_view = false;
$tbl_storage_engine = $pma_table->getStorageEngine();
$show_comment = $pma_table->getComment();
}
$tbl_collation = $pma_table->getCollation();
$table_info_num_rows = $pma_table->getNumRows();
$row_format = $pma_table->getRowFormat();
$auto_increment = $pma_table->getAutoIncrement();
$create_options = $pma_table->getCreateOptions();
}
unset($reread_info);
if (isset($result) && empty($message_to_show)) {
if (empty($_message)) {
if (empty($sql_query)) {
$_message = Message::success(__('No change'));
} else {
$_message = $result
? Message::success()
: Message::error();
}
if ($this->response->isAjax()) {
$this->response->setRequestStatus($_message->isSuccess());
$this->response->addJSON('message', $_message);
if (! empty($sql_query)) {
$this->response->addJSON(
'sql_query',
Generator::getMessage('', $sql_query)
);
}
return;
}
} else {
$_message = $result
? Message::success($_message)
: Message::error($_message);
}
if (! empty($warning_messages)) {
$_message = new Message();
$_message->addMessagesString($warning_messages);
$_message->isError(true);
if ($this->response->isAjax()) {
$this->response->setRequestStatus(false);
$this->response->addJSON('message', $_message);
if (! empty($sql_query)) {
$this->response->addJSON(
'sql_query',
Generator::getMessage('', $sql_query)
);
}
return;
}
unset($warning_messages);
}
if (empty($sql_query)) {
$this->response->addHTML(
$_message->getDisplay()
);
} else {
$this->response->addHTML(
Generator::getMessage($_message, $sql_query)
);
}
unset($_message);
}
$url_params['goto'] = $url_params['back'] = Url::getFromRoute('/table/operations');
$columns = $this->dbi->getColumns($db, $table);
$hideOrderTable = false;
// `ALTER TABLE ORDER BY` does not make sense for InnoDB tables that contain
// a user-defined clustered index (PRIMARY KEY or NOT NULL UNIQUE index).
// InnoDB always orders table rows according to such an index if one is present.
if ($tbl_storage_engine === 'INNODB') {
$indexes = Index::getFromTable($table, $db);
foreach ($indexes as $name => $idx) {
if ($name === 'PRIMARY') {
$hideOrderTable = true;
break;
}
if ($idx->getNonUnique()) {
continue;
}
$notNull = true;
foreach ($idx->getColumns() as $column) {
if ($column->getNull()) {
$notNull = false;
break;
}
}
if ($notNull) {
$hideOrderTable = true;
break;
}
}
}
$comment = '';
if (mb_strstr((string) $show_comment, '; InnoDB free') === false) {
if (mb_strstr((string) $show_comment, 'InnoDB free') === false) {
// only user entered comment
$comment = (string) $show_comment;
} else {
// here we have just InnoDB generated part
$comment = '';
}
} else {
// remove InnoDB comment from end, just the minimal part (*? is non greedy)
$comment = preg_replace('@; InnoDB free:.*?$@', '', (string) $show_comment);
}
$storageEngines = StorageEngine::getArray();
$charsets = Charsets::getCharsets($this->dbi, $GLOBALS['cfg']['Server']['DisableIS']);
$collations = Charsets::getCollations($this->dbi, $GLOBALS['cfg']['Server']['DisableIS']);
$hasPackKeys = isset($create_options['pack_keys'])
&& $pma_table->isEngine(['MYISAM', 'ARIA', 'ISAM']);
$hasChecksumAndDelayKeyWrite = $pma_table->isEngine(['MYISAM', 'ARIA']);
$hasTransactionalAndPageChecksum = $pma_table->isEngine('ARIA');
$hasAutoIncrement = strlen((string) $auto_increment) > 0
&& $pma_table->isEngine(['MYISAM', 'ARIA', 'INNODB', 'PBXT', 'ROCKSDB']);
$possibleRowFormats = $this->operations->getPossibleRowFormat();
$databaseList = [];
if (count($GLOBALS['dblist']->databases) <= $GLOBALS['cfg']['MaxDbList']) {
$databaseList = $GLOBALS['dblist']->databases->getList();
}
$hasForeignKeys = ! empty($this->relation->getForeigners($db, $table, '', 'foreign'));
$hasPrivileges = $GLOBALS['table_priv'] && $GLOBALS['col_priv'] && $GLOBALS['is_reload_priv'];
$switchToNew = isset($_SESSION['pma_switch_to_new']) && $_SESSION['pma_switch_to_new'];
$partitions = [];
$partitionsChoices = [];
if (Partition::havePartitioning()) {
$partitionNames = Partition::getPartitionNames($db, $table);
if ($partitionNames[0] !== null) {
$partitions = $partitionNames;
$partitionsChoices = $this->operations->getPartitionMaintenanceChoices();
}
}
$foreigners = $this->operations->getForeignersForReferentialIntegrityCheck(
$url_params,
(bool) $cfgRelation['relwork']
);
$this->render('table/operations/index', [
'db' => $db,
'table' => $table,
'url_params' => $url_params,
'columns' => $columns,
'hide_order_table' => $hideOrderTable,
'table_comment' => $comment,
'storage_engine' => $tbl_storage_engine,
'storage_engines' => $storageEngines,
'charsets' => $charsets,
'collations' => $collations,
'tbl_collation' => $tbl_collation,
'row_formats' => $possibleRowFormats[$tbl_storage_engine] ?? [],
'row_format_current' => $GLOBALS['showtable']['Row_format'],
'has_auto_increment' => $hasAutoIncrement,
'auto_increment' => $auto_increment,
'has_pack_keys' => $hasPackKeys,
'pack_keys' => $create_options['pack_keys'] ?? '',
'has_transactional_and_page_checksum' => $hasTransactionalAndPageChecksum,
'has_checksum_and_delay_key_write' => $hasChecksumAndDelayKeyWrite,
'delay_key_write' => empty($create_options['delay_key_write']) ? '0' : '1',
'transactional' => ($create_options['transactional'] ?? '') == '0' ? '0' : '1',
'page_checksum' => $create_options['page_checksum'] ?? '',
'checksum' => empty($create_options['checksum']) ? '0' : '1',
'database_list' => $databaseList,
'has_foreign_keys' => $hasForeignKeys,
'has_privileges' => $hasPrivileges,
'switch_to_new' => $switchToNew,
'is_system_schema' => $isSystemSchema,
'is_view' => $tbl_is_view,
'partitions' => $partitions,
'partitions_choices' => $partitionsChoices,
'foreigners' => $foreigners,
]);
}
}

View file

@ -0,0 +1,211 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Response;
use PhpMyAdmin\Table\Partition;
use PhpMyAdmin\Template;
use function strlen;
final class PartitionController extends AbstractController
{
/** @var Partition */
private $model;
/**
* @param Response $response
* @param string $db
* @param string $table
* @param Partition $partition
*/
public function __construct($response, Template $template, $db, $table, $partition)
{
parent::__construct($response, $template, $db, $table);
$this->model = $partition;
}
public function analyze(): void
{
$partitionName = $_POST['partition_name'] ?? '';
if (strlen($partitionName) === 0) {
return;
}
[$rows, $query] = $this->model->analyze($this->db, $this->table, $partitionName);
$message = Generator::getMessage(
__('Your SQL query has been executed successfully.'),
$query,
'success'
);
$this->render('table/partition/analyze', [
'partition_name' => $partitionName,
'message' => $message,
'rows' => $rows,
]);
}
public function check(): void
{
$partitionName = $_POST['partition_name'] ?? '';
if (strlen($partitionName) === 0) {
return;
}
[$rows, $query] = $this->model->check($this->db, $this->table, $partitionName);
$message = Generator::getMessage(
__('Your SQL query has been executed successfully.'),
$query,
'success'
);
$this->render('table/partition/check', [
'partition_name' => $partitionName,
'message' => $message,
'rows' => $rows,
]);
}
public function drop(): void
{
$partitionName = $_POST['partition_name'] ?? '';
if (strlen($partitionName) === 0) {
return;
}
[$result, $query] = $this->model->drop($this->db, $this->table, $partitionName);
if ($result) {
$message = Generator::getMessage(
__('Your SQL query has been executed successfully.'),
$query,
'success'
);
} else {
$message = Generator::getMessage(
__('Error'),
$query,
'error'
);
}
$this->render('table/partition/drop', [
'partition_name' => $partitionName,
'message' => $message,
]);
}
public function optimize(): void
{
$partitionName = $_POST['partition_name'] ?? '';
if (strlen($partitionName) === 0) {
return;
}
[$rows, $query] = $this->model->optimize($this->db, $this->table, $partitionName);
$message = Generator::getMessage(
__('Your SQL query has been executed successfully.'),
$query,
'success'
);
$this->render('table/partition/optimize', [
'partition_name' => $partitionName,
'message' => $message,
'rows' => $rows,
]);
}
public function rebuild(): void
{
$partitionName = $_POST['partition_name'] ?? '';
if (strlen($partitionName) === 0) {
return;
}
[$result, $query] = $this->model->rebuild($this->db, $this->table, $partitionName);
if ($result) {
$message = Generator::getMessage(
__('Your SQL query has been executed successfully.'),
$query,
'success'
);
} else {
$message = Generator::getMessage(
__('Error'),
$query,
'error'
);
}
$this->render('table/partition/rebuild', [
'partition_name' => $partitionName,
'message' => $message,
]);
}
public function repair(): void
{
$partitionName = $_POST['partition_name'] ?? '';
if (strlen($partitionName) === 0) {
return;
}
[$rows, $query] = $this->model->repair($this->db, $this->table, $partitionName);
$message = Generator::getMessage(
__('Your SQL query has been executed successfully.'),
$query,
'success'
);
$this->render('table/partition/repair', [
'partition_name' => $partitionName,
'message' => $message,
'rows' => $rows,
]);
}
public function truncate(): void
{
$partitionName = $_POST['partition_name'] ?? '';
if (strlen($partitionName) === 0) {
return;
}
[$result, $query] = $this->model->truncate($this->db, $this->table, $partitionName);
if ($result) {
$message = Generator::getMessage(
__('Your SQL query has been executed successfully.'),
$query,
'success'
);
} else {
$message = Generator::getMessage(
__('Error'),
$query,
'error'
);
}
$this->render('table/partition/truncate', [
'partition_name' => $partitionName,
'message' => $message,
]);
}
}

View file

@ -0,0 +1,72 @@
<?php
/**
* Controller for table privileges
*/
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Response;
use PhpMyAdmin\Server\Privileges;
use PhpMyAdmin\Template;
use PhpMyAdmin\Util;
/**
* Controller for table privileges
*/
class PrivilegesController extends AbstractController
{
/** @var Privileges */
private $privileges;
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name
* @param string $table Table name
* @param DatabaseInterface $dbi
*/
public function __construct($response, Template $template, $db, $table, Privileges $privileges, $dbi)
{
parent::__construct($response, $template, $db, $table);
$this->privileges = $privileges;
$this->dbi = $dbi;
}
/**
* @param array $params Request parameters
*/
public function index(array $params): string
{
global $cfg, $text_dir, $PMA_Theme;
$scriptName = Util::getScriptNameForOption(
$cfg['DefaultTabTable'],
'table'
);
$privileges = [];
if ($this->dbi->isSuperUser()) {
$privileges = $this->privileges->getAllPrivileges(
$params['checkprivsdb'],
$params['checkprivstable']
);
}
return $this->template->render('table/privileges/index', [
'db' => $params['checkprivsdb'],
'table' => $params['checkprivstable'],
'is_superuser' => $this->dbi->isSuperUser(),
'table_url' => $scriptName,
'theme_image_path' => $PMA_Theme->getImgPath(),
'text_dir' => $text_dir,
'is_createuser' => $this->dbi->isCreateUser(),
'is_grantuser' => $this->dbi->isGrantUser(),
'privileges' => $privileges,
]);
}
}

View file

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Controllers\SqlController;
use PhpMyAdmin\RecentFavoriteTable;
/**
* Browse recent and favorite tables chosen from navigation.
*/
class RecentFavoriteController extends AbstractController
{
public function index(): void
{
global $containerBuilder;
RecentFavoriteTable::getInstance('recent')->removeIfInvalid(
$_REQUEST['db'],
$_REQUEST['table']
);
RecentFavoriteTable::getInstance('favorite')->removeIfInvalid(
$_REQUEST['db'],
$_REQUEST['table']
);
/** @var SqlController $controller */
$controller = $containerBuilder->get(SqlController::class);
$controller->index();
}
}

View file

@ -0,0 +1,388 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Core;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Index;
use PhpMyAdmin\Relation;
use PhpMyAdmin\Response;
use PhpMyAdmin\Table;
use PhpMyAdmin\Template;
use PhpMyAdmin\Util;
use function array_key_exists;
use function array_keys;
use function array_values;
use function htmlspecialchars;
use function mb_strtoupper;
use function md5;
use function strtoupper;
use function uksort;
use function usort;
/**
* Display table relations for viewing and editing.
*
* Includes phpMyAdmin relations and InnoDB relations.
*/
final class RelationController extends AbstractController
{
/** @var Relation */
private $relation;
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name
* @param string $table Table name
* @param DatabaseInterface $dbi
*/
public function __construct(
$response,
Template $template,
$db,
$table,
Relation $relation,
$dbi
) {
parent::__construct($response, $template, $db, $table);
$this->relation = $relation;
$this->dbi = $dbi;
}
/**
* Index
*/
public function index(): void
{
global $route;
$options = [
'CASCADE' => 'CASCADE',
'SET_NULL' => 'SET NULL',
'NO_ACTION' => 'NO ACTION',
'RESTRICT' => 'RESTRICT',
];
$table = $this->dbi->getTable($this->db, $this->table);
$storageEngine = mb_strtoupper((string) $table->getStatusInfo('Engine'));
$cfgRelation = $this->relation->getRelationsParam();
$relations = [];
if ($cfgRelation['relwork']) {
$relations = $this->relation->getForeigners(
$this->db,
$this->table,
'',
'internal'
);
}
$relationsForeign = [];
if (Util::isForeignKeySupported($storageEngine)) {
$relationsForeign = $this->relation->getForeigners(
$this->db,
$this->table,
'',
'foreign'
);
}
// Send table of column names to populate corresponding dropdowns depending
// on the current selection
if (isset($_POST['getDropdownValues'])
&& $_POST['getDropdownValues'] === 'true'
) {
// if both db and table are selected
if (isset($_POST['foreignTable'])) {
$this->getDropdownValueForTable();
} else { // if only the db is selected
$this->getDropdownValueForDatabase($storageEngine);
}
return;
}
$this->addScriptFiles(['table/relation.js', 'indexes.js']);
// Set the database
$this->dbi->selectDb($this->db);
// updates for Internal relations
if (isset($_POST['destination_db']) && $cfgRelation['relwork']) {
$this->updateForInternalRelation($table, $cfgRelation, $relations);
}
// updates for foreign keys
$this->updateForForeignKeys($table, $options, $relationsForeign);
// Updates for display field
if ($cfgRelation['displaywork'] && isset($_POST['display_field'])) {
$this->updateForDisplayField($table, $cfgRelation);
}
// If we did an update, refresh our data
if (isset($_POST['destination_db']) && $cfgRelation['relwork']) {
$relations = $this->relation->getForeigners(
$this->db,
$this->table,
'',
'internal'
);
}
if (isset($_POST['destination_foreign_db'])
&& Util::isForeignKeySupported($storageEngine)
) {
$relationsForeign = $this->relation->getForeigners(
$this->db,
$this->table,
'',
'foreign'
);
}
/**
* Dialog
*/
// Now find out the columns of our $table
// need to use DatabaseInterface::QUERY_STORE with $this->dbi->numRows()
// in mysqli
$columns = $this->dbi->getColumns($this->db, $this->table);
$column_array = [];
$column_hash_array = [];
$column_array[''] = '';
foreach ($columns as $column) {
if (strtoupper($storageEngine) !== 'INNODB'
&& empty($column['Key'])
) {
continue;
}
$column_array[$column['Field']] = $column['Field'];
$column_hash_array[$column['Field']] = md5($column['Field']);
}
if ($GLOBALS['cfg']['NaturalOrder']) {
uksort($column_array, 'strnatcasecmp');
}
// common form
$engine = $this->dbi->getTable($this->db, $this->table)->getStorageEngine();
$this->render('table/relation/common_form', [
'is_foreign_key_supported' => Util::isForeignKeySupported($engine),
'db' => $this->db,
'table' => $this->table,
'cfg_relation' => $cfgRelation,
'tbl_storage_engine' => $storageEngine,
'existrel' => $relations,
'existrel_foreign' => array_key_exists('foreign_keys_data', $relationsForeign)
? $relationsForeign['foreign_keys_data']
: [],
'options_array' => $options,
'column_array' => $column_array,
'column_hash_array' => $column_hash_array,
'save_row' => array_values($columns),
'url_params' => $GLOBALS['url_params'],
'databases' => $GLOBALS['dblist']->databases,
'dbi' => $this->dbi,
'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'],
'route' => $route,
]);
}
/**
* Update for display field
*
* @param Table $table table
* @param array $cfgRelation relation parameters
*/
private function updateForDisplayField(Table $table, array $cfgRelation): void
{
if (! $table->updateDisplayField(
$_POST['display_field'],
$cfgRelation
)) {
return;
}
$this->response->addHTML(
Generator::getMessage(
__('Display column was successfully updated.'),
'',
'success'
)
);
}
/**
* Update for FK
*
* @param Table $table Table
* @param array $options Options
* @param array $relationsForeign External relations
*/
private function updateForForeignKeys(Table $table, array $options, array $relationsForeign): void
{
$multi_edit_columns_name = $_POST['foreign_key_fields_name'] ?? null;
$preview_sql_data = '';
$seen_error = false;
// (for now, one index name only; we keep the definitions if the
// foreign db is not the same)
if (isset($_POST['destination_foreign_db'], $_POST['destination_foreign_table'])
&& isset($_POST['destination_foreign_column'])) {
[
$html,
$preview_sql_data,
$display_query,
$seen_error,
] = $table->updateForeignKeys(
$_POST['destination_foreign_db'],
$multi_edit_columns_name,
$_POST['destination_foreign_table'],
$_POST['destination_foreign_column'],
$options,
$this->table,
array_key_exists('foreign_keys_data', $relationsForeign)
? $relationsForeign['foreign_keys_data']
: []
);
$this->response->addHTML($html);
}
// If there is a request for SQL previewing.
if (isset($_POST['preview_sql'])) {
Core::previewSQL($preview_sql_data);
exit;
}
if (empty($display_query) || $seen_error) {
return;
}
$GLOBALS['display_query'] = $display_query;
$this->response->addHTML(
Generator::getMessage(
__('Your SQL query has been executed successfully.'),
null,
'success'
)
);
}
/**
* Update for internal relation
*
* @param Table $table Table
* @param array $cfgRelation Relation parameters
* @param array $relations Relations
*/
private function updateForInternalRelation(Table $table, array $cfgRelation, array $relations): void
{
$multi_edit_columns_name = $_POST['fields_name'] ?? null;
if (! $table->updateInternalRelations(
$multi_edit_columns_name,
$_POST['destination_db'],
$_POST['destination_table'],
$_POST['destination_column'],
$cfgRelation,
$relations
)) {
return;
}
$this->response->addHTML(
Generator::getMessage(
__('Internal relationships were successfully updated.'),
'',
'success'
)
);
}
/**
* Send table columns for foreign table dropdown
*/
public function getDropdownValueForTable(): void
{
$foreignTable = $_POST['foreignTable'];
$table_obj = $this->dbi->getTable($_POST['foreignDb'], $foreignTable);
// Since views do not have keys defined on them provide the full list of
// columns
if ($table_obj->isView()) {
$columnList = $table_obj->getColumns(false, false);
} else {
$columnList = $table_obj->getIndexedColumns(false, false);
}
$columns = [];
foreach ($columnList as $column) {
$columns[] = htmlspecialchars($column);
}
if ($GLOBALS['cfg']['NaturalOrder']) {
usort($columns, 'strnatcasecmp');
}
$this->response->addJSON('columns', $columns);
// @todo should be: $server->db($db)->table($table)->primary()
$primary = Index::getPrimary($foreignTable, $_POST['foreignDb']);
if ($primary === false) {
return;
}
$this->response->addJSON('primary', array_keys($primary->getColumns()));
}
/**
* Send database selection values for dropdown
*
* @param string $storageEngine Storage engine.
*/
public function getDropdownValueForDatabase(string $storageEngine): void
{
$tables = [];
$foreign = isset($_POST['foreign']) && $_POST['foreign'] === 'true';
if ($foreign) {
$query = 'SHOW TABLE STATUS FROM '
. Util::backquote($_POST['foreignDb']);
$tables_rs = $this->dbi->query(
$query,
DatabaseInterface::CONNECT_USER,
DatabaseInterface::QUERY_STORE
);
while ($row = $this->dbi->fetchArray($tables_rs)) {
if (! isset($row['Engine'])
|| mb_strtoupper($row['Engine']) != $storageEngine
) {
continue;
}
$tables[] = htmlspecialchars($row['Name']);
}
} else {
$query = 'SHOW TABLES FROM '
. Util::backquote($_POST['foreignDb']);
$tables_rs = $this->dbi->query(
$query,
DatabaseInterface::CONNECT_USER,
DatabaseInterface::QUERY_STORE
);
while ($row = $this->dbi->fetchArray($tables_rs)) {
$tables[] = htmlspecialchars($row[0]);
}
}
if ($GLOBALS['cfg']['NaturalOrder']) {
usort($tables, 'strnatcasecmp');
}
$this->response->addJSON('tables', $tables);
}
}

View file

@ -0,0 +1,651 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Controllers\Database\SqlController as DatabaseSqlController;
use PhpMyAdmin\Controllers\SqlController;
use PhpMyAdmin\Controllers\Table\SqlController as TableSqlController;
use PhpMyAdmin\Core;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\File;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\InsertEdit;
use PhpMyAdmin\Message;
use PhpMyAdmin\Plugins\IOTransformationsPlugin;
use PhpMyAdmin\Relation;
use PhpMyAdmin\Response;
use PhpMyAdmin\Table;
use PhpMyAdmin\Template;
use PhpMyAdmin\Transformations;
use PhpMyAdmin\Util;
use function array_values;
use function class_exists;
use function count;
use function implode;
use function in_array;
use function is_file;
use function method_exists;
use function parse_str;
use function sprintf;
/**
* Manipulation of table data like inserting, replacing and updating.
*/
final class ReplaceController extends AbstractController
{
/** @var InsertEdit */
private $insertEdit;
/** @var Transformations */
private $transformations;
/** @var Relation */
private $relation;
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
* @param DatabaseInterface $dbi
*/
public function __construct(
$response,
Template $template,
$db,
$table,
InsertEdit $insertEdit,
Transformations $transformations,
Relation $relation,
$dbi
) {
parent::__construct($response, $template, $db, $table);
$this->insertEdit = $insertEdit;
$this->transformations = $transformations;
$this->relation = $relation;
$this->dbi = $dbi;
}
public function index(): void
{
global $containerBuilder, $db, $table, $url_params, $message;
global $err_url, $mime_map, $unsaved_values, $active_page, $disp_query, $disp_message;
global $goto_include, $loop_array, $using_key, $is_insert, $is_insertignore, $query;
global $value_sets, $func_no_param, $func_optional_param, $gis_from_text_functions, $gis_from_wkb_functions;
global $query_fields, $insert_errors, $row_skipped, $query_values;
global $total_affected_rows, $last_messages, $warning_messages, $error_messages, $return_to_sql_query;
Util::checkParameters(['db', 'table', 'goto']);
$this->dbi->selectDb($db);
/**
* Initializes some variables
*/
$goto_include = false;
$this->addScriptFiles([
'makegrid.js',
'vendor/stickyfill.min.js',
'sql.js',
'indexes.js',
'gis_data_editor.js',
]);
// check whether insert row mode, if so include /table/change
$this->insertEdit->isInsertRow();
$after_insert_actions = [
'new_insert',
'same_insert',
'edit_next',
];
if (isset($_POST['after_insert'])
&& in_array($_POST['after_insert'], $after_insert_actions)
) {
$url_params['after_insert'] = $_POST['after_insert'];
if (isset($_POST['where_clause'])) {
foreach ($_POST['where_clause'] as $one_where_clause) {
if ($_POST['after_insert'] === 'same_insert') {
$url_params['where_clause'][] = $one_where_clause;
} elseif ($_POST['after_insert'] === 'edit_next') {
$this->insertEdit->setSessionForEditNext($one_where_clause);
}
}
}
}
//get $goto_include for different cases
$goto_include = $this->insertEdit->getGotoInclude($goto_include);
// Defines the url to return in case of failure of the query
$err_url = $this->insertEdit->getErrorUrl($url_params);
/**
* Prepares the update/insert of a row
*/
[
$loop_array,
$using_key,
$is_insert,
$is_insertignore,
] = $this->insertEdit->getParamsForUpdateOrInsert();
$query = [];
$value_sets = [];
$func_no_param = [
'CONNECTION_ID',
'CURRENT_USER',
'CURDATE',
'CURTIME',
'CURRENT_DATE',
'CURRENT_TIME',
'DATABASE',
'LAST_INSERT_ID',
'NOW',
'PI',
'RAND',
'SYSDATE',
'UNIX_TIMESTAMP',
'USER',
'UTC_DATE',
'UTC_TIME',
'UTC_TIMESTAMP',
'UUID',
'UUID_SHORT',
'VERSION',
];
$func_optional_param = [
'RAND',
'UNIX_TIMESTAMP',
];
$gis_from_text_functions = [
'GeomFromText',
'GeomCollFromText',
'LineFromText',
'MLineFromText',
'PointFromText',
'MPointFromText',
'PolyFromText',
'MPolyFromText',
];
$gis_from_wkb_functions = [];
if ($this->dbi->getVersion() >= 50600) {
$gis_from_wkb_functions = [
'ST_GeomFromText',
'ST_GeomCollFromText',
'ST_LineFromText',
'ST_MLineFromText',
'ST_PointFromText',
'ST_MPointFromText',
'ST_PolyFromText',
'ST_MPolyFromText',
];
}
$mime_map = $this->transformations->getMime($db, $table);
if ($mime_map === null) {
$mime_map = [];
}
$query_fields = [];
$insert_errors = [];
$row_skipped = false;
$unsaved_values = [];
foreach ($loop_array as $rownumber => $where_clause) {
// skip fields to be ignored
if (! $using_key && isset($_POST['insert_ignore_' . $where_clause])) {
continue;
}
// Defines the SET part of the sql query
$query_values = [];
// Map multi-edit keys to single-level arrays, dependent on how we got the fields
$multi_edit_columns
= $_POST['fields']['multi_edit'][$rownumber] ?? [];
$multi_edit_columns_name
= $_POST['fields_name']['multi_edit'][$rownumber] ?? [];
$multi_edit_columns_prev
= $_POST['fields_prev']['multi_edit'][$rownumber] ?? null;
$multi_edit_funcs
= $_POST['funcs']['multi_edit'][$rownumber] ?? null;
$multi_edit_salt
= $_POST['salt']['multi_edit'][$rownumber] ?? null;
$multi_edit_columns_type
= $_POST['fields_type']['multi_edit'][$rownumber] ?? null;
$multi_edit_columns_null
= $_POST['fields_null']['multi_edit'][$rownumber] ?? null;
$multi_edit_columns_null_prev
= $_POST['fields_null_prev']['multi_edit'][$rownumber] ?? null;
$multi_edit_auto_increment
= $_POST['auto_increment']['multi_edit'][$rownumber] ?? null;
$multi_edit_virtual
= $_POST['virtual']['multi_edit'][$rownumber] ?? null;
// When a select field is nullified, it's not present in $_POST
// so initialize it; this way, the foreach($multi_edit_columns) will process it
foreach ($multi_edit_columns_name as $key => $val) {
if (isset($multi_edit_columns[$key])) {
continue;
}
$multi_edit_columns[$key] = '';
}
// Iterate in the order of $multi_edit_columns_name,
// not $multi_edit_columns, to avoid problems
// when inserting multiple entries
$insert_fail = false;
foreach ($multi_edit_columns_name as $key => $column_name) {
$current_value = $multi_edit_columns[$key];
// Note: $key is an md5 of the fieldname. The actual fieldname is
// available in $multi_edit_columns_name[$key]
$file_to_insert = new File();
$file_to_insert->checkTblChangeForm((string) $key, (string) $rownumber);
$possibly_uploaded_val = $file_to_insert->getContent();
if ($possibly_uploaded_val !== false) {
$current_value = $possibly_uploaded_val;
}
// Apply Input Transformation if defined
if (! empty($mime_map[$column_name])
&& ! empty($mime_map[$column_name]['input_transformation'])
) {
$filename = 'libraries/classes/Plugins/Transformations/'
. $mime_map[$column_name]['input_transformation'];
if (is_file($filename)) {
$classname = $this->transformations->getClassName($filename);
if (class_exists($classname)) {
/** @var IOTransformationsPlugin $transformation_plugin */
$transformation_plugin = new $classname();
$transformation_options = $this->transformations->getOptions(
$mime_map[$column_name]['input_transformation_options']
);
$current_value = $transformation_plugin->applyTransformation(
$current_value,
$transformation_options
);
// check if transformation was successful or not
// and accordingly set error messages & insert_fail
if (method_exists($transformation_plugin, 'isSuccess')
&& ! $transformation_plugin->isSuccess()
) {
$insert_fail = true;
$row_skipped = true;
$insert_errors[] = sprintf(
__('Row: %1$s, Column: %2$s, Error: %3$s'),
$rownumber,
$column_name,
$transformation_plugin->getError()
);
}
}
}
}
if ($file_to_insert->isError()) {
$insert_errors[] = $file_to_insert->getError();
}
// delete $file_to_insert temporary variable
$file_to_insert->cleanUp();
$current_value = $this->insertEdit->getCurrentValueForDifferentTypes(
$possibly_uploaded_val,
$key,
$multi_edit_columns_type,
$current_value,
$multi_edit_auto_increment,
$rownumber,
$multi_edit_columns_name,
$multi_edit_columns_null,
$multi_edit_columns_null_prev,
$is_insert,
$using_key,
$where_clause,
$table,
$multi_edit_funcs
);
$current_value_as_an_array = $this->insertEdit->getCurrentValueAsAnArrayForMultipleEdit(
$multi_edit_funcs,
$multi_edit_salt,
$gis_from_text_functions,
$current_value,
$gis_from_wkb_functions,
$func_optional_param,
$func_no_param,
$key
);
if (! isset($multi_edit_virtual, $multi_edit_virtual[$key])) {
[
$query_values,
$query_fields,
] = $this->insertEdit->getQueryValuesForInsertAndUpdateInMultipleEdit(
$multi_edit_columns_name,
$multi_edit_columns_null,
$current_value,
$multi_edit_columns_prev,
$multi_edit_funcs,
$is_insert,
$query_values,
$query_fields,
$current_value_as_an_array,
$value_sets,
$key,
$multi_edit_columns_null_prev
);
}
if (! isset($multi_edit_columns_null[$key])) {
continue;
}
$multi_edit_columns[$key] = null;
}
// temporarily store rows not inserted
// so that they can be populated again.
if ($insert_fail) {
$unsaved_values[$rownumber] = $multi_edit_columns;
}
if ($insert_fail || count($query_values) <= 0) {
continue;
}
if ($is_insert) {
$value_sets[] = implode(', ', $query_values);
} else {
// build update query
$query[] = 'UPDATE ' . Util::backquote($table)
. ' SET ' . implode(', ', $query_values)
. ' WHERE ' . $where_clause
. ($_POST['clause_is_unique'] ? '' : ' LIMIT 1');
}
}
unset(
$multi_edit_columns_name,
$multi_edit_columns_prev,
$multi_edit_funcs,
$multi_edit_columns_type,
$multi_edit_columns_null,
$func_no_param,
$multi_edit_auto_increment,
$current_value_as_an_array,
$key,
$current_value,
$loop_array,
$where_clause,
$using_key,
$multi_edit_columns_null_prev,
$insert_fail
);
// Builds the sql query
if ($is_insert && count($value_sets) > 0) {
$query = $this->insertEdit->buildSqlQuery($is_insertignore, $query_fields, $value_sets);
} elseif (empty($query) && ! isset($_POST['preview_sql']) && ! $row_skipped) {
// No change -> move back to the calling script
//
// Note: logic passes here for inline edit
$message = Message::success(__('No change'));
// Avoid infinite recursion
if ($goto_include === '/table/replace') {
$goto_include = '/table/change';
}
$active_page = $goto_include;
if ($goto_include === '/sql') {
/** @var SqlController $controller */
$controller = $containerBuilder->get(SqlController::class);
$controller->index();
return;
}
if ($goto_include === '/database/sql') {
/** @var DatabaseSqlController $controller */
$controller = $containerBuilder->get(DatabaseSqlController::class);
$controller->index();
return;
}
if ($goto_include === '/table/change') {
/** @var ChangeController $controller */
$controller = $containerBuilder->get(ChangeController::class);
$controller->index();
return;
}
if ($goto_include === '/table/sql') {
/** @var TableSqlController $controller */
$controller = $containerBuilder->get(TableSqlController::class);
$controller->index();
return;
}
include ROOT_PATH . Core::securePath((string) $goto_include);
return;
}
unset($multi_edit_columns, $is_insertignore);
// If there is a request for SQL previewing.
if (isset($_POST['preview_sql'])) {
Core::previewSQL($query);
return;
}
/**
* Executes the sql query and get the result, then move back to the calling
* page
*/
[
$url_params,
$total_affected_rows,
$last_messages,
$warning_messages,
$error_messages,
$return_to_sql_query,
] = $this->insertEdit->executeSqlQuery($url_params, $query);
if ($is_insert && (count($value_sets) > 0 || $row_skipped)) {
$message = Message::getMessageForInsertedRows(
$total_affected_rows
);
$unsaved_values = array_values($unsaved_values);
} else {
$message = Message::getMessageForAffectedRows(
$total_affected_rows
);
}
if ($row_skipped) {
$goto_include = '/table/change';
$message->addMessagesString($insert_errors, '<br>');
$message->isError(true);
}
$message->addMessages($last_messages, '<br>');
if (! empty($warning_messages)) {
$message->addMessagesString($warning_messages, '<br>');
$message->isError(true);
}
if (! empty($error_messages)) {
$message->addMessagesString($error_messages);
$message->isError(true);
}
unset(
$error_messages,
$warning_messages,
$total_affected_rows,
$last_messages,
$row_skipped,
$insert_errors
);
/**
* The following section only applies to grid editing.
* However, verifying isAjax() is not enough to ensure we are coming from
* grid editing. If we are coming from the Edit or Copy link in Browse mode,
* ajax_page_request is present in the POST parameters.
*/
if ($this->response->isAjax() && ! isset($_POST['ajax_page_request'])) {
/**
* If we are in grid editing, we need to process the relational and
* transformed fields, if they were edited. After that, output the correct
* link/transformed value and exit
*/
if (isset($_POST['rel_fields_list']) && $_POST['rel_fields_list'] != '') {
$map = $this->relation->getForeigners($db, $table, '', 'both');
/** @var array<int,array> $relation_fields */
$relation_fields = [];
parse_str($_POST['rel_fields_list'], $relation_fields);
// loop for each relation cell
foreach ($relation_fields as $cell_index => $curr_rel_field) {
foreach ($curr_rel_field as $relation_field => $relation_field_value) {
$where_comparison = "='" . $relation_field_value . "'";
$dispval = $this->insertEdit->getDisplayValueForForeignTableColumn(
$where_comparison,
$map,
$relation_field
);
$extra_data['relations'][$cell_index] = $this->insertEdit->getLinkForRelationalDisplayField(
$map,
$relation_field,
$where_comparison,
$dispval,
$relation_field_value
);
}
}
}
if (isset($_POST['do_transformations'])
&& $_POST['do_transformations'] == true
) {
$edited_values = [];
parse_str($_POST['transform_fields_list'], $edited_values);
if (! isset($extra_data)) {
$extra_data = [];
}
$transformation_types = [
'input_transformation',
'transformation',
];
foreach ($mime_map as $transformation) {
$column_name = $transformation['column_name'];
foreach ($transformation_types as $type) {
$file = Core::securePath($transformation[$type]);
$extra_data = $this->insertEdit->transformEditedValues(
$db,
$table,
$transformation,
$edited_values,
$file,
$column_name,
$extra_data,
$type
);
}
}
}
// Need to check the inline edited value can be truncated by MySQL
// without informing while saving
$column_name = $_POST['fields_name']['multi_edit'][0][0];
$this->insertEdit->verifyWhetherValueCanBeTruncatedAndAppendExtraData(
$db,
$table,
$column_name,
$extra_data
);
/**Get the total row count of the table*/
$_table = new Table($_POST['table'], $_POST['db']);
$extra_data['row_count'] = $_table->countRecords();
$extra_data['sql_query'] = Generator::getMessage(
$message,
$GLOBALS['display_query']
);
$this->response->setRequestStatus($message->isSuccess());
$this->response->addJSON('message', $message);
$this->response->addJSON($extra_data);
return;
}
if (! empty($return_to_sql_query)) {
$disp_query = $GLOBALS['sql_query'];
$disp_message = $message;
unset($message);
$GLOBALS['sql_query'] = $return_to_sql_query;
}
$this->addScriptFiles(['vendor/jquery/additional-methods.js', 'table/change.js']);
$active_page = $goto_include;
/**
* If user asked for "and then Insert another new row" we have to remove
* WHERE clause information so that /table/change does not go back
* to the current record
*/
if (isset($_POST['after_insert']) && $_POST['after_insert'] === 'new_insert') {
unset($_POST['where_clause']);
}
if ($goto_include === '/sql') {
/** @var SqlController $controller */
$controller = $containerBuilder->get(SqlController::class);
$controller->index();
return;
}
if ($goto_include === '/database/sql') {
/** @var DatabaseSqlController $controller */
$controller = $containerBuilder->get(DatabaseSqlController::class);
$controller->index();
return;
}
if ($goto_include === '/table/change') {
/** @var ChangeController $controller */
$controller = $containerBuilder->get(ChangeController::class);
$controller->index();
return;
}
if ($goto_include === '/table/sql') {
/** @var TableSqlController $controller */
$controller = $containerBuilder->get(TableSqlController::class);
$controller->index();
return;
}
/**
* Load target page.
*/
require ROOT_PATH . Core::securePath((string) $goto_include);
}
}

View file

@ -0,0 +1,444 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Core;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Operations;
use PhpMyAdmin\Relation;
use PhpMyAdmin\RelationCleanup;
use PhpMyAdmin\Response;
use PhpMyAdmin\Sql;
use PhpMyAdmin\Table\Search;
use PhpMyAdmin\Template;
use PhpMyAdmin\Transformations;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function in_array;
use function intval;
use function mb_strtolower;
use function md5;
use function preg_match;
use function preg_replace;
use function str_ireplace;
use function str_replace;
use function strncasecmp;
use function strtoupper;
/**
* Handles table search tab.
*
* Display table search form, create SQL query from form data
* and call Sql::executeQueryAndSendQueryResponse() to execute it.
*/
class SearchController extends AbstractController
{
/**
* Names of columns
*
* @access private
* @var array
*/
private $columnNames;
/**
* Types of columns
*
* @access private
* @var array
*/
private $columnTypes;
/**
* Types of columns without any replacement
*
* @access private
* @var array
*/
private $originalColumnTypes;
/**
* Collations of columns
*
* @access private
* @var array
*/
private $columnCollations;
/**
* Null Flags of columns
*
* @access private
* @var array
*/
private $columnNullFlags;
/**
* Whether a geometry column is present
*
* @access private
* @var bool
*/
private $geomColumnFlag;
/**
* Foreign Keys
*
* @access private
* @var array
*/
private $foreigners;
/** @var Search */
private $search;
/** @var Relation */
private $relation;
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name
* @param string $table Table name
* @param DatabaseInterface $dbi
*/
public function __construct(
$response,
Template $template,
$db,
$table,
Search $search,
Relation $relation,
$dbi
) {
parent::__construct($response, $template, $db, $table);
$this->search = $search;
$this->relation = $relation;
$this->dbi = $dbi;
$this->columnNames = [];
$this->columnTypes = [];
$this->originalColumnTypes = [];
$this->columnCollations = [];
$this->columnNullFlags = [];
$this->geomColumnFlag = false;
$this->foreigners = [];
$this->loadTableInfo();
}
/**
* Gets all the columns of a table along with their types, collations
* and whether null or not.
*/
private function loadTableInfo(): void
{
// Gets the list and number of columns
$columns = $this->dbi->getColumns(
$this->db,
$this->table,
null,
true
);
// Get details about the geometry functions
$geom_types = Util::getGISDatatypes();
foreach ($columns as $row) {
// set column name
$this->columnNames[] = $row['Field'];
$type = (string) $row['Type'];
// before any replacement
$this->originalColumnTypes[] = mb_strtolower($type);
// check whether table contains geometric columns
if (in_array($type, $geom_types)) {
$this->geomColumnFlag = true;
}
// reformat mysql query output
if (strncasecmp($type, 'set', 3) == 0
|| strncasecmp($type, 'enum', 4) == 0
) {
$type = str_replace(',', ', ', $type);
} else {
// strip the "BINARY" attribute, except if we find "BINARY(" because
// this would be a BINARY or VARBINARY column type
if (! preg_match('@BINARY[\(]@i', $type)) {
$type = str_ireplace('BINARY', '', $type);
}
$type = str_ireplace('ZEROFILL', '', $type);
$type = str_ireplace('UNSIGNED', '', $type);
$type = mb_strtolower($type);
}
if (empty($type)) {
$type = '&nbsp;';
}
$this->columnTypes[] = $type;
$this->columnNullFlags[] = $row['Null'];
$this->columnCollations[]
= ! empty($row['Collation']) && $row['Collation'] !== 'NULL'
? $row['Collation']
: '';
}
// Retrieve foreign keys
$this->foreigners = $this->relation->getForeigners($this->db, $this->table);
}
/**
* Index action
*/
public function index(): void
{
global $db, $table, $url_params, $cfg, $err_url;
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
$this->addScriptFiles([
'makegrid.js',
'vendor/stickyfill.min.js',
'sql.js',
'table/select.js',
'table/change.js',
'vendor/jquery/jquery.uitablefilter.js',
'gis_data_editor.js',
]);
if (isset($_POST['range_search'])) {
$this->rangeSearchAction();
return;
}
/**
* No selection criteria received -> display the selection form
*/
if (! isset($_POST['columnsToDisplay'])
&& ! isset($_POST['displayAllColumns'])
) {
$this->displaySelectionFormAction();
} else {
$this->doSelectionAction();
}
}
/**
* Get data row action
*
* @return void
*/
public function getDataRowAction()
{
if (! Core::checkSqlQuerySignature($_POST['where_clause'], $_POST['where_clause_sign'])) {
return;
}
$extra_data = [];
$row_info_query = 'SELECT * FROM ' . Util::backquote($_POST['db']) . '.'
. Util::backquote($_POST['table']) . ' WHERE ' . $_POST['where_clause'];
$result = $this->dbi->query(
$row_info_query . ';',
DatabaseInterface::CONNECT_USER,
DatabaseInterface::QUERY_STORE
);
$fields_meta = $this->dbi->getFieldsMeta($result);
while ($row = $this->dbi->fetchAssoc($result)) {
// for bit fields we need to convert them to printable form
$i = 0;
foreach ($row as $col => $val) {
if ($fields_meta[$i]->type === 'bit') {
$row[$col] = Util::printableBitValue(
(int) $val,
(int) $fields_meta[$i]->length
);
}
$i++;
}
$extra_data['row_info'] = $row;
}
$this->response->addJSON($extra_data);
}
/**
* Do selection action
*
* @return void
*/
public function doSelectionAction()
{
global $PMA_Theme;
/**
* Selection criteria have been submitted -> do the work
*/
$sql_query = $this->search->buildSqlQuery();
/**
* Add this to ensure following procedures included running correctly.
*/
$sql = new Sql(
$this->dbi,
$this->relation,
new RelationCleanup($this->dbi, $this->relation),
new Operations($this->dbi, $this->relation),
new Transformations(),
$this->template
);
$this->response->addHTML($sql->executeQueryAndSendQueryResponse(
null, // analyzed_sql_results
false, // is_gotofile
$this->db, // db
$this->table, // table
null, // find_real_end
null, // sql_query_for_bookmark
null, // extra_data
null, // message_to_show
null, // sql_data
$GLOBALS['goto'], // goto
$PMA_Theme->getImgPath(),
null, // disp_query
null, // disp_message
$sql_query, // sql_query
null // complete_query
));
}
/**
* Display selection form action
*/
public function displaySelectionFormAction(): void
{
global $goto, $cfg;
if (! isset($goto)) {
$goto = Util::getScriptNameForOption(
$cfg['DefaultTabTable'],
'table'
);
}
$this->render('table/search/index', [
'db' => $this->db,
'table' => $this->table,
'goto' => $goto,
'self' => $this,
'geom_column_flag' => $this->geomColumnFlag,
'column_names' => $this->columnNames,
'column_types' => $this->columnTypes,
'column_collations' => $this->columnCollations,
'default_sliders_state' => $cfg['InitialSlidersState'],
'max_rows' => intval($cfg['MaxRows']),
]);
}
/**
* Range search action
*
* @return void
*/
public function rangeSearchAction()
{
$min_max = $this->getColumnMinMax($_POST['column']);
$this->response->addJSON('column_data', $min_max);
}
/**
* Finds minimum and maximum value of a given column.
*
* @param string $column Column name
*
* @return array
*/
public function getColumnMinMax($column)
{
$sql_query = 'SELECT MIN(' . Util::backquote($column) . ') AS `min`, '
. 'MAX(' . Util::backquote($column) . ') AS `max` '
. 'FROM ' . Util::backquote($this->db) . '.'
. Util::backquote($this->table);
return $this->dbi->fetchSingleRow($sql_query);
}
/**
* Provides a column's type, collation, operators list, and criteria value
* to display in table search form
*
* @param int $search_index Row number in table search form
* @param int $column_index Column index in ColumnNames array
*
* @return array Array containing column's properties
*/
public function getColumnProperties($search_index, $column_index)
{
$selected_operator = ($_POST['criteriaColumnOperators'][$search_index] ?? '');
$entered_value = ($_POST['criteriaValues'] ?? '');
//Gets column's type and collation
$type = $this->columnTypes[$column_index];
$collation = $this->columnCollations[$column_index];
$cleanType = preg_replace('@\(.*@s', '', $type);
//Gets column's comparison operators depending on column type
$typeOperators = $this->dbi->types->getTypeOperatorsHtml(
$cleanType,
$this->columnNullFlags[$column_index],
$selected_operator
);
$func = $this->template->render('table/search/column_comparison_operators', [
'search_index' => $search_index,
'type_operators' => $typeOperators,
]);
//Gets link to browse foreign data(if any) and criteria inputbox
$foreignData = $this->relation->getForeignData(
$this->foreigners,
$this->columnNames[$column_index],
false,
'',
''
);
$htmlAttributes = '';
if (in_array($cleanType, $this->dbi->types->getIntegerTypes())) {
$extractedColumnspec = Util::extractColumnSpec(
$this->originalColumnTypes[$column_index]
);
$is_unsigned = $extractedColumnspec['unsigned'];
$minMaxValues = $this->dbi->types->getIntegerRange(
$cleanType,
! $is_unsigned
);
$htmlAttributes = 'data-min="' . $minMaxValues[0] . '" '
. 'data-max="' . $minMaxValues[1] . '"';
}
$htmlAttributes .= ' onfocus="return '
. 'verifyAfterSearchFieldChange(' . $search_index . ', \'#tbl_search_form\')"';
$value = $this->template->render('table/search/input_box', [
'str' => '',
'column_type' => (string) $type,
'column_data_type' => strtoupper($cleanType),
'html_attributes' => $htmlAttributes,
'column_id' => 'fieldID_',
'in_zoom_search_edit' => false,
'foreigners' => $this->foreigners,
'column_name' => $this->columnNames[$column_index],
'column_name_hash' => md5($this->columnNames[$column_index]),
'foreign_data' => $foreignData,
'table' => $this->table,
'column_index' => $search_index,
'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'],
'criteria_values' => $entered_value,
'db' => $this->db,
'in_fbs' => true,
]);
return [
'type' => $type,
'collation' => $collation,
'func' => $func,
'value' => $value,
];
}
}

View file

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Config\PageSettings;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Response;
use PhpMyAdmin\SqlQueryForm;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function htmlspecialchars;
/**
* Table SQL executor
*/
final class SqlController extends AbstractController
{
/** @var SqlQueryForm */
private $sqlQueryForm;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
*/
public function __construct($response, Template $template, $db, $table, SqlQueryForm $sqlQueryForm)
{
parent::__construct($response, $template, $db, $table);
$this->sqlQueryForm = $sqlQueryForm;
}
public function index(): void
{
global $err_url, $goto, $back, $db, $table, $cfg;
$this->addScriptFiles([
'makegrid.js',
'vendor/jquery/jquery.uitablefilter.js',
'vendor/stickyfill.min.js',
'sql.js',
]);
$pageSettings = new PageSettings('Sql');
$this->response->addHTML($pageSettings->getErrorHTML());
$this->response->addHTML($pageSettings->getHTML());
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
/**
* After a syntax error, we return to this script
* with the typed query in the textarea.
*/
$goto = Url::getFromRoute('/table/sql');
$back = Url::getFromRoute('/table/sql');
$this->response->addHTML($this->sqlQueryForm->getHtml(
$_GET['sql_query'] ?? true,
false,
isset($_POST['delimiter'])
? htmlspecialchars($_POST['delimiter'])
: ';'
));
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,241 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Message;
use PhpMyAdmin\Response;
use PhpMyAdmin\Template;
use PhpMyAdmin\Tracker;
use PhpMyAdmin\Tracking;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function array_map;
use function define;
use function explode;
use function htmlspecialchars;
use function sprintf;
use function strtotime;
final class TrackingController extends AbstractController
{
/** @var Tracking */
private $tracking;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
*/
public function __construct(
$response,
Template $template,
$db,
$table,
Tracking $tracking
) {
parent::__construct($response, $template, $db, $table);
$this->tracking = $tracking;
}
public function index(): void
{
global $text_dir, $url_params, $msg, $PMA_Theme, $err_url;
global $data, $entries, $filter_ts_from, $filter_ts_to, $filter_users, $selection_schema;
global $selection_data, $selection_both, $sql_result, $db, $table, $cfg;
$this->addScriptFiles(['vendor/jquery/jquery.tablesorter.js', 'table/tracking.js']);
define('TABLE_MAY_BE_ABSENT', true);
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
$activeMessage = '';
if (Tracker::isActive()
&& Tracker::isTracked($GLOBALS['db'], $GLOBALS['table'])
&& ! (isset($_POST['toggle_activation'])
&& $_POST['toggle_activation'] === 'deactivate_now')
&& ! (isset($_POST['report_export'])
&& $_POST['export_type'] === 'sqldumpfile')
) {
$msg = Message::notice(
sprintf(
__('Tracking of %s is activated.'),
htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table'])
)
);
$activeMessage = $msg->getDisplay();
}
$url_params['goto'] = Url::getFromRoute('/table/tracking');
$url_params['back'] = Url::getFromRoute('/table/tracking');
$data = [];
$entries = [];
$filter_ts_from = null;
$filter_ts_to = null;
$filter_users = [];
$selection_schema = false;
$selection_data = false;
$selection_both = false;
// Init vars for tracking report
if (isset($_POST['report']) || isset($_POST['report_export'])) {
$data = Tracker::getTrackedData(
$GLOBALS['db'],
$GLOBALS['table'],
$_POST['version']
);
if (! isset($_POST['logtype'])) {
$_POST['logtype'] = 'schema_and_data';
}
if ($_POST['logtype'] === 'schema') {
$selection_schema = true;
} elseif ($_POST['logtype'] === 'data') {
$selection_data = true;
} else {
$selection_both = true;
}
if (! isset($_POST['date_from'])) {
$_POST['date_from'] = $data['date_from'];
}
if (! isset($_POST['date_to'])) {
$_POST['date_to'] = $data['date_to'];
}
if (! isset($_POST['users'])) {
$_POST['users'] = '*';
}
$filter_ts_from = strtotime($_POST['date_from']);
$filter_ts_to = strtotime($_POST['date_to']);
$filter_users = array_map('trim', explode(',', $_POST['users']));
}
// Prepare export
if (isset($_POST['report_export'])) {
$entries = $this->tracking->getEntries(
$data,
(int) $filter_ts_from,
(int) $filter_ts_to,
$filter_users
);
}
// Export as file download
if (isset($_POST['report_export'])
&& $_POST['export_type'] === 'sqldumpfile'
) {
$this->tracking->exportAsFileDownload($entries);
}
$actionMessage = '';
if (isset($_POST['submit_mult'])) {
if (! empty($_POST['selected_versions'])) {
if ($_POST['submit_mult'] === 'delete_version') {
foreach ($_POST['selected_versions'] as $version) {
$this->tracking->deleteTrackingVersion($version);
}
$actionMessage = Message::success(
__('Tracking versions deleted successfully.')
)->getDisplay();
}
} else {
$actionMessage = Message::notice(
__('No versions selected.')
)->getDisplay();
}
}
$deleteVersion = '';
if (isset($_POST['submit_delete_version'])) {
$deleteVersion = $this->tracking->deleteTrackingVersion($_POST['version']);
}
$createVersion = '';
if (isset($_POST['submit_create_version'])) {
$createVersion = $this->tracking->createTrackingVersion();
}
$deactivateTracking = '';
if (isset($_POST['toggle_activation'])
&& $_POST['toggle_activation'] === 'deactivate_now'
) {
$deactivateTracking = $this->tracking->changeTracking('deactivate');
}
$activateTracking = '';
if (isset($_POST['toggle_activation'])
&& $_POST['toggle_activation'] === 'activate_now'
) {
$activateTracking = $this->tracking->changeTracking('activate');
}
// Export as SQL execution
$message = '';
if (isset($_POST['report_export']) && $_POST['export_type'] === 'execution') {
$sql_result = $this->tracking->exportAsSqlExecution($entries);
$msg = Message::success(__('SQL statements executed.'));
$message = $msg->getDisplay();
}
$sqlDump = '';
if (isset($_POST['report_export']) && $_POST['export_type'] === 'sqldump') {
$sqlDump = $this->tracking->exportAsSqlDump($entries);
}
$schemaSnapshot = '';
if (isset($_POST['snapshot'])) {
$schemaSnapshot = $this->tracking->getHtmlForSchemaSnapshot($url_params);
}
$trackingReportRows = '';
if (isset($_POST['report'])
&& (isset($_POST['delete_ddlog']) || isset($_POST['delete_dmlog']))
) {
$trackingReportRows = $this->tracking->deleteTrackingReportRows($data);
}
$trackingReport = '';
if (isset($_POST['report']) || isset($_POST['report_export'])) {
$trackingReport = $this->tracking->getHtmlForTrackingReport(
$data,
$url_params,
$selection_schema,
$selection_data,
$selection_both,
(int) $filter_ts_to,
(int) $filter_ts_from,
$filter_users
);
}
$main = $this->tracking->getHtmlForMainPage(
$url_params,
$PMA_Theme->getImgPath(),
$text_dir
);
$this->render('table/tracking/index', [
'active_message' => $activeMessage,
'action_message' => $actionMessage,
'delete_version' => $deleteVersion,
'create_version' => $createVersion,
'deactivate_tracking' => $deactivateTracking,
'activate_tracking' => $activateTracking,
'message' => $message,
'sql_dump' => $sqlDump,
'schema_snapshot' => $schemaSnapshot,
'tracking_report_rows' => $trackingReportRows,
'tracking_report' => $trackingReport,
'main' => $main,
]);
}
}

View file

@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Database\Triggers;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Response;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function in_array;
use function strlen;
/**
* Triggers management.
*/
class TriggersController extends AbstractController
{
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
* @param DatabaseInterface $dbi
*/
public function __construct($response, Template $template, $db, $table, $dbi)
{
parent::__construct($response, $template, $db, $table);
$this->dbi = $dbi;
}
public function index(): void
{
global $db, $table, $tables, $num_tables, $total_num_tables, $sub_part;
global $tooltip_truename, $tooltip_aliasname, $pos;
global $errors, $url_params, $err_url, $cfg;
if (! $this->response->isAjax()) {
/**
* Displays the header and tabs
*/
if (! empty($table) && in_array($table, $this->dbi->getTables($db))) {
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
} else {
$table = '';
Util::checkParameters(['db']);
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
$err_url .= Url::getCommon(['db' => $db], '&');
if (! $this->hasDatabase()) {
return;
}
[
$tables,
$num_tables,
$total_num_tables,
$sub_part,,,
$tooltip_truename,
$tooltip_aliasname,
$pos,
] = Util::getDbInfo($db, $sub_part ?? '');
}
} elseif (strlen($db) > 0) {
$this->dbi->selectDb($db);
}
/**
* Keep a list of errors that occurred while
* processing an 'Add' or 'Edit' operation.
*/
$errors = [];
$triggers = new Triggers($this->dbi, $this->template, $this->response);
$triggers->main();
}
}

View file

@ -0,0 +1,501 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Core;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Relation;
use PhpMyAdmin\Response;
use PhpMyAdmin\Table\Search;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function array_search;
use function count;
use function htmlspecialchars;
use function in_array;
use function intval;
use function is_numeric;
use function json_encode;
use function mb_strtolower;
use function md5;
use function preg_match;
use function preg_replace;
use function str_ireplace;
use function str_replace;
use function strncasecmp;
use function strtoupper;
/**
* Handles table zoom search tab.
*
* Display table zoom search form, create SQL queries from form data.
*/
class ZoomSearchController extends AbstractController
{
/** @var Search */
private $search;
/** @var Relation */
private $relation;
/** @var array */
private $columnNames;
/** @var array */
private $columnTypes;
/** @var array */
private $originalColumnTypes;
/** @var array */
private $columnCollations;
/** @var array */
private $columnNullFlags;
/** @var bool Whether a geometry column is present */
private $geomColumnFlag;
/** @var array Foreign keys */
private $foreigners;
/** @var DatabaseInterface */
private $dbi;
/**
* @param Response $response
* @param string $db Database name.
* @param string $table Table name.
* @param DatabaseInterface $dbi
*/
public function __construct($response, Template $template, $db, $table, Search $search, Relation $relation, $dbi)
{
parent::__construct($response, $template, $db, $table);
$this->search = $search;
$this->relation = $relation;
$this->dbi = $dbi;
$this->columnNames = [];
$this->columnTypes = [];
$this->originalColumnTypes = [];
$this->columnCollations = [];
$this->columnNullFlags = [];
$this->geomColumnFlag = false;
$this->foreigners = [];
$this->loadTableInfo();
}
public function index(): void
{
global $goto, $db, $table, $url_params, $cfg, $err_url;
Util::checkParameters(['db', 'table']);
$url_params = ['db' => $db, 'table' => $table];
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$err_url .= Url::getCommon($url_params, '&');
DbTableExists::check();
$this->addScriptFiles([
'vendor/stickyfill.min.js',
'makegrid.js',
'sql.js',
'vendor/jqplot/jquery.jqplot.js',
'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js',
'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js',
'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js',
'vendor/jqplot/plugins/jqplot.highlighter.js',
'vendor/jqplot/plugins/jqplot.cursor.js',
'table/zoom_plot_jqplot.js',
'table/change.js',
]);
/**
* Handle AJAX request for data row on point select
*/
if (isset($_POST['get_data_row'])
&& $_POST['get_data_row'] == true
) {
$this->getDataRowAction();
return;
}
/**
* Handle AJAX request for changing field information
* (value,collation,operators,field values) in input form
*/
if (isset($_POST['change_tbl_info'])
&& $_POST['change_tbl_info'] == true
) {
$this->changeTableInfoAction();
return;
}
//Set default datalabel if not selected
if (! isset($_POST['zoom_submit']) || $_POST['dataLabel'] == '') {
$dataLabel = $this->relation->getDisplayField($this->db, $this->table);
} else {
$dataLabel = $_POST['dataLabel'];
}
// Displays the zoom search form
$this->displaySelectionFormAction($dataLabel);
/**
* Handle the input criteria and generate the query result
* Form for displaying query results
*/
if (! isset($_POST['zoom_submit'])
|| $_POST['criteriaColumnNames'][0] === 'pma_null'
|| $_POST['criteriaColumnNames'][1] === 'pma_null'
|| $_POST['criteriaColumnNames'][0] == $_POST['criteriaColumnNames'][1]
) {
return;
}
if (! isset($goto)) {
$goto = Util::getScriptNameForOption(
$GLOBALS['cfg']['DefaultTabTable'],
'table'
);
}
$this->zoomSubmitAction($dataLabel, $goto);
}
/**
* Gets all the columns of a table along with their types, collations
* and whether null or not.
*/
private function loadTableInfo(): void
{
// Gets the list and number of columns
$columns = $this->dbi->getColumns(
$this->db,
$this->table,
null,
true
);
// Get details about the geometry functions
$geom_types = Util::getGISDatatypes();
foreach ($columns as $row) {
// set column name
$this->columnNames[] = $row['Field'];
$type = (string) $row['Type'];
// before any replacement
$this->originalColumnTypes[] = mb_strtolower($type);
// check whether table contains geometric columns
if (in_array($type, $geom_types)) {
$this->geomColumnFlag = true;
}
// reformat mysql query output
if (strncasecmp($type, 'set', 3) == 0
|| strncasecmp($type, 'enum', 4) == 0
) {
$type = str_replace(',', ', ', $type);
} else {
// strip the "BINARY" attribute, except if we find "BINARY(" because
// this would be a BINARY or VARBINARY column type
if (! preg_match('@BINARY[\(]@i', $type)) {
$type = str_ireplace('BINARY', '', $type);
}
$type = str_ireplace('ZEROFILL', '', $type);
$type = str_ireplace('UNSIGNED', '', $type);
$type = mb_strtolower($type);
}
if (empty($type)) {
$type = '&nbsp;';
}
$this->columnTypes[] = $type;
$this->columnNullFlags[] = $row['Null'];
$this->columnCollations[]
= ! empty($row['Collation']) && $row['Collation'] !== 'NULL'
? $row['Collation']
: '';
}
// Retrieve foreign keys
$this->foreigners = $this->relation->getForeigners($this->db, $this->table);
}
/**
* Display selection form action
*
* @param string $dataLabel Data label
*
* @return void
*/
public function displaySelectionFormAction($dataLabel = null)
{
global $goto;
if (! isset($goto)) {
$goto = Util::getScriptNameForOption(
$GLOBALS['cfg']['DefaultTabTable'],
'table'
);
}
$column_names = $this->columnNames;
$criteria_column_names = $_POST['criteriaColumnNames'] ?? null;
$keys = [];
for ($i = 0; $i < 4; $i++) {
if (! isset($criteria_column_names[$i])) {
continue;
}
if ($criteria_column_names[$i] === 'pma_null') {
continue;
}
$keys[$criteria_column_names[$i]] = array_search($criteria_column_names[$i], $column_names);
}
$this->render('table/zoom_search/index', [
'db' => $this->db,
'table' => $this->table,
'goto' => $goto,
'self' => $this,
'geom_column_flag' => $this->geomColumnFlag,
'column_names' => $column_names,
'data_label' => $dataLabel,
'keys' => $keys,
'criteria_column_names' => $criteria_column_names,
'criteria_column_types' => $_POST['criteriaColumnTypes'] ?? null,
'max_plot_limit' => ! empty($_POST['maxPlotLimit'])
? intval($_POST['maxPlotLimit'])
: intval($GLOBALS['cfg']['maxRowPlotLimit']),
]);
}
/**
* Get data row action
*
* @return void
*/
public function getDataRowAction()
{
if (! Core::checkSqlQuerySignature($_POST['where_clause'], $_POST['where_clause_sign'])) {
return;
}
$extra_data = [];
$row_info_query = 'SELECT * FROM ' . Util::backquote($_POST['db']) . '.'
. Util::backquote($_POST['table']) . ' WHERE ' . $_POST['where_clause'];
$result = $this->dbi->query(
$row_info_query . ';',
DatabaseInterface::CONNECT_USER,
DatabaseInterface::QUERY_STORE
);
$fields_meta = $this->dbi->getFieldsMeta($result);
while ($row = $this->dbi->fetchAssoc($result)) {
// for bit fields we need to convert them to printable form
$i = 0;
foreach ($row as $col => $val) {
if ($fields_meta[$i]->type === 'bit') {
$row[$col] = Util::printableBitValue(
(int) $val,
(int) $fields_meta[$i]->length
);
}
$i++;
}
$extra_data['row_info'] = $row;
}
$this->response->addJSON($extra_data);
}
/**
* Change table info action
*
* @return void
*/
public function changeTableInfoAction()
{
$field = $_POST['field'];
if ($field === 'pma_null') {
$this->response->addJSON('field_type', '');
$this->response->addJSON('field_collation', '');
$this->response->addJSON('field_operators', '');
$this->response->addJSON('field_value', '');
return;
}
$key = array_search($field, $this->columnNames);
$search_index
= (isset($_POST['it']) && is_numeric($_POST['it'])
? intval($_POST['it']) : 0);
$properties = $this->getColumnProperties($search_index, $key);
$this->response->addJSON(
'field_type',
htmlspecialchars($properties['type'])
);
$this->response->addJSON('field_collation', $properties['collation']);
$this->response->addJSON('field_operators', $properties['func']);
$this->response->addJSON('field_value', $properties['value']);
}
/**
* Zoom submit action
*
* @param string $dataLabel Data label
* @param string $goto Goto
*
* @return void
*/
public function zoomSubmitAction($dataLabel, $goto)
{
//Query generation part
$sql_query = $this->search->buildSqlQuery();
$sql_query .= ' LIMIT ' . $_POST['maxPlotLimit'];
//Query execution part
$result = $this->dbi->query(
$sql_query . ';',
DatabaseInterface::CONNECT_USER,
DatabaseInterface::QUERY_STORE
);
$fields_meta = $this->dbi->getFieldsMeta($result);
$data = [];
while ($row = $this->dbi->fetchAssoc($result)) {
//Need a row with indexes as 0,1,2 for the getUniqueCondition
// hence using a temporary array
$tmpRow = [];
foreach ($row as $val) {
$tmpRow[] = $val;
}
//Get unique condition on each row (will be needed for row update)
$uniqueCondition = Util::getUniqueCondition(
$result,
count($this->columnNames),
$fields_meta,
$tmpRow,
true
);
//Append it to row array as where_clause
$row['where_clause'] = $uniqueCondition[0];
$row['where_clause_sign'] = Core::signSqlQuery($uniqueCondition[0]);
$tmpData = [
$_POST['criteriaColumnNames'][0] =>
$row[$_POST['criteriaColumnNames'][0]],
$_POST['criteriaColumnNames'][1] =>
$row[$_POST['criteriaColumnNames'][1]],
'where_clause' => $uniqueCondition[0],
'where_clause_sign' => Core::signSqlQuery($uniqueCondition[0]),
];
$tmpData[$dataLabel] = $dataLabel ? $row[$dataLabel] : '';
$data[] = $tmpData;
}
unset($tmpData);
$column_names_hashes = [];
foreach ($this->columnNames as $columnName) {
$column_names_hashes[$columnName] = md5($columnName);
}
$this->render('table/zoom_search/result_form', [
'db' => $this->db,
'table' => $this->table,
'column_names' => $this->columnNames,
'column_names_hashes' => $column_names_hashes,
'foreigners' => $this->foreigners,
'column_null_flags' => $this->columnNullFlags,
'column_types' => $this->columnTypes,
'goto' => $goto,
'data' => $data,
'data_json' => json_encode($data),
'zoom_submit' => isset($_POST['zoom_submit']),
'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'],
]);
}
/**
* Provides a column's type, collation, operators list, and criteria value
* to display in table search form
*
* @param int $search_index Row number in table search form
* @param int $column_index Column index in ColumnNames array
*
* @return array Array containing column's properties
*/
public function getColumnProperties($search_index, $column_index)
{
$selected_operator = ($_POST['criteriaColumnOperators'][$search_index] ?? '');
$entered_value = ($_POST['criteriaValues'] ?? '');
//Gets column's type and collation
$type = $this->columnTypes[$column_index];
$collation = $this->columnCollations[$column_index];
$cleanType = preg_replace('@\(.*@s', '', $type);
//Gets column's comparison operators depending on column type
$typeOperators = $this->dbi->types->getTypeOperatorsHtml(
$cleanType,
$this->columnNullFlags[$column_index],
$selected_operator
);
$func = $this->template->render('table/search/column_comparison_operators', [
'search_index' => $search_index,
'type_operators' => $typeOperators,
]);
//Gets link to browse foreign data(if any) and criteria inputbox
$foreignData = $this->relation->getForeignData(
$this->foreigners,
$this->columnNames[$column_index],
false,
'',
''
);
$htmlAttributes = '';
if (in_array($cleanType, $this->dbi->types->getIntegerTypes())) {
$extractedColumnspec = Util::extractColumnSpec(
$this->originalColumnTypes[$column_index]
);
$is_unsigned = $extractedColumnspec['unsigned'];
$minMaxValues = $this->dbi->types->getIntegerRange(
$cleanType,
! $is_unsigned
);
$htmlAttributes = 'data-min="' . $minMaxValues[0] . '" '
. 'data-max="' . $minMaxValues[1] . '"';
}
$htmlAttributes .= ' onfocus="return '
. 'verifyAfterSearchFieldChange(' . $search_index . ', \'#zoom_search_form\')"';
$value = $this->template->render('table/search/input_box', [
'str' => '',
'column_type' => (string) $type,
'column_data_type' => strtoupper($cleanType),
'html_attributes' => $htmlAttributes,
'column_id' => 'fieldID_',
'in_zoom_search_edit' => false,
'foreigners' => $this->foreigners,
'column_name' => $this->columnNames[$column_index],
'column_name_hash' => md5($this->columnNames[$column_index]),
'foreign_data' => $foreignData,
'table' => $this->table,
'column_index' => $search_index,
'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'],
'criteria_values' => $entered_value,
'db' => $this->db,
'in_fbs' => true,
]);
return [
'type' => $type,
'collation' => $collation,
'func' => $func,
'value' => $value,
];
}
}