StructureController.php 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Controllers\Database;
  4. use PhpMyAdmin\Charsets;
  5. use PhpMyAdmin\CheckUserPrivileges;
  6. use PhpMyAdmin\Config\PageSettings;
  7. use PhpMyAdmin\Core;
  8. use PhpMyAdmin\Database\CentralColumns;
  9. use PhpMyAdmin\DatabaseInterface;
  10. use PhpMyAdmin\Html\Generator;
  11. use PhpMyAdmin\Message;
  12. use PhpMyAdmin\Operations;
  13. use PhpMyAdmin\RecentFavoriteTable;
  14. use PhpMyAdmin\Relation;
  15. use PhpMyAdmin\RelationCleanup;
  16. use PhpMyAdmin\Replication;
  17. use PhpMyAdmin\ReplicationInfo;
  18. use PhpMyAdmin\Response;
  19. use PhpMyAdmin\Sanitize;
  20. use PhpMyAdmin\Sql;
  21. use PhpMyAdmin\Table;
  22. use PhpMyAdmin\Template;
  23. use PhpMyAdmin\Tracker;
  24. use PhpMyAdmin\Transformations;
  25. use PhpMyAdmin\Url;
  26. use PhpMyAdmin\Util;
  27. use function array_search;
  28. use function ceil;
  29. use function count;
  30. use function htmlspecialchars;
  31. use function implode;
  32. use function in_array;
  33. use function is_string;
  34. use function json_decode;
  35. use function json_encode;
  36. use function max;
  37. use function mb_strlen;
  38. use function mb_substr;
  39. use function md5;
  40. use function preg_match;
  41. use function preg_quote;
  42. use function sha1;
  43. use function sprintf;
  44. use function str_replace;
  45. use function strlen;
  46. use function strtotime;
  47. use function urlencode;
  48. /**
  49. * Handles database structure logic
  50. */
  51. class StructureController extends AbstractController
  52. {
  53. /** @var int Number of tables */
  54. protected $numTables;
  55. /** @var int Current position in the list */
  56. protected $position;
  57. /** @var bool DB is information_schema */
  58. protected $dbIsSystemSchema;
  59. /** @var int Number of tables */
  60. protected $totalNumTables;
  61. /** @var array Tables in the database */
  62. protected $tables;
  63. /** @var bool whether stats show or not */
  64. protected $isShowStats;
  65. /** @var Relation */
  66. private $relation;
  67. /** @var Replication */
  68. private $replication;
  69. /** @var RelationCleanup */
  70. private $relationCleanup;
  71. /** @var Operations */
  72. private $operations;
  73. /** @var ReplicationInfo */
  74. private $replicationInfo;
  75. /** @var DatabaseInterface */
  76. private $dbi;
  77. /**
  78. * @param Response $response
  79. * @param string $db Database name
  80. * @param Relation $relation
  81. * @param Replication $replication
  82. * @param DatabaseInterface $dbi
  83. */
  84. public function __construct(
  85. $response,
  86. Template $template,
  87. $db,
  88. $relation,
  89. $replication,
  90. RelationCleanup $relationCleanup,
  91. Operations $operations,
  92. $dbi
  93. ) {
  94. parent::__construct($response, $template, $db);
  95. $this->relation = $relation;
  96. $this->replication = $replication;
  97. $this->relationCleanup = $relationCleanup;
  98. $this->operations = $operations;
  99. $this->dbi = $dbi;
  100. $this->replicationInfo = new ReplicationInfo($this->dbi);
  101. }
  102. /**
  103. * Retrieves database information for further use
  104. *
  105. * @param string $subPart Page part name
  106. */
  107. private function getDatabaseInfo(string $subPart): void
  108. {
  109. [$tables, $numTables, $totalNumTables, , $isShowStats, $dbIsSystemSchema, , , $position]
  110. = Util::getDbInfo($this->db, $subPart);
  111. $this->tables = $tables;
  112. $this->numTables = $numTables;
  113. $this->position = $position;
  114. $this->dbIsSystemSchema = $dbIsSystemSchema;
  115. $this->totalNumTables = $totalNumTables;
  116. $this->isShowStats = $isShowStats;
  117. }
  118. public function index(): void
  119. {
  120. global $cfg, $db, $err_url;
  121. $parameters = [
  122. 'sort' => $_REQUEST['sort'] ?? null,
  123. 'sort_order' => $_REQUEST['sort_order'] ?? null,
  124. ];
  125. Util::checkParameters(['db']);
  126. $err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
  127. $err_url .= Url::getCommon(['db' => $db], '&');
  128. if (! $this->hasDatabase()) {
  129. return;
  130. }
  131. $this->addScriptFiles(['database/structure.js', 'table/change.js']);
  132. // Gets the database structure
  133. $this->getDatabaseInfo('_structure');
  134. // Checks if there are any tables to be shown on current page.
  135. // If there are no tables, the user is redirected to the last page
  136. // having any.
  137. if ($this->totalNumTables > 0 && $this->position > $this->totalNumTables) {
  138. $uri = './index.php?route=/database/structure' . Url::getCommonRaw([
  139. 'db' => $this->db,
  140. 'pos' => max(0, $this->totalNumTables - $cfg['MaxTableList']),
  141. 'reload' => 1,
  142. ], '&');
  143. Core::sendHeaderLocation($uri);
  144. }
  145. $this->replicationInfo->load($_POST['master_connection'] ?? null);
  146. $replicaInfo = $this->replicationInfo->getReplicaInfo();
  147. $pageSettings = new PageSettings('DbStructure');
  148. $this->response->addHTML($pageSettings->getErrorHTML());
  149. $this->response->addHTML($pageSettings->getHTML());
  150. if ($this->numTables > 0) {
  151. $urlParams = [
  152. 'pos' => $this->position,
  153. 'db' => $this->db,
  154. ];
  155. if (isset($parameters['sort'])) {
  156. $urlParams['sort'] = $parameters['sort'];
  157. }
  158. if (isset($parameters['sort_order'])) {
  159. $urlParams['sort_order'] = $parameters['sort_order'];
  160. }
  161. $listNavigator = Generator::getListNavigator(
  162. $this->totalNumTables,
  163. $this->position,
  164. $urlParams,
  165. Url::getFromRoute('/database/structure'),
  166. 'frame_content',
  167. $cfg['MaxTableList']
  168. );
  169. $tableList = $this->displayTableList($replicaInfo);
  170. }
  171. $createTable = '';
  172. if (empty($this->dbIsSystemSchema)) {
  173. $checkUserPrivileges = new CheckUserPrivileges($this->dbi);
  174. $checkUserPrivileges->getPrivileges();
  175. $createTable = $this->template->render('database/create_table', ['db' => $this->db]);
  176. }
  177. $this->render('database/structure/index', [
  178. 'database' => $this->db,
  179. 'has_tables' => $this->numTables > 0,
  180. 'list_navigator_html' => $listNavigator ?? '',
  181. 'table_list_html' => $tableList ?? '',
  182. 'is_system_schema' => ! empty($this->dbIsSystemSchema),
  183. 'create_table_html' => $createTable,
  184. ]);
  185. }
  186. public function addRemoveFavoriteTablesAction(): void
  187. {
  188. global $cfg, $db, $err_url;
  189. $parameters = [
  190. 'favorite_table' => $_REQUEST['favorite_table'] ?? null,
  191. 'favoriteTables' => $_REQUEST['favoriteTables'] ?? null,
  192. 'sync_favorite_tables' => $_REQUEST['sync_favorite_tables'] ?? null,
  193. ];
  194. Util::checkParameters(['db']);
  195. $err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
  196. $err_url .= Url::getCommon(['db' => $db], '&');
  197. if (! $this->hasDatabase() || ! $this->response->isAjax()) {
  198. return;
  199. }
  200. $favoriteInstance = RecentFavoriteTable::getInstance('favorite');
  201. if (isset($parameters['favoriteTables'])) {
  202. $favoriteTables = json_decode($parameters['favoriteTables'], true);
  203. } else {
  204. $favoriteTables = [];
  205. }
  206. // Required to keep each user's preferences separate.
  207. $user = sha1($cfg['Server']['user']);
  208. // Request for Synchronization of favorite tables.
  209. if (isset($parameters['sync_favorite_tables'])) {
  210. $cfgRelation = $this->relation->getRelationsParam();
  211. if ($cfgRelation['favoritework']) {
  212. $this->response->addJSON($this->synchronizeFavoriteTables(
  213. $favoriteInstance,
  214. $user,
  215. $favoriteTables
  216. ));
  217. }
  218. return;
  219. }
  220. $changes = true;
  221. $favoriteTable = $parameters['favorite_table'] ?? '';
  222. $alreadyFavorite = $this->checkFavoriteTable($favoriteTable);
  223. if (isset($_REQUEST['remove_favorite'])) {
  224. if ($alreadyFavorite) {
  225. // If already in favorite list, remove it.
  226. $favoriteInstance->remove($this->db, $favoriteTable);
  227. $alreadyFavorite = false; // for favorite_anchor template
  228. }
  229. } elseif (isset($_REQUEST['add_favorite'])) {
  230. if (! $alreadyFavorite) {
  231. $numTables = count($favoriteInstance->getTables());
  232. if ($numTables == $cfg['NumFavoriteTables']) {
  233. $changes = false;
  234. } else {
  235. // Otherwise add to favorite list.
  236. $favoriteInstance->add($this->db, $favoriteTable);
  237. $alreadyFavorite = true; // for favorite_anchor template
  238. }
  239. }
  240. }
  241. $favoriteTables[$user] = $favoriteInstance->getTables();
  242. $json = [];
  243. $json['changes'] = $changes;
  244. if (! $changes) {
  245. $json['message'] = $this->template->render('components/error_message', [
  246. 'msg' => __('Favorite List is full!'),
  247. ]);
  248. $this->response->addJSON($json);
  249. return;
  250. }
  251. // Check if current table is already in favorite list.
  252. $favoriteParams = [
  253. 'db' => $this->db,
  254. 'ajax_request' => true,
  255. 'favorite_table' => $favoriteTable,
  256. ($alreadyFavorite ? 'remove' : 'add') . '_favorite' => true,
  257. ];
  258. $json['user'] = $user;
  259. $json['favoriteTables'] = json_encode($favoriteTables);
  260. $json['list'] = $favoriteInstance->getHtmlList();
  261. $json['anchor'] = $this->template->render('database/structure/favorite_anchor', [
  262. 'table_name_hash' => md5($favoriteTable),
  263. 'db_table_name_hash' => md5($this->db . '.' . $favoriteTable),
  264. 'fav_params' => $favoriteParams,
  265. 'already_favorite' => $alreadyFavorite,
  266. ]);
  267. $this->response->addJSON($json);
  268. }
  269. /**
  270. * Handles request for real row count on database level view page.
  271. */
  272. public function handleRealRowCountRequestAction(): void
  273. {
  274. global $cfg, $db, $err_url;
  275. $parameters = [
  276. 'real_row_count_all' => $_REQUEST['real_row_count_all'] ?? null,
  277. 'table' => $_REQUEST['table'] ?? null,
  278. ];
  279. Util::checkParameters(['db']);
  280. $err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
  281. $err_url .= Url::getCommon(['db' => $db], '&');
  282. if (! $this->hasDatabase() || ! $this->response->isAjax()) {
  283. return;
  284. }
  285. // If there is a request to update all table's row count.
  286. if (! isset($parameters['real_row_count_all'])) {
  287. // Get the real row count for the table.
  288. $realRowCount = (int) $this->dbi
  289. ->getTable($this->db, (string) $parameters['table'])
  290. ->getRealRowCountTable();
  291. // Format the number.
  292. $realRowCount = Util::formatNumber($realRowCount, 0);
  293. $this->response->addJSON(['real_row_count' => $realRowCount]);
  294. return;
  295. }
  296. // Array to store the results.
  297. $realRowCountAll = [];
  298. // Iterate over each table and fetch real row count.
  299. foreach ($this->tables as $table) {
  300. $rowCount = $this->dbi
  301. ->getTable($this->db, $table['TABLE_NAME'])
  302. ->getRealRowCountTable();
  303. $realRowCountAll[] = [
  304. 'table' => $table['TABLE_NAME'],
  305. 'row_count' => $rowCount,
  306. ];
  307. }
  308. $this->response->addJSON(['real_row_count_all' => json_encode($realRowCountAll)]);
  309. }
  310. public function copyTable(): void
  311. {
  312. global $db, $message;
  313. $selected = $_POST['selected'] ?? [];
  314. $targetDb = $_POST['target_db'] ?? null;
  315. $selectedCount = count($selected);
  316. for ($i = 0; $i < $selectedCount; $i++) {
  317. Table::moveCopy(
  318. $db,
  319. $selected[$i],
  320. $targetDb,
  321. $selected[$i],
  322. $_POST['what'],
  323. false,
  324. 'one_table'
  325. );
  326. if (empty($_POST['adjust_privileges'])) {
  327. continue;
  328. }
  329. $this->operations->adjustPrivilegesCopyTable(
  330. $db,
  331. $selected[$i],
  332. $targetDb,
  333. $selected[$i]
  334. );
  335. }
  336. $message = Message::success();
  337. if (empty($_POST['message'])) {
  338. $_POST['message'] = $message;
  339. }
  340. $this->index();
  341. }
  342. /**
  343. * @param array $replicaInfo
  344. */
  345. protected function displayTableList($replicaInfo): string
  346. {
  347. global $PMA_Theme;
  348. $html = '';
  349. // filtering
  350. $html .= $this->template->render('filter', ['filter_value' => '']);
  351. $i = $sum_entries = 0;
  352. $overhead_check = false;
  353. $create_time_all = '';
  354. $update_time_all = '';
  355. $check_time_all = '';
  356. $num_columns = $GLOBALS['cfg']['PropertiesNumColumns'] > 1
  357. ? ceil($this->numTables / $GLOBALS['cfg']['PropertiesNumColumns']) + 1
  358. : 0;
  359. $row_count = 0;
  360. $sum_size = 0;
  361. $overhead_size = 0;
  362. $hidden_fields = [];
  363. $overall_approx_rows = false;
  364. $structure_table_rows = [];
  365. foreach ($this->tables as $keyname => $current_table) {
  366. // Get valid statistics whatever is the table type
  367. $drop_query = '';
  368. $drop_message = '';
  369. $overhead = '';
  370. $input_class = ['checkall'];
  371. // Sets parameters for links
  372. $tableUrlParams = [
  373. 'db' => $this->db,
  374. 'table' => $current_table['TABLE_NAME'],
  375. ];
  376. // do not list the previous table's size info for a view
  377. [
  378. $current_table,
  379. $formatted_size,
  380. $unit,
  381. $formatted_overhead,
  382. $overhead_unit,
  383. $overhead_size,
  384. $table_is_view,
  385. $sum_size,
  386. ] = $this->getStuffForEngineTypeTable(
  387. $current_table,
  388. $sum_size,
  389. $overhead_size
  390. );
  391. $curTable = $this->dbi
  392. ->getTable($this->db, $current_table['TABLE_NAME']);
  393. if (! $curTable->isMerge()) {
  394. $sum_entries += $current_table['TABLE_ROWS'];
  395. }
  396. $collationDefinition = '---';
  397. if (isset($current_table['Collation'])) {
  398. $tableCollation = Charsets::findCollationByName(
  399. $this->dbi,
  400. $GLOBALS['cfg']['Server']['DisableIS'],
  401. $current_table['Collation']
  402. );
  403. if ($tableCollation !== null) {
  404. $collationDefinition = $this->template->render('database/structure/collation_definition', [
  405. 'valueTitle' => $tableCollation->getDescription(),
  406. 'value' => $tableCollation->getName(),
  407. ]);
  408. }
  409. }
  410. if ($this->isShowStats) {
  411. $overhead = '-';
  412. if ($formatted_overhead != '') {
  413. $overhead = $this->template->render('database/structure/overhead', [
  414. 'table_url_params' => $tableUrlParams,
  415. 'formatted_overhead' => $formatted_overhead,
  416. 'overhead_unit' => $overhead_unit,
  417. ]);
  418. $overhead_check = true;
  419. $input_class[] = 'tbl-overhead';
  420. }
  421. }
  422. if ($GLOBALS['cfg']['ShowDbStructureCharset']) {
  423. $charset = '';
  424. if (isset($tableCollation)) {
  425. $charset = $tableCollation->getCharset();
  426. }
  427. }
  428. if ($GLOBALS['cfg']['ShowDbStructureCreation']) {
  429. $create_time = $current_table['Create_time'] ?? '';
  430. if ($create_time
  431. && (! $create_time_all
  432. || $create_time < $create_time_all)
  433. ) {
  434. $create_time_all = $create_time;
  435. }
  436. }
  437. if ($GLOBALS['cfg']['ShowDbStructureLastUpdate']) {
  438. $update_time = $current_table['Update_time'] ?? '';
  439. if ($update_time
  440. && (! $update_time_all
  441. || $update_time < $update_time_all)
  442. ) {
  443. $update_time_all = $update_time;
  444. }
  445. }
  446. if ($GLOBALS['cfg']['ShowDbStructureLastCheck']) {
  447. $check_time = $current_table['Check_time'] ?? '';
  448. if ($check_time
  449. && (! $check_time_all
  450. || $check_time < $check_time_all)
  451. ) {
  452. $check_time_all = $check_time;
  453. }
  454. }
  455. $truename = $current_table['TABLE_NAME'];
  456. $i++;
  457. $row_count++;
  458. if ($table_is_view) {
  459. $hidden_fields[] = '<input type="hidden" name="views[]" value="'
  460. . htmlspecialchars($current_table['TABLE_NAME']) . '">';
  461. }
  462. /*
  463. * Always activate links for Browse, Search and Empty, even if
  464. * the icons are greyed, because
  465. * 1. for views, we don't know the number of rows at this point
  466. * 2. for tables, another source could have populated them since the
  467. * page was generated
  468. *
  469. * I could have used the PHP ternary conditional operator but I find
  470. * the code easier to read without this operator.
  471. */
  472. $may_have_rows = $current_table['TABLE_ROWS'] > 0 || $table_is_view;
  473. if (! $this->dbIsSystemSchema) {
  474. $drop_query = sprintf(
  475. 'DROP %s %s',
  476. $table_is_view || $current_table['ENGINE'] == null ? 'VIEW'
  477. : 'TABLE',
  478. Util::backquote(
  479. $current_table['TABLE_NAME']
  480. )
  481. );
  482. $drop_message = sprintf(
  483. ($table_is_view || $current_table['ENGINE'] == null
  484. ? __('View %s has been dropped.')
  485. : __('Table %s has been dropped.')),
  486. str_replace(
  487. ' ',
  488. '&nbsp;',
  489. htmlspecialchars($current_table['TABLE_NAME'])
  490. )
  491. );
  492. }
  493. if ($num_columns > 0
  494. && $this->numTables > $num_columns
  495. && ($row_count % $num_columns) == 0
  496. ) {
  497. $row_count = 1;
  498. $html .= $this->template->render('database/structure/table_header', [
  499. 'db' => $this->db,
  500. 'db_is_system_schema' => $this->dbIsSystemSchema,
  501. 'replication' => $replicaInfo['status'],
  502. 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
  503. 'is_show_stats' => $this->isShowStats,
  504. 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
  505. 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
  506. 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
  507. 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
  508. 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
  509. 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
  510. 'structure_table_rows' => $structure_table_rows,
  511. ]);
  512. $structure_table_rows = [];
  513. }
  514. [$approx_rows, $show_superscript] = $this->isRowCountApproximated(
  515. $current_table,
  516. $table_is_view
  517. );
  518. [$do, $ignored] = $this->getReplicationStatus($replicaInfo, $truename);
  519. $structure_table_rows[] = [
  520. 'table_name_hash' => md5($current_table['TABLE_NAME']),
  521. 'db_table_name_hash' => md5($this->db . '.' . $current_table['TABLE_NAME']),
  522. 'db' => $this->db,
  523. 'curr' => $i,
  524. 'input_class' => implode(' ', $input_class),
  525. 'table_is_view' => $table_is_view,
  526. 'current_table' => $current_table,
  527. 'may_have_rows' => $may_have_rows,
  528. 'browse_table_label_title' => htmlspecialchars($current_table['TABLE_COMMENT']),
  529. 'browse_table_label_truename' => $truename,
  530. 'empty_table_sql_query' => 'TRUNCATE ' . Util::backquote(
  531. $current_table['TABLE_NAME']
  532. ),
  533. 'empty_table_message_to_show' => urlencode(
  534. sprintf(
  535. __('Table %s has been emptied.'),
  536. htmlspecialchars(
  537. $current_table['TABLE_NAME']
  538. )
  539. )
  540. ),
  541. 'tracking_icon' => $this->getTrackingIcon($truename),
  542. 'server_slave_status' => $replicaInfo['status'],
  543. 'table_url_params' => $tableUrlParams,
  544. 'db_is_system_schema' => $this->dbIsSystemSchema,
  545. 'drop_query' => $drop_query,
  546. 'drop_message' => $drop_message,
  547. 'collation' => $collationDefinition,
  548. 'formatted_size' => $formatted_size,
  549. 'unit' => $unit,
  550. 'overhead' => $overhead,
  551. 'create_time' => isset($create_time) && $create_time
  552. ? Util::localisedDate(strtotime($create_time)) : '-',
  553. 'update_time' => isset($update_time) && $update_time
  554. ? Util::localisedDate(strtotime($update_time)) : '-',
  555. 'check_time' => isset($check_time) && $check_time
  556. ? Util::localisedDate(strtotime($check_time)) : '-',
  557. 'charset' => $charset ?? '',
  558. 'is_show_stats' => $this->isShowStats,
  559. 'ignored' => $ignored,
  560. 'do' => $do,
  561. 'approx_rows' => $approx_rows,
  562. 'show_superscript' => $show_superscript,
  563. 'already_favorite' => $this->checkFavoriteTable(
  564. $current_table['TABLE_NAME']
  565. ),
  566. 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
  567. 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
  568. 'limit_chars' => $GLOBALS['cfg']['LimitChars'],
  569. 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
  570. 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
  571. 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
  572. 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
  573. 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
  574. ];
  575. $overall_approx_rows = $overall_approx_rows || $approx_rows;
  576. }
  577. $databaseCollation = [];
  578. $databaseCharset = '';
  579. $collation = Charsets::findCollationByName(
  580. $this->dbi,
  581. $GLOBALS['cfg']['Server']['DisableIS'],
  582. $this->dbi->getDbCollation($this->db)
  583. );
  584. if ($collation !== null) {
  585. $databaseCollation = [
  586. 'name' => $collation->getName(),
  587. 'description' => $collation->getDescription(),
  588. ];
  589. $databaseCharset = $collation->getCharset();
  590. }
  591. return $html . $this->template->render('database/structure/table_header', [
  592. 'db' => $this->db,
  593. 'db_is_system_schema' => $this->dbIsSystemSchema,
  594. 'replication' => $replicaInfo['status'],
  595. 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
  596. 'is_show_stats' => $this->isShowStats,
  597. 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
  598. 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
  599. 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
  600. 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
  601. 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
  602. 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
  603. 'structure_table_rows' => $structure_table_rows,
  604. 'body_for_table_summary' => [
  605. 'num_tables' => $this->numTables,
  606. 'server_slave_status' => $replicaInfo['status'],
  607. 'db_is_system_schema' => $this->dbIsSystemSchema,
  608. 'sum_entries' => $sum_entries,
  609. 'database_collation' => $databaseCollation,
  610. 'is_show_stats' => $this->isShowStats,
  611. 'database_charset' => $databaseCharset,
  612. 'sum_size' => $sum_size,
  613. 'overhead_size' => $overhead_size,
  614. 'create_time_all' => $create_time_all ? Util::localisedDate(strtotime($create_time_all)) : '-',
  615. 'update_time_all' => $update_time_all ? Util::localisedDate(strtotime($update_time_all)) : '-',
  616. 'check_time_all' => $check_time_all ? Util::localisedDate(strtotime($check_time_all)) : '-',
  617. 'approx_rows' => $overall_approx_rows,
  618. 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
  619. 'db' => $GLOBALS['db'],
  620. 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
  621. 'dbi' => $this->dbi,
  622. 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
  623. 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
  624. 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
  625. 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
  626. 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
  627. ],
  628. 'check_all_tables' => [
  629. 'theme_image_path' => $PMA_Theme->getImgPath(),
  630. 'text_dir' => $GLOBALS['text_dir'],
  631. 'overhead_check' => $overhead_check,
  632. 'db_is_system_schema' => $this->dbIsSystemSchema,
  633. 'hidden_fields' => $hidden_fields,
  634. 'disable_multi_table' => $GLOBALS['cfg']['DisableMultiTableMaintenance'],
  635. 'central_columns_work' => $GLOBALS['cfgRelation']['centralcolumnswork'] ?? null,
  636. ],
  637. ]);
  638. }
  639. /**
  640. * Returns the tracking icon if the table is tracked
  641. *
  642. * @param string $table table name
  643. *
  644. * @return string HTML for tracking icon
  645. */
  646. protected function getTrackingIcon(string $table): string
  647. {
  648. $tracking_icon = '';
  649. if (Tracker::isActive()) {
  650. $is_tracked = Tracker::isTracked($this->db, $table);
  651. if ($is_tracked
  652. || Tracker::getVersion($this->db, $table) > 0
  653. ) {
  654. $tracking_icon = $this->template->render('database/structure/tracking_icon', [
  655. 'db' => $this->db,
  656. 'table' => $table,
  657. 'is_tracked' => $is_tracked,
  658. ]);
  659. }
  660. }
  661. return $tracking_icon;
  662. }
  663. /**
  664. * Returns whether the row count is approximated
  665. *
  666. * @param array $current_table array containing details about the table
  667. * @param bool $table_is_view whether the table is a view
  668. *
  669. * @return array
  670. */
  671. protected function isRowCountApproximated(
  672. array $current_table,
  673. bool $table_is_view
  674. ): array {
  675. $approx_rows = false;
  676. $show_superscript = '';
  677. // there is a null value in the ENGINE
  678. // - when the table needs to be repaired, or
  679. // - when it's a view
  680. // so ensure that we'll display "in use" below for a table
  681. // that needs to be repaired
  682. if (isset($current_table['TABLE_ROWS'])
  683. && ($current_table['ENGINE'] != null || $table_is_view)
  684. ) {
  685. // InnoDB/TokuDB table: we did not get an accurate row count
  686. $approx_rows = ! $table_is_view
  687. && in_array($current_table['ENGINE'], ['InnoDB', 'TokuDB'])
  688. && ! $current_table['COUNTED'];
  689. if ($table_is_view
  690. && $current_table['TABLE_ROWS'] >= $GLOBALS['cfg']['MaxExactCountViews']
  691. ) {
  692. $approx_rows = true;
  693. $show_superscript = Generator::showHint(
  694. Sanitize::sanitizeMessage(
  695. sprintf(
  696. __(
  697. 'This view has at least this number of '
  698. . 'rows. Please refer to %sdocumentation%s.'
  699. ),
  700. '[doc@cfg_MaxExactCountViews]',
  701. '[/doc]'
  702. )
  703. )
  704. );
  705. }
  706. }
  707. return [
  708. $approx_rows,
  709. $show_superscript,
  710. ];
  711. }
  712. /**
  713. * Returns the replication status of the table.
  714. *
  715. * @param array $replicaInfo
  716. * @param string $table table name
  717. *
  718. * @return array
  719. */
  720. protected function getReplicationStatus($replicaInfo, string $table): array
  721. {
  722. $do = $ignored = false;
  723. if ($replicaInfo['status']) {
  724. $nbServSlaveDoDb = count(
  725. $replicaInfo['Do_DB']
  726. );
  727. $nbServSlaveIgnoreDb = count(
  728. $replicaInfo['Ignore_DB']
  729. );
  730. $searchDoDBInTruename = array_search(
  731. $table,
  732. $replicaInfo['Do_DB']
  733. );
  734. $searchDoDBInDB = array_search(
  735. $this->db,
  736. $replicaInfo['Do_DB']
  737. );
  738. $do = (is_string($searchDoDBInTruename) && strlen($searchDoDBInTruename) > 0)
  739. || (is_string($searchDoDBInDB) && strlen($searchDoDBInDB) > 0)
  740. || ($nbServSlaveDoDb == 0 && $nbServSlaveIgnoreDb == 0)
  741. || $this->hasTable(
  742. $replicaInfo['Wild_Do_Table'],
  743. $table
  744. );
  745. $searchDb = array_search(
  746. $this->db,
  747. $replicaInfo['Ignore_DB']
  748. );
  749. $searchTable = array_search(
  750. $table,
  751. $replicaInfo['Ignore_Table']
  752. );
  753. $ignored = (is_string($searchTable) && strlen($searchTable) > 0)
  754. || (is_string($searchDb) && strlen($searchDb) > 0)
  755. || $this->hasTable(
  756. $replicaInfo['Wild_Ignore_Table'],
  757. $table
  758. );
  759. }
  760. return [
  761. $do,
  762. $ignored,
  763. ];
  764. }
  765. /**
  766. * Synchronize favorite tables
  767. *
  768. * @param RecentFavoriteTable $favoriteInstance Instance of this class
  769. * @param string $user The user hash
  770. * @param array $favoriteTables Existing favorites
  771. *
  772. * @return array
  773. */
  774. protected function synchronizeFavoriteTables(
  775. RecentFavoriteTable $favoriteInstance,
  776. string $user,
  777. array $favoriteTables
  778. ): array {
  779. $favoriteInstanceTables = $favoriteInstance->getTables();
  780. if (empty($favoriteInstanceTables)
  781. && isset($favoriteTables[$user])
  782. ) {
  783. foreach ($favoriteTables[$user] as $key => $value) {
  784. $favoriteInstance->add($value['db'], $value['table']);
  785. }
  786. }
  787. $favoriteTables[$user] = $favoriteInstance->getTables();
  788. $json = [
  789. 'favoriteTables' => json_encode($favoriteTables),
  790. 'list' => $favoriteInstance->getHtmlList(),
  791. ];
  792. $serverId = $GLOBALS['server'];
  793. // Set flag when localStorage and pmadb(if present) are in sync.
  794. $_SESSION['tmpval']['favorites_synced'][$serverId] = true;
  795. return $json;
  796. }
  797. /**
  798. * Function to check if a table is already in favorite list.
  799. *
  800. * @param string $currentTable current table
  801. */
  802. protected function checkFavoriteTable(string $currentTable): bool
  803. {
  804. // ensure $_SESSION['tmpval']['favoriteTables'] is initialized
  805. RecentFavoriteTable::getInstance('favorite');
  806. $favoriteTables = $_SESSION['tmpval']['favoriteTables'][$GLOBALS['server']] ?? [];
  807. foreach ($favoriteTables as $value) {
  808. if ($value['db'] == $this->db && $value['table'] == $currentTable) {
  809. return true;
  810. }
  811. }
  812. return false;
  813. }
  814. /**
  815. * Find table with truename
  816. *
  817. * @param array $db DB to look into
  818. * @param string $truename Table name
  819. *
  820. * @return bool
  821. */
  822. protected function hasTable(array $db, $truename)
  823. {
  824. foreach ($db as $db_table) {
  825. if ($this->db == $this->replication->extractDbOrTable($db_table)
  826. && preg_match(
  827. '@^' .
  828. preg_quote(mb_substr($this->replication->extractDbOrTable($db_table, 'table'), 0, -1), '@') . '@',
  829. $truename
  830. )
  831. ) {
  832. return true;
  833. }
  834. }
  835. return false;
  836. }
  837. /**
  838. * Get the value set for ENGINE table,
  839. *
  840. * @internal param bool $table_is_view whether table is view or not
  841. *
  842. * @param array $current_table current table
  843. * @param int $sum_size total table size
  844. * @param int $overhead_size overhead size
  845. *
  846. * @return array
  847. */
  848. protected function getStuffForEngineTypeTable(
  849. array $current_table,
  850. $sum_size,
  851. $overhead_size
  852. ) {
  853. $formatted_size = '-';
  854. $unit = '';
  855. $formatted_overhead = '';
  856. $overhead_unit = '';
  857. $table_is_view = false;
  858. switch ($current_table['ENGINE']) {
  859. // MyISAM, ISAM or Heap table: Row count, data size and index size
  860. // are accurate; data size is accurate for ARCHIVE
  861. case 'MyISAM':
  862. case 'ISAM':
  863. case 'HEAP':
  864. case 'MEMORY':
  865. case 'ARCHIVE':
  866. case 'Aria':
  867. case 'Maria':
  868. [
  869. $current_table,
  870. $formatted_size,
  871. $unit,
  872. $formatted_overhead,
  873. $overhead_unit,
  874. $overhead_size,
  875. $sum_size,
  876. ] = $this->getValuesForAriaTable(
  877. $current_table,
  878. $sum_size,
  879. $overhead_size,
  880. $formatted_size,
  881. $unit,
  882. $formatted_overhead,
  883. $overhead_unit
  884. );
  885. break;
  886. case 'InnoDB':
  887. case 'PBMS':
  888. case 'TokuDB':
  889. // InnoDB table: Row count is not accurate but data and index sizes are.
  890. // PBMS table in Drizzle: TABLE_ROWS is taken from table cache,
  891. // so it may be unavailable
  892. [$current_table, $formatted_size, $unit, $sum_size]
  893. = $this->getValuesForInnodbTable(
  894. $current_table,
  895. $sum_size
  896. );
  897. break;
  898. // Mysql 5.0.x (and lower) uses MRG_MyISAM
  899. // and MySQL 5.1.x (and higher) uses MRG_MYISAM
  900. // Both are aliases for MERGE
  901. case 'MRG_MyISAM':
  902. case 'MRG_MYISAM':
  903. case 'MERGE':
  904. case 'BerkeleyDB':
  905. // Merge or BerkleyDB table: Only row count is accurate.
  906. if ($this->isShowStats) {
  907. $formatted_size = ' - ';
  908. $unit = '';
  909. }
  910. break;
  911. // for a view, the ENGINE is sometimes reported as null,
  912. // or on some servers it's reported as "SYSTEM VIEW"
  913. case null:
  914. case 'SYSTEM VIEW':
  915. // possibly a view, do nothing
  916. break;
  917. default:
  918. // Unknown table type.
  919. if ($this->isShowStats) {
  920. $formatted_size = __('unknown');
  921. $unit = '';
  922. }
  923. }
  924. if ($current_table['TABLE_TYPE'] === 'VIEW'
  925. || $current_table['TABLE_TYPE'] === 'SYSTEM VIEW'
  926. ) {
  927. // countRecords() takes care of $cfg['MaxExactCountViews']
  928. $current_table['TABLE_ROWS'] = $this->dbi
  929. ->getTable($this->db, $current_table['TABLE_NAME'])
  930. ->countRecords(true);
  931. $table_is_view = true;
  932. }
  933. return [
  934. $current_table,
  935. $formatted_size,
  936. $unit,
  937. $formatted_overhead,
  938. $overhead_unit,
  939. $overhead_size,
  940. $table_is_view,
  941. $sum_size,
  942. ];
  943. }
  944. /**
  945. * Get values for ARIA/MARIA tables
  946. *
  947. * @param array $current_table current table
  948. * @param int $sum_size sum size
  949. * @param int $overhead_size overhead size
  950. * @param int $formatted_size formatted size
  951. * @param string $unit unit
  952. * @param int $formatted_overhead overhead formatted
  953. * @param string $overhead_unit overhead unit
  954. *
  955. * @return array
  956. */
  957. protected function getValuesForAriaTable(
  958. array $current_table,
  959. $sum_size,
  960. $overhead_size,
  961. $formatted_size,
  962. $unit,
  963. $formatted_overhead,
  964. $overhead_unit
  965. ) {
  966. if ($this->dbIsSystemSchema) {
  967. $current_table['Rows'] = $this->dbi
  968. ->getTable($this->db, $current_table['Name'])
  969. ->countRecords();
  970. }
  971. if ($this->isShowStats) {
  972. /** @var int $tblsize */
  973. $tblsize = $current_table['Data_length']
  974. + $current_table['Index_length'];
  975. $sum_size += $tblsize;
  976. [$formatted_size, $unit] = Util::formatByteDown(
  977. $tblsize,
  978. 3,
  979. $tblsize > 0 ? 1 : 0
  980. );
  981. if (isset($current_table['Data_free'])
  982. && $current_table['Data_free'] > 0
  983. ) {
  984. [$formatted_overhead, $overhead_unit]
  985. = Util::formatByteDown(
  986. $current_table['Data_free'],
  987. 3,
  988. ($current_table['Data_free'] > 0 ? 1 : 0)
  989. );
  990. $overhead_size += $current_table['Data_free'];
  991. }
  992. }
  993. return [
  994. $current_table,
  995. $formatted_size,
  996. $unit,
  997. $formatted_overhead,
  998. $overhead_unit,
  999. $overhead_size,
  1000. $sum_size,
  1001. ];
  1002. }
  1003. /**
  1004. * Get values for InnoDB table
  1005. *
  1006. * @param array $current_table current table
  1007. * @param int $sum_size sum size
  1008. *
  1009. * @return array
  1010. */
  1011. protected function getValuesForInnodbTable(
  1012. array $current_table,
  1013. $sum_size
  1014. ) {
  1015. $formatted_size = $unit = '';
  1016. if ((in_array($current_table['ENGINE'], ['InnoDB', 'TokuDB'])
  1017. && $current_table['TABLE_ROWS'] < $GLOBALS['cfg']['MaxExactCount'])
  1018. || ! isset($current_table['TABLE_ROWS'])
  1019. ) {
  1020. $current_table['COUNTED'] = true;
  1021. $current_table['TABLE_ROWS'] = $this->dbi
  1022. ->getTable($this->db, $current_table['TABLE_NAME'])
  1023. ->countRecords(true);
  1024. } else {
  1025. $current_table['COUNTED'] = false;
  1026. }
  1027. if ($this->isShowStats) {
  1028. /** @var int $tblsize */
  1029. $tblsize = $current_table['Data_length']
  1030. + $current_table['Index_length'];
  1031. $sum_size += $tblsize;
  1032. [$formatted_size, $unit] = Util::formatByteDown(
  1033. $tblsize,
  1034. 3,
  1035. ($tblsize > 0 ? 1 : 0)
  1036. );
  1037. }
  1038. return [
  1039. $current_table,
  1040. $formatted_size,
  1041. $unit,
  1042. $sum_size,
  1043. ];
  1044. }
  1045. public function showCreate(): void
  1046. {
  1047. $selected = $_POST['selected_tbl'] ?? [];
  1048. if (empty($selected)) {
  1049. $this->response->setRequestStatus(false);
  1050. $this->response->addJSON('message', __('No table selected.'));
  1051. return;
  1052. }
  1053. $tables = $this->getShowCreateTables($selected);
  1054. $showCreate = $this->template->render('database/structure/show_create', ['tables' => $tables]);
  1055. $this->response->addJSON('message', $showCreate);
  1056. }
  1057. /**
  1058. * @param string[] $selected Selected tables.
  1059. *
  1060. * @return array<string, array<int, array<string, string>>>
  1061. */
  1062. private function getShowCreateTables(array $selected): array
  1063. {
  1064. $tables = ['tables' => [], 'views' => []];
  1065. foreach ($selected as $table) {
  1066. $object = $this->dbi->getTable($this->db, $table);
  1067. $tables[$object->isView() ? 'views' : 'tables'][] = [
  1068. 'name' => Core::mimeDefaultFunction($table),
  1069. 'show_create' => Core::mimeDefaultFunction($object->showCreate()),
  1070. ];
  1071. }
  1072. return $tables;
  1073. }
  1074. public function copyForm(): void
  1075. {
  1076. global $db, $dblist;
  1077. $selected = $_POST['selected_tbl'] ?? [];
  1078. if (empty($selected)) {
  1079. $this->response->setRequestStatus(false);
  1080. $this->response->addJSON('message', __('No table selected.'));
  1081. return;
  1082. }
  1083. $urlParams = ['db' => $db];
  1084. foreach ($selected as $selectedValue) {
  1085. $urlParams['selected'][] = $selectedValue;
  1086. }
  1087. $databasesList = $dblist->databases;
  1088. foreach ($databasesList as $key => $databaseName) {
  1089. if ($databaseName == $db) {
  1090. $databasesList->offsetUnset($key);
  1091. break;
  1092. }
  1093. }
  1094. $this->response->disable();
  1095. $this->render('database/structure/copy_form', [
  1096. 'url_params' => $urlParams,
  1097. 'options' => $databasesList->getList(),
  1098. ]);
  1099. }
  1100. public function centralColumnsAdd(): void
  1101. {
  1102. global $message;
  1103. $selected = $_POST['selected_tbl'] ?? [];
  1104. if (empty($selected)) {
  1105. $this->response->setRequestStatus(false);
  1106. $this->response->addJSON('message', __('No table selected.'));
  1107. return;
  1108. }
  1109. $centralColumns = new CentralColumns($this->dbi);
  1110. $error = $centralColumns->syncUniqueColumns($selected);
  1111. $message = $error instanceof Message ? $error : Message::success(__('Success!'));
  1112. unset($_POST['submit_mult']);
  1113. $this->index();
  1114. }
  1115. public function centralColumnsMakeConsistent(): void
  1116. {
  1117. global $db, $message;
  1118. $selected = $_POST['selected_tbl'] ?? [];
  1119. if (empty($selected)) {
  1120. $this->response->setRequestStatus(false);
  1121. $this->response->addJSON('message', __('No table selected.'));
  1122. return;
  1123. }
  1124. $centralColumns = new CentralColumns($this->dbi);
  1125. $error = $centralColumns->makeConsistentWithList($db, $selected);
  1126. $message = $error instanceof Message ? $error : Message::success(__('Success!'));
  1127. unset($_POST['submit_mult']);
  1128. $this->index();
  1129. }
  1130. public function centralColumnsRemove(): void
  1131. {
  1132. global $message;
  1133. $selected = $_POST['selected_tbl'] ?? [];
  1134. if (empty($selected)) {
  1135. $this->response->setRequestStatus(false);
  1136. $this->response->addJSON('message', __('No table selected.'));
  1137. return;
  1138. }
  1139. $centralColumns = new CentralColumns($this->dbi);
  1140. $error = $centralColumns->deleteColumnsFromList($_POST['db'], $selected);
  1141. $message = $error instanceof Message ? $error : Message::success(__('Success!'));
  1142. unset($_POST['submit_mult']);
  1143. $this->index();
  1144. }
  1145. public function addPrefix(): void
  1146. {
  1147. global $db;
  1148. $selected = $_POST['selected_tbl'] ?? [];
  1149. if (empty($selected)) {
  1150. $this->response->setRequestStatus(false);
  1151. $this->response->addJSON('message', __('No table selected.'));
  1152. return;
  1153. }
  1154. $params = ['db' => $db];
  1155. foreach ($selected as $selectedValue) {
  1156. $params['selected'][] = $selectedValue;
  1157. }
  1158. $this->response->disable();
  1159. $this->render('database/structure/add_prefix', ['url_params' => $params]);
  1160. }
  1161. public function changePrefixForm(): void
  1162. {
  1163. global $db;
  1164. $selected = $_POST['selected_tbl'] ?? [];
  1165. $submit_mult = $_POST['submit_mult'] ?? '';
  1166. if (empty($selected)) {
  1167. $this->response->setRequestStatus(false);
  1168. $this->response->addJSON('message', __('No table selected.'));
  1169. return;
  1170. }
  1171. $route = '/database/structure/replace-prefix';
  1172. if ($submit_mult === 'copy_tbl_change_prefix') {
  1173. $route = '/database/structure/copy-table-with-prefix';
  1174. }
  1175. $urlParams = ['db' => $db];
  1176. foreach ($selected as $selectedValue) {
  1177. $urlParams['selected'][] = $selectedValue;
  1178. }
  1179. $this->response->disable();
  1180. $this->render('database/structure/change_prefix_form', [
  1181. 'route' => $route,
  1182. 'url_params' => $urlParams,
  1183. ]);
  1184. }
  1185. public function dropForm(): void
  1186. {
  1187. global $db;
  1188. $selected = $_POST['selected_tbl'] ?? [];
  1189. if (empty($selected)) {
  1190. $this->response->setRequestStatus(false);
  1191. $this->response->addJSON('message', __('No table selected.'));
  1192. return;
  1193. }
  1194. $views = $this->dbi->getVirtualTables($db);
  1195. $full_query_views = '';
  1196. $full_query = '';
  1197. foreach ($selected as $selectedValue) {
  1198. $current = $selectedValue;
  1199. if (! empty($views) && in_array($current, $views)) {
  1200. $full_query_views .= (empty($full_query_views) ? 'DROP VIEW ' : ', ')
  1201. . Util::backquote(htmlspecialchars($current));
  1202. } else {
  1203. $full_query .= (empty($full_query) ? 'DROP TABLE ' : ', ')
  1204. . Util::backquote(htmlspecialchars($current));
  1205. }
  1206. }
  1207. if (! empty($full_query)) {
  1208. $full_query .= ';<br>' . "\n";
  1209. }
  1210. if (! empty($full_query_views)) {
  1211. $full_query .= $full_query_views . ';<br>' . "\n";
  1212. }
  1213. $_url_params = ['db' => $db];
  1214. foreach ($selected as $selectedValue) {
  1215. $_url_params['selected'][] = $selectedValue;
  1216. }
  1217. foreach ($views as $current) {
  1218. $_url_params['views'][] = $current;
  1219. }
  1220. $this->render('database/structure/drop_form', [
  1221. 'url_params' => $_url_params,
  1222. 'full_query' => $full_query,
  1223. 'is_foreign_key_check' => Util::isForeignKeyCheck(),
  1224. ]);
  1225. }
  1226. public function emptyForm(): void
  1227. {
  1228. global $db;
  1229. $selected = $_POST['selected_tbl'] ?? [];
  1230. if (empty($selected)) {
  1231. $this->response->setRequestStatus(false);
  1232. $this->response->addJSON('message', __('No table selected.'));
  1233. return;
  1234. }
  1235. $fullQuery = '';
  1236. $urlParams = ['db' => $db];
  1237. foreach ($selected as $selectedValue) {
  1238. $fullQuery .= 'TRUNCATE ';
  1239. $fullQuery .= Util::backquote(htmlspecialchars($selectedValue)) . ';<br>';
  1240. $urlParams['selected'][] = $selectedValue;
  1241. }
  1242. $this->render('database/structure/empty_form', [
  1243. 'url_params' => $urlParams,
  1244. 'full_query' => $fullQuery,
  1245. 'is_foreign_key_check' => Util::isForeignKeyCheck(),
  1246. ]);
  1247. }
  1248. public function dropTable(): void
  1249. {
  1250. global $db, $message, $reload, $sql_query;
  1251. $reload = $_POST['reload'] ?? $reload ?? null;
  1252. $mult_btn = $_POST['mult_btn'] ?? '';
  1253. $selected = $_POST['selected'] ?? [];
  1254. $views = $this->dbi->getVirtualTables($db);
  1255. if ($mult_btn !== __('Yes')) {
  1256. $message = Message::success(__('No change'));
  1257. if (empty($_POST['message'])) {
  1258. $_POST['message'] = Message::success();
  1259. }
  1260. unset($_POST['mult_btn']);
  1261. $this->index();
  1262. return;
  1263. }
  1264. $default_fk_check_value = Util::handleDisableFKCheckInit();
  1265. $sql_query = '';
  1266. $sql_query_views = '';
  1267. $selectedCount = count($selected);
  1268. for ($i = 0; $i < $selectedCount; $i++) {
  1269. $this->relationCleanup->table($db, $selected[$i]);
  1270. $current = $selected[$i];
  1271. if (! empty($views) && in_array($current, $views)) {
  1272. $sql_query_views .= (empty($sql_query_views) ? 'DROP VIEW ' : ', ') . Util::backquote($current);
  1273. } else {
  1274. $sql_query .= (empty($sql_query) ? 'DROP TABLE ' : ', ') . Util::backquote($current);
  1275. }
  1276. $reload = 1;
  1277. }
  1278. if (! empty($sql_query)) {
  1279. $sql_query .= ';';
  1280. } elseif (! empty($sql_query_views)) {
  1281. $sql_query = $sql_query_views . ';';
  1282. unset($sql_query_views);
  1283. }
  1284. // Unset cache values for tables count, issue #14205
  1285. if (isset($_SESSION['tmpval'])) {
  1286. if (isset($_SESSION['tmpval']['table_limit_offset'])) {
  1287. unset($_SESSION['tmpval']['table_limit_offset']);
  1288. }
  1289. if (isset($_SESSION['tmpval']['table_limit_offset_db'])) {
  1290. unset($_SESSION['tmpval']['table_limit_offset_db']);
  1291. }
  1292. }
  1293. $this->dbi->selectDb($db);
  1294. $result = $this->dbi->tryQuery($sql_query);
  1295. if ($result && ! empty($sql_query_views)) {
  1296. $sql_query .= ' ' . $sql_query_views . ';';
  1297. $result = $this->dbi->tryQuery($sql_query_views);
  1298. unset($sql_query_views);
  1299. }
  1300. if (! $result) {
  1301. $message = Message::error((string) $this->dbi->getError());
  1302. }
  1303. Util::handleDisableFKCheckCleanup($default_fk_check_value);
  1304. $message = Message::success();
  1305. if (empty($_POST['message'])) {
  1306. $_POST['message'] = $message;
  1307. }
  1308. unset($_POST['mult_btn']);
  1309. $this->index();
  1310. }
  1311. public function emptyTable(): void
  1312. {
  1313. global $db, $table, $message, $sql_query;
  1314. $mult_btn = $_POST['mult_btn'] ?? '';
  1315. $selected = $_POST['selected'] ?? [];
  1316. if ($mult_btn !== __('Yes')) {
  1317. $message = Message::success(__('No change'));
  1318. if (empty($_POST['message'])) {
  1319. $_POST['message'] = Message::success();
  1320. }
  1321. unset($_POST['mult_btn']);
  1322. $this->index();
  1323. return;
  1324. }
  1325. $default_fk_check_value = Util::handleDisableFKCheckInit();
  1326. $sql_query = '';
  1327. $selectedCount = count($selected);
  1328. for ($i = 0; $i < $selectedCount; $i++) {
  1329. $aQuery = 'TRUNCATE ';
  1330. $aQuery .= Util::backquote($selected[$i]);
  1331. $sql_query .= $aQuery . ';' . "\n";
  1332. $this->dbi->selectDb($db);
  1333. $this->dbi->query($aQuery);
  1334. }
  1335. if (! empty($_REQUEST['pos'])) {
  1336. $sql = new Sql(
  1337. $this->dbi,
  1338. $this->relation,
  1339. $this->relationCleanup,
  1340. $this->operations,
  1341. new Transformations(),
  1342. $this->template
  1343. );
  1344. $_REQUEST['pos'] = $sql->calculatePosForLastPage($db, $table, $_REQUEST['pos']);
  1345. }
  1346. Util::handleDisableFKCheckCleanup($default_fk_check_value);
  1347. $message = Message::success();
  1348. if (empty($_POST['message'])) {
  1349. $_POST['message'] = $message;
  1350. }
  1351. unset($_POST['mult_btn']);
  1352. $this->index();
  1353. }
  1354. public function addPrefixTable(): void
  1355. {
  1356. global $db, $message, $sql_query;
  1357. $selected = $_POST['selected'] ?? [];
  1358. $sql_query = '';
  1359. $selectedCount = count($selected);
  1360. for ($i = 0; $i < $selectedCount; $i++) {
  1361. $newTableName = $_POST['add_prefix'] . $selected[$i];
  1362. $aQuery = 'ALTER TABLE ' . Util::backquote($selected[$i])
  1363. . ' RENAME ' . Util::backquote($newTableName);
  1364. $sql_query .= $aQuery . ';' . "\n";
  1365. $this->dbi->selectDb($db);
  1366. $this->dbi->query($aQuery);
  1367. }
  1368. $message = Message::success();
  1369. if (empty($_POST['message'])) {
  1370. $_POST['message'] = $message;
  1371. }
  1372. $this->index();
  1373. }
  1374. public function replacePrefix(): void
  1375. {
  1376. global $db, $message, $sql_query;
  1377. $selected = $_POST['selected'] ?? [];
  1378. $from_prefix = $_POST['from_prefix'] ?? '';
  1379. $to_prefix = $_POST['to_prefix'] ?? '';
  1380. $sql_query = '';
  1381. $selectedCount = count($selected);
  1382. for ($i = 0; $i < $selectedCount; $i++) {
  1383. $current = $selected[$i];
  1384. $subFromPrefix = mb_substr($current, 0, mb_strlen((string) $from_prefix));
  1385. if ($subFromPrefix === $from_prefix) {
  1386. $newTableName = $to_prefix . mb_substr(
  1387. $current,
  1388. mb_strlen((string) $from_prefix)
  1389. );
  1390. } else {
  1391. $newTableName = $current;
  1392. }
  1393. $aQuery = 'ALTER TABLE ' . Util::backquote($selected[$i])
  1394. . ' RENAME ' . Util::backquote($newTableName);
  1395. $sql_query .= $aQuery . ';' . "\n";
  1396. $this->dbi->selectDb($db);
  1397. $this->dbi->query($aQuery);
  1398. }
  1399. $message = Message::success();
  1400. if (empty($_POST['message'])) {
  1401. $_POST['message'] = $message;
  1402. }
  1403. $this->index();
  1404. }
  1405. public function copyTableWithPrefix(): void
  1406. {
  1407. global $db, $message;
  1408. $selected = $_POST['selected'] ?? [];
  1409. $from_prefix = $_POST['from_prefix'] ?? null;
  1410. $to_prefix = $_POST['to_prefix'] ?? null;
  1411. $selectedCount = count($selected);
  1412. for ($i = 0; $i < $selectedCount; $i++) {
  1413. $current = $selected[$i];
  1414. $newTableName = $to_prefix . mb_substr($current, mb_strlen((string) $from_prefix));
  1415. Table::moveCopy(
  1416. $db,
  1417. $current,
  1418. $db,
  1419. $newTableName,
  1420. 'data',
  1421. false,
  1422. 'one_table'
  1423. );
  1424. }
  1425. $message = Message::success();
  1426. if (empty($_POST['message'])) {
  1427. $_POST['message'] = $message;
  1428. }
  1429. $this->index();
  1430. }
  1431. }