Extract HTML from ListAbstract::getHtmlOptions()
[phpmyadmin.git] / libraries / classes / Controllers / Table / OperationsController.php
blob3f0631addc1699a48a489a77a58dff503fc7f706
1 <?php
2 declare(strict_types=1);
4 namespace PhpMyAdmin\Controllers\Table;
6 use PhpMyAdmin\Charsets;
7 use PhpMyAdmin\CheckUserPrivileges;
8 use PhpMyAdmin\Common;
9 use PhpMyAdmin\Controllers\SqlController;
10 use PhpMyAdmin\DatabaseInterface;
11 use PhpMyAdmin\Html\Forms\Fields\DropDown;
12 use PhpMyAdmin\Html\Generator;
13 use PhpMyAdmin\Index;
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;
21 use PhpMyAdmin\Url;
22 use PhpMyAdmin\Util;
23 use function count;
24 use function implode;
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 */
33 private $operations;
35 /** @var CheckUserPrivileges */
36 private $checkUserPrivileges;
38 /** @var Relation */
39 private $relation;
41 /**
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(
52 $response,
53 $dbi,
54 Template $template,
55 $db,
56 $table,
57 Operations $operations,
58 CheckUserPrivileges $checkUserPrivileges,
59 Relation $relation
60 ) {
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');
91 /**
92 * Runs common work
94 Common::table();
95 $url_params['goto'] = $url_params['back'] = Url::getFromRoute('/table/operations');
96 $url_query .= Url::getCommon($url_params, '&');
98 /**
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()) {
110 $tbl_is_view = true;
111 $tbl_storage_engine = __('View');
112 $show_comment = null;
113 } else {
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'
132 ? '0'
133 : '1';
134 $create_options['page_checksum'] = $create_options['page_checksum'] ?? '';
137 $pma_table = $this->dbi->getTable(
138 $db,
139 $table
141 $reread_info = false;
142 $table_alters = [];
145 * If the table has to be moved to some other database
147 if (isset($_POST['submit_move']) || isset($_POST['submit_copy'])) {
148 //$_message = '';
149 $this->operations->moveOrCopyTable($db, $table);
150 // This was ended in an Ajax call
151 return;
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();
161 unset($result);
164 * Updates table comment, type and options if required
166 if (isset($_POST['submitoptions'])) {
167 $_message = '';
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(
174 $_POST['new_name']
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(
186 $oldDb,
187 $oldTable,
188 $_POST['db'],
189 $_POST['new_name']
193 // Reselect the original DB
194 $db = $oldDb;
195 $this->dbi->selectDb($oldDb);
196 $_message .= $pma_table->getLastMessage();
197 $result = true;
198 $table = $pma_table->getName();
199 $reread_info = true;
200 $reload = true;
201 } else {
202 $_message .= $pma_table->getLastError();
203 $result = false;
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'
214 ? '0'
215 : '1';
216 $create_options['page_checksum'] = $create_options['page_checksum'] ?? '';
218 } else {
219 $new_tbl_storage_engine = '';
222 $row_format = $create_options['row_format'] ?? $pma_table->getRowFormat();
224 $table_alters = $this->operations->getTableAltersArray(
225 $pma_table,
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'),
230 $row_format,
231 $new_tbl_storage_engine,
232 (isset($create_options['transactional']) && $create_options['transactional'] == '0' ? '0' : '1'),
233 $tbl_collation
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);
240 $sql_query .= ';';
241 $result = (bool) $this->dbi->query($sql_query);
242 $reread_info = true;
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(
252 $db,
253 $table,
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(
262 'message',
263 Message::error(__('No collation provided.'))
265 return;
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();
274 } // end if
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();
283 } // end if
285 if ($reread_info) {
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()) {
292 $tbl_is_view = true;
293 $tbl_storage_engine = __('View');
294 $show_comment = null;
295 } else {
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();
306 unset($reread_info);
308 if (isset($result) && empty($message_to_show)) {
309 if (empty($_message)) {
310 if (empty($sql_query)) {
311 $_message = Message::success(__('No change'));
312 } else {
313 $_message = $result
314 ? Message::success()
315 : Message::error();
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(
323 'sql_query',
324 Generator::getMessage('', $sql_query)
327 return;
329 } else {
330 $_message = $result
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(
344 'sql_query',
345 Generator::getMessage('', $sql_query)
348 return;
350 unset($warning_messages);
353 if (empty($sql_query)) {
354 $this->response->addHTML(
355 $_message->getDisplay()
357 } else {
358 $this->response->addHTML(
359 Generator::getMessage($_message, $sql_query)
362 unset($_message);
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;
378 break;
379 } elseif (! $idx->getNonUnique()) {
380 $notNull = true;
381 foreach ($idx->getColumns() as $column) {
382 if ($column->getNull()) {
383 $notNull = false;
384 break;
387 if ($notNull) {
388 $hideOrderTable = true;
389 break;
395 $comment = '';
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;
400 } else {
401 // here we have just InnoDB generated part
402 $comment = '';
404 } else {
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',
411 null,
412 $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(
431 'new_row_format',
432 $possibleRowFormats[$tbl_storage_engine],
433 $currentRowFormat,
434 'new_row_format'
438 $databaseList = [];
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);
449 $partitions = [];
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(
461 $url_params,
462 (bool) $cfgRelation['relwork']
465 $this->response->addHTML($this->template->render('table/operations/index', [
466 'db' => $db,
467 'table' => $table,
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,
497 ]));