2 declare(strict_types
=1);
4 namespace PhpMyAdmin\Controllers\Table
;
6 use PhpMyAdmin\Charsets
;
7 use PhpMyAdmin\CheckUserPrivileges
;
9 use PhpMyAdmin\Controllers\SqlController
;
10 use PhpMyAdmin\DatabaseInterface
;
11 use PhpMyAdmin\Html\Forms\Fields\DropDown
;
12 use PhpMyAdmin\Html\Generator
;
14 use PhpMyAdmin\Message
;
15 use PhpMyAdmin\Operations
;
16 use PhpMyAdmin\Partition
;
17 use PhpMyAdmin\Relation
;
18 use PhpMyAdmin\Response
;
19 use PhpMyAdmin\StorageEngine
;
20 use PhpMyAdmin\Template
;
25 use function mb_strstr
;
26 use function mb_strtolower
;
27 use function mb_strtoupper
;
28 use function preg_replace
;
30 class OperationsController
extends AbstractController
32 /** @var Operations */
35 /** @var CheckUserPrivileges */
36 private $checkUserPrivileges;
42 * @param Response $response A Response instance.
43 * @param DatabaseInterface $dbi A DatabaseInterface instance.
44 * @param Template $template A Template instance.
45 * @param string $db Database name.
46 * @param string $table Table name.
47 * @param Operations $operations A Operations instance.
48 * @param CheckUserPrivileges $checkUserPrivileges A CheckUserPrivileges instance.
49 * @param Relation $relation A Relation instance.
51 public function __construct(
57 Operations
$operations,
58 CheckUserPrivileges
$checkUserPrivileges,
61 parent
::__construct($response, $dbi, $template, $db, $table);
62 $this->operations
= $operations;
63 $this->checkUserPrivileges
= $checkUserPrivileges;
64 $this->relation
= $relation;
67 public function index(): void
69 global $containerBuilder, $url_query, $url_params, $reread_info, $tbl_is_view, $tbl_storage_engine;
70 global $show_comment, $tbl_collation, $table_info_num_rows, $row_format, $auto_increment, $create_options;
71 global $table_alters, $warning_messages, $lowerCaseNames, $db, $table, $reload, $result;
72 global $new_tbl_storage_engine, $sql_query, $message_to_show, $columns, $hideOrderTable, $indexes;
73 global $notNull, $comment, $db_is_system_schema, $truncate_table_url_params, $drop_table_url_params;
74 global $this_sql_query;
76 $this->checkUserPrivileges
->getPrivileges();
78 // lower_case_table_names=1 `DB` becomes `db`
79 $lowerCaseNames = $this->dbi
->getLowerCaseNames() === '1';
81 if ($lowerCaseNames) {
82 $table = mb_strtolower($table);
85 $pma_table = $this->dbi
->getTable($db, $table);
87 $header = $this->response
->getHeader();
88 $scripts = $header->getScripts();
89 $scripts->addFile('table/operations.js');
95 $url_params['goto'] = $url_params['back'] = Url
::getFromRoute('/table/operations');
96 $url_query .= Url
::getCommon($url_params, '&');
99 * Gets relation settings
101 $cfgRelation = $this->relation
->getRelationsParam();
103 // reselect current db (needed in some cases probably due to
104 // the calling of PhpMyAdmin\Relation)
105 $this->dbi
->selectDb($db);
107 $reread_info = $pma_table->getStatusInfo(null, false);
108 $GLOBALS['showtable'] = $pma_table->getStatusInfo(null, (isset($reread_info) && $reread_info));
109 if ($pma_table->isView()) {
111 $tbl_storage_engine = __('View');
112 $show_comment = null;
114 $tbl_is_view = false;
115 $tbl_storage_engine = $pma_table->getStorageEngine();
116 $show_comment = $pma_table->getComment();
118 $tbl_collation = $pma_table->getCollation();
119 $table_info_num_rows = $pma_table->getNumRows();
120 $row_format = $pma_table->getRowFormat();
121 $auto_increment = $pma_table->getAutoIncrement();
122 $create_options = $pma_table->getCreateOptions();
124 // set initial value of these variables, based on the current table engine
125 if ($pma_table->isEngine('ARIA')) {
126 // the value for transactional can be implicit
127 // (no create option found, in this case it means 1)
128 // or explicit (option found with a value of 0 or 1)
129 // ($create_options['transactional'] may have been set by Table class,
130 // from the $create_options)
131 $create_options['transactional'] = isset($create_options['transactional']) && $create_options['transactional'] == '0'
134 $create_options['page_checksum'] = $create_options['page_checksum'] ??
'';
137 $pma_table = $this->dbi
->getTable(
141 $reread_info = false;
145 * If the table has to be moved to some other database
147 if (isset($_POST['submit_move']) ||
isset($_POST['submit_copy'])) {
149 $this->operations
->moveOrCopyTable($db, $table);
150 // This was ended in an Ajax call
154 * If the table has to be maintained
156 if (isset($_POST['table_maintenance'])) {
157 /** @var SqlController $controller */
158 $controller = $containerBuilder->get(SqlController
::class);
159 $controller->index();
164 * Updates table comment, type and options if required
166 if (isset($_POST['submitoptions'])) {
168 $warning_messages = [];
170 if (isset($_POST['new_name'])) {
171 // lower_case_table_names=1 `DB` becomes `db`
172 if ($lowerCaseNames) {
173 $_POST['new_name'] = mb_strtolower(
177 // Get original names before rename operation
178 $oldTable = $pma_table->getName();
179 $oldDb = $pma_table->getDbName();
181 if ($pma_table->rename($_POST['new_name'])) {
182 if (isset($_POST['adjust_privileges'])
183 && ! empty($_POST['adjust_privileges'])
185 $this->operations
->adjustPrivilegesRenameOrMoveTable(
193 // Reselect the original DB
195 $this->dbi
->selectDb($oldDb);
196 $_message .= $pma_table->getLastMessage();
198 $table = $pma_table->getName();
202 $_message .= $pma_table->getLastError();
207 if (! empty($_POST['new_tbl_storage_engine'])
208 && mb_strtoupper($_POST['new_tbl_storage_engine']) !== $tbl_storage_engine
210 $new_tbl_storage_engine = mb_strtoupper($_POST['new_tbl_storage_engine']);
212 if ($pma_table->isEngine('ARIA')) {
213 $create_options['transactional'] = isset($create_options['transactional']) && $create_options['transactional'] == '0'
216 $create_options['page_checksum'] = $create_options['page_checksum'] ??
'';
219 $new_tbl_storage_engine = '';
222 $row_format = $create_options['row_format'] ??
$pma_table->getRowFormat();
224 $table_alters = $this->operations
->getTableAltersArray(
226 $create_options['pack_keys'],
227 (empty($create_options['checksum']) ?
'0' : '1'),
228 ($create_options['page_checksum'] ??
''),
229 (empty($create_options['delay_key_write']) ?
'0' : '1'),
231 $new_tbl_storage_engine,
232 (isset($create_options['transactional']) && $create_options['transactional'] == '0' ?
'0' : '1'),
236 if (count($table_alters) > 0) {
237 $sql_query = 'ALTER TABLE '
238 . Util
::backquote($table);
239 $sql_query .= "\r\n" . implode("\r\n", $table_alters);
241 $result = (bool) $this->dbi
->query($sql_query);
243 unset($table_alters);
244 $warning_messages = $this->operations
->getWarningMessagesArray();
247 if (isset($_POST['tbl_collation'], $_POST['change_all_collations'])
248 && ! empty($_POST['tbl_collation'])
249 && ! empty($_POST['change_all_collations'])
251 $this->operations
->changeAllColumnsCollation(
254 $_POST['tbl_collation']
258 if (isset($_POST['tbl_collation']) && empty($_POST['tbl_collation'])) {
259 if ($this->response
->isAjax()) {
260 $this->response
->setRequestStatus(false);
261 $this->response
->addJSON(
263 Message
::error(__('No collation provided.'))
270 * Reordering the table has been requested by the user
272 if (isset($_POST['submitorderby']) && ! empty($_POST['order_field'])) {
273 [$sql_query, $result] = $this->operations
->getQueryAndResultForReorderingTable();
277 * A partition operation has been requested by the user
279 if (isset($_POST['submit_partition'])
280 && ! empty($_POST['partition_operation'])
282 [$sql_query, $result] = $this->operations
->getQueryAndResultForPartition();
286 // to avoid showing the old value (for example the AUTO_INCREMENT) after
287 // a change, clear the cache
288 $this->dbi
->clearTableCache();
289 $this->dbi
->selectDb($db);
290 $GLOBALS['showtable'] = $pma_table->getStatusInfo(null, true);
291 if ($pma_table->isView()) {
293 $tbl_storage_engine = __('View');
294 $show_comment = null;
296 $tbl_is_view = false;
297 $tbl_storage_engine = $pma_table->getStorageEngine();
298 $show_comment = $pma_table->getComment();
300 $tbl_collation = $pma_table->getCollation();
301 $table_info_num_rows = $pma_table->getNumRows();
302 $row_format = $pma_table->getRowFormat();
303 $auto_increment = $pma_table->getAutoIncrement();
304 $create_options = $pma_table->getCreateOptions();
308 if (isset($result) && empty($message_to_show)) {
309 if (empty($_message)) {
310 if (empty($sql_query)) {
311 $_message = Message
::success(__('No change'));
318 if ($this->response
->isAjax()) {
319 $this->response
->setRequestStatus($_message->isSuccess());
320 $this->response
->addJSON('message', $_message);
321 if (! empty($sql_query)) {
322 $this->response
->addJSON(
324 Generator
::getMessage('', $sql_query)
331 ? Message
::success($_message)
332 : Message
::error($_message);
335 if (! empty($warning_messages)) {
336 $_message = new Message();
337 $_message->addMessagesString($warning_messages);
338 $_message->isError(true);
339 if ($this->response
->isAjax()) {
340 $this->response
->setRequestStatus(false);
341 $this->response
->addJSON('message', $_message);
342 if (! empty($sql_query)) {
343 $this->response
->addJSON(
345 Generator
::getMessage('', $sql_query)
350 unset($warning_messages);
353 if (empty($sql_query)) {
354 $this->response
->addHTML(
355 $_message->getDisplay()
358 $this->response
->addHTML(
359 Generator
::getMessage($_message, $sql_query)
365 $url_params['goto'] = $url_params['back'] = Url
::getFromRoute('/table/operations');
367 $columns = $this->dbi
->getColumns($db, $table);
369 $hideOrderTable = false;
370 // `ALTER TABLE ORDER BY` does not make sense for InnoDB tables that contain
371 // a user-defined clustered index (PRIMARY KEY or NOT NULL UNIQUE index).
372 // InnoDB always orders table rows according to such an index if one is present.
373 if ($tbl_storage_engine == 'INNODB') {
374 $indexes = Index
::getFromTable($table, $db);
375 foreach ($indexes as $name => $idx) {
376 if ($name == 'PRIMARY') {
377 $hideOrderTable = true;
379 } elseif (! $idx->getNonUnique()) {
381 foreach ($idx->getColumns() as $column) {
382 if ($column->getNull()) {
388 $hideOrderTable = true;
396 if (mb_strstr((string) $show_comment, '; InnoDB free') === false) {
397 if (mb_strstr((string) $show_comment, 'InnoDB free') === false) {
398 // only user entered comment
399 $comment = (string) $show_comment;
401 // here we have just InnoDB generated part
405 // remove InnoDB comment from end, just the minimal part (*? is non greedy)
406 $comment = preg_replace('@; InnoDB free:.*?$@', '', (string) $show_comment);
409 $storageEngineSelect = StorageEngine
::getHtmlSelect(
410 'new_tbl_storage_engine',
415 $charsets = Charsets
::getCharsets($this->dbi
, $GLOBALS['cfg']['Server']['DisableIS']);
416 $collations = Charsets
::getCollations($this->dbi
, $GLOBALS['cfg']['Server']['DisableIS']);
418 $hasPackKeys = isset($create_options['pack_keys'])
419 && $pma_table->isEngine(['MYISAM', 'ARIA', 'ISAM']);
420 $hasChecksumAndDelayKeyWrite = $pma_table->isEngine(['MYISAM', 'ARIA']);
421 $hasTransactionalAndPageChecksum = $pma_table->isEngine('ARIA');
422 $hasAutoIncrement = strlen((string) $auto_increment) > 0
423 && $pma_table->isEngine(['MYISAM', 'ARIA', 'INNODB', 'PBXT', 'ROCKSDB']);
425 $rowFormatDropDown = '';
426 $possibleRowFormats = $this->operations
->getPossibleRowFormat();
428 if (isset($possibleRowFormats[$tbl_storage_engine])) {
429 $currentRowFormat = mb_strtoupper($GLOBALS['showtable']['Row_format']);
430 $rowFormatDropDown = DropDown
::generate(
432 $possibleRowFormats[$tbl_storage_engine],
439 if (count($GLOBALS['dblist']->databases
) <= $GLOBALS['cfg']['MaxDbList']) {
440 $databaseList = $GLOBALS['dblist']->databases
->getList();
443 $hasForeignKeys = ! empty($this->relation
->getForeigners($db, $table, '', 'foreign'));
444 $hasPrivileges = $GLOBALS['table_priv'] && $GLOBALS['col_priv'] && $GLOBALS['is_reload_priv'];
445 $switchToNew = isset($_SESSION['pma_switch_to_new']) && $_SESSION['pma_switch_to_new'];
447 $maintenanceActions = $this->operations
->getMaintenanceActions($pma_table);
450 $partitionsChoices = [];
452 if (Partition
::havePartitioning()) {
453 $partitionNames = Partition
::getPartitionNames($db, $table);
454 if ($partitionNames[0] !== null) {
455 $partitions = $partitionNames;
456 $partitionsChoices = $this->operations
->getPartitionMaintenanceChoices();
460 $foreigners = $this->operations
->getForeignersForReferentialIntegrityCheck(
462 (bool) $cfgRelation['relwork']
465 $this->response
->addHTML($this->template
->render('table/operations/index', [
468 'url_params' => $url_params,
469 'columns' => $columns,
470 'hide_order_table' => $hideOrderTable,
471 'table_comment' => $comment,
472 'storage_engine_select' => $storageEngineSelect,
473 'charsets' => $charsets,
474 'collations' => $collations,
475 'tbl_collation' => $tbl_collation,
476 'row_format_dropdown' => $rowFormatDropDown,
477 'has_auto_increment' => $hasAutoIncrement,
478 'auto_increment' => $auto_increment,
479 'has_pack_keys' => $hasPackKeys,
480 'pack_keys' => $create_options['pack_keys'] ??
'',
481 'has_transactional_and_page_checksum' => $hasTransactionalAndPageChecksum,
482 'has_checksum_and_delay_key_write' => $hasChecksumAndDelayKeyWrite,
483 'delay_key_write' => empty($create_options['delay_key_write']) ?
'0' : '1',
484 'transactional' => ($create_options['transactional'] ??
'') == '0' ?
'0' : '1',
485 'page_checksum' => $create_options['page_checksum'] ??
'',
486 'checksum' => empty($create_options['checksum']) ?
'0' : '1',
487 'database_list' => $databaseList,
488 'has_foreign_keys' => $hasForeignKeys,
489 'has_privileges' => $hasPrivileges,
490 'switch_to_new' => $switchToNew,
491 'maintenance_actions' => $maintenanceActions,
492 'is_system_schema' => isset($db_is_system_schema) && $db_is_system_schema,
493 'is_view' => $tbl_is_view,
494 'partitions' => $partitions,
495 'partitions_choices' => $partitionsChoices,
496 'foreigners' => $foreigners,