gl-website-deployer/admin/phpMyAdmin/libraries/classes/Controllers/Table/OperationsController.php
2024-11-19 08:02:04 +01:00

500 lines
18 KiB
PHP

<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Charsets;
use PhpMyAdmin\CheckUserPrivileges;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Index;
use PhpMyAdmin\Message;
use PhpMyAdmin\Operations;
use PhpMyAdmin\Partitioning\Partition;
use PhpMyAdmin\Query\Generator as QueryGenerator;
use PhpMyAdmin\Query\Utilities;
use PhpMyAdmin\ResponseRenderer;
use PhpMyAdmin\StorageEngine;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function __;
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;
use function urldecode;
class OperationsController extends AbstractController
{
/** @var Operations */
private $operations;
/** @var CheckUserPrivileges */
private $checkUserPrivileges;
/** @var Relation */
private $relation;
/** @var DatabaseInterface */
private $dbi;
public function __construct(
ResponseRenderer $response,
Template $template,
string $db,
string $table,
Operations $operations,
CheckUserPrivileges $checkUserPrivileges,
Relation $relation,
DatabaseInterface $dbi
) {
parent::__construct($response, $template, $db, $table);
$this->operations = $operations;
$this->checkUserPrivileges = $checkUserPrivileges;
$this->relation = $relation;
$this->dbi = $dbi;
}
public function __invoke(): void
{
global $urlParams, $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, $errorUrl, $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);
$urlParams = ['db' => $db, 'table' => $table];
$errorUrl = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
$errorUrl .= Url::getCommon($urlParams, '&');
DbTableExists::check();
$urlParams['goto'] = $urlParams['back'] = Url::getFromRoute('/table/operations');
$relationParameters = $this->relation->getRelationParameters();
/**
* Reselect current db (needed in some cases probably due to the calling of {@link 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()) {
if (isset($_POST['submit_move'], $_POST['target_db'])) {
$db = $_POST['target_db'];// Used in Header::getJsParams()
}
$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 (! 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 = QueryGenerator::getQueryForReorderingTable(
$table,
urldecode($_POST['order_field']),
$_POST['order_order'] ?? null
);
$result = $this->dbi->query($sql_query);
}
/**
* A partition operation has been requested by the user
*/
if (isset($_POST['submit_partition']) && ! empty($_POST['partition_operation'])) {
$sql_query = QueryGenerator::getQueryForPartitioningTable(
$table,
$_POST['partition_operation'],
$_POST['partition_name']
);
$result = $this->dbi->query($sql_query);
}
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);
}
$urlParams['goto'] = $urlParams['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 (isset($partitionNames[0])) {
$partitions = $partitionNames;
$partitionsChoices = $this->operations->getPartitionMaintenanceChoices();
}
}
$foreigners = $this->operations->getForeignersForReferentialIntegrityCheck(
$urlParams,
$relationParameters->relationFeature !== null
);
$this->render('table/operations/index', [
'db' => $db,
'table' => $table,
'url_params' => $urlParams,
'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,
]);
}
}