OperationsController.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Controllers\Table;
  4. use PhpMyAdmin\Charsets;
  5. use PhpMyAdmin\CheckUserPrivileges;
  6. use PhpMyAdmin\DatabaseInterface;
  7. use PhpMyAdmin\DbTableExists;
  8. use PhpMyAdmin\Html\Generator;
  9. use PhpMyAdmin\Index;
  10. use PhpMyAdmin\Message;
  11. use PhpMyAdmin\Operations;
  12. use PhpMyAdmin\Partition;
  13. use PhpMyAdmin\Query\Utilities;
  14. use PhpMyAdmin\Relation;
  15. use PhpMyAdmin\Response;
  16. use PhpMyAdmin\StorageEngine;
  17. use PhpMyAdmin\Template;
  18. use PhpMyAdmin\Url;
  19. use PhpMyAdmin\Util;
  20. use function count;
  21. use function implode;
  22. use function mb_strstr;
  23. use function mb_strtolower;
  24. use function mb_strtoupper;
  25. use function preg_replace;
  26. use function strlen;
  27. class OperationsController extends AbstractController
  28. {
  29. /** @var Operations */
  30. private $operations;
  31. /** @var CheckUserPrivileges */
  32. private $checkUserPrivileges;
  33. /** @var Relation */
  34. private $relation;
  35. /** @var DatabaseInterface */
  36. private $dbi;
  37. /**
  38. * @param Response $response
  39. * @param string $db Database name.
  40. * @param string $table Table name.
  41. * @param DatabaseInterface $dbi
  42. */
  43. public function __construct(
  44. $response,
  45. Template $template,
  46. $db,
  47. $table,
  48. Operations $operations,
  49. CheckUserPrivileges $checkUserPrivileges,
  50. Relation $relation,
  51. $dbi
  52. ) {
  53. parent::__construct($response, $template, $db, $table);
  54. $this->operations = $operations;
  55. $this->checkUserPrivileges = $checkUserPrivileges;
  56. $this->relation = $relation;
  57. $this->dbi = $dbi;
  58. }
  59. public function index(): void
  60. {
  61. global $url_params, $reread_info, $tbl_is_view, $tbl_storage_engine;
  62. global $show_comment, $tbl_collation, $table_info_num_rows, $row_format, $auto_increment, $create_options;
  63. global $table_alters, $warning_messages, $lowerCaseNames, $db, $table, $reload, $result;
  64. global $new_tbl_storage_engine, $sql_query, $message_to_show, $columns, $hideOrderTable, $indexes;
  65. global $notNull, $comment, $err_url, $cfg;
  66. $this->checkUserPrivileges->getPrivileges();
  67. // lower_case_table_names=1 `DB` becomes `db`
  68. $lowerCaseNames = $this->dbi->getLowerCaseNames() === '1';
  69. if ($lowerCaseNames) {
  70. $table = mb_strtolower($table);
  71. }
  72. $pma_table = $this->dbi->getTable($db, $table);
  73. $this->addScriptFiles(['table/operations.js']);
  74. Util::checkParameters(['db', 'table']);
  75. $isSystemSchema = Utilities::isSystemSchema($db);
  76. $url_params = ['db' => $db, 'table' => $table];
  77. $err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
  78. $err_url .= Url::getCommon($url_params, '&');
  79. DbTableExists::check();
  80. $url_params['goto'] = $url_params['back'] = Url::getFromRoute('/table/operations');
  81. /**
  82. * Gets relation settings
  83. */
  84. $cfgRelation = $this->relation->getRelationsParam();
  85. // reselect current db (needed in some cases probably due to
  86. // the calling of PhpMyAdmin\Relation)
  87. $this->dbi->selectDb($db);
  88. $reread_info = $pma_table->getStatusInfo(null, false);
  89. $GLOBALS['showtable'] = $pma_table->getStatusInfo(null, (isset($reread_info) && $reread_info));
  90. if ($pma_table->isView()) {
  91. $tbl_is_view = true;
  92. $tbl_storage_engine = __('View');
  93. $show_comment = null;
  94. } else {
  95. $tbl_is_view = false;
  96. $tbl_storage_engine = $pma_table->getStorageEngine();
  97. $show_comment = $pma_table->getComment();
  98. }
  99. $tbl_collation = $pma_table->getCollation();
  100. $table_info_num_rows = $pma_table->getNumRows();
  101. $row_format = $pma_table->getRowFormat();
  102. $auto_increment = $pma_table->getAutoIncrement();
  103. $create_options = $pma_table->getCreateOptions();
  104. // set initial value of these variables, based on the current table engine
  105. if ($pma_table->isEngine('ARIA')) {
  106. // the value for transactional can be implicit
  107. // (no create option found, in this case it means 1)
  108. // or explicit (option found with a value of 0 or 1)
  109. // ($create_options['transactional'] may have been set by Table class,
  110. // from the $create_options)
  111. $create_options['transactional'] = ($create_options['transactional'] ?? '') == '0'
  112. ? '0'
  113. : '1';
  114. $create_options['page_checksum'] = $create_options['page_checksum'] ?? '';
  115. }
  116. $pma_table = $this->dbi->getTable(
  117. $db,
  118. $table
  119. );
  120. $reread_info = false;
  121. $table_alters = [];
  122. /**
  123. * If the table has to be moved to some other database
  124. */
  125. if (isset($_POST['submit_move']) || isset($_POST['submit_copy'])) {
  126. $message = $this->operations->moveOrCopyTable($db, $table);
  127. if (! $this->response->isAjax()) {
  128. return;
  129. }
  130. $this->response->addJSON('message', $message);
  131. if ($message->isSuccess()) {
  132. $this->response->addJSON('db', $db);
  133. return;
  134. }
  135. $this->response->setRequestStatus(false);
  136. return;
  137. }
  138. /**
  139. * Updates table comment, type and options if required
  140. */
  141. if (isset($_POST['submitoptions'])) {
  142. $_message = '';
  143. $warning_messages = [];
  144. if (isset($_POST['new_name'])) {
  145. // lower_case_table_names=1 `DB` becomes `db`
  146. if ($lowerCaseNames) {
  147. $_POST['new_name'] = mb_strtolower(
  148. $_POST['new_name']
  149. );
  150. }
  151. // Get original names before rename operation
  152. $oldTable = $pma_table->getName();
  153. $oldDb = $pma_table->getDbName();
  154. if ($pma_table->rename($_POST['new_name'])) {
  155. if (isset($_POST['adjust_privileges'])
  156. && ! empty($_POST['adjust_privileges'])
  157. ) {
  158. $this->operations->adjustPrivilegesRenameOrMoveTable(
  159. $oldDb,
  160. $oldTable,
  161. $_POST['db'],
  162. $_POST['new_name']
  163. );
  164. }
  165. // Reselect the original DB
  166. $db = $oldDb;
  167. $this->dbi->selectDb($oldDb);
  168. $_message .= $pma_table->getLastMessage();
  169. $result = true;
  170. $table = $pma_table->getName();
  171. $reread_info = true;
  172. $reload = true;
  173. } else {
  174. $_message .= $pma_table->getLastError();
  175. $result = false;
  176. }
  177. }
  178. if (! empty($_POST['new_tbl_storage_engine'])
  179. && mb_strtoupper($_POST['new_tbl_storage_engine']) !== $tbl_storage_engine
  180. ) {
  181. $new_tbl_storage_engine = mb_strtoupper($_POST['new_tbl_storage_engine']);
  182. if ($pma_table->isEngine('ARIA')) {
  183. $create_options['transactional'] = ($create_options['transactional'] ?? '') == '0'
  184. ? '0'
  185. : '1';
  186. $create_options['page_checksum'] = $create_options['page_checksum'] ?? '';
  187. }
  188. } else {
  189. $new_tbl_storage_engine = '';
  190. }
  191. $row_format = $create_options['row_format'] ?? $pma_table->getRowFormat();
  192. $table_alters = $this->operations->getTableAltersArray(
  193. $pma_table,
  194. $create_options['pack_keys'],
  195. (empty($create_options['checksum']) ? '0' : '1'),
  196. ($create_options['page_checksum'] ?? ''),
  197. (empty($create_options['delay_key_write']) ? '0' : '1'),
  198. $row_format,
  199. $new_tbl_storage_engine,
  200. (isset($create_options['transactional']) && $create_options['transactional'] == '0' ? '0' : '1'),
  201. $tbl_collation
  202. );
  203. if (count($table_alters) > 0) {
  204. $sql_query = 'ALTER TABLE '
  205. . Util::backquote($table);
  206. $sql_query .= "\r\n" . implode("\r\n", $table_alters);
  207. $sql_query .= ';';
  208. $result = (bool) $this->dbi->query($sql_query);
  209. $reread_info = true;
  210. unset($table_alters);
  211. $warning_messages = $this->operations->getWarningMessagesArray();
  212. }
  213. if (isset($_POST['tbl_collation'], $_POST['change_all_collations'])
  214. && ! empty($_POST['tbl_collation'])
  215. && ! empty($_POST['change_all_collations'])
  216. ) {
  217. $this->operations->changeAllColumnsCollation(
  218. $db,
  219. $table,
  220. $_POST['tbl_collation']
  221. );
  222. }
  223. if (isset($_POST['tbl_collation']) && empty($_POST['tbl_collation'])) {
  224. if ($this->response->isAjax()) {
  225. $this->response->setRequestStatus(false);
  226. $this->response->addJSON(
  227. 'message',
  228. Message::error(__('No collation provided.'))
  229. );
  230. return;
  231. }
  232. }
  233. }
  234. /**
  235. * Reordering the table has been requested by the user
  236. */
  237. if (isset($_POST['submitorderby']) && ! empty($_POST['order_field'])) {
  238. [$sql_query, $result] = $this->operations->getQueryAndResultForReorderingTable();
  239. }
  240. /**
  241. * A partition operation has been requested by the user
  242. */
  243. if (isset($_POST['submit_partition'])
  244. && ! empty($_POST['partition_operation'])
  245. ) {
  246. [$sql_query, $result] = $this->operations->getQueryAndResultForPartition();
  247. }
  248. if ($reread_info) {
  249. // to avoid showing the old value (for example the AUTO_INCREMENT) after
  250. // a change, clear the cache
  251. $this->dbi->getCache()->clearTableCache();
  252. $this->dbi->selectDb($db);
  253. $GLOBALS['showtable'] = $pma_table->getStatusInfo(null, true);
  254. if ($pma_table->isView()) {
  255. $tbl_is_view = true;
  256. $tbl_storage_engine = __('View');
  257. $show_comment = null;
  258. } else {
  259. $tbl_is_view = false;
  260. $tbl_storage_engine = $pma_table->getStorageEngine();
  261. $show_comment = $pma_table->getComment();
  262. }
  263. $tbl_collation = $pma_table->getCollation();
  264. $table_info_num_rows = $pma_table->getNumRows();
  265. $row_format = $pma_table->getRowFormat();
  266. $auto_increment = $pma_table->getAutoIncrement();
  267. $create_options = $pma_table->getCreateOptions();
  268. }
  269. unset($reread_info);
  270. if (isset($result) && empty($message_to_show)) {
  271. if (empty($_message)) {
  272. if (empty($sql_query)) {
  273. $_message = Message::success(__('No change'));
  274. } else {
  275. $_message = $result
  276. ? Message::success()
  277. : Message::error();
  278. }
  279. if ($this->response->isAjax()) {
  280. $this->response->setRequestStatus($_message->isSuccess());
  281. $this->response->addJSON('message', $_message);
  282. if (! empty($sql_query)) {
  283. $this->response->addJSON(
  284. 'sql_query',
  285. Generator::getMessage('', $sql_query)
  286. );
  287. }
  288. return;
  289. }
  290. } else {
  291. $_message = $result
  292. ? Message::success($_message)
  293. : Message::error($_message);
  294. }
  295. if (! empty($warning_messages)) {
  296. $_message = new Message();
  297. $_message->addMessagesString($warning_messages);
  298. $_message->isError(true);
  299. if ($this->response->isAjax()) {
  300. $this->response->setRequestStatus(false);
  301. $this->response->addJSON('message', $_message);
  302. if (! empty($sql_query)) {
  303. $this->response->addJSON(
  304. 'sql_query',
  305. Generator::getMessage('', $sql_query)
  306. );
  307. }
  308. return;
  309. }
  310. unset($warning_messages);
  311. }
  312. if (empty($sql_query)) {
  313. $this->response->addHTML(
  314. $_message->getDisplay()
  315. );
  316. } else {
  317. $this->response->addHTML(
  318. Generator::getMessage($_message, $sql_query)
  319. );
  320. }
  321. unset($_message);
  322. }
  323. $url_params['goto'] = $url_params['back'] = Url::getFromRoute('/table/operations');
  324. $columns = $this->dbi->getColumns($db, $table);
  325. $hideOrderTable = false;
  326. // `ALTER TABLE ORDER BY` does not make sense for InnoDB tables that contain
  327. // a user-defined clustered index (PRIMARY KEY or NOT NULL UNIQUE index).
  328. // InnoDB always orders table rows according to such an index if one is present.
  329. if ($tbl_storage_engine === 'INNODB') {
  330. $indexes = Index::getFromTable($table, $db);
  331. foreach ($indexes as $name => $idx) {
  332. if ($name === 'PRIMARY') {
  333. $hideOrderTable = true;
  334. break;
  335. }
  336. if ($idx->getNonUnique()) {
  337. continue;
  338. }
  339. $notNull = true;
  340. foreach ($idx->getColumns() as $column) {
  341. if ($column->getNull()) {
  342. $notNull = false;
  343. break;
  344. }
  345. }
  346. if ($notNull) {
  347. $hideOrderTable = true;
  348. break;
  349. }
  350. }
  351. }
  352. $comment = '';
  353. if (mb_strstr((string) $show_comment, '; InnoDB free') === false) {
  354. if (mb_strstr((string) $show_comment, 'InnoDB free') === false) {
  355. // only user entered comment
  356. $comment = (string) $show_comment;
  357. } else {
  358. // here we have just InnoDB generated part
  359. $comment = '';
  360. }
  361. } else {
  362. // remove InnoDB comment from end, just the minimal part (*? is non greedy)
  363. $comment = preg_replace('@; InnoDB free:.*?$@', '', (string) $show_comment);
  364. }
  365. $storageEngines = StorageEngine::getArray();
  366. $charsets = Charsets::getCharsets($this->dbi, $GLOBALS['cfg']['Server']['DisableIS']);
  367. $collations = Charsets::getCollations($this->dbi, $GLOBALS['cfg']['Server']['DisableIS']);
  368. $hasPackKeys = isset($create_options['pack_keys'])
  369. && $pma_table->isEngine(['MYISAM', 'ARIA', 'ISAM']);
  370. $hasChecksumAndDelayKeyWrite = $pma_table->isEngine(['MYISAM', 'ARIA']);
  371. $hasTransactionalAndPageChecksum = $pma_table->isEngine('ARIA');
  372. $hasAutoIncrement = strlen((string) $auto_increment) > 0
  373. && $pma_table->isEngine(['MYISAM', 'ARIA', 'INNODB', 'PBXT', 'ROCKSDB']);
  374. $possibleRowFormats = $this->operations->getPossibleRowFormat();
  375. $databaseList = [];
  376. if (count($GLOBALS['dblist']->databases) <= $GLOBALS['cfg']['MaxDbList']) {
  377. $databaseList = $GLOBALS['dblist']->databases->getList();
  378. }
  379. $hasForeignKeys = ! empty($this->relation->getForeigners($db, $table, '', 'foreign'));
  380. $hasPrivileges = $GLOBALS['table_priv'] && $GLOBALS['col_priv'] && $GLOBALS['is_reload_priv'];
  381. $switchToNew = isset($_SESSION['pma_switch_to_new']) && $_SESSION['pma_switch_to_new'];
  382. $partitions = [];
  383. $partitionsChoices = [];
  384. if (Partition::havePartitioning()) {
  385. $partitionNames = Partition::getPartitionNames($db, $table);
  386. if ($partitionNames[0] !== null) {
  387. $partitions = $partitionNames;
  388. $partitionsChoices = $this->operations->getPartitionMaintenanceChoices();
  389. }
  390. }
  391. $foreigners = $this->operations->getForeignersForReferentialIntegrityCheck(
  392. $url_params,
  393. (bool) $cfgRelation['relwork']
  394. );
  395. $this->render('table/operations/index', [
  396. 'db' => $db,
  397. 'table' => $table,
  398. 'url_params' => $url_params,
  399. 'columns' => $columns,
  400. 'hide_order_table' => $hideOrderTable,
  401. 'table_comment' => $comment,
  402. 'storage_engine' => $tbl_storage_engine,
  403. 'storage_engines' => $storageEngines,
  404. 'charsets' => $charsets,
  405. 'collations' => $collations,
  406. 'tbl_collation' => $tbl_collation,
  407. 'row_formats' => $possibleRowFormats[$tbl_storage_engine] ?? [],
  408. 'row_format_current' => $GLOBALS['showtable']['Row_format'],
  409. 'has_auto_increment' => $hasAutoIncrement,
  410. 'auto_increment' => $auto_increment,
  411. 'has_pack_keys' => $hasPackKeys,
  412. 'pack_keys' => $create_options['pack_keys'] ?? '',
  413. 'has_transactional_and_page_checksum' => $hasTransactionalAndPageChecksum,
  414. 'has_checksum_and_delay_key_write' => $hasChecksumAndDelayKeyWrite,
  415. 'delay_key_write' => empty($create_options['delay_key_write']) ? '0' : '1',
  416. 'transactional' => ($create_options['transactional'] ?? '') == '0' ? '0' : '1',
  417. 'page_checksum' => $create_options['page_checksum'] ?? '',
  418. 'checksum' => empty($create_options['checksum']) ? '0' : '1',
  419. 'database_list' => $databaseList,
  420. 'has_foreign_keys' => $hasForeignKeys,
  421. 'has_privileges' => $hasPrivileges,
  422. 'switch_to_new' => $switchToNew,
  423. 'is_system_schema' => $isSystemSchema,
  424. 'is_view' => $tbl_is_view,
  425. 'partitions' => $partitions,
  426. 'partitions_choices' => $partitionsChoices,
  427. 'foreigners' => $foreigners,
  428. ]);
  429. }
  430. }