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