StructureController.php 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Holds the PhpMyAdmin\Controllers\Database\StructureController
  5. *
  6. * @package PhpMyAdmin\Controllers
  7. */
  8. declare(strict_types=1);
  9. namespace PhpMyAdmin\Controllers\Database;
  10. use PhpMyAdmin\Charsets;
  11. use PhpMyAdmin\Config\PageSettings;
  12. use PhpMyAdmin\Core;
  13. use PhpMyAdmin\DatabaseInterface;
  14. use PhpMyAdmin\Display\CreateTable;
  15. use PhpMyAdmin\Message;
  16. use PhpMyAdmin\RecentFavoriteTable;
  17. use PhpMyAdmin\Relation;
  18. use PhpMyAdmin\Replication;
  19. use PhpMyAdmin\Response;
  20. use PhpMyAdmin\Sanitize;
  21. use PhpMyAdmin\Template;
  22. use PhpMyAdmin\Tracker;
  23. use PhpMyAdmin\Url;
  24. use PhpMyAdmin\Util;
  25. /**
  26. * Handles database structure logic
  27. *
  28. * @package PhpMyAdmin\Controllers
  29. */
  30. class StructureController extends AbstractController
  31. {
  32. /**
  33. * @var int Number of tables
  34. */
  35. protected $numTables;
  36. /**
  37. * @var int Current position in the list
  38. */
  39. protected $position;
  40. /**
  41. * @var bool DB is information_schema
  42. */
  43. protected $dbIsSystemSchema;
  44. /**
  45. * @var int Number of tables
  46. */
  47. protected $totalNumTables;
  48. /**
  49. * @var array Tables in the database
  50. */
  51. protected $tables;
  52. /**
  53. * @var bool whether stats show or not
  54. */
  55. protected $isShowStats;
  56. /**
  57. * @var Relation
  58. */
  59. private $relation;
  60. /**
  61. * @var Replication
  62. */
  63. private $replication;
  64. /**
  65. * Constructor
  66. *
  67. * @param Response $response Response instance
  68. * @param DatabaseInterface $dbi DatabaseInterface instance
  69. * @param Template $template Template object
  70. * @param string $db Database name
  71. * @param Relation $relation Relation instance
  72. * @param Replication $replication Replication instance
  73. */
  74. public function __construct($response, $dbi, Template $template, $db, $relation, $replication)
  75. {
  76. parent::__construct($response, $dbi, $template, $db);
  77. $this->relation = $relation;
  78. $this->replication = $replication;
  79. }
  80. /**
  81. * Retrieves database information for further use
  82. *
  83. * @param string $subPart Page part name
  84. *
  85. * @return void
  86. */
  87. private function getDatabaseInfo(string $subPart): void
  88. {
  89. list(
  90. $tables,
  91. $numTables,
  92. $totalNumTables,
  93. ,
  94. $isShowStats,
  95. $dbIsSystemSchema,
  96. ,
  97. ,
  98. $position
  99. ) = Util::getDbInfo($this->db, $subPart);
  100. $this->tables = $tables;
  101. $this->numTables = $numTables;
  102. $this->position = $position;
  103. $this->dbIsSystemSchema = $dbIsSystemSchema;
  104. $this->totalNumTables = $totalNumTables;
  105. $this->isShowStats = $isShowStats;
  106. }
  107. /**
  108. * Index action
  109. *
  110. * @param array $params Request parameters
  111. * @return string HTML
  112. */
  113. public function index(array $params): string
  114. {
  115. global $cfg;
  116. // Drops/deletes/etc. multiple tables if required
  117. if ((! empty($params['submit_mult']) && isset($params['selected_tbl']))
  118. || isset($params['mult_btn'])
  119. ) {
  120. $this->multiSubmitAction();
  121. }
  122. // Gets the database structure
  123. $this->getDatabaseInfo('_structure');
  124. // Checks if there are any tables to be shown on current page.
  125. // If there are no tables, the user is redirected to the last page
  126. // having any.
  127. if ($this->totalNumTables > 0 && $this->position > $this->totalNumTables) {
  128. $uri = './db_structure.php' . Url::getCommonRaw([
  129. 'db' => $this->db,
  130. 'pos' => max(0, $this->totalNumTables - $cfg['MaxTableList']),
  131. 'reload' => 1,
  132. ]);
  133. Core::sendHeaderLocation($uri);
  134. }
  135. include_once ROOT_PATH . 'libraries/replication.inc.php';
  136. PageSettings::showGroup('DbStructure');
  137. if ($this->numTables > 0) {
  138. $urlParams = [
  139. 'pos' => $this->position,
  140. 'db' => $this->db,
  141. ];
  142. if (isset($params['sort'])) {
  143. $urlParams['sort'] = $params['sort'];
  144. }
  145. if (isset($params['sort_order'])) {
  146. $urlParams['sort_order'] = $params['sort_order'];
  147. }
  148. $listNavigator = Util::getListNavigator(
  149. $this->totalNumTables,
  150. $this->position,
  151. $urlParams,
  152. 'db_structure.php',
  153. 'frame_content',
  154. $cfg['MaxTableList']
  155. );
  156. $tableList = $this->displayTableList();
  157. }
  158. $createTable = '';
  159. if (empty($this->dbIsSystemSchema)) {
  160. $createTable = CreateTable::getHtml($this->db);
  161. }
  162. return $this->template->render('database/structure/index', [
  163. 'database' => $this->db,
  164. 'has_tables' => $this->numTables > 0,
  165. 'list_navigator_html' => $listNavigator ?? '',
  166. 'table_list_html' => $tableList ?? '',
  167. 'is_system_schema' => ! empty($this->dbIsSystemSchema),
  168. 'create_table_html' => $createTable,
  169. ]);
  170. }
  171. /**
  172. * Add or remove favorite tables
  173. *
  174. * @param array $params Request parameters
  175. * @return array|null JSON
  176. */
  177. public function addRemoveFavoriteTablesAction(array $params): ?array
  178. {
  179. global $cfg;
  180. $favoriteInstance = RecentFavoriteTable::getInstance('favorite');
  181. if (isset($params['favoriteTables'])) {
  182. $favoriteTables = json_decode($params['favoriteTables'], true);
  183. } else {
  184. $favoriteTables = [];
  185. }
  186. // Required to keep each user's preferences separate.
  187. $user = sha1($cfg['Server']['user']);
  188. // Request for Synchronization of favorite tables.
  189. if (isset($params['sync_favorite_tables'])) {
  190. $cfgRelation = $this->relation->getRelationsParam();
  191. if ($cfgRelation['favoritework']) {
  192. return $this->synchronizeFavoriteTables($favoriteInstance, $user, $favoriteTables);
  193. }
  194. return null;
  195. }
  196. $changes = true;
  197. $titles = Util::buildActionTitles();
  198. $favoriteTable = $params['favorite_table'];
  199. $alreadyFavorite = $this->checkFavoriteTable($favoriteTable);
  200. if (isset($params['remove_favorite'])) {
  201. if ($alreadyFavorite) {
  202. // If already in favorite list, remove it.
  203. $favoriteInstance->remove($this->db, $favoriteTable);
  204. $alreadyFavorite = false; // for favorite_anchor template
  205. }
  206. } elseif (isset($params['add_favorite'])) {
  207. if (! $alreadyFavorite) {
  208. $numTables = count($favoriteInstance->getTables());
  209. if ($numTables == $cfg['NumFavoriteTables']) {
  210. $changes = false;
  211. } else {
  212. // Otherwise add to favorite list.
  213. $favoriteInstance->add($this->db, $favoriteTable);
  214. $alreadyFavorite = true; // for favorite_anchor template
  215. }
  216. }
  217. }
  218. $favoriteTables[$user] = $favoriteInstance->getTables();
  219. $json = [];
  220. $json['changes'] = $changes;
  221. if (! $changes) {
  222. $json['message'] = $this->template->render('components/error_message', [
  223. 'msg' => __("Favorite List is full!"),
  224. ]);
  225. return $json;
  226. }
  227. // Check if current table is already in favorite list.
  228. $favoriteParams = [
  229. 'db' => $this->db,
  230. 'ajax_request' => true,
  231. 'favorite_table' => $favoriteTable,
  232. ($alreadyFavorite ? 'remove' : 'add') . '_favorite' => true,
  233. ];
  234. $json['user'] = $user;
  235. $json['favoriteTables'] = json_encode($favoriteTables);
  236. $json['list'] = $favoriteInstance->getHtmlList();
  237. $json['anchor'] = $this->template->render('database/structure/favorite_anchor', [
  238. 'table_name_hash' => md5($favoriteTable),
  239. 'db_table_name_hash' => md5($this->db . "." . $favoriteTable),
  240. 'fav_params' => $favoriteParams,
  241. 'already_favorite' => $alreadyFavorite,
  242. 'titles' => $titles,
  243. ]);
  244. return $json;
  245. }
  246. /**
  247. * Handles request for real row count on database level view page.
  248. *
  249. * @param array $params Request parameters
  250. * @return array JSON
  251. */
  252. public function handleRealRowCountRequestAction(array $params): array
  253. {
  254. // If there is a request to update all table's row count.
  255. if (! isset($params['real_row_count_all'])) {
  256. // Get the real row count for the table.
  257. $realRowCount = $this->dbi
  258. ->getTable($this->db, (string) $params['table'])
  259. ->getRealRowCountTable();
  260. // Format the number.
  261. $realRowCount = Util::formatNumber($realRowCount, 0);
  262. return ['real_row_count' => $realRowCount];
  263. }
  264. // Array to store the results.
  265. $realRowCountAll = [];
  266. // Iterate over each table and fetch real row count.
  267. foreach ($this->tables as $table) {
  268. $rowCount = $this->dbi
  269. ->getTable($this->db, $table['TABLE_NAME'])
  270. ->getRealRowCountTable();
  271. $realRowCountAll[] = [
  272. 'table' => $table['TABLE_NAME'],
  273. 'row_count' => $rowCount,
  274. ];
  275. }
  276. return ['real_row_count_all' => json_encode($realRowCountAll)];
  277. }
  278. /**
  279. * Handles actions related to multiple tables
  280. *
  281. * @return void
  282. */
  283. public function multiSubmitAction(): void
  284. {
  285. $action = 'db_structure.php';
  286. $err_url = 'db_structure.php' . Url::getCommon(
  287. ['db' => $this->db]
  288. );
  289. // see bug #2794840; in this case, code path is:
  290. // db_structure.php -> libraries/mult_submits.inc.php -> sql.php
  291. // -> db_structure.php and if we got an error on the multi submit,
  292. // we must display it here and not call again mult_submits.inc.php
  293. if (! isset($_POST['error']) || false === $_POST['error']) {
  294. include ROOT_PATH . 'libraries/mult_submits.inc.php';
  295. }
  296. if (empty($_POST['message'])) {
  297. $_POST['message'] = Message::success();
  298. }
  299. }
  300. /**
  301. * Displays the list of tables
  302. *
  303. * @return string HTML
  304. */
  305. protected function displayTableList(): string
  306. {
  307. $html = '';
  308. // filtering
  309. $html .= $this->template->render('filter', ['filter_value' => '']);
  310. $i = $sum_entries = 0;
  311. $overhead_check = false;
  312. $create_time_all = '';
  313. $update_time_all = '';
  314. $check_time_all = '';
  315. $num_columns = $GLOBALS['cfg']['PropertiesNumColumns'] > 1
  316. ? ceil($this->numTables / $GLOBALS['cfg']['PropertiesNumColumns']) + 1
  317. : 0;
  318. $row_count = 0;
  319. $sum_size = 0;
  320. $overhead_size = 0;
  321. $hidden_fields = [];
  322. $overall_approx_rows = false;
  323. $structure_table_rows = [];
  324. foreach ($this->tables as $keyname => $current_table) {
  325. // Get valid statistics whatever is the table type
  326. $drop_query = '';
  327. $drop_message = '';
  328. $overhead = '';
  329. $input_class = ['checkall'];
  330. $table_is_view = false;
  331. // Sets parameters for links
  332. $tbl_url_query = Url::getCommon(
  333. [
  334. 'db' => $this->db,
  335. 'table' => $current_table['TABLE_NAME'],
  336. ]
  337. );
  338. // do not list the previous table's size info for a view
  339. list($current_table, $formatted_size, $unit, $formatted_overhead,
  340. $overhead_unit, $overhead_size, $table_is_view, $sum_size)
  341. = $this->getStuffForEngineTypeTable(
  342. $current_table,
  343. $sum_size,
  344. $overhead_size
  345. );
  346. $curTable = $this->dbi
  347. ->getTable($this->db, $current_table['TABLE_NAME']);
  348. if (! $curTable->isMerge()) {
  349. $sum_entries += $current_table['TABLE_ROWS'];
  350. }
  351. $collationDefinition = '---';
  352. if (isset($current_table['Collation'])) {
  353. $tableCollation = Charsets::findCollationByName(
  354. $this->dbi,
  355. $GLOBALS['cfg']['Server']['DisableIS'],
  356. $current_table['Collation']
  357. );
  358. if ($tableCollation !== null) {
  359. $collationDefinition = '<dfn title="'
  360. . $tableCollation->getDescription() . '">'
  361. . $tableCollation->getName() . '</dfn>';
  362. }
  363. }
  364. if ($this->isShowStats) {
  365. $overhead = '-';
  366. if ($formatted_overhead != '') {
  367. $overhead = '<a href="tbl_structure.php'
  368. . $tbl_url_query . '#showusage">'
  369. . '<span>' . $formatted_overhead . '</span>&nbsp;'
  370. . '<span class="unit">' . $overhead_unit . '</span>'
  371. . '</a>' . "\n";
  372. $overhead_check = true;
  373. $input_class[] = 'tbl-overhead';
  374. }
  375. }
  376. if ($GLOBALS['cfg']['ShowDbStructureCharset']) {
  377. $charset = '';
  378. if (isset($tableCollation)) {
  379. $charset = $tableCollation->getCharset();
  380. }
  381. }
  382. if ($GLOBALS['cfg']['ShowDbStructureCreation']) {
  383. $create_time = isset($current_table['Create_time'])
  384. ? $current_table['Create_time'] : '';
  385. if ($create_time
  386. && (! $create_time_all
  387. || $create_time < $create_time_all)
  388. ) {
  389. $create_time_all = $create_time;
  390. }
  391. }
  392. if ($GLOBALS['cfg']['ShowDbStructureLastUpdate']) {
  393. $update_time = isset($current_table['Update_time'])
  394. ? $current_table['Update_time'] : '';
  395. if ($update_time
  396. && (! $update_time_all
  397. || $update_time < $update_time_all)
  398. ) {
  399. $update_time_all = $update_time;
  400. }
  401. }
  402. if ($GLOBALS['cfg']['ShowDbStructureLastCheck']) {
  403. $check_time = isset($current_table['Check_time'])
  404. ? $current_table['Check_time'] : '';
  405. if ($check_time
  406. && (! $check_time_all
  407. || $check_time < $check_time_all)
  408. ) {
  409. $check_time_all = $check_time;
  410. }
  411. }
  412. $truename = $current_table['TABLE_NAME'];
  413. $i++;
  414. $row_count++;
  415. if ($table_is_view) {
  416. $hidden_fields[] = '<input type="hidden" name="views[]" value="'
  417. . htmlspecialchars($current_table['TABLE_NAME']) . '">';
  418. }
  419. /*
  420. * Always activate links for Browse, Search and Empty, even if
  421. * the icons are greyed, because
  422. * 1. for views, we don't know the number of rows at this point
  423. * 2. for tables, another source could have populated them since the
  424. * page was generated
  425. *
  426. * I could have used the PHP ternary conditional operator but I find
  427. * the code easier to read without this operator.
  428. */
  429. $may_have_rows = $current_table['TABLE_ROWS'] > 0 || $table_is_view;
  430. $titles = Util::buildActionTitles();
  431. if (! $this->dbIsSystemSchema) {
  432. $drop_query = sprintf(
  433. 'DROP %s %s',
  434. $table_is_view || $current_table['ENGINE'] == null ? 'VIEW'
  435. : 'TABLE',
  436. Util::backquote(
  437. $current_table['TABLE_NAME']
  438. )
  439. );
  440. $drop_message = sprintf(
  441. ($table_is_view || $current_table['ENGINE'] == null
  442. ? __('View %s has been dropped.')
  443. : __('Table %s has been dropped.')),
  444. str_replace(
  445. ' ',
  446. '&nbsp;',
  447. htmlspecialchars($current_table['TABLE_NAME'])
  448. )
  449. );
  450. }
  451. if ($num_columns > 0
  452. && $this->numTables > $num_columns
  453. && ($row_count % $num_columns) == 0
  454. ) {
  455. $row_count = 1;
  456. $html .= $this->template->render('database/structure/table_header', [
  457. 'db' => $this->db,
  458. 'db_is_system_schema' => $this->dbIsSystemSchema,
  459. 'replication' => $GLOBALS['replication_info']['slave']['status'],
  460. 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
  461. 'is_show_stats' => $GLOBALS['is_show_stats'],
  462. 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
  463. 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
  464. 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
  465. 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
  466. 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
  467. 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
  468. 'structure_table_rows' => $structure_table_rows,
  469. ]);
  470. $structure_table_rows = [];
  471. }
  472. list($approx_rows, $show_superscript) = $this->isRowCountApproximated(
  473. $current_table,
  474. $table_is_view
  475. );
  476. list($do, $ignored) = $this->getReplicationStatus($truename);
  477. $structure_table_rows[] = [
  478. 'table_name_hash' => md5($current_table['TABLE_NAME']),
  479. 'db_table_name_hash' => md5($this->db . '.' . $current_table['TABLE_NAME']),
  480. 'db' => $this->db,
  481. 'curr' => $i,
  482. 'input_class' => implode(' ', $input_class),
  483. 'table_is_view' => $table_is_view,
  484. 'current_table' => $current_table,
  485. 'browse_table_title' => $may_have_rows ? $titles['Browse'] : $titles['NoBrowse'],
  486. 'search_table_title' => $may_have_rows ? $titles['Search'] : $titles['NoSearch'],
  487. 'browse_table_label_title' => htmlspecialchars($current_table['TABLE_COMMENT']),
  488. 'browse_table_label_truename' => $truename,
  489. 'empty_table_sql_query' => urlencode(
  490. 'TRUNCATE ' . Util::backquote(
  491. $current_table['TABLE_NAME']
  492. )
  493. ),
  494. 'empty_table_message_to_show' => urlencode(
  495. sprintf(
  496. __('Table %s has been emptied.'),
  497. htmlspecialchars(
  498. $current_table['TABLE_NAME']
  499. )
  500. )
  501. ),
  502. 'empty_table_title' => $may_have_rows ? $titles['Empty'] : $titles['NoEmpty'],
  503. 'tracking_icon' => $this->getTrackingIcon($truename),
  504. 'server_slave_status' => $GLOBALS['replication_info']['slave']['status'],
  505. 'tbl_url_query' => $tbl_url_query,
  506. 'db_is_system_schema' => $this->dbIsSystemSchema,
  507. 'titles' => $titles,
  508. 'drop_query' => $drop_query,
  509. 'drop_message' => $drop_message,
  510. 'collation' => $collationDefinition,
  511. 'formatted_size' => $formatted_size,
  512. 'unit' => $unit,
  513. 'overhead' => $overhead,
  514. 'create_time' => isset($create_time) && $create_time
  515. ? Util::localisedDate(strtotime($create_time)) : '-',
  516. 'update_time' => isset($update_time) && $update_time
  517. ? Util::localisedDate(strtotime($update_time)) : '-',
  518. 'check_time' => isset($check_time) && $check_time
  519. ? Util::localisedDate(strtotime($check_time)) : '-',
  520. 'charset' => isset($charset)
  521. ? $charset : '',
  522. 'is_show_stats' => $this->isShowStats,
  523. 'ignored' => $ignored,
  524. 'do' => $do,
  525. 'approx_rows' => $approx_rows,
  526. 'show_superscript' => $show_superscript,
  527. 'already_favorite' => $this->checkFavoriteTable(
  528. $current_table['TABLE_NAME']
  529. ),
  530. 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
  531. 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
  532. 'limit_chars' => $GLOBALS['cfg']['LimitChars'],
  533. 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
  534. 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
  535. 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
  536. 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
  537. 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
  538. ];
  539. $overall_approx_rows = $overall_approx_rows || $approx_rows;
  540. }
  541. $databaseCollation = [];
  542. $databaseCharset = '';
  543. $collation = Charsets::findCollationByName(
  544. $this->dbi,
  545. $GLOBALS['cfg']['Server']['DisableIS'],
  546. $this->dbi->getDbCollation($this->db)
  547. );
  548. if ($collation !== null) {
  549. $databaseCollation = [
  550. 'name' => $collation->getName(),
  551. 'description' => $collation->getDescription(),
  552. ];
  553. $databaseCharset = $collation->getCharset();
  554. }
  555. // table form
  556. $html .= $this->template->render('database/structure/table_header', [
  557. 'db' => $this->db,
  558. 'db_is_system_schema' => $this->dbIsSystemSchema,
  559. 'replication' => $GLOBALS['replication_info']['slave']['status'],
  560. 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
  561. 'is_show_stats' => $GLOBALS['is_show_stats'],
  562. 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
  563. 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
  564. 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
  565. 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
  566. 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
  567. 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
  568. 'structure_table_rows' => $structure_table_rows,
  569. 'body_for_table_summary' => [
  570. 'num_tables' => $this->numTables,
  571. 'server_slave_status' => $GLOBALS['replication_info']['slave']['status'],
  572. 'db_is_system_schema' => $this->dbIsSystemSchema,
  573. 'sum_entries' => $sum_entries,
  574. 'database_collation' => $databaseCollation,
  575. 'is_show_stats' => $this->isShowStats,
  576. 'database_charset' => $databaseCharset,
  577. 'sum_size' => $sum_size,
  578. 'overhead_size' => $overhead_size,
  579. 'create_time_all' => $create_time_all ? Util::localisedDate(strtotime($create_time_all)) : '-',
  580. 'update_time_all' => $update_time_all ? Util::localisedDate(strtotime($update_time_all)) : '-',
  581. 'check_time_all' => $check_time_all ? Util::localisedDate(strtotime($check_time_all)) : '-',
  582. 'approx_rows' => $overall_approx_rows,
  583. 'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
  584. 'db' => $GLOBALS['db'],
  585. 'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
  586. 'dbi' => $this->dbi,
  587. 'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
  588. 'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
  589. 'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
  590. 'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
  591. 'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
  592. ],
  593. 'check_all_tables' => [
  594. 'pma_theme_image' => $GLOBALS['pmaThemeImage'],
  595. 'text_dir' => $GLOBALS['text_dir'],
  596. 'overhead_check' => $overhead_check,
  597. 'db_is_system_schema' => $this->dbIsSystemSchema,
  598. 'hidden_fields' => $hidden_fields,
  599. 'disable_multi_table' => $GLOBALS['cfg']['DisableMultiTableMaintenance'],
  600. 'central_columns_work' => $GLOBALS['cfgRelation']['centralcolumnswork'],
  601. ],
  602. ]);
  603. return $html;
  604. }
  605. /**
  606. * Returns the tracking icon if the table is tracked
  607. *
  608. * @param string $table table name
  609. *
  610. * @return string HTML for tracking icon
  611. */
  612. protected function getTrackingIcon(string $table): string
  613. {
  614. $tracking_icon = '';
  615. if (Tracker::isActive()) {
  616. $is_tracked = Tracker::isTracked($this->db, $table);
  617. if ($is_tracked
  618. || Tracker::getVersion($this->db, $table) > 0
  619. ) {
  620. $tracking_icon = $this->template->render('database/structure/tracking_icon', [
  621. 'db' => $this->db,
  622. 'table' => $table,
  623. 'is_tracked' => $is_tracked,
  624. ]);
  625. }
  626. }
  627. return $tracking_icon;
  628. }
  629. /**
  630. * Returns whether the row count is approximated
  631. *
  632. * @param array $current_table array containing details about the table
  633. * @param boolean $table_is_view whether the table is a view
  634. *
  635. * @return array
  636. */
  637. protected function isRowCountApproximated(
  638. array $current_table,
  639. bool $table_is_view
  640. ): array {
  641. $approx_rows = false;
  642. $show_superscript = '';
  643. // there is a null value in the ENGINE
  644. // - when the table needs to be repaired, or
  645. // - when it's a view
  646. // so ensure that we'll display "in use" below for a table
  647. // that needs to be repaired
  648. if (isset($current_table['TABLE_ROWS'])
  649. && ($current_table['ENGINE'] != null || $table_is_view)
  650. ) {
  651. // InnoDB/TokuDB table: we did not get an accurate row count
  652. $approx_rows = ! $table_is_view
  653. && in_array($current_table['ENGINE'], ['InnoDB', 'TokuDB'])
  654. && ! $current_table['COUNTED'];
  655. if ($table_is_view
  656. && $current_table['TABLE_ROWS'] >= $GLOBALS['cfg']['MaxExactCountViews']
  657. ) {
  658. $approx_rows = true;
  659. $show_superscript = Util::showHint(
  660. Sanitize::sanitizeMessage(
  661. sprintf(
  662. __(
  663. 'This view has at least this number of '
  664. . 'rows. Please refer to %sdocumentation%s.'
  665. ),
  666. '[doc@cfg_MaxExactCountViews]',
  667. '[/doc]'
  668. )
  669. )
  670. );
  671. }
  672. }
  673. return [
  674. $approx_rows,
  675. $show_superscript,
  676. ];
  677. }
  678. /**
  679. * Returns the replication status of the table.
  680. *
  681. * @param string $table table name
  682. *
  683. * @return array
  684. */
  685. protected function getReplicationStatus(string $table): array
  686. {
  687. $do = $ignored = false;
  688. if ($GLOBALS['replication_info']['slave']['status']) {
  689. $nbServSlaveDoDb = count(
  690. $GLOBALS['replication_info']['slave']['Do_DB']
  691. );
  692. $nbServSlaveIgnoreDb = count(
  693. $GLOBALS['replication_info']['slave']['Ignore_DB']
  694. );
  695. $searchDoDBInTruename = array_search(
  696. $table,
  697. $GLOBALS['replication_info']['slave']['Do_DB']
  698. );
  699. $searchDoDBInDB = array_search(
  700. $this->db,
  701. $GLOBALS['replication_info']['slave']['Do_DB']
  702. );
  703. $do = (is_string($searchDoDBInTruename) && strlen($searchDoDBInTruename) > 0)
  704. || (is_string($searchDoDBInDB) && strlen($searchDoDBInDB) > 0)
  705. || ($nbServSlaveDoDb == 0 && $nbServSlaveIgnoreDb == 0)
  706. || $this->hasTable(
  707. $GLOBALS['replication_info']['slave']['Wild_Do_Table'],
  708. $table
  709. );
  710. $searchDb = array_search(
  711. $this->db,
  712. $GLOBALS['replication_info']['slave']['Ignore_DB']
  713. );
  714. $searchTable = array_search(
  715. $table,
  716. $GLOBALS['replication_info']['slave']['Ignore_Table']
  717. );
  718. $ignored = (is_string($searchTable) && strlen($searchTable) > 0)
  719. || (is_string($searchDb) && strlen($searchDb) > 0)
  720. || $this->hasTable(
  721. $GLOBALS['replication_info']['slave']['Wild_Ignore_Table'],
  722. $table
  723. );
  724. }
  725. return [
  726. $do,
  727. $ignored,
  728. ];
  729. }
  730. /**
  731. * Synchronize favorite tables
  732. *
  733. *
  734. * @param RecentFavoriteTable $favoriteInstance Instance of this class
  735. * @param string $user The user hash
  736. * @param array $favoriteTables Existing favorites
  737. *
  738. * @return array
  739. */
  740. protected function synchronizeFavoriteTables(
  741. RecentFavoriteTable $favoriteInstance,
  742. string $user,
  743. array $favoriteTables
  744. ): array {
  745. $favoriteInstanceTables = $favoriteInstance->getTables();
  746. if (empty($favoriteInstanceTables)
  747. && isset($favoriteTables[$user])
  748. ) {
  749. foreach ($favoriteTables[$user] as $key => $value) {
  750. $favoriteInstance->add($value['db'], $value['table']);
  751. }
  752. }
  753. $favoriteTables[$user] = $favoriteInstance->getTables();
  754. $json = [
  755. 'favoriteTables' => json_encode($favoriteTables),
  756. 'list' => $favoriteInstance->getHtmlList(),
  757. ];
  758. $serverId = $GLOBALS['server'];
  759. // Set flag when localStorage and pmadb(if present) are in sync.
  760. $_SESSION['tmpval']['favorites_synced'][$serverId] = true;
  761. return $json;
  762. }
  763. /**
  764. * Function to check if a table is already in favorite list.
  765. *
  766. * @param string $currentTable current table
  767. *
  768. * @return bool
  769. */
  770. protected function checkFavoriteTable(string $currentTable): bool
  771. {
  772. // ensure $_SESSION['tmpval']['favoriteTables'] is initialized
  773. RecentFavoriteTable::getInstance('favorite');
  774. foreach ($_SESSION['tmpval']['favoriteTables'][$GLOBALS['server']] as $value) {
  775. if ($value['db'] == $this->db && $value['table'] == $currentTable) {
  776. return true;
  777. }
  778. }
  779. return false;
  780. }
  781. /**
  782. * Find table with truename
  783. *
  784. * @param array $db DB to look into
  785. * @param string $truename Table name
  786. *
  787. * @return bool
  788. */
  789. protected function hasTable(array $db, $truename)
  790. {
  791. foreach ($db as $db_table) {
  792. if ($this->db == $this->replication->extractDbOrTable($db_table)
  793. && preg_match(
  794. '@^' .
  795. preg_quote(mb_substr($this->replication->extractDbOrTable($db_table, 'table'), 0, -1), '@') . '@',
  796. $truename
  797. )
  798. ) {
  799. return true;
  800. }
  801. }
  802. return false;
  803. }
  804. /**
  805. * Get the value set for ENGINE table,
  806. *
  807. * @param array $current_table current table
  808. * @param integer $sum_size total table size
  809. * @param integer $overhead_size overhead size
  810. *
  811. * @return array
  812. * @internal param bool $table_is_view whether table is view or not
  813. */
  814. protected function getStuffForEngineTypeTable(
  815. array $current_table,
  816. $sum_size,
  817. $overhead_size
  818. ) {
  819. $formatted_size = '-';
  820. $unit = '';
  821. $formatted_overhead = '';
  822. $overhead_unit = '';
  823. $table_is_view = false;
  824. switch ($current_table['ENGINE']) {
  825. // MyISAM, ISAM or Heap table: Row count, data size and index size
  826. // are accurate; data size is accurate for ARCHIVE
  827. case 'MyISAM':
  828. case 'ISAM':
  829. case 'HEAP':
  830. case 'MEMORY':
  831. case 'ARCHIVE':
  832. case 'Aria':
  833. case 'Maria':
  834. list($current_table, $formatted_size, $unit, $formatted_overhead,
  835. $overhead_unit, $overhead_size, $sum_size)
  836. = $this->getValuesForAriaTable(
  837. $current_table,
  838. $sum_size,
  839. $overhead_size,
  840. $formatted_size,
  841. $unit,
  842. $formatted_overhead,
  843. $overhead_unit
  844. );
  845. break;
  846. case 'InnoDB':
  847. case 'PBMS':
  848. case 'TokuDB':
  849. // InnoDB table: Row count is not accurate but data and index sizes are.
  850. // PBMS table in Drizzle: TABLE_ROWS is taken from table cache,
  851. // so it may be unavailable
  852. list($current_table, $formatted_size, $unit, $sum_size)
  853. = $this->getValuesForInnodbTable(
  854. $current_table,
  855. $sum_size
  856. );
  857. break;
  858. // Mysql 5.0.x (and lower) uses MRG_MyISAM
  859. // and MySQL 5.1.x (and higher) uses MRG_MYISAM
  860. // Both are aliases for MERGE
  861. case 'MRG_MyISAM':
  862. case 'MRG_MYISAM':
  863. case 'MERGE':
  864. case 'BerkeleyDB':
  865. // Merge or BerkleyDB table: Only row count is accurate.
  866. if ($this->isShowStats) {
  867. $formatted_size = ' - ';
  868. $unit = '';
  869. }
  870. break;
  871. // for a view, the ENGINE is sometimes reported as null,
  872. // or on some servers it's reported as "SYSTEM VIEW"
  873. case null:
  874. case 'SYSTEM VIEW':
  875. // possibly a view, do nothing
  876. break;
  877. default:
  878. // Unknown table type.
  879. if ($this->isShowStats) {
  880. $formatted_size = __('unknown');
  881. $unit = '';
  882. }
  883. } // end switch
  884. if ($current_table['TABLE_TYPE'] == 'VIEW'
  885. || $current_table['TABLE_TYPE'] == 'SYSTEM VIEW'
  886. ) {
  887. // countRecords() takes care of $cfg['MaxExactCountViews']
  888. $current_table['TABLE_ROWS'] = $this->dbi
  889. ->getTable($this->db, $current_table['TABLE_NAME'])
  890. ->countRecords(true);
  891. $table_is_view = true;
  892. }
  893. return [
  894. $current_table,
  895. $formatted_size,
  896. $unit,
  897. $formatted_overhead,
  898. $overhead_unit,
  899. $overhead_size,
  900. $table_is_view,
  901. $sum_size,
  902. ];
  903. }
  904. /**
  905. * Get values for ARIA/MARIA tables
  906. *
  907. * @param array $current_table current table
  908. * @param integer $sum_size sum size
  909. * @param integer $overhead_size overhead size
  910. * @param integer $formatted_size formatted size
  911. * @param string $unit unit
  912. * @param integer $formatted_overhead overhead formatted
  913. * @param string $overhead_unit overhead unit
  914. *
  915. * @return array
  916. */
  917. protected function getValuesForAriaTable(
  918. array $current_table,
  919. $sum_size,
  920. $overhead_size,
  921. $formatted_size,
  922. $unit,
  923. $formatted_overhead,
  924. $overhead_unit
  925. ) {
  926. if ($this->dbIsSystemSchema) {
  927. $current_table['Rows'] = $this->dbi
  928. ->getTable($this->db, $current_table['Name'])
  929. ->countRecords();
  930. }
  931. if ($this->isShowStats) {
  932. $tblsize = $current_table['Data_length']
  933. + $current_table['Index_length'];
  934. $sum_size += $tblsize;
  935. list($formatted_size, $unit) = Util::formatByteDown(
  936. $tblsize,
  937. 3,
  938. $tblsize > 0 ? 1 : 0
  939. );
  940. if (isset($current_table['Data_free'])
  941. && $current_table['Data_free'] > 0
  942. ) {
  943. list($formatted_overhead, $overhead_unit)
  944. = Util::formatByteDown(
  945. $current_table['Data_free'],
  946. 3,
  947. ($current_table['Data_free'] > 0 ? 1 : 0)
  948. );
  949. $overhead_size += $current_table['Data_free'];
  950. }
  951. }
  952. return [
  953. $current_table,
  954. $formatted_size,
  955. $unit,
  956. $formatted_overhead,
  957. $overhead_unit,
  958. $overhead_size,
  959. $sum_size,
  960. ];
  961. }
  962. /**
  963. * Get values for InnoDB table
  964. *
  965. * @param array $current_table current table
  966. * @param integer $sum_size sum size
  967. *
  968. * @return array
  969. */
  970. protected function getValuesForInnodbTable(
  971. array $current_table,
  972. $sum_size
  973. ) {
  974. $formatted_size = $unit = '';
  975. if ((in_array($current_table['ENGINE'], ['InnoDB', 'TokuDB'])
  976. && $current_table['TABLE_ROWS'] < $GLOBALS['cfg']['MaxExactCount'])
  977. || ! isset($current_table['TABLE_ROWS'])
  978. ) {
  979. $current_table['COUNTED'] = true;
  980. $current_table['TABLE_ROWS'] = $this->dbi
  981. ->getTable($this->db, $current_table['TABLE_NAME'])
  982. ->countRecords(true);
  983. } else {
  984. $current_table['COUNTED'] = false;
  985. }
  986. if ($this->isShowStats) {
  987. $tblsize = $current_table['Data_length']
  988. + $current_table['Index_length'];
  989. $sum_size += $tblsize;
  990. list($formatted_size, $unit) = Util::formatByteDown(
  991. $tblsize,
  992. 3,
  993. ($tblsize > 0 ? 1 : 0)
  994. );
  995. }
  996. return [
  997. $current_table,
  998. $formatted_size,
  999. $unit,
  1000. $sum_size,
  1001. ];
  1002. }
  1003. }