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="">&nbsp;</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 &amp; 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