PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/classes/Controllers/Table/OperationsController.php

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