1<?php 2/* vim: set expandtab sw=4 ts=4 sts=4: */ 3/** 4 * Set of functions for the SQL executor 5 * 6 * @package PhpMyAdmin 7 */ 8namespace PhpMyAdmin; 9 10use PhpMyAdmin\Bookmark; 11use PhpMyAdmin\Core; 12use PhpMyAdmin\DatabaseInterface; 13use PhpMyAdmin\Display\Results as DisplayResults; 14use PhpMyAdmin\Index; 15use PhpMyAdmin\Message; 16use PhpMyAdmin\Operations; 17use PhpMyAdmin\ParseAnalyze; 18use PhpMyAdmin\Relation; 19use PhpMyAdmin\RelationCleanup; 20use PhpMyAdmin\Response; 21use PhpMyAdmin\SqlParser\Statements\AlterStatement; 22use PhpMyAdmin\SqlParser\Statements\DropStatement; 23use PhpMyAdmin\SqlParser\Statements\SelectStatement; 24use PhpMyAdmin\SqlParser\Utils\Query; 25use PhpMyAdmin\Table; 26use PhpMyAdmin\Transformations; 27use PhpMyAdmin\Url; 28use PhpMyAdmin\Util; 29 30/** 31 * Set of functions for the SQL executor 32 * 33 * @package PhpMyAdmin 34 */ 35class Sql 36{ 37 /** 38 * @var Relation $relation 39 */ 40 private $relation; 41 42 /** 43 * Constructor 44 */ 45 public function __construct() 46 { 47 $this->relation = new Relation(); 48 } 49 50 /** 51 * Parses and analyzes the given SQL query. 52 * 53 * @param string $sql_query SQL query 54 * @param string $db DB name 55 * 56 * @return mixed 57 */ 58 public function parseAndAnalyze($sql_query, $db = null) 59 { 60 if (is_null($db) && isset($GLOBALS['db']) && strlen($GLOBALS['db'])) { 61 $db = $GLOBALS['db']; 62 } 63 list($analyzed_sql_results,,) = ParseAnalyze::sqlQuery($sql_query, $db); 64 return $analyzed_sql_results; 65 } 66 67 /** 68 * Handle remembered sorting order, only for single table query 69 * 70 * @param string $db database name 71 * @param string $table table name 72 * @param array &$analyzed_sql_results the analyzed query results 73 * @param string &$full_sql_query SQL query 74 * 75 * @return void 76 */ 77 private function handleSortOrder( 78 $db, $table, array &$analyzed_sql_results, &$full_sql_query 79 ) { 80 $pmatable = new Table($table, $db); 81 82 if (empty($analyzed_sql_results['order'])) { 83 84 // Retrieving the name of the column we should sort after. 85 $sortCol = $pmatable->getUiProp(Table::PROP_SORTED_COLUMN); 86 if (empty($sortCol)) { 87 return; 88 } 89 90 // Remove the name of the table from the retrieved field name. 91 $sortCol = str_replace( 92 Util::backquote($table) . '.', 93 '', 94 $sortCol 95 ); 96 97 // Create the new query. 98 $full_sql_query = Query::replaceClause( 99 $analyzed_sql_results['statement'], 100 $analyzed_sql_results['parser']->list, 101 'ORDER BY ' . $sortCol 102 ); 103 104 // TODO: Avoid reparsing the query. 105 $analyzed_sql_results = Query::getAll($full_sql_query); 106 } else { 107 // Store the remembered table into session. 108 $pmatable->setUiProp( 109 Table::PROP_SORTED_COLUMN, 110 Query::getClause( 111 $analyzed_sql_results['statement'], 112 $analyzed_sql_results['parser']->list, 113 'ORDER BY' 114 ) 115 ); 116 } 117 } 118 119 /** 120 * Append limit clause to SQL query 121 * 122 * @param array &$analyzed_sql_results the analyzed query results 123 * 124 * @return string limit clause appended SQL query 125 */ 126 private function getSqlWithLimitClause(array &$analyzed_sql_results) 127 { 128 return Query::replaceClause( 129 $analyzed_sql_results['statement'], 130 $analyzed_sql_results['parser']->list, 131 'LIMIT ' . $_SESSION['tmpval']['pos'] . ', ' 132 . $_SESSION['tmpval']['max_rows'] 133 ); 134 } 135 136 /** 137 * Verify whether the result set has columns from just one table 138 * 139 * @param array $fields_meta meta fields 140 * 141 * @return boolean whether the result set has columns from just one table 142 */ 143 private function resultSetHasJustOneTable(array $fields_meta) 144 { 145 $just_one_table = true; 146 $prev_table = ''; 147 foreach ($fields_meta as $one_field_meta) { 148 if ($one_field_meta->table != '' 149 && $prev_table != '' 150 && $one_field_meta->table != $prev_table 151 ) { 152 $just_one_table = false; 153 } 154 if ($one_field_meta->table != '') { 155 $prev_table = $one_field_meta->table; 156 } 157 } 158 return $just_one_table && $prev_table != ''; 159 } 160 161 /** 162 * Verify whether the result set contains all the columns 163 * of at least one unique key 164 * 165 * @param string $db database name 166 * @param string $table table name 167 * @param array $fields_meta meta fields 168 * 169 * @return boolean whether the result set contains a unique key 170 */ 171 private function resultSetContainsUniqueKey($db, $table, array $fields_meta) 172 { 173 $columns = $GLOBALS['dbi']->getColumns($db, $table); 174 $resultSetColumnNames = array(); 175 foreach ($fields_meta as $oneMeta) { 176 $resultSetColumnNames[] = $oneMeta->name; 177 } 178 foreach (Index::getFromTable($table, $db) as $index) { 179 if ($index->isUnique()) { 180 $indexColumns = $index->getColumns(); 181 $numberFound = 0; 182 foreach ($indexColumns as $indexColumnName => $dummy) { 183 if (in_array($indexColumnName, $resultSetColumnNames)) { 184 $numberFound++; 185 } else if (!in_array($indexColumnName, $columns)) { 186 $numberFound++; 187 } else if (strpos($columns[$indexColumnName]['Extra'], 'INVISIBLE') !== false) { 188 $numberFound++; 189 } 190 } 191 if ($numberFound == count($indexColumns)) { 192 return true; 193 } 194 } 195 } 196 return false; 197 } 198 199 /** 200 * Get the HTML for relational column dropdown 201 * During grid edit, if we have a relational field, returns the html for the 202 * dropdown 203 * 204 * @param string $db current database 205 * @param string $table current table 206 * @param string $column current column 207 * @param string $curr_value current selected value 208 * 209 * @return string $dropdown html for the dropdown 210 */ 211 private function getHtmlForRelationalColumnDropdown($db, $table, $column, $curr_value) 212 { 213 $foreigners = $this->relation->getForeigners($db, $table, $column); 214 215 $foreignData = $this->relation->getForeignData($foreigners, $column, false, '', ''); 216 217 if ($foreignData['disp_row'] == null) { 218 //Handle the case when number of values 219 //is more than $cfg['ForeignKeyMaxLimit'] 220 $_url_params = array( 221 'db' => $db, 222 'table' => $table, 223 'field' => $column 224 ); 225 226 $dropdown = '<span class="curr_value">' 227 . htmlspecialchars($_POST['curr_value']) 228 . '</span>' 229 . '<a href="browse_foreigners.php" data-post="' 230 . Url::getCommon($_url_params, '') . '"' 231 . 'class="ajax browse_foreign" ' . '>' 232 . __('Browse foreign values') 233 . '</a>'; 234 } else { 235 $dropdown = $this->relation->foreignDropdown( 236 $foreignData['disp_row'], 237 $foreignData['foreign_field'], 238 $foreignData['foreign_display'], 239 $curr_value, 240 $GLOBALS['cfg']['ForeignKeyMaxLimit'] 241 ); 242 $dropdown = '<select>' . $dropdown . '</select>'; 243 } 244 245 return $dropdown; 246 } 247 248 /** 249 * Get the HTML for the profiling table and accompanying chart if profiling is set. 250 * Otherwise returns null 251 * 252 * @param string $url_query url query 253 * @param string $db current database 254 * @param array $profiling_results array containing the profiling info 255 * 256 * @return string $profiling_table html for the profiling table and chart 257 */ 258 private function getHtmlForProfilingChart($url_query, $db, $profiling_results) 259 { 260 if (! empty($profiling_results)) { 261 $url_query = isset($url_query) 262 ? $url_query 263 : Url::getCommon(array('db' => $db)); 264 265 $profiling_table = ''; 266 $profiling_table .= '<fieldset><legend>' . __('Profiling') 267 . '</legend>' . "\n"; 268 $profiling_table .= '<div class="floatleft">'; 269 $profiling_table .= '<h3>' . __('Detailed profile') . '</h3>'; 270 $profiling_table .= '<table id="profiletable"><thead>' . "\n"; 271 $profiling_table .= ' <tr>' . "\n"; 272 $profiling_table .= ' <th>' . __('Order') 273 . '<div class="sorticon"></div></th>' . "\n"; 274 $profiling_table .= ' <th>' . __('State') 275 . Util::showMySQLDocu('general-thread-states') 276 . '<div class="sorticon"></div></th>' . "\n"; 277 $profiling_table .= ' <th>' . __('Time') 278 . '<div class="sorticon"></div></th>' . "\n"; 279 $profiling_table .= ' </tr></thead><tbody>' . "\n"; 280 list($detailed_table, $chart_json, $profiling_stats) 281 = $this->analyzeAndGetTableHtmlForProfilingResults($profiling_results); 282 $profiling_table .= $detailed_table; 283 $profiling_table .= '</tbody></table>' . "\n"; 284 $profiling_table .= '</div>'; 285 286 $profiling_table .= '<div class="floatleft">'; 287 $profiling_table .= '<h3>' . __('Summary by state') . '</h3>'; 288 $profiling_table .= '<table id="profilesummarytable"><thead>' . "\n"; 289 $profiling_table .= ' <tr>' . "\n"; 290 $profiling_table .= ' <th>' . __('State') 291 . Util::showMySQLDocu('general-thread-states') 292 . '<div class="sorticon"></div></th>' . "\n"; 293 $profiling_table .= ' <th>' . __('Total Time') 294 . '<div class="sorticon"></div></th>' . "\n"; 295 $profiling_table .= ' <th>' . __('% Time') 296 . '<div class="sorticon"></div></th>' . "\n"; 297 $profiling_table .= ' <th>' . __('Calls') 298 . '<div class="sorticon"></div></th>' . "\n"; 299 $profiling_table .= ' <th>' . __('ø Time') 300 . '<div class="sorticon"></div></th>' . "\n"; 301 $profiling_table .= ' </tr></thead><tbody>' . "\n"; 302 $profiling_table .= $this->getTableHtmlForProfilingSummaryByState( 303 $profiling_stats 304 ); 305 $profiling_table .= '</tbody></table>' . "\n"; 306 307 $profiling_table .= <<<EOT 308<script type="text/javascript"> 309 url_query = '$url_query'; 310</script> 311EOT; 312 $profiling_table .= "</div>"; 313 $profiling_table .= "<div class='clearfloat'></div>"; 314 315 //require_once 'libraries/chart.lib.php'; 316 $profiling_table .= '<div id="profilingChartData" class="hide">'; 317 $profiling_table .= json_encode($chart_json); 318 $profiling_table .= '</div>'; 319 $profiling_table .= '<div id="profilingchart" class="hide">'; 320 $profiling_table .= '</div>'; 321 $profiling_table .= '<script type="text/javascript">'; 322 $profiling_table .= "AJAX.registerOnload('sql.js', function () {"; 323 $profiling_table .= 'makeProfilingChart();'; 324 $profiling_table .= 'initProfilingTables();'; 325 $profiling_table .= '});'; 326 $profiling_table .= '</script>'; 327 $profiling_table .= '</fieldset>' . "\n"; 328 } else { 329 $profiling_table = null; 330 } 331 return $profiling_table; 332 } 333 334 /** 335 * Function to get HTML for detailed profiling results table, profiling stats, and 336 * $chart_json for displaying the chart. 337 * 338 * @param array $profiling_results profiling results 339 * 340 * @return mixed 341 */ 342 private function analyzeAndGetTableHtmlForProfilingResults( 343 $profiling_results 344 ) { 345 $profiling_stats = array( 346 'total_time' => 0, 347 'states' => array(), 348 ); 349 $chart_json = Array(); 350 $i = 1; 351 $table = ''; 352 foreach ($profiling_results as $one_result) { 353 if (isset($profiling_stats['states'][ucwords($one_result['Status'])])) { 354 $states = $profiling_stats['states']; 355 $states[ucwords($one_result['Status'])]['total_time'] 356 += $one_result['Duration']; 357 $states[ucwords($one_result['Status'])]['calls']++; 358 } else { 359 $profiling_stats['states'][ucwords($one_result['Status'])] = array( 360 'total_time' => $one_result['Duration'], 361 'calls' => 1, 362 ); 363 } 364 $profiling_stats['total_time'] += $one_result['Duration']; 365 366 $table .= ' <tr>' . "\n"; 367 $table .= '<td>' . $i++ . '</td>' . "\n"; 368 $table .= '<td>' . ucwords($one_result['Status']) 369 . '</td>' . "\n"; 370 $table .= '<td class="right">' 371 . (Util::formatNumber($one_result['Duration'], 3, 1)) 372 . 's<span class="rawvalue hide">' 373 . $one_result['Duration'] . '</span></td>' . "\n"; 374 if (isset($chart_json[ucwords($one_result['Status'])])) { 375 $chart_json[ucwords($one_result['Status'])] 376 += $one_result['Duration']; 377 } else { 378 $chart_json[ucwords($one_result['Status'])] 379 = $one_result['Duration']; 380 } 381 } 382 return array($table, $chart_json, $profiling_stats); 383 } 384 385 /** 386 * Function to get HTML for summary by state table 387 * 388 * @param array $profiling_stats profiling stats 389 * 390 * @return string $table html for the table 391 */ 392 private function getTableHtmlForProfilingSummaryByState(array $profiling_stats) 393 { 394 $table = ''; 395 foreach ($profiling_stats['states'] as $name => $stats) { 396 $table .= ' <tr>' . "\n"; 397 $table .= '<td>' . $name . '</td>' . "\n"; 398 $table .= '<td align="right">' 399 . Util::formatNumber($stats['total_time'], 3, 1) 400 . 's<span class="rawvalue hide">' 401 . $stats['total_time'] . '</span></td>' . "\n"; 402 $table .= '<td align="right">' 403 . Util::formatNumber( 404 100 * ($stats['total_time'] / $profiling_stats['total_time']), 405 0, 2 406 ) 407 . '%</td>' . "\n"; 408 $table .= '<td align="right">' . $stats['calls'] . '</td>' 409 . "\n"; 410 $table .= '<td align="right">' 411 . Util::formatNumber( 412 $stats['total_time'] / $stats['calls'], 3, 1 413 ) 414 . 's<span class="rawvalue hide">' 415 . number_format($stats['total_time'] / $stats['calls'], 8, '.', '') 416 . '</span></td>' . "\n"; 417 $table .= ' </tr>' . "\n"; 418 } 419 return $table; 420 } 421 422 /** 423 * Get the HTML for the enum column dropdown 424 * During grid edit, if we have a enum field, returns the html for the 425 * dropdown 426 * 427 * @param string $db current database 428 * @param string $table current table 429 * @param string $column current column 430 * @param string $curr_value currently selected value 431 * 432 * @return string $dropdown html for the dropdown 433 */ 434 private function getHtmlForEnumColumnDropdown($db, $table, $column, $curr_value) 435 { 436 $values = $this->getValuesForColumn($db, $table, $column); 437 $dropdown = '<option value=""> </option>'; 438 $dropdown .= $this->getHtmlForOptionsList($values, array($curr_value)); 439 $dropdown = '<select>' . $dropdown . '</select>'; 440 return $dropdown; 441 } 442 443 /** 444 * Get value of a column for a specific row (marked by $where_clause) 445 * 446 * @param string $db current database 447 * @param string $table current table 448 * @param string $column current column 449 * @param string $where_clause where clause to select a particular row 450 * 451 * @return string with value 452 */ 453 private function getFullValuesForSetColumn($db, $table, $column, $where_clause) 454 { 455 $result = $GLOBALS['dbi']->fetchSingleRow( 456 "SELECT `$column` FROM `$db`.`$table` WHERE $where_clause" 457 ); 458 459 return $result[$column]; 460 } 461 462 /** 463 * Get the HTML for the set column dropdown 464 * During grid edit, if we have a set field, returns the html for the 465 * dropdown 466 * 467 * @param string $db current database 468 * @param string $table current table 469 * @param string $column current column 470 * @param string $curr_value currently selected value 471 * 472 * @return string $dropdown html for the set column 473 */ 474 private function getHtmlForSetColumn($db, $table, $column, $curr_value) 475 { 476 $values = $this->getValuesForColumn($db, $table, $column); 477 $dropdown = ''; 478 $full_values = 479 isset($_POST['get_full_values']) ? $_POST['get_full_values'] : false; 480 $where_clause = 481 isset($_POST['where_clause']) ? $_POST['where_clause'] : null; 482 483 // If the $curr_value was truncated, we should 484 // fetch the correct full values from the table 485 if ($full_values && ! empty($where_clause)) { 486 $curr_value = $this->getFullValuesForSetColumn( 487 $db, $table, $column, $where_clause 488 ); 489 } 490 491 //converts characters of $curr_value to HTML entities 492 $converted_curr_value = htmlentities( 493 $curr_value, ENT_COMPAT, "UTF-8" 494 ); 495 496 $selected_values = explode(',', $converted_curr_value); 497 498 $dropdown .= $this->getHtmlForOptionsList($values, $selected_values); 499 500 $select_size = (sizeof($values) > 10) ? 10 : sizeof($values); 501 $dropdown = '<select multiple="multiple" size="' . $select_size . '">' 502 . $dropdown . '</select>'; 503 504 return $dropdown; 505 } 506 507 /** 508 * Get all the values for a enum column or set column in a table 509 * 510 * @param string $db current database 511 * @param string $table current table 512 * @param string $column current column 513 * 514 * @return array $values array containing the value list for the column 515 */ 516 private function getValuesForColumn($db, $table, $column) 517 { 518 $field_info_query = $GLOBALS['dbi']->getColumnsSql($db, $table, $column); 519 520 $field_info_result = $GLOBALS['dbi']->fetchResult( 521 $field_info_query, 522 null, 523 null, 524 DatabaseInterface::CONNECT_USER, 525 DatabaseInterface::QUERY_STORE 526 ); 527 528 $values = Util::parseEnumSetValues($field_info_result[0]['Type']); 529 530 return $values; 531 } 532 533 /** 534 * Get HTML for options list 535 * 536 * @param array $values set of values 537 * @param array $selected_values currently selected values 538 * 539 * @return string $options HTML for options list 540 */ 541 private function getHtmlForOptionsList(array $values, array $selected_values) 542 { 543 $options = ''; 544 foreach ($values as $value) { 545 $options .= '<option value="' . $value . '"'; 546 if (in_array($value, $selected_values, true)) { 547 $options .= ' selected="selected" '; 548 } 549 $options .= '>' . $value . '</option>'; 550 } 551 return $options; 552 } 553 554 /** 555 * Function to get html for bookmark support if bookmarks are enabled. Else will 556 * return null 557 * 558 * @param array $displayParts the parts to display 559 * @param array $cfgBookmark configuration setting for bookmarking 560 * @param string $sql_query sql query 561 * @param string $db current database 562 * @param string $table current table 563 * @param string $complete_query complete query 564 * @param string $bkm_user bookmarking user 565 * 566 * @return string $html 567 */ 568 public function getHtmlForBookmark(array $displayParts, array $cfgBookmark, $sql_query, $db, 569 $table, $complete_query, $bkm_user 570 ) { 571 if ($displayParts['bkm_form'] == '1' 572 && (! empty($cfgBookmark) && empty($_GET['id_bookmark'])) 573 && ! empty($sql_query) 574 ) { 575 $goto = 'sql.php' 576 . Url::getCommon( 577 array( 578 'db' => $db, 579 'table' => $table, 580 'sql_query' => $sql_query, 581 'id_bookmark'=> 1, 582 ) 583 ); 584 $bkm_sql_query = isset($complete_query) ? $complete_query : $sql_query; 585 $html = '<form action="sql.php" method="post"' 586 . ' onsubmit="return ! emptyCheckTheField(this,' 587 . '\'bkm_fields[bkm_label]\');"' 588 . ' class="bookmarkQueryForm print_ignore">'; 589 $html .= Url::getHiddenInputs(); 590 $html .= '<input type="hidden" name="db"' 591 . ' value="' . htmlspecialchars($db) . '" />'; 592 $html .= '<input type="hidden" name="goto" value="' . $goto . '" />'; 593 $html .= '<input type="hidden" name="bkm_fields[bkm_database]"' 594 . ' value="' . htmlspecialchars($db) . '" />'; 595 $html .= '<input type="hidden" name="bkm_fields[bkm_user]"' 596 . ' value="' . $bkm_user . '" />'; 597 $html .= '<input type="hidden" name="bkm_fields[bkm_sql_query]"' 598 . ' value="' 599 . htmlspecialchars($bkm_sql_query) 600 . '" />'; 601 $html .= '<fieldset>'; 602 $html .= '<legend>'; 603 $html .= Util::getIcon( 604 'b_bookmark', __('Bookmark this SQL query'), true 605 ); 606 $html .= '</legend>'; 607 $html .= '<div class="formelement">'; 608 $html .= '<label>' . __('Label:'); 609 $html .= '<input type="text" name="bkm_fields[bkm_label]" value="" />' . 610 '</label>'; 611 $html .= '</div>'; 612 $html .= '<div class="formelement">'; 613 $html .= '<label>' . 614 '<input type="checkbox" name="bkm_all_users" value="true" />'; 615 $html .= __('Let every user access this bookmark') . '</label>'; 616 $html .= '</div>'; 617 $html .= '<div class="clearfloat"></div>'; 618 $html .= '</fieldset>'; 619 $html .= '<fieldset class="tblFooters">'; 620 $html .= '<input type="hidden" name="store_bkm" value="1" />'; 621 $html .= '<input type="submit"' 622 . ' value="' . __('Bookmark this SQL query') . '" />'; 623 $html .= '</fieldset>'; 624 $html .= '</form>'; 625 626 } else { 627 $html = null; 628 } 629 630 return $html; 631 } 632 633 /** 634 * Function to check whether to remember the sorting order or not 635 * 636 * @param array $analyzed_sql_results the analyzed query and other variables set 637 * after analyzing the query 638 * 639 * @return boolean 640 */ 641 private function isRememberSortingOrder(array $analyzed_sql_results) 642 { 643 return $GLOBALS['cfg']['RememberSorting'] 644 && ! ($analyzed_sql_results['is_count'] 645 || $analyzed_sql_results['is_export'] 646 || $analyzed_sql_results['is_func'] 647 || $analyzed_sql_results['is_analyse']) 648 && $analyzed_sql_results['select_from'] 649 && isset($analyzed_sql_results['select_expr']) 650 && isset($analyzed_sql_results['select_tables']) 651 && ((empty($analyzed_sql_results['select_expr'])) 652 || ((count($analyzed_sql_results['select_expr']) == 1) 653 && ($analyzed_sql_results['select_expr'][0] == '*'))) 654 && count($analyzed_sql_results['select_tables']) == 1; 655 } 656 657 /** 658 * Function to check whether the LIMIT clause should be appended or not 659 * 660 * @param array $analyzed_sql_results the analyzed query and other variables set 661 * after analyzing the query 662 * 663 * @return boolean 664 */ 665 private function isAppendLimitClause(array $analyzed_sql_results) 666 { 667 // Assigning LIMIT clause to an syntactically-wrong query 668 // is not needed. Also we would want to show the true query 669 // and the true error message to the query executor 670 671 return (isset($analyzed_sql_results['parser']) 672 && count($analyzed_sql_results['parser']->errors) === 0) 673 && ($_SESSION['tmpval']['max_rows'] != 'all') 674 && ! ($analyzed_sql_results['is_export'] 675 || $analyzed_sql_results['is_analyse']) 676 && ($analyzed_sql_results['select_from'] 677 || $analyzed_sql_results['is_subquery']) 678 && empty($analyzed_sql_results['limit']); 679 } 680 681 /** 682 * Function to check whether this query is for just browsing 683 * 684 * @param array $analyzed_sql_results the analyzed query and other variables set 685 * after analyzing the query 686 * @param boolean $find_real_end whether the real end should be found 687 * 688 * @return boolean 689 */ 690 public function isJustBrowsing(array $analyzed_sql_results, $find_real_end) 691 { 692 return ! $analyzed_sql_results['is_group'] 693 && ! $analyzed_sql_results['is_func'] 694 && empty($analyzed_sql_results['union']) 695 && empty($analyzed_sql_results['distinct']) 696 && $analyzed_sql_results['select_from'] 697 && (count($analyzed_sql_results['select_tables']) === 1) 698 && (empty($analyzed_sql_results['statement']->where) 699 || (count($analyzed_sql_results['statement']->where) == 1 700 && $analyzed_sql_results['statement']->where[0]->expr ==='1')) 701 && empty($analyzed_sql_results['group']) 702 && ! isset($find_real_end) 703 && ! $analyzed_sql_results['is_subquery'] 704 && ! $analyzed_sql_results['join'] 705 && empty($analyzed_sql_results['having']); 706 } 707 708 /** 709 * Function to check whether the related transformation information should be deleted 710 * 711 * @param array $analyzed_sql_results the analyzed query and other variables set 712 * after analyzing the query 713 * 714 * @return boolean 715 */ 716 private function isDeleteTransformationInfo(array $analyzed_sql_results) 717 { 718 return !empty($analyzed_sql_results['querytype']) 719 && (($analyzed_sql_results['querytype'] == 'ALTER') 720 || ($analyzed_sql_results['querytype'] == 'DROP')); 721 } 722 723 /** 724 * Function to check whether the user has rights to drop the database 725 * 726 * @param array $analyzed_sql_results the analyzed query and other variables set 727 * after analyzing the query 728 * @param boolean $allowUserDropDatabase whether the user is allowed to drop db 729 * @param boolean $is_superuser whether this user is a superuser 730 * 731 * @return boolean 732 */ 733 public function hasNoRightsToDropDatabase(array $analyzed_sql_results, 734 $allowUserDropDatabase, $is_superuser 735 ) { 736 return ! $allowUserDropDatabase 737 && isset($analyzed_sql_results['drop_database']) 738 && $analyzed_sql_results['drop_database'] 739 && ! $is_superuser; 740 } 741 742 /** 743 * Function to set a column property 744 * 745 * @param Table $pmatable Table instance 746 * @param string $request_index col_order|col_visib 747 * 748 * @return boolean $retval 749 */ 750 private function setColumnProperty($pmatable, $request_index) 751 { 752 $property_value = array_map('intval', explode(',', $_POST[$request_index])); 753 switch($request_index) { 754 case 'col_order': 755 $property_to_set = Table::PROP_COLUMN_ORDER; 756 break; 757 case 'col_visib': 758 $property_to_set = Table::PROP_COLUMN_VISIB; 759 break; 760 default: 761 $property_to_set = ''; 762 } 763 $retval = $pmatable->setUiProp( 764 $property_to_set, 765 $property_value, 766 isset($_POST['table_create_time']) ? $_POST['table_create_time'] : null 767 ); 768 if (gettype($retval) != 'boolean') { 769 $response = Response::getInstance(); 770 $response->setRequestStatus(false); 771 $response->addJSON('message', $retval->getString()); 772 exit; 773 } 774 775 return $retval; 776 } 777 778 /** 779 * Function to check the request for setting the column order or visibility 780 * 781 * @param string $table the current table 782 * @param string $db the current database 783 * 784 * @return void 785 */ 786 public function setColumnOrderOrVisibility($table, $db) 787 { 788 $pmatable = new Table($table, $db); 789 $retval = false; 790 791 // set column order 792 if (isset($_POST['col_order'])) { 793 $retval = $this->setColumnProperty($pmatable, 'col_order'); 794 } 795 796 // set column visibility 797 if ($retval === true && isset($_POST['col_visib'])) { 798 $retval = $this->setColumnProperty($pmatable, 'col_visib'); 799 } 800 801 $response = Response::getInstance(); 802 $response->setRequestStatus($retval == true); 803 exit; 804 } 805 806 /** 807 * Function to add a bookmark 808 * 809 * @param string $goto goto page URL 810 * 811 * @return void 812 */ 813 public function addBookmark($goto) 814 { 815 $bookmark = Bookmark::createBookmark( 816 $GLOBALS['dbi'], 817 $GLOBALS['cfg']['Server']['user'], 818 $_POST['bkm_fields'], 819 (isset($_POST['bkm_all_users']) 820 && $_POST['bkm_all_users'] == 'true' ? true : false 821 ) 822 ); 823 $result = $bookmark->save(); 824 $response = Response::getInstance(); 825 if ($response->isAjax()) { 826 if ($result) { 827 $msg = Message::success(__('Bookmark %s has been created.')); 828 $msg->addParam($_POST['bkm_fields']['bkm_label']); 829 $response->addJSON('message', $msg); 830 } else { 831 $msg = Message::error(__('Bookmark not created!')); 832 $response->setRequestStatus(false); 833 $response->addJSON('message', $msg); 834 } 835 exit; 836 } else { 837 // go back to sql.php to redisplay query; do not use & in this case: 838 /** 839 * @todo In which scenario does this happen? 840 */ 841 Core::sendHeaderLocation( 842 './' . $goto 843 . '&label=' . $_POST['bkm_fields']['bkm_label'] 844 ); 845 } 846 } 847 848 /** 849 * Function to find the real end of rows 850 * 851 * @param string $db the current database 852 * @param string $table the current table 853 * 854 * @return mixed the number of rows if "retain" param is true, otherwise true 855 */ 856 public function findRealEndOfRows($db, $table) 857 { 858 $unlim_num_rows = $GLOBALS['dbi']->getTable($db, $table)->countRecords(true); 859 $_SESSION['tmpval']['pos'] = $this->getStartPosToDisplayRow($unlim_num_rows); 860 861 return $unlim_num_rows; 862 } 863 864 /** 865 * Function to get values for the relational columns 866 * 867 * @param string $db the current database 868 * @param string $table the current table 869 * 870 * @return void 871 */ 872 public function getRelationalValues($db, $table) 873 { 874 $column = $_POST['column']; 875 if ($_SESSION['tmpval']['relational_display'] == 'D' 876 && isset($_POST['relation_key_or_display_column']) 877 && $_POST['relation_key_or_display_column'] 878 ) { 879 $curr_value = $_POST['relation_key_or_display_column']; 880 } else { 881 $curr_value = $_POST['curr_value']; 882 } 883 $dropdown = $this->getHtmlForRelationalColumnDropdown( 884 $db, $table, $column, $curr_value 885 ); 886 $response = Response::getInstance(); 887 $response->addJSON('dropdown', $dropdown); 888 exit; 889 } 890 891 /** 892 * Function to get values for Enum or Set Columns 893 * 894 * @param string $db the current database 895 * @param string $table the current table 896 * @param string $columnType whether enum or set 897 * 898 * @return void 899 */ 900 public function getEnumOrSetValues($db, $table, $columnType) 901 { 902 $column = $_POST['column']; 903 $curr_value = $_POST['curr_value']; 904 $response = Response::getInstance(); 905 if ($columnType == "enum") { 906 $dropdown = $this->getHtmlForEnumColumnDropdown( 907 $db, $table, $column, $curr_value 908 ); 909 $response->addJSON('dropdown', $dropdown); 910 } else { 911 $select = $this->getHtmlForSetColumn( 912 $db, $table, $column, $curr_value 913 ); 914 $response->addJSON('select', $select); 915 } 916 exit; 917 } 918 919 /** 920 * Function to get the default sql query for browsing page 921 * 922 * @param string $db the current database 923 * @param string $table the current table 924 * 925 * @return string $sql_query the default $sql_query for browse page 926 */ 927 public function getDefaultSqlQueryForBrowse($db, $table) 928 { 929 $bookmark = Bookmark::get( 930 $GLOBALS['dbi'], 931 $GLOBALS['cfg']['Server']['user'], 932 $db, 933 $table, 934 'label', 935 false, 936 true 937 ); 938 939 if (! empty($bookmark) && ! empty($bookmark->getQuery())) { 940 $GLOBALS['using_bookmark_message'] = Message::notice( 941 __('Using bookmark "%s" as default browse query.') 942 ); 943 $GLOBALS['using_bookmark_message']->addParam($table); 944 $GLOBALS['using_bookmark_message']->addHtml( 945 Util::showDocu('faq', 'faq6-22') 946 ); 947 $sql_query = $bookmark->getQuery(); 948 } else { 949 950 $defaultOrderByClause = ''; 951 952 if (isset($GLOBALS['cfg']['TablePrimaryKeyOrder']) 953 && ($GLOBALS['cfg']['TablePrimaryKeyOrder'] !== 'NONE') 954 ) { 955 956 $primaryKey = null; 957 $primary = Index::getPrimary($table, $db); 958 959 if ($primary !== false) { 960 961 $primarycols = $primary->getColumns(); 962 963 foreach ($primarycols as $col) { 964 $primaryKey = $col->getName(); 965 break; 966 } 967 968 if ($primaryKey != null) { 969 $defaultOrderByClause = ' ORDER BY ' 970 . Util::backquote($table) . '.' 971 . Util::backquote($primaryKey) . ' ' 972 . $GLOBALS['cfg']['TablePrimaryKeyOrder']; 973 } 974 975 } 976 977 } 978 979 $sql_query = 'SELECT * FROM ' . Util::backquote($table) 980 . $defaultOrderByClause; 981 982 } 983 984 return $sql_query; 985 } 986 987 /** 988 * Responds an error when an error happens when executing the query 989 * 990 * @param boolean $is_gotofile whether goto file or not 991 * @param string $error error after executing the query 992 * @param string $full_sql_query full sql query 993 * 994 * @return void 995 */ 996 private function handleQueryExecuteError($is_gotofile, $error, $full_sql_query) 997 { 998 if ($is_gotofile) { 999 $message = Message::rawError($error); 1000 $response = Response::getInstance(); 1001 $response->setRequestStatus(false); 1002 $response->addJSON('message', $message); 1003 } else { 1004 Util::mysqlDie($error, $full_sql_query, '', ''); 1005 } 1006 exit; 1007 } 1008 1009 /** 1010 * Function to store the query as a bookmark 1011 * 1012 * @param string $db the current database 1013 * @param string $bkm_user the bookmarking user 1014 * @param string $sql_query_for_bookmark the query to be stored in bookmark 1015 * @param string $bkm_label bookmark label 1016 * @param boolean $bkm_replace whether to replace existing bookmarks 1017 * 1018 * @return void 1019 */ 1020 public function storeTheQueryAsBookmark($db, $bkm_user, $sql_query_for_bookmark, 1021 $bkm_label, $bkm_replace 1022 ) { 1023 $bfields = array( 1024 'bkm_database' => $db, 1025 'bkm_user' => $bkm_user, 1026 'bkm_sql_query' => $sql_query_for_bookmark, 1027 'bkm_label' => $bkm_label, 1028 ); 1029 1030 // Should we replace bookmark? 1031 if (isset($bkm_replace)) { 1032 $bookmarks = Bookmark::getList( 1033 $GLOBALS['dbi'], 1034 $GLOBALS['cfg']['Server']['user'], 1035 $db 1036 ); 1037 foreach ($bookmarks as $bookmark) { 1038 if ($bookmark->getLabel() == $bkm_label) { 1039 $bookmark->delete(); 1040 } 1041 } 1042 } 1043 1044 $bookmark = Bookmark::createBookmark( 1045 $GLOBALS['dbi'], 1046 $GLOBALS['cfg']['Server']['user'], 1047 $bfields, 1048 isset($_POST['bkm_all_users']) 1049 ); 1050 $bookmark->save(); 1051 } 1052 1053 /** 1054 * Executes the SQL query and measures its execution time 1055 * 1056 * @param string $full_sql_query the full sql query 1057 * 1058 * @return array ($result, $querytime) 1059 */ 1060 private function executeQueryAndMeasureTime($full_sql_query) 1061 { 1062 // close session in case the query takes too long 1063 session_write_close(); 1064 1065 // Measure query time. 1066 $querytime_before = array_sum(explode(' ', microtime())); 1067 1068 $result = @$GLOBALS['dbi']->tryQuery( 1069 $full_sql_query, DatabaseInterface::CONNECT_USER, DatabaseInterface::QUERY_STORE 1070 ); 1071 $querytime_after = array_sum(explode(' ', microtime())); 1072 1073 // reopen session 1074 session_start(); 1075 1076 return array($result, $querytime_after - $querytime_before); 1077 } 1078 1079 /** 1080 * Function to get the affected or changed number of rows after executing a query 1081 * 1082 * @param boolean $is_affected whether the query affected a table 1083 * @param mixed $result results of executing the query 1084 * 1085 * @return int $num_rows number of rows affected or changed 1086 */ 1087 private function getNumberOfRowsAffectedOrChanged($is_affected, $result) 1088 { 1089 if (! $is_affected) { 1090 $num_rows = ($result) ? @$GLOBALS['dbi']->numRows($result) : 0; 1091 } else { 1092 $num_rows = @$GLOBALS['dbi']->affectedRows(); 1093 } 1094 1095 return $num_rows; 1096 } 1097 1098 /** 1099 * Checks if the current database has changed 1100 * This could happen if the user sends a query like "USE `database`;" 1101 * 1102 * @param string $db the database in the query 1103 * 1104 * @return int $reload whether to reload the navigation(1) or not(0) 1105 */ 1106 private function hasCurrentDbChanged($db) 1107 { 1108 if (strlen($db) > 0) { 1109 $current_db = $GLOBALS['dbi']->fetchValue('SELECT DATABASE()'); 1110 // $current_db is false, except when a USE statement was sent 1111 return ($current_db != false) && ($db !== $current_db); 1112 } 1113 1114 return false; 1115 } 1116 1117 /** 1118 * If a table, database or column gets dropped, clean comments. 1119 * 1120 * @param string $db current database 1121 * @param string $table current table 1122 * @param string $column current column 1123 * @param bool $purge whether purge set or not 1124 * 1125 * @return array $extra_data 1126 */ 1127 private function cleanupRelations($db, $table, $column, $purge) 1128 { 1129 if (! empty($purge) && strlen($db) > 0) { 1130 if (strlen($table) > 0) { 1131 if (isset($column) && strlen($column) > 0) { 1132 RelationCleanup::column($db, $table, $column); 1133 } else { 1134 RelationCleanup::table($db, $table); 1135 } 1136 } else { 1137 RelationCleanup::database($db); 1138 } 1139 } 1140 } 1141 1142 /** 1143 * Function to count the total number of rows for the same 'SELECT' query without 1144 * the 'LIMIT' clause that may have been programatically added 1145 * 1146 * @param int $num_rows number of rows affected/changed by the query 1147 * @param bool $justBrowsing whether just browsing or not 1148 * @param string $db the current database 1149 * @param string $table the current table 1150 * @param array $analyzed_sql_results the analyzed query and other variables set 1151 * after analyzing the query 1152 * 1153 * @return int $unlim_num_rows unlimited number of rows 1154 */ 1155 private function countQueryResults( 1156 $num_rows, $justBrowsing, $db, $table, array $analyzed_sql_results 1157 ) { 1158 1159 /* Shortcut for not analyzed/empty query */ 1160 if (empty($analyzed_sql_results)) { 1161 return 0; 1162 } 1163 1164 if (!$this->isAppendLimitClause($analyzed_sql_results)) { 1165 // if we did not append a limit, set this to get a correct 1166 // "Showing rows..." message 1167 // $_SESSION['tmpval']['max_rows'] = 'all'; 1168 $unlim_num_rows = $num_rows; 1169 } elseif ($this->isAppendLimitClause($analyzed_sql_results) && $_SESSION['tmpval']['max_rows'] > $num_rows) { 1170 // When user has not defined a limit in query and total rows in 1171 // result are less than max_rows to display, there is no need 1172 // to count total rows for that query again 1173 $unlim_num_rows = $_SESSION['tmpval']['pos'] + $num_rows; 1174 } elseif ($analyzed_sql_results['querytype'] == 'SELECT' 1175 || $analyzed_sql_results['is_subquery'] 1176 ) { 1177 // c o u n t q u e r y 1178 1179 // If we are "just browsing", there is only one table (and no join), 1180 // and no WHERE clause (or just 'WHERE 1 '), 1181 // we do a quick count (which uses MaxExactCount) because 1182 // SQL_CALC_FOUND_ROWS is not quick on large InnoDB tables 1183 1184 // However, do not count again if we did it previously 1185 // due to $find_real_end == true 1186 if ($justBrowsing) { 1187 // Get row count (is approximate for InnoDB) 1188 $unlim_num_rows = $GLOBALS['dbi']->getTable($db, $table)->countRecords(); 1189 /** 1190 * @todo Can we know at this point that this is InnoDB, 1191 * (in this case there would be no need for getting 1192 * an exact count)? 1193 */ 1194 if ($unlim_num_rows < $GLOBALS['cfg']['MaxExactCount']) { 1195 // Get the exact count if approximate count 1196 // is less than MaxExactCount 1197 /** 1198 * @todo In countRecords(), MaxExactCount is also verified, 1199 * so can we avoid checking it twice? 1200 */ 1201 $unlim_num_rows = $GLOBALS['dbi']->getTable($db, $table) 1202 ->countRecords(true); 1203 } 1204 1205 } else { 1206 1207 // The SQL_CALC_FOUND_ROWS option of the SELECT statement is used. 1208 1209 // For UNION statements, only a SQL_CALC_FOUND_ROWS is required 1210 // after the first SELECT. 1211 1212 $count_query = Query::replaceClause( 1213 $analyzed_sql_results['statement'], 1214 $analyzed_sql_results['parser']->list, 1215 'SELECT SQL_CALC_FOUND_ROWS', 1216 null, 1217 true 1218 ); 1219 1220 // Another LIMIT clause is added to avoid long delays. 1221 // A complete result will be returned anyway, but the LIMIT would 1222 // stop the query as soon as the result that is required has been 1223 // computed. 1224 1225 if (empty($analyzed_sql_results['union'])) { 1226 $count_query .= ' LIMIT 1'; 1227 } 1228 1229 // Running the count query. 1230 $GLOBALS['dbi']->tryQuery($count_query); 1231 1232 $unlim_num_rows = $GLOBALS['dbi']->fetchValue('SELECT FOUND_ROWS()'); 1233 } // end else "just browsing" 1234 } else {// not $is_select 1235 $unlim_num_rows = 0; 1236 } 1237 1238 return $unlim_num_rows; 1239 } 1240 1241 /** 1242 * Function to handle all aspects relating to executing the query 1243 * 1244 * @param array $analyzed_sql_results analyzed sql results 1245 * @param string $full_sql_query full sql query 1246 * @param boolean $is_gotofile whether to go to a file 1247 * @param string $db current database 1248 * @param string $table current table 1249 * @param boolean $find_real_end whether to find the real end 1250 * @param string $sql_query_for_bookmark sql query to be stored as bookmark 1251 * @param array $extra_data extra data 1252 * 1253 * @return mixed 1254 */ 1255 private function executeTheQuery(array $analyzed_sql_results, $full_sql_query, $is_gotofile, 1256 $db, $table, $find_real_end, $sql_query_for_bookmark, $extra_data 1257 ) { 1258 $response = Response::getInstance(); 1259 $response->getHeader()->getMenu()->setTable($table); 1260 1261 // Only if we ask to see the php code 1262 if (isset($GLOBALS['show_as_php'])) { 1263 $result = null; 1264 $num_rows = 0; 1265 $unlim_num_rows = 0; 1266 } else { // If we don't ask to see the php code 1267 if (isset($_SESSION['profiling']) 1268 && Util::profilingSupported() 1269 ) { 1270 $GLOBALS['dbi']->query('SET PROFILING=1;'); 1271 } 1272 1273 list( 1274 $result, 1275 $GLOBALS['querytime'] 1276 ) = $this->executeQueryAndMeasureTime($full_sql_query); 1277 1278 // Displays an error message if required and stop parsing the script 1279 $error = $GLOBALS['dbi']->getError(); 1280 if ($error && $GLOBALS['cfg']['IgnoreMultiSubmitErrors']) { 1281 $extra_data['error'] = $error; 1282 } elseif ($error) { 1283 $this->handleQueryExecuteError($is_gotofile, $error, $full_sql_query); 1284 } 1285 1286 // If there are no errors and bookmarklabel was given, 1287 // store the query as a bookmark 1288 if (! empty($_POST['bkm_label']) && ! empty($sql_query_for_bookmark)) { 1289 $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); 1290 $this->storeTheQueryAsBookmark( 1291 $db, $cfgBookmark['user'], 1292 $sql_query_for_bookmark, $_POST['bkm_label'], 1293 isset($_POST['bkm_replace']) ? $_POST['bkm_replace'] : null 1294 ); 1295 } // end store bookmarks 1296 1297 // Gets the number of rows affected/returned 1298 // (This must be done immediately after the query because 1299 // mysql_affected_rows() reports about the last query done) 1300 $num_rows = $this->getNumberOfRowsAffectedOrChanged( 1301 $analyzed_sql_results['is_affected'], $result 1302 ); 1303 1304 // Grabs the profiling results 1305 if (isset($_SESSION['profiling']) 1306 && Util::profilingSupported() 1307 ) { 1308 $profiling_results = $GLOBALS['dbi']->fetchResult('SHOW PROFILE;'); 1309 } 1310 1311 $justBrowsing = $this->isJustBrowsing( 1312 $analyzed_sql_results, isset($find_real_end) ? $find_real_end : null 1313 ); 1314 1315 $unlim_num_rows = $this->countQueryResults( 1316 $num_rows, $justBrowsing, $db, $table, $analyzed_sql_results 1317 ); 1318 1319 $this->cleanupRelations( 1320 isset($db) ? $db : '', 1321 isset($table) ? $table : '', 1322 isset($_POST['dropped_column']) ? $_POST['dropped_column'] : null, 1323 isset($_POST['purge']) ? $_POST['purge'] : null 1324 ); 1325 1326 if (isset($_POST['dropped_column']) 1327 && strlen($db) > 0 1328 && strlen($table) > 0 1329 ) { 1330 // to refresh the list of indexes (Ajax mode) 1331 $extra_data['indexes_list'] = Index::getHtmlForIndexes( 1332 $table, 1333 $db 1334 ); 1335 } 1336 } 1337 1338 return array($result, $num_rows, $unlim_num_rows, 1339 isset($profiling_results) ? $profiling_results : null, $extra_data 1340 ); 1341 } 1342 /** 1343 * Delete related transformation information 1344 * 1345 * @param string $db current database 1346 * @param string $table current table 1347 * @param array $analyzed_sql_results analyzed sql results 1348 * 1349 * @return void 1350 */ 1351 private function deleteTransformationInfo($db, $table, array $analyzed_sql_results) 1352 { 1353 if (! isset($analyzed_sql_results['statement'])) { 1354 return; 1355 } 1356 $statement = $analyzed_sql_results['statement']; 1357 if ($statement instanceof AlterStatement) { 1358 if (!empty($statement->altered[0]) 1359 && $statement->altered[0]->options->has('DROP') 1360 ) { 1361 if (!empty($statement->altered[0]->field->column)) { 1362 Transformations::clear( 1363 $db, 1364 $table, 1365 $statement->altered[0]->field->column 1366 ); 1367 } 1368 } 1369 } elseif ($statement instanceof DropStatement) { 1370 Transformations::clear($db, $table); 1371 } 1372 } 1373 1374 /** 1375 * Function to get the message for the no rows returned case 1376 * 1377 * @param string $message_to_show message to show 1378 * @param array $analyzed_sql_results analyzed sql results 1379 * @param int $num_rows number of rows 1380 * 1381 * @return string $message 1382 */ 1383 private function getMessageForNoRowsReturned($message_to_show, 1384 array $analyzed_sql_results, $num_rows 1385 ) { 1386 if ($analyzed_sql_results['querytype'] == 'DELETE"') { 1387 $message = Message::getMessageForDeletedRows($num_rows); 1388 } elseif ($analyzed_sql_results['is_insert']) { 1389 if ($analyzed_sql_results['querytype'] == 'REPLACE') { 1390 // For REPLACE we get DELETED + INSERTED row count, 1391 // so we have to call it affected 1392 $message = Message::getMessageForAffectedRows($num_rows); 1393 } else { 1394 $message = Message::getMessageForInsertedRows($num_rows); 1395 } 1396 $insert_id = $GLOBALS['dbi']->insertId(); 1397 if ($insert_id != 0) { 1398 // insert_id is id of FIRST record inserted in one insert, 1399 // so if we inserted multiple rows, we had to increment this 1400 $message->addText('[br]'); 1401 // need to use a temporary because the Message class 1402 // currently supports adding parameters only to the first 1403 // message 1404 $_inserted = Message::notice(__('Inserted row id: %1$d')); 1405 $_inserted->addParam($insert_id + $num_rows - 1); 1406 $message->addMessage($_inserted); 1407 } 1408 } elseif ($analyzed_sql_results['is_affected']) { 1409 $message = Message::getMessageForAffectedRows($num_rows); 1410 1411 // Ok, here is an explanation for the !$is_select. 1412 // The form generated by PhpMyAdmin\SqlQueryForm 1413 // and db_sql.php has many submit buttons 1414 // on the same form, and some confusion arises from the 1415 // fact that $message_to_show is sent for every case. 1416 // The $message_to_show containing a success message and sent with 1417 // the form should not have priority over errors 1418 } elseif (! empty($message_to_show) 1419 && $analyzed_sql_results['querytype'] != 'SELECT' 1420 ) { 1421 $message = Message::rawSuccess(htmlspecialchars($message_to_show)); 1422 } elseif (! empty($GLOBALS['show_as_php'])) { 1423 $message = Message::success(__('Showing as PHP code')); 1424 } elseif (isset($GLOBALS['show_as_php'])) { 1425 /* User disable showing as PHP, query is only displayed */ 1426 $message = Message::notice(__('Showing SQL query')); 1427 } else { 1428 $message = Message::success( 1429 __('MySQL returned an empty result set (i.e. zero rows).') 1430 ); 1431 } 1432 1433 if (isset($GLOBALS['querytime'])) { 1434 $_querytime = Message::notice( 1435 '(' . __('Query took %01.4f seconds.') . ')' 1436 ); 1437 $_querytime->addParam($GLOBALS['querytime']); 1438 $message->addMessage($_querytime); 1439 } 1440 1441 // In case of ROLLBACK, notify the user. 1442 if (isset($_POST['rollback_query'])) { 1443 $message->addText(__('[ROLLBACK occurred.]')); 1444 } 1445 1446 return $message; 1447 } 1448 1449 /** 1450 * Function to respond back when the query returns zero rows 1451 * This method is called 1452 * 1-> When browsing an empty table 1453 * 2-> When executing a query on a non empty table which returns zero results 1454 * 3-> When executing a query on an empty table 1455 * 4-> When executing an INSERT, UPDATE, DELETE query from the SQL tab 1456 * 5-> When deleting a row from BROWSE tab 1457 * 6-> When searching using the SEARCH tab which returns zero results 1458 * 7-> When changing the structure of the table except change operation 1459 * 1460 * @param array $analyzed_sql_results analyzed sql results 1461 * @param string $db current database 1462 * @param string $table current table 1463 * @param string $message_to_show message to show 1464 * @param int $num_rows number of rows 1465 * @param DisplayResults $displayResultsObject DisplayResult instance 1466 * @param array $extra_data extra data 1467 * @param string $pmaThemeImage uri of the theme image 1468 * @param array|null $profiling_results profiling results 1469 * @param object $result executed query results 1470 * @param string $sql_query sql query 1471 * @param string $complete_query complete sql query 1472 * 1473 * @return string html 1474 */ 1475 private function getQueryResponseForNoResultsReturned(array $analyzed_sql_results, $db, 1476 $table, $message_to_show, $num_rows, $displayResultsObject, $extra_data, 1477 $pmaThemeImage, $profiling_results, $result, $sql_query, $complete_query 1478 ) { 1479 if ($this->isDeleteTransformationInfo($analyzed_sql_results)) { 1480 $this->deleteTransformationInfo($db, $table, $analyzed_sql_results); 1481 } 1482 1483 if (isset($extra_data['error'])) { 1484 $message = Message::rawError($extra_data['error']); 1485 } else { 1486 $message = $this->getMessageForNoRowsReturned( 1487 isset($message_to_show) ? $message_to_show : null, 1488 $analyzed_sql_results, $num_rows 1489 ); 1490 } 1491 1492 $html_output = ''; 1493 $html_message = Util::getMessage( 1494 $message, $GLOBALS['sql_query'], 'success' 1495 ); 1496 $html_output .= $html_message; 1497 if (!isset($GLOBALS['show_as_php'])) { 1498 1499 if (! empty($GLOBALS['reload'])) { 1500 $extra_data['reload'] = 1; 1501 $extra_data['db'] = $GLOBALS['db']; 1502 } 1503 1504 // For ajax requests add message and sql_query as JSON 1505 if (empty($_REQUEST['ajax_page_request'])) { 1506 $extra_data['message'] = $message; 1507 if ($GLOBALS['cfg']['ShowSQL']) { 1508 $extra_data['sql_query'] = $html_message; 1509 } 1510 } 1511 1512 $response = Response::getInstance(); 1513 $response->addJSON(isset($extra_data) ? $extra_data : array()); 1514 1515 if (!empty($analyzed_sql_results['is_select']) && 1516 !isset($extra_data['error'])) { 1517 $url_query = isset($url_query) ? $url_query : null; 1518 1519 $displayParts = array( 1520 'edit_lnk' => null, 1521 'del_lnk' => null, 1522 'sort_lnk' => '1', 1523 'nav_bar' => '0', 1524 'bkm_form' => '1', 1525 'text_btn' => '1', 1526 'pview_lnk' => '1' 1527 ); 1528 1529 $html_output .= $this->getHtmlForSqlQueryResultsTable( 1530 $displayResultsObject, 1531 $pmaThemeImage, $url_query, $displayParts, 1532 false, 0, $num_rows, true, $result, 1533 $analyzed_sql_results, true 1534 ); 1535 1536 if (isset($profiling_results)) { 1537 $header = $response->getHeader(); 1538 $scripts = $header->getScripts(); 1539 $scripts->addFile('sql.js'); 1540 $html_output .= $this->getHtmlForProfilingChart( 1541 $url_query, 1542 $db, 1543 isset($profiling_results) ? $profiling_results : [] 1544 ); 1545 } 1546 1547 $html_output .= $displayResultsObject->getCreateViewQueryResultOp( 1548 $analyzed_sql_results 1549 ); 1550 1551 $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); 1552 if ($cfgBookmark) { 1553 $html_output .= $this->getHtmlForBookmark( 1554 $displayParts, 1555 $cfgBookmark, 1556 $sql_query, $db, $table, 1557 isset($complete_query) ? $complete_query : $sql_query, 1558 $cfgBookmark['user'] 1559 ); 1560 } 1561 } 1562 } 1563 1564 return $html_output; 1565 } 1566 1567 /** 1568 * Function to send response for ajax grid edit 1569 * 1570 * @param object $result result of the executed query 1571 * 1572 * @return void 1573 */ 1574 private function sendResponseForGridEdit($result) 1575 { 1576 $row = $GLOBALS['dbi']->fetchRow($result); 1577 $field_flags = $GLOBALS['dbi']->fieldFlags($result, 0); 1578 if (stristr($field_flags, DisplayResults::BINARY_FIELD)) { 1579 $row[0] = bin2hex($row[0]); 1580 } 1581 $response = Response::getInstance(); 1582 $response->addJSON('value', $row[0]); 1583 exit; 1584 } 1585 1586 /** 1587 * Function to get html for the sql query results div 1588 * 1589 * @param string $previous_update_query_html html for the previously executed query 1590 * @param string $profiling_chart_html html for profiling 1591 * @param Message $missing_unique_column_msg message for the missing unique column 1592 * @param Message $bookmark_created_msg message for bookmark creation 1593 * @param string $table_html html for the table for displaying sql 1594 * results 1595 * @param string $indexes_problems_html html for displaying errors in indexes 1596 * @param string $bookmark_support_html html for displaying bookmark form 1597 * 1598 * @return string $html_output 1599 */ 1600 private function getHtmlForSqlQueryResults($previous_update_query_html, 1601 $profiling_chart_html, $missing_unique_column_msg, $bookmark_created_msg, 1602 $table_html, $indexes_problems_html, $bookmark_support_html 1603 ) { 1604 //begin the sqlqueryresults div here. container div 1605 $html_output = '<div class="sqlqueryresults ajax">'; 1606 $html_output .= isset($previous_update_query_html) 1607 ? $previous_update_query_html : ''; 1608 $html_output .= isset($profiling_chart_html) ? $profiling_chart_html : ''; 1609 $html_output .= isset($missing_unique_column_msg) 1610 ? $missing_unique_column_msg->getDisplay() : ''; 1611 $html_output .= isset($bookmark_created_msg) 1612 ? $bookmark_created_msg->getDisplay() : ''; 1613 $html_output .= $table_html; 1614 $html_output .= isset($indexes_problems_html) ? $indexes_problems_html : ''; 1615 $html_output .= isset($bookmark_support_html) ? $bookmark_support_html : ''; 1616 $html_output .= '</div>'; // end sqlqueryresults div 1617 1618 return $html_output; 1619 } 1620 1621 /** 1622 * Returns a message for successful creation of a bookmark or null if a bookmark 1623 * was not created 1624 * 1625 * @return Message $bookmark_created_msg 1626 */ 1627 private function getBookmarkCreatedMessage() 1628 { 1629 if (isset($_GET['label'])) { 1630 $bookmark_created_msg = Message::success( 1631 __('Bookmark %s has been created.') 1632 ); 1633 $bookmark_created_msg->addParam($_GET['label']); 1634 } else { 1635 $bookmark_created_msg = null; 1636 } 1637 1638 return $bookmark_created_msg; 1639 } 1640 1641 /** 1642 * Function to get html for the sql query results table 1643 * 1644 * @param DisplayResults $displayResultsObject instance of DisplayResult 1645 * @param string $pmaThemeImage theme image uri 1646 * @param string $url_query url query 1647 * @param array $displayParts the parts to display 1648 * @param bool $editable whether the result table is 1649 * editable or not 1650 * @param int $unlim_num_rows unlimited number of rows 1651 * @param int $num_rows number of rows 1652 * @param bool $showtable whether to show table or not 1653 * @param object $result result of the executed query 1654 * @param array $analyzed_sql_results analyzed sql results 1655 * @param bool $is_limited_display Show only limited operations or not 1656 * 1657 * @return string 1658 */ 1659 private function getHtmlForSqlQueryResultsTable($displayResultsObject, 1660 $pmaThemeImage, $url_query, array $displayParts, 1661 $editable, $unlim_num_rows, $num_rows, $showtable, $result, 1662 array $analyzed_sql_results, $is_limited_display = false 1663 ) { 1664 $printview = isset($_POST['printview']) && $_POST['printview'] == '1' ? '1' : null; 1665 $table_html = ''; 1666 $browse_dist = ! empty($_POST['is_browse_distinct']); 1667 1668 if ($analyzed_sql_results['is_procedure']) { 1669 1670 do { 1671 if (! isset($result)) { 1672 $result = $GLOBALS['dbi']->storeResult(); 1673 } 1674 $num_rows = $GLOBALS['dbi']->numRows($result); 1675 1676 if ($result !== false && $num_rows > 0) { 1677 1678 $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); 1679 if (! is_array($fields_meta)) { 1680 $fields_cnt = 0; 1681 } else { 1682 $fields_cnt = count($fields_meta); 1683 } 1684 1685 $displayResultsObject->setProperties( 1686 $num_rows, 1687 $fields_meta, 1688 $analyzed_sql_results['is_count'], 1689 $analyzed_sql_results['is_export'], 1690 $analyzed_sql_results['is_func'], 1691 $analyzed_sql_results['is_analyse'], 1692 $num_rows, 1693 $fields_cnt, 1694 $GLOBALS['querytime'], 1695 $pmaThemeImage, 1696 $GLOBALS['text_dir'], 1697 $analyzed_sql_results['is_maint'], 1698 $analyzed_sql_results['is_explain'], 1699 $analyzed_sql_results['is_show'], 1700 $showtable, 1701 $printview, 1702 $url_query, 1703 $editable, 1704 $browse_dist 1705 ); 1706 1707 $displayParts = array( 1708 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, 1709 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, 1710 'sort_lnk' => '1', 1711 'nav_bar' => '1', 1712 'bkm_form' => '1', 1713 'text_btn' => '1', 1714 'pview_lnk' => '1' 1715 ); 1716 1717 $table_html .= $displayResultsObject->getTable( 1718 $result, 1719 $displayParts, 1720 $analyzed_sql_results, 1721 $is_limited_display 1722 ); 1723 } 1724 1725 $GLOBALS['dbi']->freeResult($result); 1726 unset($result); 1727 1728 } while ($GLOBALS['dbi']->moreResults() && $GLOBALS['dbi']->nextResult()); 1729 1730 } else { 1731 $fields_meta = array(); 1732 if (isset($result) && ! is_bool($result)) { 1733 $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); 1734 } 1735 $fields_cnt = count($fields_meta); 1736 $_SESSION['is_multi_query'] = false; 1737 $displayResultsObject->setProperties( 1738 $unlim_num_rows, 1739 $fields_meta, 1740 $analyzed_sql_results['is_count'], 1741 $analyzed_sql_results['is_export'], 1742 $analyzed_sql_results['is_func'], 1743 $analyzed_sql_results['is_analyse'], 1744 $num_rows, 1745 $fields_cnt, $GLOBALS['querytime'], 1746 $pmaThemeImage, $GLOBALS['text_dir'], 1747 $analyzed_sql_results['is_maint'], 1748 $analyzed_sql_results['is_explain'], 1749 $analyzed_sql_results['is_show'], 1750 $showtable, 1751 $printview, 1752 $url_query, 1753 $editable, 1754 $browse_dist 1755 ); 1756 1757 if (! is_bool($result)) { 1758 $table_html .= $displayResultsObject->getTable( 1759 $result, 1760 $displayParts, 1761 $analyzed_sql_results, 1762 $is_limited_display 1763 ); 1764 } 1765 $GLOBALS['dbi']->freeResult($result); 1766 } 1767 1768 return $table_html; 1769 } 1770 1771 /** 1772 * Function to get html for the previous query if there is such. If not will return 1773 * null 1774 * 1775 * @param string $disp_query display query 1776 * @param bool $showSql whether to show sql 1777 * @param array $sql_data sql data 1778 * @param string $disp_message display message 1779 * 1780 * @return string $previous_update_query_html 1781 */ 1782 private function getHtmlForPreviousUpdateQuery($disp_query, $showSql, $sql_data, 1783 $disp_message 1784 ) { 1785 // previous update query (from tbl_replace) 1786 if (isset($disp_query) && ($showSql == true) && empty($sql_data)) { 1787 $previous_update_query_html = Util::getMessage( 1788 $disp_message, $disp_query, 'success' 1789 ); 1790 } else { 1791 $previous_update_query_html = null; 1792 } 1793 1794 return $previous_update_query_html; 1795 } 1796 1797 /** 1798 * To get the message if a column index is missing. If not will return null 1799 * 1800 * @param string $table current table 1801 * @param string $db current database 1802 * @param boolean $editable whether the results table can be editable or not 1803 * @param boolean $has_unique whether there is a unique key 1804 * 1805 * @return Message $message 1806 */ 1807 private function getMessageIfMissingColumnIndex($table, $db, $editable, $has_unique) 1808 { 1809 if (!empty($table) && ($GLOBALS['dbi']->isSystemSchema($db) || !$editable)) { 1810 $missing_unique_column_msg = Message::notice( 1811 sprintf( 1812 __( 1813 'Current selection does not contain a unique column.' 1814 . ' Grid edit, checkbox, Edit, Copy and Delete features' 1815 . ' are not available. %s' 1816 ), 1817 Util::showDocu( 1818 'config', 1819 'cfg_RowActionLinksWithoutUnique' 1820 ) 1821 ) 1822 ); 1823 } elseif (! empty($table) && ! $has_unique) { 1824 $missing_unique_column_msg = Message::notice( 1825 sprintf( 1826 __( 1827 'Current selection does not contain a unique column.' 1828 . ' Grid edit, Edit, Copy and Delete features may result in' 1829 . ' undesired behavior. %s' 1830 ), 1831 Util::showDocu( 1832 'config', 1833 'cfg_RowActionLinksWithoutUnique' 1834 ) 1835 ) 1836 ); 1837 } else { 1838 $missing_unique_column_msg = null; 1839 } 1840 1841 return $missing_unique_column_msg; 1842 } 1843 1844 /** 1845 * Function to get html to display problems in indexes 1846 * 1847 * @param string $query_type query type 1848 * @param array|null $selectedTables array of table names selected from the 1849 * database structure page, for an action 1850 * like check table, optimize table, 1851 * analyze table or repair table 1852 * @param string $db current database 1853 * 1854 * @return string 1855 */ 1856 private function getHtmlForIndexesProblems($query_type, $selectedTables, $db) 1857 { 1858 // BEGIN INDEX CHECK See if indexes should be checked. 1859 if (isset($query_type) 1860 && $query_type == 'check_tbl' 1861 && isset($selectedTables) 1862 && is_array($selectedTables) 1863 ) { 1864 $indexes_problems_html = ''; 1865 foreach ($selectedTables as $tbl_name) { 1866 $check = Index::findDuplicates($tbl_name, $db); 1867 if (! empty($check)) { 1868 $indexes_problems_html .= sprintf( 1869 __('Problems with indexes of table `%s`'), $tbl_name 1870 ); 1871 $indexes_problems_html .= $check; 1872 } 1873 } 1874 } else { 1875 $indexes_problems_html = null; 1876 } 1877 1878 return $indexes_problems_html; 1879 } 1880 1881 /** 1882 * Function to display results when the executed query returns non empty results 1883 * 1884 * @param object $result executed query results 1885 * @param array $analyzed_sql_results analysed sql results 1886 * @param string $db current database 1887 * @param string $table current table 1888 * @param string $message message to show 1889 * @param array $sql_data sql data 1890 * @param DisplayResults $displayResultsObject Instance of DisplayResults 1891 * @param string $pmaThemeImage uri of the theme image 1892 * @param int $unlim_num_rows unlimited number of rows 1893 * @param int $num_rows number of rows 1894 * @param string $disp_query display query 1895 * @param string $disp_message display message 1896 * @param array $profiling_results profiling results 1897 * @param string $query_type query type 1898 * @param array|null $selectedTables array of table names selected 1899 * from the database structure page, for 1900 * an action like check table, 1901 * optimize table, analyze table or 1902 * repair table 1903 * @param string $sql_query sql query 1904 * @param string $complete_query complete sql query 1905 * 1906 * @return string html 1907 */ 1908 private function getQueryResponseForResultsReturned($result, array $analyzed_sql_results, 1909 $db, $table, $message, $sql_data, $displayResultsObject, $pmaThemeImage, 1910 $unlim_num_rows, $num_rows, $disp_query, $disp_message, $profiling_results, 1911 $query_type, $selectedTables, $sql_query, $complete_query 1912 ) { 1913 // If we are retrieving the full value of a truncated field or the original 1914 // value of a transformed field, show it here 1915 if (isset($_POST['grid_edit']) && $_POST['grid_edit'] == true) { 1916 $this->sendResponseForGridEdit($result); 1917 // script has exited at this point 1918 } 1919 1920 // Gets the list of fields properties 1921 if (isset($result) && $result) { 1922 $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result); 1923 } 1924 1925 // Should be initialized these parameters before parsing 1926 $showtable = isset($showtable) ? $showtable : null; 1927 $url_query = isset($url_query) ? $url_query : null; 1928 1929 $response = Response::getInstance(); 1930 $header = $response->getHeader(); 1931 $scripts = $header->getScripts(); 1932 1933 $just_one_table = $this->resultSetHasJustOneTable($fields_meta); 1934 1935 // hide edit and delete links: 1936 // - for information_schema 1937 // - if the result set does not contain all the columns of a unique key 1938 // (unless this is an updatable view) 1939 // - if the SELECT query contains a join or a subquery 1940 1941 $updatableView = false; 1942 1943 $statement = isset($analyzed_sql_results['statement']) ? $analyzed_sql_results['statement'] : null; 1944 if ($statement instanceof SelectStatement) { 1945 if (!empty($statement->expr)) { 1946 if ($statement->expr[0]->expr === '*') { 1947 $_table = new Table($table, $db); 1948 $updatableView = $_table->isUpdatableView(); 1949 } 1950 } 1951 1952 if ($analyzed_sql_results['join'] 1953 || $analyzed_sql_results['is_subquery'] 1954 || count($analyzed_sql_results['select_tables']) !== 1 1955 ) { 1956 $just_one_table = false; 1957 } 1958 } 1959 1960 $has_unique = $this->resultSetContainsUniqueKey( 1961 $db, $table, $fields_meta 1962 ); 1963 1964 $editable = ($has_unique 1965 || $GLOBALS['cfg']['RowActionLinksWithoutUnique'] 1966 || $updatableView) 1967 && $just_one_table; 1968 1969 $_SESSION['tmpval']['possible_as_geometry'] = $editable; 1970 1971 $displayParts = array( 1972 'edit_lnk' => $displayResultsObject::UPDATE_ROW, 1973 'del_lnk' => $displayResultsObject::DELETE_ROW, 1974 'sort_lnk' => '1', 1975 'nav_bar' => '1', 1976 'bkm_form' => '1', 1977 'text_btn' => '0', 1978 'pview_lnk' => '1' 1979 ); 1980 1981 if ($GLOBALS['dbi']->isSystemSchema($db) || !$editable) { 1982 $displayParts = array( 1983 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, 1984 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, 1985 'sort_lnk' => '1', 1986 'nav_bar' => '1', 1987 'bkm_form' => '1', 1988 'text_btn' => '1', 1989 'pview_lnk' => '1' 1990 ); 1991 1992 } 1993 if (isset($_POST['printview']) && $_POST['printview'] == '1') { 1994 $displayParts = array( 1995 'edit_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, 1996 'del_lnk' => $displayResultsObject::NO_EDIT_OR_DELETE, 1997 'sort_lnk' => '0', 1998 'nav_bar' => '0', 1999 'bkm_form' => '0', 2000 'text_btn' => '0', 2001 'pview_lnk' => '0' 2002 ); 2003 } 2004 2005 if (isset($_POST['table_maintenance'])) { 2006 $scripts->addFile('makegrid.js'); 2007 $scripts->addFile('sql.js'); 2008 $table_maintenance_html = ''; 2009 if (isset($message)) { 2010 $message = Message::success($message); 2011 $table_maintenance_html = Util::getMessage( 2012 $message, $GLOBALS['sql_query'], 'success' 2013 ); 2014 } 2015 $table_maintenance_html .= $this->getHtmlForSqlQueryResultsTable( 2016 $displayResultsObject, 2017 $pmaThemeImage, $url_query, $displayParts, 2018 false, $unlim_num_rows, $num_rows, $showtable, $result, 2019 $analyzed_sql_results 2020 ); 2021 if (empty($sql_data) || ($sql_data['valid_queries'] = 1)) { 2022 $response->addHTML($table_maintenance_html); 2023 exit(); 2024 } 2025 } 2026 2027 if (!isset($_POST['printview']) || $_POST['printview'] != '1') { 2028 $scripts->addFile('makegrid.js'); 2029 $scripts->addFile('sql.js'); 2030 unset($GLOBALS['message']); 2031 //we don't need to buffer the output in getMessage here. 2032 //set a global variable and check against it in the function 2033 $GLOBALS['buffer_message'] = false; 2034 } 2035 2036 $previous_update_query_html = $this->getHtmlForPreviousUpdateQuery( 2037 isset($disp_query) ? $disp_query : null, 2038 $GLOBALS['cfg']['ShowSQL'], isset($sql_data) ? $sql_data : null, 2039 isset($disp_message) ? $disp_message : null 2040 ); 2041 2042 $profiling_chart_html = $this->getHtmlForProfilingChart( 2043 $url_query, $db, isset($profiling_results) ? $profiling_results :array() 2044 ); 2045 2046 $missing_unique_column_msg = $this->getMessageIfMissingColumnIndex( 2047 $table, $db, $editable, $has_unique 2048 ); 2049 2050 $bookmark_created_msg = $this->getBookmarkCreatedMessage(); 2051 2052 $table_html = $this->getHtmlForSqlQueryResultsTable( 2053 $displayResultsObject, 2054 $pmaThemeImage, $url_query, $displayParts, 2055 $editable, $unlim_num_rows, $num_rows, $showtable, $result, 2056 $analyzed_sql_results 2057 ); 2058 2059 $indexes_problems_html = $this->getHtmlForIndexesProblems( 2060 isset($query_type) ? $query_type : null, 2061 isset($selectedTables) ? $selectedTables : null, $db 2062 ); 2063 2064 $cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']); 2065 if ($cfgBookmark) { 2066 $bookmark_support_html = $this->getHtmlForBookmark( 2067 $displayParts, 2068 $cfgBookmark, 2069 $sql_query, $db, $table, 2070 isset($complete_query) ? $complete_query : $sql_query, 2071 $cfgBookmark['user'] 2072 ); 2073 } else { 2074 $bookmark_support_html = ''; 2075 } 2076 2077 $html_output = isset($table_maintenance_html) ? $table_maintenance_html : ''; 2078 2079 $html_output .= $this->getHtmlForSqlQueryResults( 2080 $previous_update_query_html, $profiling_chart_html, 2081 $missing_unique_column_msg, $bookmark_created_msg, 2082 $table_html, $indexes_problems_html, $bookmark_support_html 2083 ); 2084 2085 return $html_output; 2086 } 2087 2088 /** 2089 * Function to execute the query and send the response 2090 * 2091 * @param array $analyzed_sql_results analysed sql results 2092 * @param bool $is_gotofile whether goto file or not 2093 * @param string $db current database 2094 * @param string $table current table 2095 * @param bool|null $find_real_end whether to find real end or not 2096 * @param string $sql_query_for_bookmark the sql query to be stored as bookmark 2097 * @param array|null $extra_data extra data 2098 * @param string $message_to_show message to show 2099 * @param string $message message 2100 * @param array|null $sql_data sql data 2101 * @param string $goto goto page url 2102 * @param string $pmaThemeImage uri of the PMA theme image 2103 * @param string $disp_query display query 2104 * @param string $disp_message display message 2105 * @param string $query_type query type 2106 * @param string $sql_query sql query 2107 * @param array|null $selectedTables array of table names selected from the 2108 * database structure page, for an action 2109 * like check table, optimize table, 2110 * analyze table or repair table 2111 * @param string $complete_query complete query 2112 * 2113 * @return void 2114 */ 2115 public function executeQueryAndSendQueryResponse($analyzed_sql_results, 2116 $is_gotofile, $db, $table, $find_real_end, $sql_query_for_bookmark, 2117 $extra_data, $message_to_show, $message, $sql_data, $goto, $pmaThemeImage, 2118 $disp_query, $disp_message, $query_type, $sql_query, $selectedTables, 2119 $complete_query 2120 ) { 2121 if ($analyzed_sql_results == null) { 2122 // Parse and analyze the query 2123 list( 2124 $analyzed_sql_results, 2125 $db, 2126 $table_from_sql 2127 ) = ParseAnalyze::sqlQuery($sql_query, $db); 2128 // @todo: possibly refactor 2129 extract($analyzed_sql_results); 2130 2131 if ($table != $table_from_sql && !empty($table_from_sql)) { 2132 $table = $table_from_sql; 2133 } 2134 } 2135 2136 $html_output = $this->executeQueryAndGetQueryResponse( 2137 $analyzed_sql_results, // analyzed_sql_results 2138 $is_gotofile, // is_gotofile 2139 $db, // db 2140 $table, // table 2141 $find_real_end, // find_real_end 2142 $sql_query_for_bookmark, // sql_query_for_bookmark 2143 $extra_data, // extra_data 2144 $message_to_show, // message_to_show 2145 $message, // message 2146 $sql_data, // sql_data 2147 $goto, // goto 2148 $pmaThemeImage, // pmaThemeImage 2149 $disp_query, // disp_query 2150 $disp_message, // disp_message 2151 $query_type, // query_type 2152 $sql_query, // sql_query 2153 $selectedTables, // selectedTables 2154 $complete_query // complete_query 2155 ); 2156 2157 $response = Response::getInstance(); 2158 $response->addHTML($html_output); 2159 } 2160 2161 /** 2162 * Function to execute the query and send the response 2163 * 2164 * @param array $analyzed_sql_results analysed sql results 2165 * @param bool $is_gotofile whether goto file or not 2166 * @param string $db current database 2167 * @param string $table current table 2168 * @param bool|null $find_real_end whether to find real end or not 2169 * @param string $sql_query_for_bookmark the sql query to be stored as bookmark 2170 * @param array|null $extra_data extra data 2171 * @param string $message_to_show message to show 2172 * @param string $message message 2173 * @param array|null $sql_data sql data 2174 * @param string $goto goto page url 2175 * @param string $pmaThemeImage uri of the PMA theme image 2176 * @param string $disp_query display query 2177 * @param string $disp_message display message 2178 * @param string $query_type query type 2179 * @param string $sql_query sql query 2180 * @param array|null $selectedTables array of table names selected from the 2181 * database structure page, for an action 2182 * like check table, optimize table, 2183 * analyze table or repair table 2184 * @param string $complete_query complete query 2185 * 2186 * @return string html 2187 */ 2188 public function executeQueryAndGetQueryResponse(array $analyzed_sql_results, 2189 $is_gotofile, $db, $table, $find_real_end, $sql_query_for_bookmark, 2190 $extra_data, $message_to_show, $message, $sql_data, $goto, $pmaThemeImage, 2191 $disp_query, $disp_message, $query_type, $sql_query, $selectedTables, 2192 $complete_query 2193 ) { 2194 // Handle disable/enable foreign key checks 2195 $default_fk_check = Util::handleDisableFKCheckInit(); 2196 2197 // Handle remembered sorting order, only for single table query. 2198 // Handling is not required when it's a union query 2199 // (the parser never sets the 'union' key to 0). 2200 // Handling is also not required if we came from the "Sort by key" 2201 // drop-down. 2202 if (! empty($analyzed_sql_results) 2203 && $this->isRememberSortingOrder($analyzed_sql_results) 2204 && empty($analyzed_sql_results['union']) 2205 && ! isset($_POST['sort_by_key']) 2206 ) { 2207 if (! isset($_SESSION['sql_from_query_box'])) { 2208 $this->handleSortOrder($db, $table, $analyzed_sql_results, $sql_query); 2209 } else { 2210 unset($_SESSION['sql_from_query_box']); 2211 } 2212 2213 } 2214 2215 $displayResultsObject = new DisplayResults( 2216 $GLOBALS['db'], $GLOBALS['table'], $goto, $sql_query 2217 ); 2218 $displayResultsObject->setConfigParamsForDisplayTable(); 2219 2220 // assign default full_sql_query 2221 $full_sql_query = $sql_query; 2222 2223 // Do append a "LIMIT" clause? 2224 if ($this->isAppendLimitClause($analyzed_sql_results)) { 2225 $full_sql_query = $this->getSqlWithLimitClause($analyzed_sql_results); 2226 } 2227 2228 $GLOBALS['reload'] = $this->hasCurrentDbChanged($db); 2229 $GLOBALS['dbi']->selectDb($db); 2230 2231 // Execute the query 2232 list($result, $num_rows, $unlim_num_rows, $profiling_results, $extra_data) 2233 = $this->executeTheQuery( 2234 $analyzed_sql_results, 2235 $full_sql_query, 2236 $is_gotofile, 2237 $db, 2238 $table, 2239 isset($find_real_end) ? $find_real_end : null, 2240 isset($sql_query_for_bookmark) ? $sql_query_for_bookmark : null, 2241 isset($extra_data) ? $extra_data : null 2242 ); 2243 2244 if ($GLOBALS['dbi']->moreResults()) { 2245 $GLOBALS['dbi']->nextResult(); 2246 } 2247 2248 $operations = new Operations(); 2249 $warning_messages = $operations->getWarningMessagesArray(); 2250 2251 // No rows returned -> move back to the calling page 2252 if ((0 == $num_rows && 0 == $unlim_num_rows) 2253 || $analyzed_sql_results['is_affected'] 2254 ) { 2255 $html_output = $this->getQueryResponseForNoResultsReturned( 2256 $analyzed_sql_results, $db, $table, 2257 isset($message_to_show) ? $message_to_show : null, 2258 $num_rows, $displayResultsObject, $extra_data, 2259 $pmaThemeImage, $profiling_results, isset($result) ? $result : null, 2260 $sql_query, isset($complete_query) ? $complete_query : null 2261 ); 2262 } else { 2263 // At least one row is returned -> displays a table with results 2264 $html_output = $this->getQueryResponseForResultsReturned( 2265 isset($result) ? $result : null, 2266 $analyzed_sql_results, 2267 $db, 2268 $table, 2269 isset($message) ? $message : null, 2270 isset($sql_data) ? $sql_data : null, 2271 $displayResultsObject, 2272 $pmaThemeImage, 2273 $unlim_num_rows, 2274 $num_rows, 2275 isset($disp_query) ? $disp_query : null, 2276 isset($disp_message) ? $disp_message : null, 2277 $profiling_results, 2278 isset($query_type) ? $query_type : null, 2279 isset($selectedTables) ? $selectedTables : null, 2280 $sql_query, 2281 isset($complete_query) ? $complete_query : null 2282 ); 2283 } 2284 2285 // Handle disable/enable foreign key checks 2286 Util::handleDisableFKCheckCleanup($default_fk_check); 2287 2288 foreach ($warning_messages as $warning) { 2289 $message = Message::notice(Message::sanitize($warning)); 2290 $html_output .= $message->getDisplay(); 2291 } 2292 2293 return $html_output; 2294 } 2295 2296 /** 2297 * Function to define pos to display a row 2298 * 2299 * @param int $number_of_line Number of the line to display 2300 * @param int $max_rows Number of rows by page 2301 * 2302 * @return int Start position to display the line 2303 */ 2304 private function getStartPosToDisplayRow($number_of_line, $max_rows = null) 2305 { 2306 if (null === $max_rows) { 2307 $max_rows = $_SESSION['tmpval']['max_rows']; 2308 } 2309 2310 return @((ceil($number_of_line / $max_rows) - 1) * $max_rows); 2311 } 2312 2313 /** 2314 * Function to calculate new pos if pos is higher than number of rows 2315 * of displayed table 2316 * 2317 * @param string $db Database name 2318 * @param string $table Table name 2319 * @param int|null $pos Initial position 2320 * 2321 * @return int Number of pos to display last page 2322 */ 2323 public function calculatePosForLastPage($db, $table, $pos) 2324 { 2325 if (null === $pos) { 2326 $pos = $_SESSION['tmpval']['pos']; 2327 } 2328 2329 $_table = new Table($table, $db); 2330 $unlim_num_rows = $_table->countRecords(true); 2331 //If position is higher than number of rows 2332 if ($unlim_num_rows <= $pos && 0 != $pos) { 2333 $pos = $this->getStartPosToDisplayRow($unlim_num_rows); 2334 } 2335 2336 return $pos; 2337 } 2338} 2339