Completed
Push — master ( b97460...3f6e36 )
by Maurício
08:29
created

libraries/classes/Sql.php (1 issue)

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