Passed
Push — master ( 2c0405...86bdc5 )
by Maurício
07:21
created

Util::showCopyToClipboard()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
/* vim: set expandtab sw=4 ts=4 sts=4: */
3
/**
4
 * Hold the PhpMyAdmin\Util class
5
 *
6
 * @package PhpMyAdmin
7
 */
8
declare(strict_types=1);
9
10
namespace PhpMyAdmin;
11
12
use PhpMyAdmin\Core;
13
use PhpMyAdmin\DatabaseInterface;
14
use PhpMyAdmin\FileListing;
15
use PhpMyAdmin\Message;
16
use PhpMyAdmin\Plugins\ImportPlugin;
17
use PhpMyAdmin\Response;
18
use PhpMyAdmin\Sanitize;
19
use PhpMyAdmin\SqlParser\Context;
20
use PhpMyAdmin\SqlParser\Lexer;
21
use PhpMyAdmin\SqlParser\Parser;
22
use PhpMyAdmin\SqlParser\Token;
23
use PhpMyAdmin\SqlParser\Utils\Error as ParserError;
24
use PhpMyAdmin\Template;
25
use PhpMyAdmin\Url;
26
use Williamdes\MariaDBMySQLKBS\Search as KBSearch;
27
use Williamdes\MariaDBMySQLKBS\KBException;
28
29
/**
30
 * Misc functions used all over the scripts.
31
 *
32
 * @package PhpMyAdmin
33
 */
34
class Util
35
{
36
    /**
37
     * Checks whether configuration value tells to show icons.
38
     *
39
     * @param string $value Configuration option name
40
     *
41
     * @return boolean Whether to show icons.
42
     */
43
    public static function showIcons($value)
44
    {
45
        return in_array($GLOBALS['cfg'][$value], ['icons', 'both']);
46
    }
47
48
    /**
49
     * Checks whether configuration value tells to show text.
50
     *
51
     * @param string $value Configuration option name
52
     *
53
     * @return boolean Whether to show text.
54
     */
55
    public static function showText($value)
56
    {
57
        return in_array($GLOBALS['cfg'][$value], ['text', 'both']);
58
    }
59
60
    /**
61
     * Returns an HTML IMG tag for a particular icon from a theme,
62
     * which may be an actual file or an icon from a sprite.
63
     * This function takes into account the ActionLinksMode
64
     * configuration setting and wraps the image tag in a span tag.
65
     *
66
     * @param string  $icon          name of icon file
67
     * @param string  $alternate     alternate text
68
     * @param boolean $force_text    whether to force alternate text to be displayed
69
     * @param boolean $menu_icon     whether this icon is for the menu bar or not
70
     * @param string  $control_param which directive controls the display
71
     *
72
     * @return string an html snippet
73
     */
74
    public static function getIcon(
75
        $icon,
76
        $alternate = '',
77
        $force_text = false,
78
        $menu_icon = false,
79
        $control_param = 'ActionLinksMode'
80
    ) {
81
        $include_icon = $include_text = false;
82
        if (self::showIcons($control_param)) {
83
            $include_icon = true;
84
        }
85
        if ($force_text
86
            || self::showText($control_param)
87
        ) {
88
            $include_text = true;
89
        }
90
        // Sometimes use a span (we rely on this in js/sql.js). But for menu bar
91
        // we don't need a span
92
        $button = $menu_icon ? '' : '<span class="nowrap">';
93
        if ($include_icon) {
94
            $button .= self::getImage($icon, $alternate);
95
        }
96
        if ($include_icon && $include_text) {
97
            $button .= '&nbsp;';
98
        }
99
        if ($include_text) {
100
            $button .= $alternate;
101
        }
102
        $button .= $menu_icon ? '' : '</span>';
103
104
        return $button;
105
    }
106
107
    /**
108
     * Returns an HTML IMG tag for a particular image from a theme
109
     *
110
     * The image name should match CSS class defined in icons.css.php
111
     *
112
     * @param string $image      The name of the file to get
113
     * @param string $alternate  Used to set 'alt' and 'title' attributes
114
     *                           of the image
115
     * @param array  $attributes An associative array of other attributes
116
     *
117
     * @return string an html IMG tag
118
     */
119
    public static function getImage($image, $alternate = '', array $attributes = [])
120
    {
121
        $alternate = htmlspecialchars($alternate);
122
123
        // Set $url accordingly
124
        if (isset($GLOBALS['pmaThemeImage'])) {
125
            $url = $GLOBALS['pmaThemeImage'] . $image;
0 ignored issues
show
Unused Code introduced by
The assignment to $url is dead and can be removed.
Loading history...
126
        } else {
127
            $url = './themes/pmahomme/' . $image;
128
        }
129
130
        if (isset($attributes['class'])) {
131
            $attributes['class'] = "icon ic_$image " . $attributes['class'];
132
        } else {
133
            $attributes['class'] = "icon ic_$image";
134
        }
135
136
        // set all other attributes
137
        $attr_str = '';
138
        foreach ($attributes as $key => $value) {
139
            if (! in_array($key, ['alt', 'title'])) {
140
                $attr_str .= " $key=\"$value\"";
141
            }
142
        }
143
144
        // override the alt attribute
145
        if (isset($attributes['alt'])) {
146
            $alt = $attributes['alt'];
147
        } else {
148
            $alt = $alternate;
149
        }
150
151
        // override the title attribute
152
        if (isset($attributes['title'])) {
153
            $title = $attributes['title'];
154
        } else {
155
            $title = $alternate;
156
        }
157
158
        // generate the IMG tag
159
        $template = '<img src="themes/dot.gif" title="%s" alt="%s"%s />';
160
        $retval = sprintf($template, $title, $alt, $attr_str);
161
162
        return $retval;
163
    }
164
165
    /**
166
     * Returns the formatted maximum size for an upload
167
     *
168
     * @param integer $max_upload_size the size
169
     *
170
     * @return string the message
171
     *
172
     * @access  public
173
     */
174
    public static function getFormattedMaximumUploadSize($max_upload_size)
175
    {
176
        // I have to reduce the second parameter (sensitiveness) from 6 to 4
177
        // to avoid weird results like 512 kKib
178
        list($max_size, $max_unit) = self::formatByteDown($max_upload_size, 4);
179
        return '(' . sprintf(__('Max: %s%s'), $max_size, $max_unit) . ')';
180
    }
181
182
    /**
183
     * Generates a hidden field which should indicate to the browser
184
     * the maximum size for upload
185
     *
186
     * @param integer $max_size the size
187
     *
188
     * @return string the INPUT field
189
     *
190
     * @access  public
191
     */
192
    public static function generateHiddenMaxFileSize($max_size)
193
    {
194
        return '<input type="hidden" name="MAX_FILE_SIZE" value="'
195
            . $max_size . '" />';
196
    }
197
198
    /**
199
     * Add slashes before "_" and "%" characters for using them in MySQL
200
     * database, table and field names.
201
     * Note: This function does not escape backslashes!
202
     *
203
     * @param string $name the string to escape
204
     *
205
     * @return string the escaped string
206
     *
207
     * @access  public
208
     */
209
    public static function escapeMysqlWildcards($name)
210
    {
211
        return strtr($name, ['_' => '\\_', '%' => '\\%']);
212
    } // end of the 'escapeMysqlWildcards()' function
213
214
    /**
215
     * removes slashes before "_" and "%" characters
216
     * Note: This function does not unescape backslashes!
217
     *
218
     * @param string $name the string to escape
219
     *
220
     * @return string   the escaped string
221
     *
222
     * @access  public
223
     */
224
    public static function unescapeMysqlWildcards($name)
225
    {
226
        return strtr($name, ['\\_' => '_', '\\%' => '%']);
227
    } // end of the 'unescapeMysqlWildcards()' function
228
229
    /**
230
     * removes quotes (',",`) from a quoted string
231
     *
232
     * checks if the string is quoted and removes this quotes
233
     *
234
     * @param string $quoted_string string to remove quotes from
235
     * @param string $quote         type of quote to remove
236
     *
237
     * @return string unqoted string
238
     */
239
    public static function unQuote($quoted_string, $quote = null)
240
    {
241
        $quotes = [];
242
243
        if ($quote === null) {
244
            $quotes[] = '`';
245
            $quotes[] = '"';
246
            $quotes[] = "'";
247
        } else {
248
            $quotes[] = $quote;
249
        }
250
251
        foreach ($quotes as $quote) {
252
            if (mb_substr($quoted_string, 0, 1) === $quote
253
                && mb_substr($quoted_string, -1, 1) === $quote
254
            ) {
255
                $unquoted_string = mb_substr($quoted_string, 1, -1);
256
                // replace escaped quotes
257
                $unquoted_string = str_replace(
258
                    $quote . $quote,
259
                    $quote,
260
                    $unquoted_string
261
                );
262
                return $unquoted_string;
263
            }
264
        }
265
266
        return $quoted_string;
267
    }
268
269
    /**
270
     * format sql strings
271
     *
272
     * @param string  $sqlQuery raw SQL string
273
     * @param boolean $truncate truncate the query if it is too long
274
     *
275
     * @return string the formatted sql
276
     *
277
     * @global array  $cfg the configuration array
278
     *
279
     * @access  public
280
     * @todo    move into PMA_Sql
281
     */
282
    public static function formatSql($sqlQuery, $truncate = false)
283
    {
284
        global $cfg;
285
286
        if ($truncate
287
            && mb_strlen($sqlQuery) > $cfg['MaxCharactersInDisplayedSQL']
288
        ) {
289
            $sqlQuery = mb_substr(
290
                $sqlQuery,
291
                0,
292
                $cfg['MaxCharactersInDisplayedSQL']
293
            ) . '[...]';
294
        }
295
        return '<code class="sql"><pre>' . "\n"
296
            . htmlspecialchars($sqlQuery) . "\n"
297
            . '</pre></code>';
298
    } // end of the "formatSql()" function
299
300
    /**
301
     * Displays a button to copy content to clipboard
302
     *
303
     * @param string $text Text to copy to clipboard
304
     *
305
     * @return string  the html link
306
     *
307
     * @access  public
308
     */
309
    public static function showCopyToClipboard($text) {
310
        $open_link = '  <a href="#" class="copyQueryBtn" data-text="' . $text . '">' . __('Copy') . '</a>';
311
        return $open_link;
312
    } // end of the 'showCopyToClipboard()' function
313
314
    /**
315
     * Displays a link to the documentation as an icon
316
     *
317
     * @param string  $link   documentation link
318
     * @param string  $target optional link target
319
     * @param boolean $bbcode optional flag indicating whether to output bbcode
320
     *
321
     * @return string the html link
322
     *
323
     * @access public
324
     */
325
    public static function showDocLink($link, $target = 'documentation', $bbcode = false)
326
    {
327
        if ($bbcode) {
328
            return "[a@$link@$target][dochelpicon][/a]";
329
        }
330
331
        return '<a href="' . $link . '" target="' . $target . '">'
332
            . self::getImage('b_help', __('Documentation'))
333
            . '</a>';
334
    } // end of the 'showDocLink()' function
335
336
    /**
337
     * Get a URL link to the official MySQL documentation
338
     *
339
     * @param string $link   contains name of page/anchor that is being linked
340
     * @param string $anchor anchor to page part
341
     *
342
     * @return string  the URL link
343
     *
344
     * @access  public
345
     */
346
    public static function getMySQLDocuURL($link, $anchor = '')
347
    {
348
        // Fixup for newly used names:
349
        $link = str_replace('_', '-', mb_strtolower($link));
350
351
        if (empty($link)) {
352
            $link = 'index';
353
        }
354
        $mysql = '5.5';
355
        $lang = 'en';
356
        if (isset($GLOBALS['dbi'])) {
357
            $serverVersion = $GLOBALS['dbi']->getVersion();
358
            if ($serverVersion >= 50700) {
359
                $mysql = '5.7';
360
            } elseif ($serverVersion >= 50600) {
361
                $mysql = '5.6';
362
            } elseif ($serverVersion >= 50500) {
363
                $mysql = '5.5';
364
            }
365
        }
366
        $url = 'https://dev.mysql.com/doc/refman/'
367
            . $mysql . '/' . $lang . '/' . $link . '.html';
368
        if (! empty($anchor)) {
369
            $url .= '#' . $anchor;
370
        }
371
372
        return Core::linkURL($url);
373
    }
374
375
    /**
376
     * Get a link to variable documentation
377
     *
378
     * @param string  $name       The variable name
379
     * @param boolean $useMariaDB Use only MariaDB documentation
380
     * @param string  $text       (optional) The text for the link
381
     * @return string link or empty string
382
     */
383
    public static function linkToVarDocumentation(
384
        string $name,
385
        bool $useMariaDB = false,
386
        string $text = null
387
    ): string {
388
        $html = '';
389
        try {
390
            $type = KBSearch::MYSQL;
391
            if ($useMariaDB) {
392
                $type = KBSearch::MARIADB;
393
            }
394
            $docLink = KBSearch::getByName($name, $type);
395
            $html = Util::showMySQLDocu(
396
                $name,
397
                false,
398
                $docLink,
399
                $text
400
            );
401
        } catch (KBException $e) {
402
            unset($e);// phpstan workaround
403
        }
404
        return $html;
405
    }
406
407
    /**
408
     * Displays a link to the official MySQL documentation
409
     *
410
     * @param string      $link    contains name of page/anchor that is being linked
411
     * @param bool        $bigIcon whether to use big icon (like in left frame)
412
     * @param string|null $url     href attribute
413
     * @param string|null $text    text of link
414
     * @param string      $anchor  anchor to page part
415
     *
416
     * @return string  the html link
417
     *
418
     * @access  public
419
     */
420
    public static function showMySQLDocu(
421
        $link,
422
        bool $bigIcon = false,
423
        $url = null,
424
        $text = null,
425
        $anchor = ''
426
    ): string {
427
        if ($url === null) {
428
            $url = self::getMySQLDocuURL($link, $anchor);
429
        }
430
        $openLink = '<a href="' . htmlspecialchars($url) . '" target="mysql_doc">';
431
        $closeLink = '</a>';
432
        $html = '';
433
434
        if ($bigIcon) {
435
            $html = $openLink .
436
                    self::getImage('b_sqlhelp', __('Documentation'))
437
                    . $closeLink;
438
        } elseif ($text !== null) {
439
            $html = $openLink . $text . $closeLink;
440
        } else {
441
            $html = self::showDocLink($url, 'mysql_doc');
442
        }
443
444
        return $html;
445
    } // end of the 'showMySQLDocu()' function
446
447
    /**
448
     * Returns link to documentation.
449
     *
450
     * @param string $page   Page in documentation
451
     * @param string $anchor Optional anchor in page
452
     *
453
     * @return string URL
454
     */
455
    public static function getDocuLink($page, $anchor = '')
456
    {
457
        /* Construct base URL */
458
        $url =  $page . '.html';
459
        if (!empty($anchor)) {
460
            $url .= '#' . $anchor;
461
        }
462
463
        /* Check if we have built local documentation, however
464
         * provide consistent URL for testsuite
465
         */
466
        if (! defined('TESTSUITE') && @file_exists('doc/html/index.html')) {
467
            if ($GLOBALS['PMA_Config']->get('is_setup')) {
468
                return '../doc/html/' . $url;
469
            }
470
471
            return './doc/html/' . $url;
472
        }
473
474
        return Core::linkURL('https://docs.phpmyadmin.net/en/latest/' . $url);
475
    }
476
477
    /**
478
     * Displays a link to the phpMyAdmin documentation
479
     *
480
     * @param string  $page   Page in documentation
481
     * @param string  $anchor Optional anchor in page
482
     * @param boolean $bbcode Optional flag indicating whether to output bbcode
483
     *
484
     * @return string  the html link
485
     *
486
     * @access  public
487
     */
488
    public static function showDocu($page, $anchor = '', $bbcode = false)
489
    {
490
        return self::showDocLink(self::getDocuLink($page, $anchor), 'documentation', $bbcode);
491
    } // end of the 'showDocu()' function
492
493
    /**
494
     * Displays a link to the PHP documentation
495
     *
496
     * @param string $target anchor in documentation
497
     *
498
     * @return string  the html link
499
     *
500
     * @access  public
501
     */
502
    public static function showPHPDocu($target)
503
    {
504
        $url = Core::getPHPDocLink($target);
505
506
        return self::showDocLink($url);
507
    } // end of the 'showPHPDocu()' function
508
509
    /**
510
     * Returns HTML code for a tooltip
511
     *
512
     * @param string $message the message for the tooltip
513
     *
514
     * @return string
515
     *
516
     * @access  public
517
     */
518
    public static function showHint($message)
519
    {
520
        if ($GLOBALS['cfg']['ShowHint']) {
521
            $classClause = ' class="pma_hint"';
522
        } else {
523
            $classClause = '';
524
        }
525
        return '<span' . $classClause . '>'
526
            . self::getImage('b_help')
527
            . '<span class="hide">' . $message . '</span>'
528
            . '</span>';
529
    }
530
531
    /**
532
     * Displays a MySQL error message in the main panel when $exit is true.
533
     * Returns the error message otherwise.
534
     *
535
     * @param string|bool $server_msg     Server's error message.
536
     * @param string      $sql_query      The SQL query that failed.
537
     * @param bool        $is_modify_link Whether to show a "modify" link or not.
538
     * @param string      $back_url       URL for the "back" link (full path is
539
     *                                    not required).
540
     * @param bool        $exit           Whether execution should be stopped or
541
     *                                    the error message should be returned.
542
     *
543
     * @return string
544
     *
545
     * @global string $table The current table.
546
     * @global string $db    The current database.
547
     *
548
     * @access public
549
     */
550
    public static function mysqlDie(
551
        $server_msg = '',
552
        $sql_query = '',
553
        $is_modify_link = true,
554
        $back_url = '',
555
        $exit = true
556
    ) {
557
        global $table, $db;
558
559
        /**
560
         * Error message to be built.
561
         * @var string $error_msg
562
         */
563
        $error_msg = '';
564
565
        // Checking for any server errors.
566
        if (empty($server_msg)) {
567
            $server_msg = $GLOBALS['dbi']->getError();
568
        }
569
570
        // Finding the query that failed, if not specified.
571
        if ((empty($sql_query) && (!empty($GLOBALS['sql_query'])))) {
572
            $sql_query = $GLOBALS['sql_query'];
573
        }
574
        $sql_query = trim($sql_query);
575
576
        /**
577
         * The lexer used for analysis.
578
         * @var Lexer $lexer
579
         */
580
        $lexer = new Lexer($sql_query);
581
582
        /**
583
         * The parser used for analysis.
584
         * @var Parser $parser
585
         */
586
        $parser = new Parser($lexer->list);
587
588
        /**
589
         * The errors found by the lexer and the parser.
590
         * @var array $errors
591
         */
592
        $errors = ParserError::get([$lexer, $parser]);
593
594
        if (empty($sql_query)) {
595
            $formatted_sql = '';
596
        } elseif (count($errors)) {
597
            $formatted_sql = htmlspecialchars($sql_query);
598
        } else {
599
            $formatted_sql = self::formatSql($sql_query, true);
600
        }
601
602
        $error_msg .= '<div class="error"><h1>' . __('Error') . '</h1>';
603
604
        // For security reasons, if the MySQL refuses the connection, the query
605
        // is hidden so no details are revealed.
606
        if ((!empty($sql_query)) && (!(mb_strstr($sql_query, 'connect')))) {
607
            // Static analysis errors.
608
            if (!empty($errors)) {
609
                $error_msg .= '<p><strong>' . __('Static analysis:')
610
                    . '</strong></p>';
611
                $error_msg .= '<p>' . sprintf(
612
                    __('%d errors were found during analysis.'),
613
                    count($errors)
614
                ) . '</p>';
615
                $error_msg .= '<p><ol>';
616
                $error_msg .= implode(
617
                    ParserError::format(
618
                        $errors,
619
                        '<li>%2$s (near "%4$s" at position %5$d)</li>'
620
                    )
621
                );
622
                $error_msg .= '</ol></p>';
623
            }
624
625
            // Display the SQL query and link to MySQL documentation.
626
            $error_msg .= '<p><strong>' . __('SQL query:') . '</strong>' . self::showCopyToClipboard($sql_query) . "\n";
627
            $formattedSqlToLower = mb_strtolower($formatted_sql);
628
629
            // TODO: Show documentation for all statement types.
630
            if (mb_strstr($formattedSqlToLower, 'select')) {
631
                // please show me help to the error on select
632
                $error_msg .= self::showMySQLDocu('SELECT');
633
            }
634
635
            if ($is_modify_link) {
636
                $_url_params = [
637
                    'sql_query' => $sql_query,
638
                    'show_query' => 1,
639
                ];
640
                if (strlen($table) > 0) {
641
                    $_url_params['db'] = $db;
642
                    $_url_params['table'] = $table;
643
                    $doedit_goto = '<a href="tbl_sql.php'
644
                        . Url::getCommon($_url_params) . '">';
645
                } elseif (strlen($db) > 0) {
646
                    $_url_params['db'] = $db;
647
                    $doedit_goto = '<a href="db_sql.php'
648
                        . Url::getCommon($_url_params) . '">';
649
                } else {
650
                    $doedit_goto = '<a href="server_sql.php'
651
                        . Url::getCommon($_url_params) . '">';
652
                }
653
654
                $error_msg .= $doedit_goto
655
                   . self::getIcon('b_edit', __('Edit'))
656
                   . '</a>';
657
            }
658
659
            $error_msg .= '    </p>' . "\n"
660
                . '<p>' . "\n"
661
                . $formatted_sql . "\n"
662
                . '</p>' . "\n";
663
        }
664
665
        // Display server's error.
666
        if (!empty($server_msg)) {
667
            $server_msg = preg_replace(
668
                "@((\015\012)|(\015)|(\012)){3,}@",
669
                "\n\n",
670
                $server_msg
671
            );
672
673
            // Adds a link to MySQL documentation.
674
            $error_msg .= '<p>' . "\n"
675
                . '    <strong>' . __('MySQL said: ') . '</strong>'
676
                . self::showMySQLDocu('Error-messages-server')
677
                . "\n"
678
                . '</p>' . "\n";
679
680
            // The error message will be displayed within a CODE segment.
681
            // To preserve original formatting, but allow word-wrapping,
682
            // a couple of replacements are done.
683
            // All non-single blanks and  TAB-characters are replaced with their
684
            // HTML-counterpart
685
            $server_msg = str_replace(
686
                ['  ', "\t"],
687
                ['&nbsp;&nbsp;', '&nbsp;&nbsp;&nbsp;&nbsp;'],
688
                $server_msg
689
            );
690
691
            // Replace line breaks
692
            $server_msg = nl2br($server_msg);
693
694
            $error_msg .= '<code>' . $server_msg . '</code><br/>';
695
        }
696
697
        $error_msg .= '</div>';
698
        $_SESSION['Import_message']['message'] = $error_msg;
699
700
        if (!$exit) {
701
            return $error_msg;
702
        }
703
704
        /**
705
         * If this is an AJAX request, there is no "Back" link and
706
         * `Response()` is used to send the response.
707
         */
708
        $response = Response::getInstance();
709
        if ($response->isAjax()) {
710
            $response->setRequestStatus(false);
711
            $response->addJSON('message', $error_msg);
712
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
713
        }
714
715
        if (!empty($back_url)) {
716
            if (mb_strstr($back_url, '?')) {
717
                $back_url .= '&amp;no_history=true';
718
            } else {
719
                $back_url .= '?no_history=true';
720
            }
721
722
            $_SESSION['Import_message']['go_back_url'] = $back_url;
723
724
            $error_msg .= '<fieldset class="tblFooters">'
725
                . '[ <a href="' . $back_url . '">' . __('Back') . '</a> ]'
726
                . '</fieldset>' . "\n\n";
727
        }
728
729
        exit($error_msg);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
730
    }
731
732
    /**
733
     * Check the correct row count
734
     *
735
     * @param string $db    the db name
736
     * @param array  $table the table infos
737
     *
738
     * @return int $rowCount the possibly modified row count
739
     *
740
     */
741
    private static function _checkRowCount($db, array $table)
742
    {
743
        $rowCount = 0;
744
745
        if ($table['Rows'] === null) {
746
            // Do not check exact row count here,
747
            // if row count is invalid possibly the table is defect
748
            // and this would break the navigation panel;
749
            // but we can check row count if this is a view or the
750
            // information_schema database
751
            // since Table::countRecords() returns a limited row count
752
            // in this case.
753
754
            // set this because Table::countRecords() can use it
755
            $tbl_is_view = $table['TABLE_TYPE'] == 'VIEW';
756
757
            if ($tbl_is_view || $GLOBALS['dbi']->isSystemSchema($db)) {
758
                $rowCount = $GLOBALS['dbi']
759
                    ->getTable($db, $table['Name'])
760
                    ->countRecords();
761
            }
762
        }
763
        return $rowCount;
764
    }
765
766
    /**
767
     * returns array with tables of given db with extended information and grouped
768
     *
769
     * @param string   $db           name of db
770
     * @param string   $tables       name of tables
771
     * @param integer  $limit_offset list offset
772
     * @param int|bool $limit_count  max tables to return
773
     *
774
     * @return array    (recursive) grouped table list
775
     */
776
    public static function getTableList(
777
        $db,
778
        $tables = null,
779
        $limit_offset = 0,
780
        $limit_count = false
781
    ) {
782
        $sep = $GLOBALS['cfg']['NavigationTreeTableSeparator'];
783
784
        if ($tables === null) {
785
            $tables = $GLOBALS['dbi']->getTablesFull(
786
                $db,
787
                '',
788
                false,
789
                $limit_offset,
790
                $limit_count
791
            );
792
            if ($GLOBALS['cfg']['NaturalOrder']) {
793
                uksort($tables, 'strnatcasecmp');
794
            }
795
        }
796
797
        if (count($tables) < 1) {
798
            return $tables;
799
        }
800
801
        $default = [
802
            'Name'      => '',
803
            'Rows'      => 0,
804
            'Comment'   => '',
805
            'disp_name' => '',
806
        ];
807
808
        $table_groups = [];
809
810
        foreach ($tables as $table_name => $table) {
811
            $table['Rows'] = self::_checkRowCount($db, $table);
812
813
            // in $group we save the reference to the place in $table_groups
814
            // where to store the table info
815
            if ($GLOBALS['cfg']['NavigationTreeEnableGrouping']
816
                && $sep && mb_strstr($table_name, $sep)
817
            ) {
818
                $parts = explode($sep, $table_name);
819
820
                $group =& $table_groups;
821
                $i = 0;
822
                $group_name_full = '';
823
                $parts_cnt = count($parts) - 1;
824
825
                while (($i < $parts_cnt)
826
                    && ($i < $GLOBALS['cfg']['NavigationTreeTableLevel'])
827
                ) {
828
                    $group_name = $parts[$i] . $sep;
829
                    $group_name_full .= $group_name;
830
831
                    if (! isset($group[$group_name])) {
832
                        $group[$group_name] = [];
833
                        $group[$group_name]['is' . $sep . 'group'] = true;
834
                        $group[$group_name]['tab' . $sep . 'count'] = 1;
835
                        $group[$group_name]['tab' . $sep . 'group']
836
                            = $group_name_full;
837
                    } elseif (! isset($group[$group_name]['is' . $sep . 'group'])) {
838
                        $table = $group[$group_name];
839
                        $group[$group_name] = [];
840
                        $group[$group_name][$group_name] = $table;
841
                        $group[$group_name]['is' . $sep . 'group'] = true;
842
                        $group[$group_name]['tab' . $sep . 'count'] = 1;
843
                        $group[$group_name]['tab' . $sep . 'group']
844
                            = $group_name_full;
845
                    } else {
846
                        $group[$group_name]['tab' . $sep . 'count']++;
847
                    }
848
849
                    $group =& $group[$group_name];
850
                    $i++;
851
                }
852
            } else {
853
                if (! isset($table_groups[$table_name])) {
854
                    $table_groups[$table_name] = [];
855
                }
856
                $group =& $table_groups;
857
            }
858
859
            $table['disp_name'] = $table['Name'];
860
            $group[$table_name] = array_merge($default, $table);
861
        }
862
863
        return $table_groups;
864
    }
865
866
    /* ----------------------- Set of misc functions ----------------------- */
867
868
    /**
869
     * Adds backquotes on both sides of a database, table or field name.
870
     * and escapes backquotes inside the name with another backquote
871
     *
872
     * example:
873
     * <code>
874
     * echo backquote('owner`s db'); // `owner``s db`
875
     *
876
     * </code>
877
     *
878
     * @param mixed   $a_name the database, table or field name to "backquote"
879
     *                        or array of it
880
     * @param boolean $do_it  a flag to bypass this function (used by dump
881
     *                        functions)
882
     *
883
     * @return mixed    the "backquoted" database, table or field name
884
     *
885
     * @access  public
886
     */
887
    public static function backquote($a_name, $do_it = true)
888
    {
889
        if (is_array($a_name)) {
890
            foreach ($a_name as &$data) {
891
                $data = self::backquote($data, $do_it);
892
            }
893
            return $a_name;
894
        }
895
896
        if (! $do_it) {
897
            if (!(Context::isKeyword($a_name) & Token::FLAG_KEYWORD_RESERVED)
898
            ) {
899
                return $a_name;
900
            }
901
        }
902
903
        // '0' is also empty for php :-(
904
        if (strlen((string) $a_name) > 0 && $a_name !== '*') {
905
            return '`' . str_replace('`', '``', $a_name) . '`';
906
        }
907
908
        return $a_name;
909
    } // end of the 'backquote()' function
910
911
    /**
912
     * Adds backquotes on both sides of a database, table or field name.
913
     * in compatibility mode
914
     *
915
     * example:
916
     * <code>
917
     * echo backquoteCompat('owner`s db'); // `owner``s db`
918
     *
919
     * </code>
920
     *
921
     * @param mixed   $a_name        the database, table or field name to
922
     *                               "backquote" or array of it
923
     * @param string  $compatibility string compatibility mode (used by dump
924
     *                               functions)
925
     * @param boolean $do_it         a flag to bypass this function (used by dump
926
     *                               functions)
927
     *
928
     * @return mixed the "backquoted" database, table or field name
929
     *
930
     * @access  public
931
     */
932
    public static function backquoteCompat(
933
        $a_name,
934
        $compatibility = 'MSSQL',
935
        $do_it = true
936
    ) {
937
        if (is_array($a_name)) {
938
            foreach ($a_name as &$data) {
939
                $data = self::backquoteCompat($data, $compatibility, $do_it);
940
            }
941
            return $a_name;
942
        }
943
944
        if (! $do_it) {
945
            if (!Context::isKeyword($a_name)) {
946
                return $a_name;
947
            }
948
        }
949
950
        // @todo add more compatibility cases (ORACLE for example)
951
        switch ($compatibility) {
952
            case 'MSSQL':
953
                $quote = '"';
954
                break;
955
            default:
956
                $quote = "`";
957
                break;
958
        }
959
960
        // '0' is also empty for php :-(
961
        if (strlen((string)$a_name) > 0 && $a_name !== '*') {
962
            return $quote . $a_name . $quote;
963
        }
964
965
        return $a_name;
966
    } // end of the 'backquoteCompat()' function
967
968
    /**
969
     * Prepare the message and the query
970
     * usually the message is the result of the query executed
971
     *
972
     * @param Message|string $message   the message to display
973
     * @param string         $sql_query the query to display
974
     * @param string         $type      the type (level) of the message
975
     *
976
     * @return string
977
     *
978
     * @access  public
979
     */
980
    public static function getMessage(
981
        $message,
982
        $sql_query = null,
983
        $type = 'notice'
984
    ) {
985
        global $cfg;
986
        $template = new Template();
987
        $retval = '';
988
989
        if (null === $sql_query) {
990
            if (! empty($GLOBALS['display_query'])) {
991
                $sql_query = $GLOBALS['display_query'];
992
            } elseif (! empty($GLOBALS['unparsed_sql'])) {
993
                $sql_query = $GLOBALS['unparsed_sql'];
994
            } elseif (! empty($GLOBALS['sql_query'])) {
995
                $sql_query = $GLOBALS['sql_query'];
996
            } else {
997
                $sql_query = '';
998
            }
999
        }
1000
1001
        $render_sql = $cfg['ShowSQL'] == true && ! empty($sql_query) && $sql_query !== ';';
1002
1003
        if (isset($GLOBALS['using_bookmark_message'])) {
1004
            $retval .= $GLOBALS['using_bookmark_message']->getDisplay();
1005
            unset($GLOBALS['using_bookmark_message']);
1006
        }
1007
1008
        if ($render_sql) {
1009
            $retval .= '<div class="result_query">' . "\n";
1010
        }
1011
1012
        if ($message instanceof Message) {
1013
            if (isset($GLOBALS['special_message'])) {
1014
                $message->addText($GLOBALS['special_message']);
1015
                unset($GLOBALS['special_message']);
1016
            }
1017
            $retval .= $message->getDisplay();
1018
        } else {
1019
            $retval .= '<div class="' . $type . '">';
1020
            $retval .= Sanitize::sanitize($message);
1021
            if (isset($GLOBALS['special_message'])) {
1022
                $retval .= Sanitize::sanitize($GLOBALS['special_message']);
1023
                unset($GLOBALS['special_message']);
1024
            }
1025
            $retval .= '</div>';
1026
        }
1027
1028
        if ($render_sql) {
1029
            $query_too_big = false;
1030
1031
            $queryLength = mb_strlen($sql_query);
1032
            if ($queryLength > $cfg['MaxCharactersInDisplayedSQL']) {
1033
                // when the query is large (for example an INSERT of binary
1034
                // data), the parser chokes; so avoid parsing the query
1035
                $query_too_big = true;
1036
                $query_base = mb_substr(
1037
                    $sql_query,
1038
                    0,
1039
                    $cfg['MaxCharactersInDisplayedSQL']
1040
                ) . '[...]';
1041
            } else {
1042
                $query_base = $sql_query;
1043
            }
1044
1045
            // Html format the query to be displayed
1046
            // If we want to show some sql code it is easiest to create it here
1047
            /* SQL-Parser-Analyzer */
1048
1049
            if (! empty($GLOBALS['show_as_php'])) {
1050
                $new_line = '\\n"<br />' . "\n" . '&nbsp;&nbsp;&nbsp;&nbsp;. "';
1051
                $query_base = htmlspecialchars(addslashes($query_base));
1052
                $query_base = preg_replace(
1053
                    '/((\015\012)|(\015)|(\012))/',
1054
                    $new_line,
1055
                    $query_base
1056
                );
1057
                $query_base = '<code class="php"><pre>' . "\n"
1058
                    . '$sql = "' . $query_base . '";' . "\n"
1059
                    . '</pre></code>';
1060
            } elseif ($query_too_big) {
1061
                $query_base = htmlspecialchars($query_base);
1062
            } else {
1063
                $query_base = self::formatSql($query_base);
1064
            }
1065
1066
            // Prepares links that may be displayed to edit/explain the query
1067
            // (don't go to default pages, we must go to the page
1068
            // where the query box is available)
1069
1070
            // Basic url query part
1071
            $url_params = [];
1072
            if (! isset($GLOBALS['db'])) {
1073
                $GLOBALS['db'] = '';
1074
            }
1075
            if (strlen($GLOBALS['db']) > 0) {
1076
                $url_params['db'] = $GLOBALS['db'];
1077
                if (strlen($GLOBALS['table']) > 0) {
1078
                    $url_params['table'] = $GLOBALS['table'];
1079
                    $edit_link = 'tbl_sql.php';
1080
                } else {
1081
                    $edit_link = 'db_sql.php';
1082
                }
1083
            } else {
1084
                $edit_link = 'server_sql.php';
1085
            }
1086
1087
            // Want to have the query explained
1088
            // but only explain a SELECT (that has not been explained)
1089
            /* SQL-Parser-Analyzer */
1090
            $explain_link = '';
1091
            $is_select = preg_match('@^SELECT[[:space:]]+@i', $sql_query);
1092
            if (! empty($cfg['SQLQuery']['Explain']) && ! $query_too_big) {
1093
                $explain_params = $url_params;
1094
                if ($is_select) {
1095
                    $explain_params['sql_query'] = 'EXPLAIN ' . $sql_query;
1096
                    $explain_link = ' [&nbsp;'
1097
                        . self::linkOrButton(
1098
                            'import.php' . Url::getCommon($explain_params),
1099
                            __('Explain SQL')
1100
                        ) . '&nbsp;]';
1101
                } elseif (preg_match(
1102
                    '@^EXPLAIN[[:space:]]+SELECT[[:space:]]+@i',
1103
                    $sql_query
1104
                )) {
1105
                    $explain_params['sql_query']
1106
                        = mb_substr($sql_query, 8);
1107
                    $explain_link = ' [&nbsp;'
1108
                        . self::linkOrButton(
1109
                            'import.php' . Url::getCommon($explain_params),
1110
                            __('Skip Explain SQL')
1111
                        ) . ']';
1112
                    $url = 'https://mariadb.org/explain_analyzer/analyze/'
1113
                        . '?client=phpMyAdmin&raw_explain='
1114
                        . urlencode(self::_generateRowQueryOutput($sql_query));
1115
                    $explain_link .= ' ['
1116
                        . self::linkOrButton(
1117
                            htmlspecialchars('url.php?url=' . urlencode($url)),
1118
                            sprintf(__('Analyze Explain at %s'), 'mariadb.org'),
1119
                            [],
1120
                            '_blank'
1121
                        ) . '&nbsp;]';
1122
                }
1123
            } //show explain
1124
1125
            $url_params['sql_query']  = $sql_query;
1126
            $url_params['show_query'] = 1;
1127
1128
            // even if the query is big and was truncated, offer the chance
1129
            // to edit it (unless it's enormous, see linkOrButton() )
1130
            if (! empty($cfg['SQLQuery']['Edit'])
1131
                && empty($GLOBALS['show_as_php'])
1132
            ) {
1133
                $edit_link .= Url::getCommon($url_params);
1134
                $edit_link = ' [&nbsp;'
1135
                    . self::linkOrButton($edit_link, __('Edit'))
1136
                    . '&nbsp;]';
1137
            } else {
1138
                $edit_link = '';
1139
            }
1140
1141
            // Also we would like to get the SQL formed in some nice
1142
            // php-code
1143
            if (! empty($cfg['SQLQuery']['ShowAsPHP']) && ! $query_too_big) {
1144
                if (! empty($GLOBALS['show_as_php'])) {
1145
                    $php_link = ' [&nbsp;'
1146
                        . self::linkOrButton(
1147
                            'import.php' . Url::getCommon($url_params),
1148
                            __('Without PHP code')
1149
                        )
1150
                        . '&nbsp;]';
1151
1152
                    $php_link .= ' [&nbsp;'
1153
                        . self::linkOrButton(
1154
                            'import.php' . Url::getCommon($url_params),
1155
                            __('Submit query')
1156
                        )
1157
                        . '&nbsp;]';
1158
                } else {
1159
                    $php_params = $url_params;
1160
                    $php_params['show_as_php'] = 1;
1161
                    $php_link = ' [&nbsp;'
1162
                        . self::linkOrButton(
1163
                            'import.php' . Url::getCommon($php_params),
1164
                            __('Create PHP code')
1165
                        )
1166
                        . '&nbsp;]';
1167
                }
1168
            } else {
1169
                $php_link = '';
1170
            } //show as php
1171
1172
            // Refresh query
1173
            if (! empty($cfg['SQLQuery']['Refresh'])
1174
                && ! isset($GLOBALS['show_as_php']) // 'Submit query' does the same
1175
                && preg_match('@^(SELECT|SHOW)[[:space:]]+@i', $sql_query)
1176
            ) {
1177
                $refresh_link = 'import.php' . Url::getCommon($url_params);
1178
                $refresh_link = ' [&nbsp;'
1179
                    . self::linkOrButton($refresh_link, __('Refresh')) . ']';
1180
            } else {
1181
                $refresh_link = '';
1182
            } //refresh
1183
1184
            $retval .= '<div class="sqlOuter">';
1185
            $retval .= $query_base;
1186
            $retval .= '</div>';
1187
1188
            $retval .= '<div class="tools print_ignore">';
1189
            $retval .= '<form action="sql.php" method="post">';
1190
            $retval .= Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']);
1191
            $retval .= '<input type="hidden" name="sql_query" value="'
1192
                . htmlspecialchars($sql_query) . '" />';
1193
1194
            // avoid displaying a Profiling checkbox that could
1195
            // be checked, which would reexecute an INSERT, for example
1196
            if (! empty($refresh_link) && self::profilingSupported()) {
1197
                $retval .= '<input type="hidden" name="profiling_form" value="1" />';
1198
                $retval .= $template->render('checkbox', [
1199
                    'html_field_name' => 'profiling',
1200
                    'label' => __('Profiling'),
1201
                    'checked' => isset($_SESSION['profiling']),
1202
                    'onclick' => true,
1203
                    'html_field_id' => '',
1204
                ]);
1205
            }
1206
            $retval .= '</form>';
1207
1208
            /**
1209
             * TODO: Should we have $cfg['SQLQuery']['InlineEdit']?
1210
             */
1211
            if (! empty($cfg['SQLQuery']['Edit'])
1212
                && ! $query_too_big
1213
                && empty($GLOBALS['show_as_php'])
1214
            ) {
1215
                $inline_edit_link = ' ['
1216
                    . self::linkOrButton(
1217
                        '#',
1218
                        _pgettext('Inline edit query', 'Edit inline'),
1219
                        ['class' => 'inline_edit_sql']
1220
                    )
1221
                    . ']';
1222
            } else {
1223
                $inline_edit_link = '';
1224
            }
1225
            $retval .= $inline_edit_link . $edit_link . $explain_link . $php_link
1226
                . $refresh_link;
1227
            $retval .= '</div>';
1228
1229
            $retval .= '</div>';
1230
        }
1231
1232
        return $retval;
1233
    } // end of the 'getMessage()' function
1234
1235
    /**
1236
     * Execute an EXPLAIN query and formats results similar to MySQL command line
1237
     * utility.
1238
     *
1239
     * @param string $sqlQuery EXPLAIN query
1240
     *
1241
     * @return string query resuls
1242
     */
1243
    private static function _generateRowQueryOutput($sqlQuery)
1244
    {
1245
        $ret = '';
1246
        $result = $GLOBALS['dbi']->query($sqlQuery);
1247
        if ($result) {
1248
            $devider = '+';
1249
            $columnNames = '|';
1250
            $fieldsMeta = $GLOBALS['dbi']->getFieldsMeta($result);
1251
            foreach ($fieldsMeta as $meta) {
1252
                $devider .= '---+';
1253
                $columnNames .= ' ' . $meta->name . ' |';
1254
            }
1255
            $devider .= "\n";
1256
1257
            $ret .= $devider . $columnNames . "\n" . $devider;
1258
            while ($row = $GLOBALS['dbi']->fetchRow($result)) {
1259
                $values = '|';
1260
                foreach ($row as $value) {
1261
                    if (is_null($value)) {
1262
                        $value = 'NULL';
1263
                    }
1264
                    $values .= ' ' . $value . ' |';
1265
                }
1266
                $ret .= $values . "\n";
1267
            }
1268
            $ret .= $devider;
1269
        }
1270
        return $ret;
1271
    }
1272
1273
    /**
1274
     * Verifies if current MySQL server supports profiling
1275
     *
1276
     * @access  public
1277
     *
1278
     * @return boolean whether profiling is supported
1279
     */
1280
    public static function profilingSupported()
1281
    {
1282
        if (!self::cacheExists('profiling_supported')) {
1283
            // 5.0.37 has profiling but for example, 5.1.20 does not
1284
            // (avoid a trip to the server for MySQL before 5.0.37)
1285
            // and do not set a constant as we might be switching servers
1286
            if ($GLOBALS['dbi']->fetchValue("SELECT @@have_profiling")
1287
            ) {
1288
                self::cacheSet('profiling_supported', true);
1289
            } else {
1290
                self::cacheSet('profiling_supported', false);
1291
            }
1292
        }
1293
1294
        return self::cacheGet('profiling_supported');
1295
    }
1296
1297
    /**
1298
     * Formats $value to byte view
1299
     *
1300
     * @param double|int $value the value to format
1301
     * @param int        $limes the sensitiveness
1302
     * @param int        $comma the number of decimals to retain
1303
     *
1304
     * @return array    the formatted value and its unit
1305
     *
1306
     * @access  public
1307
     */
1308
    public static function formatByteDown($value, $limes = 6, $comma = 0)
1309
    {
1310
        if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
1311
            return null;
1312
        }
1313
1314
        $byteUnits = [
1315
            /* l10n: shortcuts for Byte */
1316
            __('B'),
1317
            /* l10n: shortcuts for Kilobyte */
1318
            __('KiB'),
1319
            /* l10n: shortcuts for Megabyte */
1320
            __('MiB'),
1321
            /* l10n: shortcuts for Gigabyte */
1322
            __('GiB'),
1323
            /* l10n: shortcuts for Terabyte */
1324
            __('TiB'),
1325
            /* l10n: shortcuts for Petabyte */
1326
            __('PiB'),
1327
            /* l10n: shortcuts for Exabyte */
1328
            __('EiB')
1329
        ];
1330
1331
        $dh = pow(10, $comma);
1332
        $li = pow(10, $limes);
1333
        $unit = $byteUnits[0];
1334
1335
        for ($d = 6, $ex = 15; $d >= 1; $d--, $ex -= 3) {
1336
            $unitSize = $li * pow(10, $ex);
1337
            if (isset($byteUnits[$d]) && $value >= $unitSize) {
1338
                // use 1024.0 to avoid integer overflow on 64-bit machines
1339
                $value = round($value / (pow(1024, $d) / $dh)) / $dh;
1340
                $unit = $byteUnits[$d];
1341
                break 1;
1342
            } // end if
1343
        } // end for
1344
1345
        if ($unit != $byteUnits[0]) {
1346
            // if the unit is not bytes (as represented in current language)
1347
            // reformat with max length of 5
1348
            // 4th parameter=true means do not reformat if value < 1
1349
            $return_value = self::formatNumber($value, 5, $comma, true, false);
1350
        } else {
1351
            // do not reformat, just handle the locale
1352
            $return_value = self::formatNumber($value, 0);
1353
        }
1354
1355
        return [trim($return_value), $unit];
1356
    } // end of the 'formatByteDown' function
1357
1358
1359
    /**
1360
     * Formats $value to the given length and appends SI prefixes
1361
     * with a $length of 0 no truncation occurs, number is only formatted
1362
     * to the current locale
1363
     *
1364
     * examples:
1365
     * <code>
1366
     * echo formatNumber(123456789, 6);     // 123,457 k
1367
     * echo formatNumber(-123456789, 4, 2); //    -123.46 M
1368
     * echo formatNumber(-0.003, 6);        //      -3 m
1369
     * echo formatNumber(0.003, 3, 3);      //       0.003
1370
     * echo formatNumber(0.00003, 3, 2);    //       0.03 m
1371
     * echo formatNumber(0, 6);             //       0
1372
     * </code>
1373
     *
1374
     * @param double  $value          the value to format
1375
     * @param integer $digits_left    number of digits left of the comma
1376
     * @param integer $digits_right   number of digits right of the comma
1377
     * @param boolean $only_down      do not reformat numbers below 1
1378
     * @param boolean $noTrailingZero removes trailing zeros right of the comma
1379
     *                                (default: true)
1380
     *
1381
     * @return string   the formatted value and its unit
1382
     *
1383
     * @access  public
1384
     */
1385
    public static function formatNumber(
1386
        $value,
1387
        $digits_left = 3,
1388
        $digits_right = 0,
1389
        $only_down = false,
1390
        $noTrailingZero = true
1391
    ) {
1392
        if ($value == 0) {
1393
            return '0';
1394
        }
1395
1396
        $originalValue = $value;
1397
        //number_format is not multibyte safe, str_replace is safe
1398
        if ($digits_left === 0) {
1399
            $value = number_format(
1400
                (float) $value,
1401
                $digits_right,
1402
                /* l10n: Decimal separator */
1403
                __('.'),
1404
                /* l10n: Thousands separator */
1405
                __(',')
1406
            );
1407
            if (($originalValue != 0) && (floatval($value) == 0)) {
1408
                $value = ' <' . (1 / pow(10, $digits_right));
1409
            }
1410
            return $value;
1411
        }
1412
1413
        // this units needs no translation, ISO
1414
        $units = [
1415
            -8 => 'y',
1416
            -7 => 'z',
1417
            -6 => 'a',
1418
            -5 => 'f',
1419
            -4 => 'p',
1420
            -3 => 'n',
1421
            -2 => '&micro;',
1422
            -1 => 'm',
1423
            0 => ' ',
1424
            1 => 'k',
1425
            2 => 'M',
1426
            3 => 'G',
1427
            4 => 'T',
1428
            5 => 'P',
1429
            6 => 'E',
1430
            7 => 'Z',
1431
            8 => 'Y'
1432
        ];
1433
        /* l10n: Decimal separator */
1434
        $decimal_sep = __('.');
1435
        /* l10n: Thousands separator */
1436
        $thousands_sep = __(',');
1437
1438
        // check for negative value to retain sign
1439
        if ($value < 0) {
1440
            $sign = '-';
1441
            $value = abs($value);
1442
        } else {
1443
            $sign = '';
1444
        }
1445
1446
        $dh = pow(10, $digits_right);
1447
1448
        /*
1449
         * This gives us the right SI prefix already,
1450
         * but $digits_left parameter not incorporated
1451
         */
1452
        $d = floor(log10((float) $value) / 3);
1453
        /*
1454
         * Lowering the SI prefix by 1 gives us an additional 3 zeros
1455
         * So if we have 3,6,9,12.. free digits ($digits_left - $cur_digits)
1456
         * to use, then lower the SI prefix
1457
         */
1458
        $cur_digits = floor(log10($value / pow(1000, $d)) + 1);
1459
        if ($digits_left > $cur_digits) {
1460
            $d -= floor(($digits_left - $cur_digits) / 3);
1461
        }
1462
1463
        if ($d < 0 && $only_down) {
1464
            $d = 0;
1465
        }
1466
1467
        $value = round($value / (pow(1000, $d) / $dh)) / $dh;
1468
        $unit = $units[$d];
1469
1470
        // number_format is not multibyte safe, str_replace is safe
1471
        $formattedValue = number_format(
1472
            $value,
1473
            $digits_right,
1474
            $decimal_sep,
1475
            $thousands_sep
1476
        );
1477
        // If we don't want any zeros, remove them now
1478
        if ($noTrailingZero && strpos($formattedValue, $decimal_sep) !== false) {
1479
            $formattedValue = preg_replace('/' . preg_quote($decimal_sep) . '?0+$/', '', $formattedValue);
1480
        }
1481
1482
        if ($originalValue != 0 && floatval($value) == 0) {
1483
            return ' <' . number_format(
1484
                (1 / pow(10, $digits_right)),
1485
                $digits_right,
1486
                $decimal_sep,
1487
                $thousands_sep
1488
            )
1489
            . ' ' . $unit;
1490
        }
1491
1492
        return $sign . $formattedValue . ' ' . $unit;
1493
    } // end of the 'formatNumber' function
1494
1495
    /**
1496
     * Returns the number of bytes when a formatted size is given
1497
     *
1498
     * @param string $formatted_size the size expression (for example 8MB)
1499
     *
1500
     * @return integer  The numerical part of the expression (for example 8)
1501
     */
1502
    public static function extractValueFromFormattedSize($formatted_size)
1503
    {
1504
        $return_value = -1;
1505
1506
        $formatted_size = (string) $formatted_size;
1507
1508
        if (preg_match('/^[0-9]+GB$/', $formatted_size)) {
1509
            $return_value = mb_substr($formatted_size, 0, -2)
1510
                * pow(1024, 3);
1511
        } elseif (preg_match('/^[0-9]+MB$/', $formatted_size)) {
1512
            $return_value = mb_substr($formatted_size, 0, -2)
1513
                * pow(1024, 2);
1514
        } elseif (preg_match('/^[0-9]+K$/', $formatted_size)) {
1515
            $return_value = mb_substr($formatted_size, 0, -1)
1516
                * pow(1024, 1);
1517
        }
1518
        return $return_value;
1519
    }// end of the 'extractValueFromFormattedSize' function
1520
1521
    /**
1522
     * Writes localised date
1523
     *
1524
     * @param integer $timestamp the current timestamp
1525
     * @param string  $format    format
1526
     *
1527
     * @return string   the formatted date
1528
     *
1529
     * @access  public
1530
     */
1531
    public static function localisedDate($timestamp = -1, $format = '')
1532
    {
1533
        $month = [
1534
            /* l10n: Short month name */
1535
            __('Jan'),
1536
            /* l10n: Short month name */
1537
            __('Feb'),
1538
            /* l10n: Short month name */
1539
            __('Mar'),
1540
            /* l10n: Short month name */
1541
            __('Apr'),
1542
            /* l10n: Short month name */
1543
            _pgettext('Short month name', 'May'),
1544
            /* l10n: Short month name */
1545
            __('Jun'),
1546
            /* l10n: Short month name */
1547
            __('Jul'),
1548
            /* l10n: Short month name */
1549
            __('Aug'),
1550
            /* l10n: Short month name */
1551
            __('Sep'),
1552
            /* l10n: Short month name */
1553
            __('Oct'),
1554
            /* l10n: Short month name */
1555
            __('Nov'),
1556
            /* l10n: Short month name */
1557
            __('Dec')];
1558
        $day_of_week = [
1559
            /* l10n: Short week day name */
1560
            _pgettext('Short week day name', 'Sun'),
1561
            /* l10n: Short week day name */
1562
            __('Mon'),
1563
            /* l10n: Short week day name */
1564
            __('Tue'),
1565
            /* l10n: Short week day name */
1566
            __('Wed'),
1567
            /* l10n: Short week day name */
1568
            __('Thu'),
1569
            /* l10n: Short week day name */
1570
            __('Fri'),
1571
            /* l10n: Short week day name */
1572
            __('Sat')];
1573
1574
        if ($format == '') {
1575
            /* l10n: See https://secure.php.net/manual/en/function.strftime.php */
1576
            $format = __('%B %d, %Y at %I:%M %p');
1577
        }
1578
1579
        if ($timestamp == -1) {
1580
            $timestamp = time();
1581
        }
1582
1583
        $date = preg_replace(
1584
            '@%[aA]@',
1585
            $day_of_week[(int)strftime('%w', (int) $timestamp)],
1586
            $format
1587
        );
1588
        $date = preg_replace(
1589
            '@%[bB]@',
1590
            $month[(int) strftime('%m', (int) $timestamp) - 1],
1591
            $date
1592
        );
1593
1594
        /* Fill in AM/PM */
1595
        $hours = (int) date('H', (int) $timestamp);
1596
        if ($hours >= 12) {
1597
            $am_pm = _pgettext('AM/PM indication in time', 'PM');
1598
        } else {
1599
            $am_pm = _pgettext('AM/PM indication in time', 'AM');
1600
        }
1601
        $date = preg_replace('@%[pP]@', $am_pm, $date);
1602
1603
        $ret = strftime($date, (int) $timestamp);
1604
        // Some OSes such as Win8.1 Traditional Chinese version did not produce UTF-8
1605
        // output here. See https://github.com/phpmyadmin/phpmyadmin/issues/10598
1606
        if (mb_detect_encoding($ret, 'UTF-8', true) != 'UTF-8') {
1607
            $ret = date('Y-m-d H:i:s', (int) $timestamp);
1608
        }
1609
1610
        return $ret;
1611
    } // end of the 'localisedDate()' function
1612
1613
    /**
1614
     * returns a tab for tabbed navigation.
1615
     * If the variables $link and $args ar left empty, an inactive tab is created
1616
     *
1617
     * @param array $tab        array with all options
1618
     * @param array $url_params tab specific URL parameters
1619
     *
1620
     * @return string  html code for one tab, a link if valid otherwise a span
1621
     *
1622
     * @access  public
1623
     */
1624
    public static function getHtmlTab(array $tab, array $url_params = [])
1625
    {
1626
        $template = new Template();
1627
        // default values
1628
        $defaults = [
1629
            'text'      => '',
1630
            'class'     => '',
1631
            'active'    => null,
1632
            'link'      => '',
1633
            'sep'       => '?',
1634
            'attr'      => '',
1635
            'args'      => '',
1636
            'warning'   => '',
1637
            'fragment'  => '',
1638
            'id'        => '',
1639
        ];
1640
1641
        $tab = array_merge($defaults, $tab);
1642
1643
        // determine additional style-class
1644
        if (empty($tab['class'])) {
1645
            if (! empty($tab['active'])
1646
                || Core::isValid($GLOBALS['active_page'], 'identical', $tab['link'])
1647
            ) {
1648
                $tab['class'] = 'active';
1649
            } elseif (is_null($tab['active']) && empty($GLOBALS['active_page'])
1650
                && (basename($GLOBALS['PMA_PHP_SELF']) == $tab['link'])
1651
            ) {
1652
                $tab['class'] = 'active';
1653
            }
1654
        }
1655
1656
        // build the link
1657
        if (! empty($tab['link'])) {
1658
            // If there are any tab specific URL parameters, merge those with
1659
            // the general URL parameters
1660
            if (! empty($tab['args']) && is_array($tab['args'])) {
1661
                $url_params = array_merge($url_params, $tab['args']);
1662
            }
1663
            $tab['link'] = htmlentities($tab['link']) . Url::getCommon($url_params);
1664
        }
1665
1666
        if (! empty($tab['fragment'])) {
1667
            $tab['link'] .= $tab['fragment'];
1668
        }
1669
1670
        // display icon
1671
        if (isset($tab['icon'])) {
1672
            // avoid generating an alt tag, because it only illustrates
1673
            // the text that follows and if browser does not display
1674
            // images, the text is duplicated
1675
            $tab['text'] = self::getIcon(
1676
                $tab['icon'],
1677
                $tab['text'],
1678
                false,
1679
                true,
1680
                'TabsMode'
1681
            );
1682
        } elseif (empty($tab['text'])) {
1683
            // check to not display an empty link-text
1684
            $tab['text'] = '?';
1685
            trigger_error(
1686
                'empty linktext in function ' . __FUNCTION__ . '()',
1687
                E_USER_NOTICE
1688
            );
1689
        }
1690
1691
        //Set the id for the tab, if set in the params
1692
        $tabId = (empty($tab['id']) ? null : $tab['id']);
1693
1694
        $item = [];
1695
        if (!empty($tab['link'])) {
1696
            $item = [
1697
                'content' => $tab['text'],
1698
                'url' => [
1699
                    'href' => empty($tab['link']) ? null : $tab['link'],
1700
                    'id' => $tabId,
1701
                    'class' => 'tab' . htmlentities($tab['class']),
1702
                ],
1703
            ];
1704
        } else {
1705
            $item['content'] = '<span class="tab' . htmlentities($tab['class']) . '"'
1706
                . $tabId . '>' . $tab['text'] . '</span>';
1707
        }
1708
1709
        $item['class'] = $tab['class'] == 'active' ? 'active' : '';
1710
1711
        return $template->render('list/item', $item);
1712
    }
1713
1714
    /**
1715
     * returns html-code for a tab navigation
1716
     *
1717
     * @param array  $tabs       one element per tab
1718
     * @param array  $url_params additional URL parameters
1719
     * @param string $menu_id    HTML id attribute for the menu container
1720
     * @param bool   $resizable  whether to add a "resizable" class
1721
     *
1722
     * @return string  html-code for tab-navigation
1723
     */
1724
    public static function getHtmlTabs(
1725
        array $tabs,
1726
        array $url_params,
1727
        $menu_id,
1728
        $resizable = false
1729
    ) {
1730
        $class = '';
1731
        if ($resizable) {
1732
            $class = ' class="resizable-menu"';
1733
        }
1734
1735
        $tab_navigation = '<div id="' . htmlentities($menu_id)
1736
            . 'container" class="menucontainer">'
1737
            . '<i class="scrollindicator scrollindicator--left"><a href="#" class="tab"></a></i>'
1738
            . '<div class="navigationbar"><ul id="' . htmlentities($menu_id) . '" ' . $class . '>';
1739
1740
        foreach ($tabs as $tab) {
1741
            $tab_navigation .= self::getHtmlTab($tab, $url_params);
1742
        }
1743
        $tab_navigation .= '';
1744
1745
        $tab_navigation .=
1746
              '<div class="clearfloat"></div>'
1747
            . '</ul></div>' . "\n"
1748
            . '<i class="scrollindicator scrollindicator--right"><a href="#" class="tab"></a></i>'
1749
            . '</div>' . "\n";
1750
1751
        return $tab_navigation;
1752
    }
1753
1754
    /**
1755
     * Displays a link, or a link with code to trigger POST request.
1756
     *
1757
     * POST is used in following cases:
1758
     *
1759
     * - URL is too long
1760
     * - URL components are over Suhosin limits
1761
     * - There is SQL query in the parameters
1762
     *
1763
     * @param string $url        the URL
1764
     * @param string $message    the link message
1765
     * @param mixed  $tag_params string: js confirmation; array: additional tag
1766
     *                           params (f.e. style="")
1767
     * @param string $target     target
1768
     *
1769
     * @return string  the results to be echoed or saved in an array
1770
     */
1771
    public static function linkOrButton(
1772
        $url,
1773
        $message,
1774
        $tag_params = [],
1775
        $target = ''
1776
    ) {
1777
        $url_length = strlen($url);
1778
1779
        if (! is_array($tag_params)) {
1780
            $tmp = $tag_params;
1781
            $tag_params = [];
1782
            if (! empty($tmp)) {
1783
                $tag_params['onclick'] = 'return confirmLink(this, \''
1784
                    . Sanitize::escapeJsString($tmp) . '\')';
1785
            }
1786
            unset($tmp);
1787
        }
1788
        if (! empty($target)) {
1789
            $tag_params['target'] = $target;
1790
            if ($target === '_blank' && strncmp($url, 'url.php?', 8) == 0) {
1791
                $tag_params['rel'] = 'noopener noreferrer';
1792
            }
1793
        }
1794
1795
        // Suhosin: Check that each query parameter is not above maximum
1796
        $in_suhosin_limits = true;
1797
        if ($url_length <= $GLOBALS['cfg']['LinkLengthLimit']) {
1798
            $suhosin_get_MaxValueLength = ini_get('suhosin.get.max_value_length');
1799
            if ($suhosin_get_MaxValueLength) {
1800
                $query_parts = self::splitURLQuery($url);
1801
                foreach ($query_parts as $query_pair) {
1802
                    if (strpos($query_pair, '=') === false) {
1803
                        continue;
1804
                    }
1805
1806
                    list(, $eachval) = explode('=', $query_pair);
1807
                    if (strlen($eachval) > $suhosin_get_MaxValueLength
1808
                    ) {
1809
                        $in_suhosin_limits = false;
1810
                        break;
1811
                    }
1812
                }
1813
            }
1814
        }
1815
1816
        $tag_params_strings = [];
1817
        if (($url_length > $GLOBALS['cfg']['LinkLengthLimit'])
1818
            || ! $in_suhosin_limits
1819
            || strpos($url, 'sql_query=') !== false
1820
        ) {
1821
            $parts = explode('?', $url, 2);
1822
            /*
1823
             * The data-post indicates that client should do POST
1824
             * this is handled in js/ajax.js
1825
             */
1826
            $tag_params_strings[] = 'data-post="' . (isset($parts[1]) ? $parts[1] : '') . '"';
1827
            $url = $parts[0];
1828
            if (array_key_exists('class', $tag_params)
1829
                && strpos($tag_params['class'], 'create_view') !== false
1830
            ) {
1831
                $url .= '?' . explode('&', $parts[1], 2)[0];
1832
            }
1833
        }
1834
1835
        foreach ($tag_params as $par_name => $par_value) {
1836
            $tag_params_strings[] = $par_name . '="' . htmlspecialchars($par_value) . '"';
1837
        }
1838
1839
        // no whitespace within an <a> else Safari will make it part of the link
1840
        return '<a href="' . $url . '" '
1841
            . implode(' ', $tag_params_strings) . '>'
1842
            . $message . '</a>';
1843
    } // end of the 'linkOrButton()' function
1844
1845
    /**
1846
     * Splits a URL string by parameter
1847
     *
1848
     * @param string $url the URL
1849
     *
1850
     * @return array  the parameter/value pairs, for example [0] db=sakila
1851
     */
1852
    public static function splitURLQuery($url)
1853
    {
1854
        // decode encoded url separators
1855
        $separator = Url::getArgSeparator();
1856
        // on most places separator is still hard coded ...
1857
        if ($separator !== '&') {
1858
            // ... so always replace & with $separator
1859
            $url = str_replace(htmlentities('&'), $separator, $url);
1860
            $url = str_replace('&', $separator, $url);
1861
        }
1862
1863
        $url = str_replace(htmlentities($separator), $separator, $url);
1864
        // end decode
1865
1866
        $url_parts = parse_url($url);
1867
1868
        if (! empty($url_parts['query'])) {
1869
            return explode($separator, $url_parts['query']);
1870
        }
1871
1872
        return [];
1873
    }
1874
1875
    /**
1876
     * Returns a given timespan value in a readable format.
1877
     *
1878
     * @param int $seconds the timespan
1879
     *
1880
     * @return string  the formatted value
1881
     */
1882
    public static function timespanFormat($seconds)
1883
    {
1884
        $days = floor($seconds / 86400);
1885
        if ($days > 0) {
1886
            $seconds -= $days * 86400;
1887
        }
1888
1889
        $hours = floor($seconds / 3600);
1890
        if ($days > 0 || $hours > 0) {
1891
            $seconds -= $hours * 3600;
1892
        }
1893
1894
        $minutes = floor($seconds / 60);
1895
        if ($days > 0 || $hours > 0 || $minutes > 0) {
1896
            $seconds -= $minutes * 60;
1897
        }
1898
1899
        return sprintf(
1900
            __('%s days, %s hours, %s minutes and %s seconds'),
1901
            (string)$days,
1902
            (string)$hours,
1903
            (string)$minutes,
1904
            (string)$seconds
1905
        );
1906
    }
1907
1908
    /**
1909
     * Function added to avoid path disclosures.
1910
     * Called by each script that needs parameters, it displays
1911
     * an error message and, by default, stops the execution.
1912
     *
1913
     * @param string[] $params  The names of the parameters needed by the calling
1914
     *                          script
1915
     * @param boolean  $request Check parameters in request
1916
     *
1917
     * @return void
1918
     *
1919
     * @access public
1920
     */
1921
    public static function checkParameters($params, $request = false)
1922
    {
1923
        $reported_script_name = basename($GLOBALS['PMA_PHP_SELF']);
1924
        $found_error = false;
1925
        $error_message = '';
1926
        if ($request) {
1927
            $array = $_REQUEST;
1928
        } else {
1929
            $array = $GLOBALS;
1930
        }
1931
1932
        foreach ($params as $param) {
1933
            if (! isset($array[$param])) {
1934
                $error_message .= $reported_script_name
1935
                    . ': ' . __('Missing parameter:') . ' '
1936
                    . $param
1937
                    . self::showDocu('faq', 'faqmissingparameters', true)
1938
                    . '[br]';
1939
                $found_error = true;
1940
            }
1941
        }
1942
        if ($found_error) {
1943
            Core::fatalError($error_message);
1944
        }
1945
    } // end function
1946
1947
    /**
1948
     * Function to generate unique condition for specified row.
1949
     *
1950
     * @param resource       $handle               current query result
1951
     * @param integer        $fields_cnt           number of fields
1952
     * @param array          $fields_meta          meta information about fields
1953
     * @param array          $row                  current row
1954
     * @param boolean        $force_unique         generate condition only on pk
1955
     *                                             or unique
1956
     * @param string|boolean $restrict_to_table    restrict the unique condition
1957
     *                                             to this table or false if
1958
     *                                             none
1959
     * @param array|null     $analyzed_sql_results the analyzed query
1960
     *
1961
     * @access public
1962
     *
1963
     * @return array the calculated condition and whether condition is unique
1964
     */
1965
    public static function getUniqueCondition(
1966
        $handle,
1967
        $fields_cnt,
1968
        array $fields_meta,
1969
        array $row,
1970
        $force_unique = false,
1971
        $restrict_to_table = false,
1972
        $analyzed_sql_results = null
1973
    ) {
1974
        $primary_key          = '';
1975
        $unique_key           = '';
1976
        $nonprimary_condition = '';
1977
        $preferred_condition = '';
1978
        $primary_key_array    = [];
1979
        $unique_key_array     = [];
1980
        $nonprimary_condition_array = [];
1981
        $condition_array = [];
1982
1983
        for ($i = 0; $i < $fields_cnt; ++$i) {
1984
            $con_val     = '';
1985
            $field_flags = $GLOBALS['dbi']->fieldFlags($handle, $i);
1986
            $meta        = $fields_meta[$i];
1987
1988
            // do not use a column alias in a condition
1989
            if (! isset($meta->orgname) || strlen($meta->orgname) === 0) {
1990
                $meta->orgname = $meta->name;
1991
1992
                if (!empty($analyzed_sql_results['statement']->expr)) {
1993
                    foreach ($analyzed_sql_results['statement']->expr as $expr) {
1994
                        if ((empty($expr->alias)) || (empty($expr->column))) {
1995
                            continue;
1996
                        }
1997
                        if (strcasecmp($meta->name, $expr->alias) == 0) {
1998
                            $meta->orgname = $expr->column;
1999
                            break;
2000
                        }
2001
                    }
2002
                }
2003
            }
2004
2005
            // Do not use a table alias in a condition.
2006
            // Test case is:
2007
            // select * from galerie x WHERE
2008
            //(select count(*) from galerie y where y.datum=x.datum)>1
2009
            //
2010
            // But orgtable is present only with mysqli extension so the
2011
            // fix is only for mysqli.
2012
            // Also, do not use the original table name if we are dealing with
2013
            // a view because this view might be updatable.
2014
            // (The isView() verification should not be costly in most cases
2015
            // because there is some caching in the function).
2016
            if (isset($meta->orgtable)
2017
                && ($meta->table != $meta->orgtable)
2018
                && ! $GLOBALS['dbi']->getTable($GLOBALS['db'], $meta->table)->isView()
2019
            ) {
2020
                $meta->table = $meta->orgtable;
2021
            }
2022
2023
            // If this field is not from the table which the unique clause needs
2024
            // to be restricted to.
2025
            if ($restrict_to_table && $restrict_to_table != $meta->table) {
2026
                continue;
2027
            }
2028
2029
            // to fix the bug where float fields (primary or not)
2030
            // can't be matched because of the imprecision of
2031
            // floating comparison, use CONCAT
2032
            // (also, the syntax "CONCAT(field) IS NULL"
2033
            // that we need on the next "if" will work)
2034
            if ($meta->type == 'real') {
2035
                $con_key = 'CONCAT(' . self::backquote($meta->table) . '.'
0 ignored issues
show
Bug introduced by
Are you sure self::backquote($meta->table) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2035
                $con_key = 'CONCAT(' . /** @scrutinizer ignore-type */ self::backquote($meta->table) . '.'
Loading history...
2036
                    . self::backquote($meta->orgname) . ')';
0 ignored issues
show
Bug introduced by
Are you sure self::backquote($meta->orgname) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2036
                    . /** @scrutinizer ignore-type */ self::backquote($meta->orgname) . ')';
Loading history...
2037
            } else {
2038
                $con_key = self::backquote($meta->table) . '.'
2039
                    . self::backquote($meta->orgname);
2040
            } // end if... else...
2041
            $condition = ' ' . $con_key . ' ';
2042
2043
            if (! isset($row[$i]) || is_null($row[$i])) {
2044
                $con_val = 'IS NULL';
2045
            } else {
2046
                // timestamp is numeric on some MySQL 4.1
2047
                // for real we use CONCAT above and it should compare to string
2048
                if ($meta->numeric
2049
                    && ($meta->type != 'timestamp')
2050
                    && ($meta->type != 'real')
2051
                ) {
2052
                    $con_val = '= ' . $row[$i];
2053
                } elseif ((($meta->type == 'blob') || ($meta->type == 'string'))
2054
                    && stristr($field_flags, 'BINARY')
2055
                    && ! empty($row[$i])
2056
                ) {
2057
                    // hexify only if this is a true not empty BLOB or a BINARY
2058
2059
                    // do not waste memory building a too big condition
2060
                    if (mb_strlen($row[$i]) < 1000) {
2061
                        // use a CAST if possible, to avoid problems
2062
                        // if the field contains wildcard characters % or _
2063
                        $con_val = '= CAST(0x' . bin2hex($row[$i]) . ' AS BINARY)';
2064
                    } elseif ($fields_cnt == 1) {
2065
                        // when this blob is the only field present
2066
                        // try settling with length comparison
2067
                        $condition = ' CHAR_LENGTH(' . $con_key . ') ';
2068
                        $con_val = ' = ' . mb_strlen($row[$i]);
2069
                    } else {
2070
                        // this blob won't be part of the final condition
2071
                        $con_val = null;
2072
                    }
2073
                } elseif (in_array($meta->type, self::getGISDatatypes())
2074
                    && ! empty($row[$i])
2075
                ) {
2076
                    // do not build a too big condition
2077
                    if (mb_strlen($row[$i]) < 5000) {
2078
                        $condition .= '=0x' . bin2hex($row[$i]) . ' AND';
2079
                    } else {
2080
                        $condition = '';
2081
                    }
2082
                } elseif ($meta->type == 'bit') {
2083
                    $con_val = "= b'"
2084
                        . self::printableBitValue((int) $row[$i], (int) $meta->length) . "'";
2085
                } else {
2086
                    $con_val = '= \''
2087
                        . $GLOBALS['dbi']->escapeString($row[$i]) . '\'';
2088
                }
2089
            }
2090
2091
            if ($con_val != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $con_val of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
2092
                $condition .= $con_val . ' AND';
2093
2094
                if ($meta->primary_key > 0) {
2095
                    $primary_key .= $condition;
2096
                    $primary_key_array[$con_key] = $con_val;
2097
                } elseif ($meta->unique_key > 0) {
2098
                    $unique_key  .= $condition;
2099
                    $unique_key_array[$con_key] = $con_val;
2100
                }
2101
2102
                $nonprimary_condition .= $condition;
2103
                $nonprimary_condition_array[$con_key] = $con_val;
2104
            }
2105
        } // end for
2106
2107
        // Correction University of Virginia 19991216:
2108
        // prefer primary or unique keys for condition,
2109
        // but use conjunction of all values if no primary key
2110
        $clause_is_unique = true;
2111
2112
        if ($primary_key) {
2113
            $preferred_condition = $primary_key;
2114
            $condition_array = $primary_key_array;
2115
        } elseif ($unique_key) {
2116
            $preferred_condition = $unique_key;
2117
            $condition_array = $unique_key_array;
2118
        } elseif (! $force_unique) {
2119
            $preferred_condition = $nonprimary_condition;
2120
            $condition_array = $nonprimary_condition_array;
2121
            $clause_is_unique = false;
2122
        }
2123
2124
        $where_clause = trim(preg_replace('|\s?AND$|', '', $preferred_condition));
2125
        return([$where_clause, $clause_is_unique, $condition_array]);
2126
    } // end function
2127
2128
    /**
2129
     * Generate the charset query part
2130
     *
2131
     * @param string           $collation Collation
2132
     * @param boolean optional $override  force 'CHARACTER SET' keyword
0 ignored issues
show
Bug introduced by
The type PhpMyAdmin\optional was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2133
     *
2134
     * @return string
2135
     */
2136
    public static function getCharsetQueryPart($collation, $override = false)
2137
    {
2138
        list($charset) = explode('_', $collation);
2139
        $keyword = ' CHARSET=';
2140
2141
        if ($override) {
2142
            $keyword = ' CHARACTER SET ';
2143
        }
2144
        return $keyword . $charset
2145
            . ($charset == $collation ? '' : ' COLLATE ' . $collation);
2146
    }
2147
2148
    /**
2149
     * Generate a button or image tag
2150
     *
2151
     * @param string $button_name  name of button element
2152
     * @param string $button_class class of button or image element
2153
     * @param string $text         text to display
2154
     * @param string $image        image to display
2155
     * @param string $value        value
2156
     *
2157
     * @return string              html content
2158
     *
2159
     * @access  public
2160
     */
2161
    public static function getButtonOrImage(
2162
        $button_name,
2163
        $button_class,
2164
        $text,
2165
        $image,
2166
        $value = ''
2167
    ) {
2168
        if ($value == '') {
2169
            $value = $text;
2170
        }
2171
        if ($GLOBALS['cfg']['ActionLinksMode'] == 'text') {
2172
            return ' <input type="submit" name="' . $button_name . '"'
2173
                . ' value="' . htmlspecialchars($value) . '"'
2174
                . ' title="' . htmlspecialchars($text) . '" />' . "\n";
2175
        }
2176
        return '<button class="' . $button_class . '" type="submit"'
2177
            . ' name="' . $button_name . '" value="' . htmlspecialchars($value)
2178
            . '" title="' . htmlspecialchars($text) . '">' . "\n"
2179
            . self::getIcon($image, $text)
2180
            . '</button>' . "\n";
2181
    } // end function
2182
2183
    /**
2184
     * Generate a pagination selector for browsing resultsets
2185
     *
2186
     * @param string $name        The name for the request parameter
2187
     * @param int    $rows        Number of rows in the pagination set
2188
     * @param int    $pageNow     current page number
2189
     * @param int    $nbTotalPage number of total pages
2190
     * @param int    $showAll     If the number of pages is lower than this
2191
     *                            variable, no pages will be omitted in pagination
2192
     * @param int    $sliceStart  How many rows at the beginning should always
2193
     *                            be shown?
2194
     * @param int    $sliceEnd    How many rows at the end should always be shown?
2195
     * @param int    $percent     Percentage of calculation page offsets to hop to a
2196
     *                            next page
2197
     * @param int    $range       Near the current page, how many pages should
2198
     *                            be considered "nearby" and displayed as well?
2199
     * @param string $prompt      The prompt to display (sometimes empty)
2200
     *
2201
     * @return string
2202
     *
2203
     * @access  public
2204
     */
2205
    public static function pageselector(
2206
        $name,
2207
        $rows,
2208
        $pageNow = 1,
2209
        $nbTotalPage = 1,
2210
        $showAll = 200,
2211
        $sliceStart = 5,
2212
        $sliceEnd = 5,
2213
        $percent = 20,
2214
        $range = 10,
2215
        $prompt = ''
2216
    ) {
2217
        $increment = floor($nbTotalPage / $percent);
2218
        $pageNowMinusRange = ($pageNow - $range);
2219
        $pageNowPlusRange = ($pageNow + $range);
2220
2221
        $gotopage = $prompt . ' <select class="pageselector ajax"';
2222
2223
        $gotopage .= ' name="' . $name . '" >';
2224
        if ($nbTotalPage < $showAll) {
2225
            $pages = range(1, $nbTotalPage);
2226
        } else {
2227
            $pages = [];
2228
2229
            // Always show first X pages
2230
            for ($i = 1; $i <= $sliceStart; $i++) {
2231
                $pages[] = $i;
2232
            }
2233
2234
            // Always show last X pages
2235
            for ($i = $nbTotalPage - $sliceEnd; $i <= $nbTotalPage; $i++) {
2236
                $pages[] = $i;
2237
            }
2238
2239
            // Based on the number of results we add the specified
2240
            // $percent percentage to each page number,
2241
            // so that we have a representing page number every now and then to
2242
            // immediately jump to specific pages.
2243
            // As soon as we get near our currently chosen page ($pageNow -
2244
            // $range), every page number will be shown.
2245
            $i = $sliceStart;
2246
            $x = $nbTotalPage - $sliceEnd;
2247
            $met_boundary = false;
2248
2249
            while ($i <= $x) {
2250
                if ($i >= $pageNowMinusRange && $i <= $pageNowPlusRange) {
2251
                    // If our pageselector comes near the current page, we use 1
2252
                    // counter increments
2253
                    $i++;
2254
                    $met_boundary = true;
2255
                } else {
2256
                    // We add the percentage increment to our current page to
2257
                    // hop to the next one in range
2258
                    $i += $increment;
2259
2260
                    // Make sure that we do not cross our boundaries.
2261
                    if ($i > $pageNowMinusRange && ! $met_boundary) {
2262
                        $i = $pageNowMinusRange;
2263
                    }
2264
                }
2265
2266
                if ($i > 0 && $i <= $x) {
2267
                    $pages[] = $i;
2268
                }
2269
            }
2270
2271
            /*
2272
            Add page numbers with "geometrically increasing" distances.
2273
2274
            This helps me a lot when navigating through giant tables.
2275
2276
            Test case: table with 2.28 million sets, 76190 pages. Page of interest
2277
            is between 72376 and 76190.
2278
            Selecting page 72376.
2279
            Now, old version enumerated only +/- 10 pages around 72376 and the
2280
            percentage increment produced steps of about 3000.
2281
2282
            The following code adds page numbers +/- 2,4,8,16,32,64,128,256 etc.
2283
            around the current page.
2284
            */
2285
            $i = $pageNow;
2286
            $dist = 1;
2287
            while ($i < $x) {
2288
                $dist = 2 * $dist;
2289
                $i = $pageNow + $dist;
2290
                if ($i > 0 && $i <= $x) {
2291
                    $pages[] = $i;
2292
                }
2293
            }
2294
2295
            $i = $pageNow;
2296
            $dist = 1;
2297
            while ($i > 0) {
2298
                $dist = 2 * $dist;
2299
                $i = $pageNow - $dist;
2300
                if ($i > 0 && $i <= $x) {
2301
                    $pages[] = $i;
2302
                }
2303
            }
2304
2305
            // Since because of ellipsing of the current page some numbers may be
2306
            // double, we unify our array:
2307
            sort($pages);
2308
            $pages = array_unique($pages);
2309
        }
2310
2311
        foreach ($pages as $i) {
2312
            if ($i == $pageNow) {
2313
                $selected = 'selected="selected" style="font-weight: bold"';
2314
            } else {
2315
                $selected = '';
2316
            }
2317
            $gotopage .= '                <option ' . $selected
2318
                . ' value="' . (($i - 1) * $rows) . '">' . $i . '</option>' . "\n";
2319
        }
2320
2321
        $gotopage .= ' </select>';
2322
2323
        return $gotopage;
2324
    } // end function
2325
2326
    /**
2327
     * Prepare navigation for a list
2328
     *
2329
     * @param int      $count       number of elements in the list
2330
     * @param int      $pos         current position in the list
2331
     * @param array    $_url_params url parameters
2332
     * @param string   $script      script name for form target
2333
     * @param string   $frame       target frame
2334
     * @param int      $max_count   maximum number of elements to display from
2335
     *                              the list
2336
     * @param string   $name        the name for the request parameter
2337
     * @param string[] $classes     additional classes for the container
2338
     *
2339
     * @return string $list_navigator_html the  html content
2340
     *
2341
     * @access  public
2342
     *
2343
     * @todo    use $pos from $_url_params
2344
     */
2345
    public static function getListNavigator(
2346
        $count,
2347
        $pos,
2348
        array $_url_params,
2349
        $script,
2350
        $frame,
2351
        $max_count,
2352
        $name = 'pos',
2353
        $classes = []
2354
    ) {
2355
2356
        // This is often coming from $cfg['MaxTableList'] and
2357
        // people sometimes set it to empty string
2358
        $max_count = intval($max_count);
2359
        if ($max_count <= 0) {
2360
            $max_count = 250;
2361
        }
2362
2363
        $class = $frame == 'frame_navigation' ? ' class="ajax"' : '';
2364
2365
        $list_navigator_html = '';
2366
2367
        if ($max_count < $count) {
2368
            $classes[] = 'pageselector';
2369
            $list_navigator_html .= '<div class="' . implode(' ', $classes) . '">';
2370
2371
            if ($frame != 'frame_navigation') {
2372
                $list_navigator_html .= __('Page number:');
2373
            }
2374
2375
            // Move to the beginning or to the previous page
2376
            if ($pos > 0) {
2377
                $caption1 = '';
2378
                $caption2 = '';
2379
                if (self::showIcons('TableNavigationLinksMode')) {
2380
                    $caption1 .= '&lt;&lt; ';
2381
                    $caption2 .= '&lt; ';
2382
                }
2383
                if (self::showText('TableNavigationLinksMode')) {
2384
                    $caption1 .= _pgettext('First page', 'Begin');
2385
                    $caption2 .= _pgettext('Previous page', 'Previous');
2386
                }
2387
                $title1 = ' title="' . _pgettext('First page', 'Begin') . '"';
2388
                $title2 = ' title="' . _pgettext('Previous page', 'Previous') . '"';
2389
2390
                $_url_params[$name] = 0;
2391
                $list_navigator_html .= '<a' . $class . $title1 . ' href="' . $script
2392
                    . Url::getCommon($_url_params) . '">' . $caption1
2393
                    . '</a>';
2394
2395
                $_url_params[$name] = $pos - $max_count;
2396
                $list_navigator_html .= ' <a' . $class . $title2
2397
                    . ' href="' . $script . Url::getCommon($_url_params) . '">'
2398
                    . $caption2 . '</a>';
2399
            }
2400
2401
            $list_navigator_html .= '<form action="' . basename($script)
2402
                . '" method="post">';
2403
2404
            $list_navigator_html .= Url::getHiddenInputs($_url_params);
2405
            $list_navigator_html .= self::pageselector(
2406
                $name,
2407
                $max_count,
2408
                floor(($pos + 1) / $max_count) + 1,
0 ignored issues
show
Bug introduced by
floor($pos + 1 / $max_count) + 1 of type double is incompatible with the type integer expected by parameter $pageNow of PhpMyAdmin\Util::pageselector(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2408
                /** @scrutinizer ignore-type */ floor(($pos + 1) / $max_count) + 1,
Loading history...
2409
                ceil($count / $max_count)
0 ignored issues
show
Bug introduced by
ceil($count / $max_count) of type double is incompatible with the type integer expected by parameter $nbTotalPage of PhpMyAdmin\Util::pageselector(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2409
                /** @scrutinizer ignore-type */ ceil($count / $max_count)
Loading history...
2410
            );
2411
            $list_navigator_html .= '</form>';
2412
2413
            if ($pos + $max_count < $count) {
2414
                $caption3 = '';
2415
                $caption4 = '';
2416
                if (self::showText('TableNavigationLinksMode')) {
2417
                    $caption3 .= _pgettext('Next page', 'Next');
2418
                    $caption4 .= _pgettext('Last page', 'End');
2419
                }
2420
                if (self::showIcons('TableNavigationLinksMode')) {
2421
                    $caption3 .= ' &gt;';
2422
                    $caption4 .= ' &gt;&gt;';
2423
                    if (! self::showText('TableNavigationLinksMode')) {
2424
                    }
2425
                }
2426
                $title3 = ' title="' . _pgettext('Next page', 'Next') . '"';
2427
                $title4 = ' title="' . _pgettext('Last page', 'End') . '"';
2428
2429
                $_url_params[$name] = $pos + $max_count;
2430
                $list_navigator_html .= '<a' . $class . $title3 . ' href="' . $script
2431
                    . Url::getCommon($_url_params) . '" >' . $caption3
2432
                    . '</a>';
2433
2434
                $_url_params[$name] = floor($count / $max_count) * $max_count;
2435
                if ($_url_params[$name] == $count) {
2436
                    $_url_params[$name] = $count - $max_count;
2437
                }
2438
2439
                $list_navigator_html .= ' <a' . $class . $title4
2440
                    . ' href="' . $script . Url::getCommon($_url_params) . '" >'
2441
                    . $caption4 . '</a>';
2442
            }
2443
            $list_navigator_html .= '</div>' . "\n";
2444
        }
2445
2446
        return $list_navigator_html;
2447
    }
2448
2449
    /**
2450
     * replaces %u in given path with current user name
2451
     *
2452
     * example:
2453
     * <code>
2454
     * $user_dir = userDir('/var/pma_tmp/%u/'); // '/var/pma_tmp/root/'
2455
     *
2456
     * </code>
2457
     *
2458
     * @param string $dir with wildcard for user
2459
     *
2460
     * @return string  per user directory
2461
     */
2462
    public static function userDir($dir)
2463
    {
2464
        // add trailing slash
2465
        if (mb_substr($dir, -1) != '/') {
2466
            $dir .= '/';
2467
        }
2468
2469
        return str_replace('%u', Core::securePath($GLOBALS['cfg']['Server']['user']), $dir);
2470
    }
2471
2472
    /**
2473
     * returns html code for db link to default db page
2474
     *
2475
     * @param string $database database
2476
     *
2477
     * @return string  html link to default db page
2478
     */
2479
    public static function getDbLink($database = '')
2480
    {
2481
        if (strlen((string) $database) === 0) {
2482
            if (strlen((string) $GLOBALS['db']) === 0) {
2483
                return '';
2484
            }
2485
            $database = $GLOBALS['db'];
2486
        } else {
2487
            $database = self::unescapeMysqlWildcards($database);
2488
        }
2489
2490
        return '<a href="'
2491
            . self::getScriptNameForOption(
2492
                $GLOBALS['cfg']['DefaultTabDatabase'],
2493
                'database'
2494
            )
2495
            . Url::getCommon(['db' => $database]) . '" title="'
2496
            . htmlspecialchars(
2497
                sprintf(
2498
                    __('Jump to database “%s”.'),
2499
                    $database
2500
                )
2501
            )
2502
            . '">' . htmlspecialchars($database) . '</a>';
2503
    }
2504
2505
    /**
2506
     * Prepare a lightbulb hint explaining a known external bug
2507
     * that affects a functionality
2508
     *
2509
     * @param string $functionality   localized message explaining the func.
2510
     * @param string $component       'mysql' (eventually, 'php')
2511
     * @param string $minimum_version of this component
2512
     * @param string $bugref          bug reference for this component
2513
     *
2514
     * @return String
2515
     */
2516
    public static function getExternalBug(
2517
        $functionality,
2518
        $component,
2519
        $minimum_version,
2520
        $bugref
2521
    ) {
2522
        $ext_but_html = '';
2523
        if (($component == 'mysql') && ($GLOBALS['dbi']->getVersion() < $minimum_version)) {
2524
            $ext_but_html .= self::showHint(
2525
                sprintf(
2526
                    __('The %s functionality is affected by a known bug, see %s'),
2527
                    $functionality,
2528
                    Core::linkURL('https://bugs.mysql.com/') . $bugref
2529
                )
2530
            );
2531
        }
2532
        return $ext_but_html;
2533
    }
2534
2535
    /**
2536
     * Generates a set of radio HTML fields
2537
     *
2538
     * @param string  $html_field_name the radio HTML field
2539
     * @param array   $choices         the choices values and labels
2540
     * @param string  $checked_choice  the choice to check by default
2541
     * @param boolean $line_break      whether to add HTML line break after a choice
2542
     * @param boolean $escape_label    whether to use htmlspecialchars() on label
2543
     * @param string  $class           enclose each choice with a div of this class
2544
     * @param string  $id_prefix       prefix for the id attribute, name will be
2545
     *                                 used if this is not supplied
2546
     *
2547
     * @return string                  set of html radio fiels
2548
     */
2549
    public static function getRadioFields(
2550
        $html_field_name,
2551
        array $choices,
2552
        $checked_choice = '',
2553
        $line_break = true,
2554
        $escape_label = true,
2555
        $class = '',
2556
        $id_prefix = ''
2557
    ) {
2558
        $template = new Template();
2559
        $radio_html = '';
2560
2561
        foreach ($choices as $choice_value => $choice_label) {
2562
            if (! $id_prefix) {
2563
                $id_prefix = $html_field_name;
2564
            }
2565
            $html_field_id = $id_prefix . '_' . $choice_value;
2566
2567
            if ($choice_value == $checked_choice) {
2568
                $checked = 1;
2569
            } else {
2570
                $checked = 0;
2571
            }
2572
            $radio_html .= $template->render('radio_fields', [
2573
                'class' => $class,
2574
                'html_field_name' => $html_field_name,
2575
                'html_field_id' => $html_field_id,
2576
                'choice_value' => $choice_value,
2577
                'is_line_break' => $line_break,
2578
                'choice_label' => $choice_label,
2579
                'escape_label' => $escape_label,
2580
                'checked' => $checked,
2581
            ]);
2582
        }
2583
2584
        return $radio_html;
2585
    }
2586
2587
    /**
2588
     * Generates and returns an HTML dropdown
2589
     *
2590
     * @param string $select_name   name for the select element
2591
     * @param array  $choices       choices values
2592
     * @param string $active_choice the choice to select by default
2593
     * @param string $id            id of the select element; can be different in
2594
     *                              case the dropdown is present more than once
2595
     *                              on the page
2596
     * @param string $class         class for the select element
2597
     * @param string $placeholder   Placeholder for dropdown if nothing else
2598
     *                              is selected
2599
     *
2600
     * @return string               html content
2601
     *
2602
     * @todo    support titles
2603
     */
2604
    public static function getDropdown(
2605
        $select_name,
2606
        array $choices,
2607
        $active_choice,
2608
        $id,
2609
        $class = '',
2610
        $placeholder = null
2611
    ) {
2612
        $template = new Template();
2613
        $resultOptions = [];
2614
        $selected = false;
2615
2616
        foreach ($choices as $one_choice_value => $one_choice_label) {
2617
            $resultOptions[$one_choice_value]['value'] = $one_choice_value;
2618
            $resultOptions[$one_choice_value]['selected'] = false;
2619
2620
            if ($one_choice_value == $active_choice) {
2621
                $resultOptions[$one_choice_value]['selected'] = true;
2622
                $selected = true;
2623
            }
2624
            $resultOptions[$one_choice_value]['label'] = $one_choice_label;
2625
        }
2626
        return $template->render('dropdown', [
2627
            'select_name' => $select_name,
2628
            'id' => $id,
2629
            'class' => $class,
2630
            'placeholder' => $placeholder,
2631
            'selected' => $selected,
2632
            'result_options' => $resultOptions,
2633
        ]);
2634
    }
2635
2636
    /**
2637
     * Generates a slider effect (jQjuery)
2638
     * Takes care of generating the initial <div> and the link
2639
     * controlling the slider; you have to generate the </div> yourself
2640
     * after the sliding section.
2641
     *
2642
     * @param string      $id              the id of the <div> on which to apply the effect
2643
     * @param string      $message         the message to show as a link
2644
     * @param string|null $overrideDefault override InitialSlidersState config
2645
     *
2646
     * @return string         html div element
2647
     *
2648
     */
2649
    public static function getDivForSliderEffect($id = '', $message = '', $overrideDefault = null)
2650
    {
2651
        $template = new Template();
2652
        return $template->render('div_for_slider_effect', [
2653
            'id' => $id,
2654
            'initial_sliders_state' => ($overrideDefault != null) ? $overrideDefault : $GLOBALS['cfg']['InitialSlidersState'],
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $overrideDefault of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
2655
            'message' => $message,
2656
        ]);
2657
    }
2658
2659
    /**
2660
     * Creates an AJAX sliding toggle button
2661
     * (or and equivalent form when AJAX is disabled)
2662
     *
2663
     * @param string $action      The URL for the request to be executed
2664
     * @param string $select_name The name for the dropdown box
2665
     * @param array  $options     An array of options (see PhpMyAdmin\Rte\Footer)
2666
     * @param string $callback    A JS snippet to execute when the request is
2667
     *                            successfully processed
2668
     *
2669
     * @return string   HTML code for the toggle button
2670
     */
2671
    public static function toggleButton($action, $select_name, array $options, $callback)
2672
    {
2673
        $template = new Template();
2674
        // Do the logic first
2675
        $link = "$action&amp;" . urlencode($select_name) . "=";
2676
        $link_on = $link . urlencode($options[1]['value']);
2677
        $link_off = $link . urlencode($options[0]['value']);
2678
2679
        if ($options[1]['selected'] == true) {
2680
            $state = 'on';
2681
        } elseif ($options[0]['selected'] == true) {
2682
            $state = 'off';
2683
        } else {
2684
            $state = 'on';
2685
        }
2686
2687
        return $template->render('toggle_button', [
2688
            'pma_theme_image' => $GLOBALS['pmaThemeImage'],
2689
            'text_dir' => $GLOBALS['text_dir'],
2690
            'link_on' => $link_on,
2691
            'link_off' => $link_off,
2692
            'toggle_on' => $options[1]['label'],
2693
            'toggle_off' => $options[0]['label'],
2694
            'callback' => $callback,
2695
            'state' => $state
2696
        ]);
2697
    }
2698
2699
    /**
2700
     * Clears cache content which needs to be refreshed on user change.
2701
     *
2702
     * @return void
2703
     */
2704
    public static function clearUserCache()
2705
    {
2706
        self::cacheUnset('is_superuser');
2707
        self::cacheUnset('is_createuser');
2708
        self::cacheUnset('is_grantuser');
2709
    }
2710
2711
    /**
2712
     * Calculates session cache key
2713
     *
2714
     * @return string
2715
     */
2716
    public static function cacheKey()
2717
    {
2718
        if (isset($GLOBALS['cfg']['Server']['user'])) {
2719
            return 'server_' . $GLOBALS['server'] . '_' . $GLOBALS['cfg']['Server']['user'];
2720
        }
2721
2722
        return 'server_' . $GLOBALS['server'];
2723
    }
2724
2725
    /**
2726
     * Verifies if something is cached in the session
2727
     *
2728
     * @param string $var variable name
2729
     *
2730
     * @return boolean
2731
     */
2732
    public static function cacheExists($var)
2733
    {
2734
        return isset($_SESSION['cache'][self::cacheKey()][$var]);
2735
    }
2736
2737
    /**
2738
     * Gets cached information from the session
2739
     *
2740
     * @param string   $var      variable name
2741
     * @param \Closure $callback callback to fetch the value
2742
     *
2743
     * @return mixed
2744
     */
2745
    public static function cacheGet($var, $callback = null)
2746
    {
2747
        if (self::cacheExists($var)) {
2748
            return $_SESSION['cache'][self::cacheKey()][$var];
2749
        }
2750
2751
        if ($callback) {
2752
            $val = $callback();
2753
            self::cacheSet($var, $val);
2754
            return $val;
2755
        }
2756
        return null;
2757
    }
2758
2759
    /**
2760
     * Caches information in the session
2761
     *
2762
     * @param string $var variable name
2763
     * @param mixed  $val value
2764
     *
2765
     * @return mixed
2766
     */
2767
    public static function cacheSet($var, $val = null)
2768
    {
2769
        $_SESSION['cache'][self::cacheKey()][$var] = $val;
2770
    }
2771
2772
    /**
2773
     * Removes cached information from the session
2774
     *
2775
     * @param string $var variable name
2776
     *
2777
     * @return void
2778
     */
2779
    public static function cacheUnset($var)
2780
    {
2781
        unset($_SESSION['cache'][self::cacheKey()][$var]);
2782
    }
2783
2784
    /**
2785
     * Converts a bit value to printable format;
2786
     * in MySQL a BIT field can be from 1 to 64 bits so we need this
2787
     * function because in PHP, decbin() supports only 32 bits
2788
     * on 32-bit servers
2789
     *
2790
     * @param int $value  coming from a BIT field
2791
     * @param int $length length
2792
     *
2793
     * @return string the printable value
2794
     */
2795
    public static function printableBitValue(int $value, int $length): string
2796
    {
2797
        // if running on a 64-bit server or the length is safe for decbin()
2798
        if (PHP_INT_SIZE == 8 || $length < 33) {
2799
            $printable = decbin($value);
2800
        } else {
2801
            // FIXME: does not work for the leftmost bit of a 64-bit value
2802
            $i = 0;
2803
            $printable = '';
2804
            while ($value >= pow(2, $i)) {
2805
                ++$i;
2806
            }
2807
            if ($i != 0) {
2808
                --$i;
2809
            }
2810
2811
            while ($i >= 0) {
2812
                if ($value - pow(2, $i) < 0) {
2813
                    $printable = '0' . $printable;
2814
                } else {
2815
                    $printable = '1' . $printable;
2816
                    $value = $value - pow(2, $i);
2817
                }
2818
                --$i;
2819
            }
2820
            $printable = strrev($printable);
2821
        }
2822
        $printable = str_pad($printable, $length, '0', STR_PAD_LEFT);
2823
        return $printable;
2824
    }
2825
2826
    /**
2827
     * Converts a BIT type default value
2828
     * for example, b'010' becomes 010
2829
     *
2830
     * @param string $bit_default_value value
2831
     *
2832
     * @return string the converted value
2833
     */
2834
    public static function convertBitDefaultValue($bit_default_value)
2835
    {
2836
        return rtrim(ltrim($bit_default_value, "b'"), "'");
2837
    }
2838
2839
    /**
2840
     * Extracts the various parts from a column spec
2841
     *
2842
     * @param string $columnspec Column specification
2843
     *
2844
     * @return array associative array containing type, spec_in_brackets
2845
     *          and possibly enum_set_values (another array)
2846
     */
2847
    public static function extractColumnSpec($columnspec)
2848
    {
2849
        $first_bracket_pos = mb_strpos($columnspec, '(');
2850
        if ($first_bracket_pos) {
2851
            $spec_in_brackets = chop(
2852
                mb_substr(
2853
                    $columnspec,
2854
                    $first_bracket_pos + 1,
2855
                    mb_strrpos($columnspec, ')') - $first_bracket_pos - 1
2856
                )
2857
            );
2858
            // convert to lowercase just to be sure
2859
            $type = mb_strtolower(
2860
                chop(mb_substr($columnspec, 0, $first_bracket_pos))
2861
            );
2862
        } else {
2863
            // Split trailing attributes such as unsigned,
2864
            // binary, zerofill and get data type name
2865
            $type_parts = explode(' ', $columnspec);
2866
            $type = mb_strtolower($type_parts[0]);
2867
            $spec_in_brackets = '';
2868
        }
2869
2870
        if ('enum' == $type || 'set' == $type) {
2871
            // Define our working vars
2872
            $enum_set_values = self::parseEnumSetValues($columnspec, false);
2873
            $printtype = $type
2874
                . '(' . str_replace("','", "', '", $spec_in_brackets) . ')';
2875
            $binary = false;
2876
            $unsigned = false;
2877
            $zerofill = false;
2878
        } else {
2879
            $enum_set_values = [];
2880
2881
            /* Create printable type name */
2882
            $printtype = mb_strtolower($columnspec);
2883
2884
            // Strip the "BINARY" attribute, except if we find "BINARY(" because
2885
            // this would be a BINARY or VARBINARY column type;
2886
            // by the way, a BLOB should not show the BINARY attribute
2887
            // because this is not accepted in MySQL syntax.
2888
            if (preg_match('@binary@', $printtype)
2889
                && ! preg_match('@binary[\(]@', $printtype)
2890
            ) {
2891
                $printtype = preg_replace('@binary@', '', $printtype);
2892
                $binary = true;
2893
            } else {
2894
                $binary = false;
2895
            }
2896
2897
            $printtype = preg_replace(
2898
                '@zerofill@',
2899
                '',
2900
                $printtype,
2901
                -1,
2902
                $zerofill_cnt
2903
            );
2904
            $zerofill = ($zerofill_cnt > 0);
2905
            $printtype = preg_replace(
2906
                '@unsigned@',
2907
                '',
2908
                $printtype,
2909
                -1,
2910
                $unsigned_cnt
2911
            );
2912
            $unsigned = ($unsigned_cnt > 0);
2913
            $printtype = trim($printtype);
2914
        }
2915
2916
        $attribute     = ' ';
2917
        if ($binary) {
2918
            $attribute = 'BINARY';
2919
        }
2920
        if ($unsigned) {
2921
            $attribute = 'UNSIGNED';
2922
        }
2923
        if ($zerofill) {
2924
            $attribute = 'UNSIGNED ZEROFILL';
2925
        }
2926
2927
        $can_contain_collation = false;
2928
        if (! $binary
2929
            && preg_match(
2930
                "@^(char|varchar|text|tinytext|mediumtext|longtext|set|enum)@",
2931
                $type
2932
            )
2933
        ) {
2934
            $can_contain_collation = true;
2935
        }
2936
2937
        // for the case ENUM('&#8211;','&ldquo;')
2938
        $displayed_type = htmlspecialchars($printtype);
2939
        if (mb_strlen($printtype) > $GLOBALS['cfg']['LimitChars']) {
2940
            $displayed_type  = '<abbr title="' . htmlspecialchars($printtype) . '">';
2941
            $displayed_type .= htmlspecialchars(
2942
                mb_substr(
2943
                    $printtype,
2944
                    0,
2945
                    $GLOBALS['cfg']['LimitChars']
2946
                ) . '...'
2947
            );
2948
            $displayed_type .= '</abbr>';
2949
        }
2950
2951
        return [
2952
            'type' => $type,
2953
            'spec_in_brackets' => $spec_in_brackets,
2954
            'enum_set_values'  => $enum_set_values,
2955
            'print_type' => $printtype,
2956
            'binary' => $binary,
2957
            'unsigned' => $unsigned,
2958
            'zerofill' => $zerofill,
2959
            'attribute' => $attribute,
2960
            'can_contain_collation' => $can_contain_collation,
2961
            'displayed_type' => $displayed_type
2962
        ];
2963
    }
2964
2965
    /**
2966
     * Verifies if this table's engine supports foreign keys
2967
     *
2968
     * @param string $engine engine
2969
     *
2970
     * @return boolean
2971
     */
2972
    public static function isForeignKeySupported($engine)
2973
    {
2974
        $engine = strtoupper((string)$engine);
2975
        if (($engine == 'INNODB') || ($engine == 'PBXT')) {
2976
            return true;
2977
        } elseif ($engine == 'NDBCLUSTER' || $engine == 'NDB') {
2978
            $ndbver = strtolower(
2979
                $GLOBALS['dbi']->fetchValue("SELECT @@ndb_version_string")
2980
            );
2981
            if (substr($ndbver, 0, 4) == 'ndb-') {
2982
                $ndbver = substr($ndbver, 4);
2983
            }
2984
            return version_compare($ndbver, '7.3', '>=');
2985
        }
2986
2987
        return false;
2988
    }
2989
2990
    /**
2991
     * Is Foreign key check enabled?
2992
     *
2993
     * @return bool
2994
     */
2995
    public static function isForeignKeyCheck()
2996
    {
2997
        if ($GLOBALS['cfg']['DefaultForeignKeyChecks'] === 'enable') {
2998
            return true;
2999
        } elseif ($GLOBALS['cfg']['DefaultForeignKeyChecks'] === 'disable') {
3000
            return false;
3001
        }
3002
        return ($GLOBALS['dbi']->getVariable('FOREIGN_KEY_CHECKS') == 'ON');
3003
    }
3004
3005
    /**
3006
    * Get HTML for Foreign key check checkbox
3007
    *
3008
    * @return string HTML for checkbox
3009
    */
3010
    public static function getFKCheckbox()
3011
    {
3012
        $template = new Template();
3013
        return $template->render('fk_checkbox', [
3014
            'checked' => self::isForeignKeyCheck(),
3015
        ]);
3016
    }
3017
3018
    /**
3019
     * Handle foreign key check request
3020
     *
3021
     * @return bool Default foreign key checks value
3022
     */
3023
    public static function handleDisableFKCheckInit()
3024
    {
3025
        $default_fk_check_value
3026
            = $GLOBALS['dbi']->getVariable('FOREIGN_KEY_CHECKS') == 'ON';
3027
        if (isset($_REQUEST['fk_checks'])) {
3028
            if (empty($_REQUEST['fk_checks'])) {
3029
                // Disable foreign key checks
3030
                $GLOBALS['dbi']->setVariable('FOREIGN_KEY_CHECKS', 'OFF');
3031
            } else {
3032
                // Enable foreign key checks
3033
                $GLOBALS['dbi']->setVariable('FOREIGN_KEY_CHECKS', 'ON');
3034
            }
3035
        } // else do nothing, go with default
3036
        return $default_fk_check_value;
3037
    }
3038
3039
    /**
3040
     * Cleanup changes done for foreign key check
3041
     *
3042
     * @param bool $default_fk_check_value original value for 'FOREIGN_KEY_CHECKS'
3043
     *
3044
     * @return void
3045
     */
3046
    public static function handleDisableFKCheckCleanup($default_fk_check_value)
3047
    {
3048
        $GLOBALS['dbi']->setVariable(
3049
            'FOREIGN_KEY_CHECKS',
3050
            $default_fk_check_value ? 'ON' : 'OFF'
3051
        );
3052
    }
3053
3054
    /**
3055
     * Converts GIS data to Well Known Text format
3056
     *
3057
     * @param string $data        GIS data
3058
     * @param bool   $includeSRID Add SRID to the WKT
3059
     *
3060
     * @return string GIS data in Well Know Text format
3061
     */
3062
    public static function asWKT($data, $includeSRID = false)
3063
    {
3064
        // Convert to WKT format
3065
        $hex = bin2hex($data);
3066
        $wktsql     = "SELECT ASTEXT(x'" . $hex . "')";
3067
        if ($includeSRID) {
3068
            $wktsql .= ", SRID(x'" . $hex . "')";
3069
        }
3070
3071
        $wktresult  = $GLOBALS['dbi']->tryQuery(
3072
            $wktsql
3073
        );
3074
        $wktarr     = $GLOBALS['dbi']->fetchRow($wktresult, 0);
3075
        $wktval     = $wktarr[0];
3076
3077
        if ($includeSRID) {
3078
            $srid = $wktarr[1];
3079
            $wktval = "'" . $wktval . "'," . $srid;
3080
        }
3081
        @$GLOBALS['dbi']->freeResult($wktresult);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for freeResult(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

3081
        /** @scrutinizer ignore-unhandled */ @$GLOBALS['dbi']->freeResult($wktresult);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
3082
3083
        return $wktval;
3084
    }
3085
3086
    /**
3087
     * If the string starts with a \r\n pair (0x0d0a) add an extra \n
3088
     *
3089
     * @param string $string string
3090
     *
3091
     * @return string with the chars replaced
3092
     */
3093
    public static function duplicateFirstNewline($string)
3094
    {
3095
        $first_occurence = mb_strpos($string, "\r\n");
3096
        if ($first_occurence === 0) {
3097
            $string = "\n" . $string;
3098
        }
3099
        return $string;
3100
    }
3101
3102
    /**
3103
     * Get the action word corresponding to a script name
3104
     * in order to display it as a title in navigation panel
3105
     *
3106
     * @param string $target a valid value for $cfg['NavigationTreeDefaultTabTable'],
3107
     *                       $cfg['NavigationTreeDefaultTabTable2'],
3108
     *                       $cfg['DefaultTabTable'] or $cfg['DefaultTabDatabase']
3109
     *
3110
     * @return string Title for the $cfg value
3111
     */
3112
    public static function getTitleForTarget($target)
3113
    {
3114
        $mapping = [
3115
            'structure' =>  __('Structure'),
3116
            'sql' => __('SQL'),
3117
            'search' => __('Search'),
3118
            'insert' => __('Insert'),
3119
            'browse' => __('Browse'),
3120
            'operations' => __('Operations'),
3121
3122
            // For backward compatiblity
3123
3124
            // Values for $cfg['DefaultTabTable']
3125
            'tbl_structure.php' =>  __('Structure'),
3126
            'tbl_sql.php' => __('SQL'),
3127
            'tbl_select.php' => __('Search'),
3128
            'tbl_change.php' => __('Insert'),
3129
            'sql.php' => __('Browse'),
3130
            // Values for $cfg['DefaultTabDatabase']
3131
            'db_structure.php' => __('Structure'),
3132
            'db_sql.php' => __('SQL'),
3133
            'db_search.php' => __('Search'),
3134
            'db_operations.php' => __('Operations'),
3135
        ];
3136
        return isset($mapping[$target]) ? $mapping[$target] : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return IssetNode ? $mapping[$target] : false could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
3137
    }
3138
3139
    /**
3140
     * Get the script name corresponding to a plain English config word
3141
     * in order to append in links on navigation and main panel
3142
     *
3143
     * @param string $target   a valid value for
3144
     *                         $cfg['NavigationTreeDefaultTabTable'],
3145
     *                         $cfg['NavigationTreeDefaultTabTable2'],
3146
     *                         $cfg['DefaultTabTable'], $cfg['DefaultTabDatabase'] or
3147
     *                         $cfg['DefaultTabServer']
3148
     * @param string $location one out of 'server', 'table', 'database'
3149
     *
3150
     * @return string script name corresponding to the config word
3151
     */
3152
    public static function getScriptNameForOption($target, $location)
3153
    {
3154
        if ($location == 'server') {
3155
            // Values for $cfg['DefaultTabServer']
3156
            switch ($target) {
3157
                case 'welcome':
3158
                    return 'index.php';
3159
                case 'databases':
3160
                    return 'server_databases.php';
3161
                case 'status':
3162
                    return 'server_status.php';
3163
                case 'variables':
3164
                    return 'server_variables.php';
3165
                case 'privileges':
3166
                    return 'server_privileges.php';
3167
            }
3168
        } elseif ($location == 'database') {
3169
            // Values for $cfg['DefaultTabDatabase']
3170
            switch ($target) {
3171
                case 'structure':
3172
                    return 'db_structure.php';
3173
                case 'sql':
3174
                    return 'db_sql.php';
3175
                case 'search':
3176
                    return 'db_search.php';
3177
                case 'operations':
3178
                    return 'db_operations.php';
3179
            }
3180
        } elseif ($location == 'table') {
3181
            // Values for $cfg['DefaultTabTable'],
3182
            // $cfg['NavigationTreeDefaultTabTable'] and
3183
            // $cfg['NavigationTreeDefaultTabTable2']
3184
            switch ($target) {
3185
                case 'structure':
3186
                    return 'tbl_structure.php';
3187
                case 'sql':
3188
                    return 'tbl_sql.php';
3189
                case 'search':
3190
                    return 'tbl_select.php';
3191
                case 'insert':
3192
                    return 'tbl_change.php';
3193
                case 'browse':
3194
                    return 'sql.php';
3195
            }
3196
        }
3197
3198
        return $target;
3199
    }
3200
3201
    /**
3202
     * Formats user string, expanding @VARIABLES@, accepting strftime format
3203
     * string.
3204
     *
3205
     * @param string       $string  Text where to do expansion.
3206
     * @param array|string $escape  Function to call for escaping variable values.
3207
     *                              Can also be an array of:
3208
     *                              - the escape method name
3209
     *                              - the class that contains the method
3210
     *                              - location of the class (for inclusion)
3211
     * @param array        $updates Array with overrides for default parameters
3212
     *                              (obtained from GLOBALS).
3213
     *
3214
     * @return string
3215
     */
3216
    public static function expandUserString(
3217
        $string,
3218
        $escape = null,
3219
        array $updates = []
3220
    ) {
3221
        /* Content */
3222
        $vars = [];
3223
        $vars['http_host'] = Core::getenv('HTTP_HOST');
3224
        $vars['server_name'] = $GLOBALS['cfg']['Server']['host'];
3225
        $vars['server_verbose'] = $GLOBALS['cfg']['Server']['verbose'];
3226
3227
        if (empty($GLOBALS['cfg']['Server']['verbose'])) {
3228
            $vars['server_verbose_or_name'] = $GLOBALS['cfg']['Server']['host'];
3229
        } else {
3230
            $vars['server_verbose_or_name'] = $GLOBALS['cfg']['Server']['verbose'];
3231
        }
3232
3233
        $vars['database'] = $GLOBALS['db'];
3234
        $vars['table'] = $GLOBALS['table'];
3235
        $vars['phpmyadmin_version'] = 'phpMyAdmin ' . PMA_VERSION;
3236
3237
        /* Update forced variables */
3238
        foreach ($updates as $key => $val) {
3239
            $vars[$key] = $val;
3240
        }
3241
3242
        /* Replacement mapping */
3243
        /*
3244
         * The __VAR__ ones are for backward compatibility, because user
3245
         * might still have it in cookies.
3246
         */
3247
        $replace = [
3248
            '@HTTP_HOST@' => $vars['http_host'],
3249
            '@SERVER@' => $vars['server_name'],
3250
            '__SERVER__' => $vars['server_name'],
3251
            '@VERBOSE@' => $vars['server_verbose'],
3252
            '@VSERVER@' => $vars['server_verbose_or_name'],
3253
            '@DATABASE@' => $vars['database'],
3254
            '__DB__' => $vars['database'],
3255
            '@TABLE@' => $vars['table'],
3256
            '__TABLE__' => $vars['table'],
3257
            '@PHPMYADMIN@' => $vars['phpmyadmin_version'],
3258
            ];
3259
3260
        /* Optional escaping */
3261
        if (! is_null($escape)) {
3262
            if (is_array($escape)) {
3263
                $escape_class = new $escape[1];
3264
                $escape_method = $escape[0];
3265
            }
3266
            foreach ($replace as $key => $val) {
3267
                if (is_array($escape)) {
3268
                    $replace[$key] = $escape_class->$escape_method($val);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $escape_class does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $escape_method does not seem to be defined for all execution paths leading up to this point.
Loading history...
3269
                } else {
3270
                    $replace[$key] = ($escape == 'backquote')
3271
                        ? self::$escape($val)
3272
                        : $escape($val);
3273
                }
3274
            }
3275
        }
3276
3277
        /* Backward compatibility in 3.5.x */
3278
        if (mb_strpos($string, '@FIELDS@') !== false) {
3279
            $string = strtr($string, ['@FIELDS@' => '@COLUMNS@']);
3280
        }
3281
3282
        /* Fetch columns list if required */
3283
        if (mb_strpos($string, '@COLUMNS@') !== false) {
3284
            $columns_list = $GLOBALS['dbi']->getColumns(
3285
                $GLOBALS['db'],
3286
                $GLOBALS['table']
3287
            );
3288
3289
            // sometimes the table no longer exists at this point
3290
            if (! is_null($columns_list)) {
3291
                $column_names = [];
3292
                foreach ($columns_list as $column) {
3293
                    if (! is_null($escape)) {
3294
                        $column_names[] = self::$escape($column['Field']);
3295
                    } else {
3296
                        $column_names[] = $column['Field'];
3297
                    }
3298
                }
3299
                $replace['@COLUMNS@'] = implode(',', $column_names);
3300
            } else {
3301
                $replace['@COLUMNS@'] = '*';
3302
            }
3303
        }
3304
3305
        /* Do the replacement */
3306
        return strtr((string) strftime($string), $replace);
3307
    }
3308
3309
    /**
3310
     * Prepare the form used to browse anywhere on the local server for a file to
3311
     * import
3312
     *
3313
     * @param string $max_upload_size maximum upload size
3314
     *
3315
     * @return String
3316
     */
3317
    public static function getBrowseUploadFileBlock($max_upload_size)
3318
    {
3319
        $block_html = '';
3320
3321
        if ($GLOBALS['is_upload'] && ! empty($GLOBALS['cfg']['UploadDir'])) {
3322
            $block_html .= '<label for="radio_import_file">';
3323
        } else {
3324
            $block_html .= '<label for="input_import_file">';
3325
        }
3326
3327
        $block_html .= __("Browse your computer:") . '</label>'
3328
            . '<div id="upload_form_status" class="hide"></div>'
3329
            . '<div id="upload_form_status_info" class="hide"></div>'
3330
            . '<input type="file" name="import_file" id="input_import_file" />'
3331
            . self::getFormattedMaximumUploadSize($max_upload_size) . "\n"
0 ignored issues
show
Bug introduced by
$max_upload_size of type string is incompatible with the type integer expected by parameter $max_upload_size of PhpMyAdmin\Util::getFormattedMaximumUploadSize(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3331
            . self::getFormattedMaximumUploadSize(/** @scrutinizer ignore-type */ $max_upload_size) . "\n"
Loading history...
3332
            // some browsers should respect this :)
3333
            . self::generateHiddenMaxFileSize($max_upload_size) . "\n";
0 ignored issues
show
Bug introduced by
$max_upload_size of type string is incompatible with the type integer expected by parameter $max_size of PhpMyAdmin\Util::generateHiddenMaxFileSize(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3333
            . self::generateHiddenMaxFileSize(/** @scrutinizer ignore-type */ $max_upload_size) . "\n";
Loading history...
3334
3335
        return $block_html;
3336
    }
3337
3338
    /**
3339
     * Prepare the form used to select a file to import from the server upload
3340
     * directory
3341
     *
3342
     * @param ImportPlugin[] $import_list array of import plugins
3343
     * @param string         $uploaddir   upload directory
3344
     *
3345
     * @return String
3346
     */
3347
    public static function getSelectUploadFileBlock($import_list, $uploaddir)
3348
    {
3349
        $fileListing = new FileListing();
3350
3351
        $block_html = '';
3352
        $block_html .= '<label for="radio_local_import_file">'
3353
            . sprintf(
3354
                __("Select from the web server upload directory <b>%s</b>:"),
3355
                htmlspecialchars(self::userDir($uploaddir))
3356
            )
3357
            . '</label>';
3358
3359
        $extensions = '';
3360
        foreach ($import_list as $import_plugin) {
3361
            if (! empty($extensions)) {
3362
                $extensions .= '|';
3363
            }
3364
            $extensions .= $import_plugin->getProperties()->getExtension();
3365
        }
3366
3367
        $matcher = '@\.(' . $extensions . ')(\.('
3368
            . $fileListing->supportedDecompressions() . '))?$@';
3369
3370
        $active = (isset($GLOBALS['timeout_passed']) && $GLOBALS['timeout_passed']
3371
            && isset($GLOBALS['local_import_file']))
3372
            ? $GLOBALS['local_import_file']
3373
            : '';
3374
3375
        $files = $fileListing->getFileSelectOptions(
3376
            self::userDir($uploaddir),
3377
            $matcher,
3378
            $active
3379
        );
3380
3381
        if ($files === false) {
0 ignored issues
show
introduced by
The condition $files === false is always true.
Loading history...
3382
            Message::error(
3383
                __('The directory you set for upload work cannot be reached.')
3384
            )->display();
3385
        } elseif (! empty($files)) {
3386
            $block_html .= "\n"
3387
                . '    <select style="margin: 5px" size="1" '
3388
                . 'name="local_import_file" '
3389
                . 'id="select_local_import_file">' . "\n"
3390
                . '        <option value="">&nbsp;</option>' . "\n"
3391
                . $files
3392
                . '    </select>' . "\n";
3393
        } elseif (empty($files)) {
3394
            $block_html .= '<i>' . __('There are no files to upload!') . '</i>';
3395
        }
3396
3397
        return $block_html;
3398
    }
3399
3400
    /**
3401
     * Build titles and icons for action links
3402
     *
3403
     * @return array   the action titles
3404
     */
3405
    public static function buildActionTitles()
3406
    {
3407
        $titles = [];
3408
3409
        $titles['Browse']     = self::getIcon('b_browse', __('Browse'));
3410
        $titles['NoBrowse']   = self::getIcon('bd_browse', __('Browse'));
3411
        $titles['Search']     = self::getIcon('b_select', __('Search'));
3412
        $titles['NoSearch']   = self::getIcon('bd_select', __('Search'));
3413
        $titles['Insert']     = self::getIcon('b_insrow', __('Insert'));
3414
        $titles['NoInsert']   = self::getIcon('bd_insrow', __('Insert'));
3415
        $titles['Structure']  = self::getIcon('b_props', __('Structure'));
3416
        $titles['Drop']       = self::getIcon('b_drop', __('Drop'));
3417
        $titles['NoDrop']     = self::getIcon('bd_drop', __('Drop'));
3418
        $titles['Empty']      = self::getIcon('b_empty', __('Empty'));
3419
        $titles['NoEmpty']    = self::getIcon('bd_empty', __('Empty'));
3420
        $titles['Edit']       = self::getIcon('b_edit', __('Edit'));
3421
        $titles['NoEdit']     = self::getIcon('bd_edit', __('Edit'));
3422
        $titles['Export']     = self::getIcon('b_export', __('Export'));
3423
        $titles['NoExport']   = self::getIcon('bd_export', __('Export'));
3424
        $titles['Execute']    = self::getIcon('b_nextpage', __('Execute'));
3425
        $titles['NoExecute']  = self::getIcon('bd_nextpage', __('Execute'));
3426
        // For Favorite/NoFavorite, we need icon only.
3427
        $titles['Favorite']  = self::getIcon('b_favorite', '');
3428
        $titles['NoFavorite'] = self::getIcon('b_no_favorite', '');
3429
3430
        return $titles;
3431
    }
3432
3433
    /**
3434
     * This function processes the datatypes supported by the DB,
3435
     * as specified in Types->getColumns() and either returns an array
3436
     * (useful for quickly checking if a datatype is supported)
3437
     * or an HTML snippet that creates a drop-down list.
3438
     *
3439
     * @param bool   $html     Whether to generate an html snippet or an array
3440
     * @param string $selected The value to mark as selected in HTML mode
3441
     *
3442
     * @return mixed   An HTML snippet or an array of datatypes.
3443
     *
3444
     */
3445
    public static function getSupportedDatatypes($html = false, $selected = '')
3446
    {
3447
        if ($html) {
3448
            // NOTE: the SELECT tag in not included in this snippet.
3449
            $retval = '';
3450
3451
            foreach ($GLOBALS['dbi']->types->getColumns() as $key => $value) {
3452
                if (is_array($value)) {
3453
                    $retval .= "<optgroup label='" . htmlspecialchars($key) . "'>";
3454
                    foreach ($value as $subvalue) {
3455
                        if ($subvalue == $selected) {
3456
                            $retval .= sprintf(
3457
                                '<option selected="selected" title="%s">%s</option>',
3458
                                $GLOBALS['dbi']->types->getTypeDescription($subvalue),
3459
                                $subvalue
3460
                            );
3461
                        } elseif ($subvalue === '-') {
3462
                            $retval .= '<option disabled="disabled">';
3463
                            $retval .= $subvalue;
3464
                            $retval .= '</option>';
3465
                        } else {
3466
                            $retval .= sprintf(
3467
                                '<option title="%s">%s</option>',
3468
                                $GLOBALS['dbi']->types->getTypeDescription($subvalue),
3469
                                $subvalue
3470
                            );
3471
                        }
3472
                    }
3473
                    $retval .= '</optgroup>';
3474
                } else {
3475
                    if ($selected == $value) {
3476
                        $retval .= sprintf(
3477
                            '<option selected="selected" title="%s">%s</option>',
3478
                            $GLOBALS['dbi']->types->getTypeDescription($value),
3479
                            $value
3480
                        );
3481
                    } else {
3482
                        $retval .= sprintf(
3483
                            '<option title="%s">%s</option>',
3484
                            $GLOBALS['dbi']->types->getTypeDescription($value),
3485
                            $value
3486
                        );
3487
                    }
3488
                }
3489
            }
3490
        } else {
3491
            $retval = [];
3492
            foreach ($GLOBALS['dbi']->types->getColumns() as $value) {
3493
                if (is_array($value)) {
3494
                    foreach ($value as $subvalue) {
3495
                        if ($subvalue !== '-') {
3496
                            $retval[] = $subvalue;
3497
                        }
3498
                    }
3499
                } else {
3500
                    if ($value !== '-') {
3501
                        $retval[] = $value;
3502
                    }
3503
                }
3504
            }
3505
        }
3506
3507
        return $retval;
3508
    } // end getSupportedDatatypes()
3509
3510
    /**
3511
     * Returns a list of datatypes that are not (yet) handled by PMA.
3512
     * Used by: tbl_change.php and libraries/db_routines.inc.php
3513
     *
3514
     * @return array   list of datatypes
3515
     */
3516
    public static function unsupportedDatatypes()
3517
    {
3518
        $no_support_types = [];
3519
        return $no_support_types;
3520
    }
3521
3522
    /**
3523
     * Return GIS data types
3524
     *
3525
     * @param bool $upper_case whether to return values in upper case
3526
     *
3527
     * @return string[] GIS data types
3528
     */
3529
    public static function getGISDatatypes($upper_case = false)
3530
    {
3531
        $gis_data_types = [
3532
            'geometry',
3533
            'point',
3534
            'linestring',
3535
            'polygon',
3536
            'multipoint',
3537
            'multilinestring',
3538
            'multipolygon',
3539
            'geometrycollection'
3540
        ];
3541
        if ($upper_case) {
3542
            for ($i = 0, $nb = count($gis_data_types); $i < $nb; $i++) {
3543
                $gis_data_types[$i]
3544
                    = mb_strtoupper($gis_data_types[$i]);
3545
            }
3546
        }
3547
        return $gis_data_types;
3548
    }
3549
3550
    /**
3551
     * Generates GIS data based on the string passed.
3552
     *
3553
     * @param string $gis_string GIS string
3554
     *
3555
     * @return string GIS data enclosed in 'GeomFromText' function
3556
     */
3557
    public static function createGISData($gis_string)
3558
    {
3559
        $gis_string = trim($gis_string);
3560
        $geom_types = '(POINT|MULTIPOINT|LINESTRING|MULTILINESTRING|'
3561
            . 'POLYGON|MULTIPOLYGON|GEOMETRYCOLLECTION)';
3562
        if (preg_match("/^'" . $geom_types . "\(.*\)',[0-9]*$/i", $gis_string)) {
3563
            return 'GeomFromText(' . $gis_string . ')';
3564
        } elseif (preg_match("/^" . $geom_types . "\(.*\)$/i", $gis_string)) {
3565
            return "GeomFromText('" . $gis_string . "')";
3566
        }
3567
3568
        return $gis_string;
3569
    }
3570
3571
    /**
3572
     * Returns the names and details of the functions
3573
     * that can be applied on geometry data types.
3574
     *
3575
     * @param string $geom_type if provided the output is limited to the functions
3576
     *                          that are applicable to the provided geometry type.
3577
     * @param bool   $binary    if set to false functions that take two geometries
3578
     *                          as arguments will not be included.
3579
     * @param bool   $display   if set to true separators will be added to the
3580
     *                          output array.
3581
     *
3582
     * @return array names and details of the functions that can be applied on
3583
     *               geometry data types.
3584
     */
3585
    public static function getGISFunctions(
3586
        $geom_type = null,
3587
        $binary = true,
3588
        $display = false
3589
    ) {
3590
        $funcs = [];
3591
        if ($display) {
3592
            $funcs[] = ['display' => ' '];
3593
        }
3594
3595
        // Unary functions common to all geometry types
3596
        $funcs['Dimension']    = ['params' => 1, 'type' => 'int'];
3597
        $funcs['Envelope']     = ['params' => 1, 'type' => 'Polygon'];
3598
        $funcs['GeometryType'] = ['params' => 1, 'type' => 'text'];
3599
        $funcs['SRID']         = ['params' => 1, 'type' => 'int'];
3600
        $funcs['IsEmpty']      = ['params' => 1, 'type' => 'int'];
3601
        $funcs['IsSimple']     = ['params' => 1, 'type' => 'int'];
3602
3603
        $geom_type = trim(mb_strtolower((string) $geom_type));
3604
        if ($display && $geom_type != 'geometry' && $geom_type != 'multipoint') {
3605
            $funcs[] = ['display' => '--------'];
3606
        }
3607
3608
        // Unary functions that are specific to each geometry type
3609
        if ($geom_type == 'point') {
3610
            $funcs['X'] = ['params' => 1, 'type' => 'float'];
3611
            $funcs['Y'] = ['params' => 1, 'type' => 'float'];
3612
        } elseif ($geom_type == 'multipoint') {
3613
            // no functions here
3614
        } elseif ($geom_type == 'linestring') {
3615
            $funcs['EndPoint']   = ['params' => 1, 'type' => 'point'];
3616
            $funcs['GLength']    = ['params' => 1, 'type' => 'float'];
3617
            $funcs['NumPoints']  = ['params' => 1, 'type' => 'int'];
3618
            $funcs['StartPoint'] = ['params' => 1, 'type' => 'point'];
3619
            $funcs['IsRing']     = ['params' => 1, 'type' => 'int'];
3620
        } elseif ($geom_type == 'multilinestring') {
3621
            $funcs['GLength']  = ['params' => 1, 'type' => 'float'];
3622
            $funcs['IsClosed'] = ['params' => 1, 'type' => 'int'];
3623
        } elseif ($geom_type == 'polygon') {
3624
            $funcs['Area']         = ['params' => 1, 'type' => 'float'];
3625
            $funcs['ExteriorRing'] = ['params' => 1, 'type' => 'linestring'];
3626
            $funcs['NumInteriorRings'] = ['params' => 1, 'type' => 'int'];
3627
        } elseif ($geom_type == 'multipolygon') {
3628
            $funcs['Area']     = ['params' => 1, 'type' => 'float'];
3629
            $funcs['Centroid'] = ['params' => 1, 'type' => 'point'];
3630
            // Not yet implemented in MySQL
3631
            //$funcs['PointOnSurface'] = array('params' => 1, 'type' => 'point');
3632
        } elseif ($geom_type == 'geometrycollection') {
3633
            $funcs['NumGeometries'] = ['params' => 1, 'type' => 'int'];
3634
        }
3635
3636
        // If we are asked for binary functions as well
3637
        if ($binary) {
3638
            // section separator
3639
            if ($display) {
3640
                $funcs[] = ['display' => '--------'];
3641
            }
3642
3643
            if ($GLOBALS['dbi']->getVersion() < 50601) {
3644
                $funcs['Crosses']    = ['params' => 2, 'type' => 'int'];
3645
                $funcs['Contains']   = ['params' => 2, 'type' => 'int'];
3646
                $funcs['Disjoint']   = ['params' => 2, 'type' => 'int'];
3647
                $funcs['Equals']     = ['params' => 2, 'type' => 'int'];
3648
                $funcs['Intersects'] = ['params' => 2, 'type' => 'int'];
3649
                $funcs['Overlaps']   = ['params' => 2, 'type' => 'int'];
3650
                $funcs['Touches']    = ['params' => 2, 'type' => 'int'];
3651
                $funcs['Within']     = ['params' => 2, 'type' => 'int'];
3652
            } else {
3653
                // If MySQl version is greater than or equal 5.6.1,
3654
                // use the ST_ prefix.
3655
                $funcs['ST_Crosses']    = ['params' => 2, 'type' => 'int'];
3656
                $funcs['ST_Contains']   = ['params' => 2, 'type' => 'int'];
3657
                $funcs['ST_Disjoint']   = ['params' => 2, 'type' => 'int'];
3658
                $funcs['ST_Equals']     = ['params' => 2, 'type' => 'int'];
3659
                $funcs['ST_Intersects'] = ['params' => 2, 'type' => 'int'];
3660
                $funcs['ST_Overlaps']   = ['params' => 2, 'type' => 'int'];
3661
                $funcs['ST_Touches']    = ['params' => 2, 'type' => 'int'];
3662
                $funcs['ST_Within']     = ['params' => 2, 'type' => 'int'];
3663
            }
3664
3665
            if ($display) {
3666
                $funcs[] = ['display' => '--------'];
3667
            }
3668
            // Minimum bounding rectangle functions
3669
            $funcs['MBRContains']   = ['params' => 2, 'type' => 'int'];
3670
            $funcs['MBRDisjoint']   = ['params' => 2, 'type' => 'int'];
3671
            $funcs['MBREquals']     = ['params' => 2, 'type' => 'int'];
3672
            $funcs['MBRIntersects'] = ['params' => 2, 'type' => 'int'];
3673
            $funcs['MBROverlaps']   = ['params' => 2, 'type' => 'int'];
3674
            $funcs['MBRTouches']    = ['params' => 2, 'type' => 'int'];
3675
            $funcs['MBRWithin']     = ['params' => 2, 'type' => 'int'];
3676
        }
3677
        return $funcs;
3678
    }
3679
3680
    /**
3681
     * Returns default function for a particular column.
3682
     *
3683
     * @param array $field       Data about the column for which
3684
     *                           to generate the dropdown
3685
     * @param bool  $insert_mode Whether the operation is 'insert'
3686
     *
3687
     * @global   array    $cfg            PMA configuration
3688
     * @global   mixed    $data           data of currently edited row
3689
     *                                    (used to detect whether to choose defaults)
3690
     *
3691
     * @return string   An HTML snippet of a dropdown list with function
3692
     *                    names appropriate for the requested column.
3693
     */
3694
    public static function getDefaultFunctionForField(array $field, $insert_mode)
3695
    {
3696
        /*
3697
         * @todo Except for $cfg, no longer use globals but pass as parameters
3698
         *       from higher levels
3699
         */
3700
        global $cfg, $data;
3701
3702
        $default_function   = '';
3703
3704
        // Can we get field class based values?
3705
        $current_class = $GLOBALS['dbi']->types->getTypeClass($field['True_Type']);
3706
        if (! empty($current_class)) {
3707
            if (isset($cfg['DefaultFunctions']['FUNC_' . $current_class])) {
3708
                $default_function
3709
                    = $cfg['DefaultFunctions']['FUNC_' . $current_class];
3710
            }
3711
        }
3712
3713
        // what function defined as default?
3714
        // for the first timestamp we don't set the default function
3715
        // if there is a default value for the timestamp
3716
        // (not including CURRENT_TIMESTAMP)
3717
        // and the column does not have the
3718
        // ON UPDATE DEFAULT TIMESTAMP attribute.
3719
        if (($field['True_Type'] == 'timestamp')
3720
            && $field['first_timestamp']
3721
            && empty($field['Default'])
3722
            && empty($data)
3723
            && $field['Extra'] != 'on update CURRENT_TIMESTAMP'
3724
            && $field['Null'] == 'NO'
3725
        ) {
3726
            $default_function = $cfg['DefaultFunctions']['first_timestamp'];
3727
        }
3728
3729
        // For primary keys of type char(36) or varchar(36) UUID if the default
3730
        // function
3731
        // Only applies to insert mode, as it would silently trash data on updates.
3732
        if ($insert_mode
3733
            && $field['Key'] == 'PRI'
3734
            && ($field['Type'] == 'char(36)' || $field['Type'] == 'varchar(36)')
3735
        ) {
3736
             $default_function = $cfg['DefaultFunctions']['FUNC_UUID'];
3737
        }
3738
3739
        return $default_function;
3740
    }
3741
3742
    /**
3743
     * Creates a dropdown box with MySQL functions for a particular column.
3744
     *
3745
     * @param array $field       Data about the column for which
3746
     *                           to generate the dropdown
3747
     * @param bool  $insert_mode Whether the operation is 'insert'
3748
     * @param array $foreignData Foreign data
3749
     *
3750
     * @return string   An HTML snippet of a dropdown list with function
3751
     *                    names appropriate for the requested column.
3752
     */
3753
    public static function getFunctionsForField(array $field, $insert_mode, array $foreignData)
3754
    {
3755
        $default_function = self::getDefaultFunctionForField($field, $insert_mode);
3756
        $dropdown_built = [];
3757
3758
        // Create the output
3759
        $retval = '<option></option>' . "\n";
3760
        // loop on the dropdown array and print all available options for that
3761
        // field.
3762
        $functions = $GLOBALS['dbi']->types->getFunctions($field['True_Type']);
3763
        foreach ($functions as $function) {
3764
            $retval .= '<option';
3765
            if (isset($foreignData['foreign_link']) && $foreignData['foreign_link'] !== false && $default_function === $function) {
3766
                $retval .= ' selected="selected"';
3767
            }
3768
            $retval .= '>' . $function . '</option>' . "\n";
3769
            $dropdown_built[$function] = true;
3770
        }
3771
3772
        // Create separator before all functions list
3773
        if (count($functions) > 0) {
3774
            $retval .= '<option value="" disabled="disabled">--------</option>'
3775
                . "\n";
3776
        }
3777
3778
        // For compatibility's sake, do not let out all other functions. Instead
3779
        // print a separator (blank) and then show ALL functions which weren't
3780
        // shown yet.
3781
        $functions = $GLOBALS['dbi']->types->getAllFunctions();
3782
        foreach ($functions as $function) {
3783
            // Skip already included functions
3784
            if (isset($dropdown_built[$function])) {
3785
                continue;
3786
            }
3787
            $retval .= '<option';
3788
            if ($default_function === $function) {
3789
                $retval .= ' selected="selected"';
3790
            }
3791
            $retval .= '>' . $function . '</option>' . "\n";
3792
        } // end for
3793
3794
        return $retval;
3795
    } // end getFunctionsForField()
3796
3797
    /**
3798
     * Checks if the current user has a specific privilege and returns true if the
3799
     * user indeed has that privilege or false if (s)he doesn't. This function must
3800
     * only be used for features that are available since MySQL 5, because it
3801
     * relies on the INFORMATION_SCHEMA database to be present.
3802
     *
3803
     * Example:   currentUserHasPrivilege('CREATE ROUTINE', 'mydb');
3804
     *            // Checks if the currently logged in user has the global
3805
     *            // 'CREATE ROUTINE' privilege or, if not, checks if the
3806
     *            // user has this privilege on database 'mydb'.
3807
     *
3808
     * @param string $priv The privilege to check
3809
     * @param mixed  $db   null, to only check global privileges
3810
     *                     string, db name where to also check for privileges
3811
     * @param mixed  $tbl  null, to only check global/db privileges
3812
     *                     string, table name where to also check for privileges
3813
     *
3814
     * @return bool
3815
     */
3816
    public static function currentUserHasPrivilege($priv, $db = null, $tbl = null)
3817
    {
3818
        // Get the username for the current user in the format
3819
        // required to use in the information schema database.
3820
        list($user, $host) = $GLOBALS['dbi']->getCurrentUserAndHost();
3821
3822
        if ($user === '') { // MySQL is started with --skip-grant-tables
3823
            return true;
3824
        }
3825
3826
        $username  = "''";
3827
        $username .= str_replace("'", "''", $user);
3828
        $username .= "''@''";
3829
        $username .= str_replace("'", "''", $host);
3830
        $username .= "''";
3831
3832
        // Prepare the query
3833
        $query = "SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`%s` "
3834
               . "WHERE GRANTEE='%s' AND PRIVILEGE_TYPE='%s'";
3835
3836
        // Check global privileges first.
3837
        $user_privileges = $GLOBALS['dbi']->fetchValue(
3838
            sprintf(
3839
                $query,
3840
                'USER_PRIVILEGES',
3841
                $username,
3842
                $priv
3843
            )
3844
        );
3845
        if ($user_privileges) {
3846
            return true;
3847
        }
3848
        // If a database name was provided and user does not have the
3849
        // required global privilege, try database-wise permissions.
3850
        if ($db !== null) {
3851
            $query .= " AND '%s' LIKE `TABLE_SCHEMA`";
3852
            $schema_privileges = $GLOBALS['dbi']->fetchValue(
3853
                sprintf(
3854
                    $query,
3855
                    'SCHEMA_PRIVILEGES',
3856
                    $username,
3857
                    $priv,
3858
                    $GLOBALS['dbi']->escapeString($db)
3859
                )
3860
            );
3861
            if ($schema_privileges) {
3862
                return true;
3863
            }
3864
        } else {
3865
            // There was no database name provided and the user
3866
            // does not have the correct global privilege.
3867
            return false;
3868
        }
3869
        // If a table name was also provided and we still didn't
3870
        // find any valid privileges, try table-wise privileges.
3871
        if ($tbl !== null) {
3872
            // need to escape wildcards in db and table names, see bug #3518484
3873
            $tbl = str_replace(['%', '_'], ['\%', '\_'], $tbl);
3874
            $query .= " AND TABLE_NAME='%s'";
3875
            $table_privileges = $GLOBALS['dbi']->fetchValue(
3876
                sprintf(
3877
                    $query,
3878
                    'TABLE_PRIVILEGES',
3879
                    $username,
3880
                    $priv,
3881
                    $GLOBALS['dbi']->escapeString($db),
3882
                    $GLOBALS['dbi']->escapeString($tbl)
3883
                )
3884
            );
3885
            if ($table_privileges) {
3886
                return true;
3887
            }
3888
        }
3889
        // If we reached this point, the user does not
3890
        // have even valid table-wise privileges.
3891
        return false;
3892
    }
3893
3894
    /**
3895
     * Returns server type for current connection
3896
     *
3897
     * Known types are: MariaDB, Percona and MySQL (default)
3898
     *
3899
     * @return string
3900
     */
3901
    public static function getServerType()
3902
    {
3903
        if ($GLOBALS['dbi']->isMariaDB()) {
3904
            return 'MariaDB';
3905
        }
3906
3907
        if ($GLOBALS['dbi']->isPercona()) {
3908
            return 'Percona Server';
3909
        }
3910
3911
        return 'MySQL';
3912
    }
3913
3914
    /**
3915
     * Returns information about SSL status for current connection
3916
     *
3917
     * @return string
3918
     */
3919
    public static function getServerSSL()
3920
    {
3921
        $server = $GLOBALS['cfg']['Server'];
3922
        $class = 'caution';
3923
        if (! $server['ssl']) {
3924
            $message = __('SSL is not being used');
3925
            if (! empty($server['socket']) || $server['host'] == '127.0.0.1' || $server['host'] == 'localhost') {
3926
                $class = '';
3927
            }
3928
        } elseif (! $server['ssl_verify']) {
3929
            $message = __('SSL is used with disabled verification');
3930
        } elseif (empty($server['ssl_ca']) && empty($server['ssl_ca'])) {
3931
            $message = __('SSL is used without certification authority');
3932
        } else {
3933
            $class = '';
3934
            $message = __('SSL is used');
3935
        }
3936
        return '<span class="' . $class . '">' . $message . '</span> ' . self::showDocu('setup', 'ssl');
3937
    }
3938
3939
3940
    /**
3941
     * Prepare HTML code for display button.
3942
     *
3943
     * @return String
3944
     */
3945
    public static function getButton()
3946
    {
3947
        return '<p class="print_ignore">'
3948
            . '<input type="button" class="button" id="print" value="'
3949
            . __('Print') . '" />'
3950
            . '</p>';
3951
    }
3952
3953
    /**
3954
     * Parses ENUM/SET values
3955
     *
3956
     * @param string $definition The definition of the column
3957
     *                           for which to parse the values
3958
     * @param bool   $escapeHtml Whether to escape html entities
3959
     *
3960
     * @return array
3961
     */
3962
    public static function parseEnumSetValues($definition, $escapeHtml = true)
3963
    {
3964
        $values_string = htmlentities($definition, ENT_COMPAT, "UTF-8");
3965
        // There is a JS port of the below parser in functions.js
3966
        // If you are fixing something here,
3967
        // you need to also update the JS port.
3968
        $values = [];
3969
        $in_string = false;
3970
        $buffer = '';
3971
3972
        for ($i = 0, $length = mb_strlen($values_string);
3973
             $i < $length;
3974
             $i++) {
3975
            $curr = mb_substr($values_string, $i, 1);
3976
            $next = ($i == mb_strlen($values_string) - 1)
3977
                ? ''
3978
                : mb_substr($values_string, $i + 1, 1);
3979
3980
            if (! $in_string && $curr == "'") {
3981
                $in_string = true;
3982
            } elseif (($in_string && $curr == "\\") && $next == "\\") {
3983
                $buffer .= "&#92;";
3984
                $i++;
3985
            } elseif (($in_string && $next == "'")
3986
                && ($curr == "'" || $curr == "\\")
3987
            ) {
3988
                $buffer .= "&#39;";
3989
                $i++;
3990
            } elseif ($in_string && $curr == "'") {
3991
                $in_string = false;
3992
                $values[] = $buffer;
3993
                $buffer = '';
3994
            } elseif ($in_string) {
3995
                 $buffer .= $curr;
3996
            }
3997
        }
3998
3999
        if (strlen($buffer) > 0) {
4000
            // The leftovers in the buffer are the last value (if any)
4001
            $values[] = $buffer;
4002
        }
4003
4004
        if (! $escapeHtml) {
4005
            foreach ($values as $key => $value) {
4006
                $values[$key] = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
4007
            }
4008
        }
4009
4010
        return $values;
4011
    }
4012
4013
    /**
4014
     * Get regular expression which occur first inside the given sql query.
4015
     *
4016
     * @param array  $regex_array Comparing regular expressions.
4017
     * @param String $query       SQL query to be checked.
4018
     *
4019
     * @return String Matching regular expression.
4020
     */
4021
    public static function getFirstOccurringRegularExpression(array $regex_array, $query)
4022
    {
4023
        $minimum_first_occurence_index = null;
4024
        $regex = null;
4025
4026
        foreach ($regex_array as $test_regex) {
4027
            if (preg_match($test_regex, $query, $matches, PREG_OFFSET_CAPTURE)) {
4028
                if (is_null($minimum_first_occurence_index)
4029
                    || ($matches[0][1] < $minimum_first_occurence_index)
4030
                ) {
4031
                    $regex = $test_regex;
4032
                    $minimum_first_occurence_index = $matches[0][1];
4033
                }
4034
            }
4035
        }
4036
        return $regex;
4037
    }
4038
4039
    /**
4040
     * Return the list of tabs for the menu with corresponding names
4041
     *
4042
     * @param string $level 'server', 'db' or 'table' level
4043
     *
4044
     * @return array list of tabs for the menu
4045
     */
4046
    public static function getMenuTabList($level = null)
4047
    {
4048
        $tabList = [
4049
            'server' => [
4050
                'databases'   => __('Databases'),
4051
                'sql'         => __('SQL'),
4052
                'status'      => __('Status'),
4053
                'rights'      => __('Users'),
4054
                'export'      => __('Export'),
4055
                'import'      => __('Import'),
4056
                'settings'    => __('Settings'),
4057
                'binlog'      => __('Binary log'),
4058
                'replication' => __('Replication'),
4059
                'vars'        => __('Variables'),
4060
                'charset'     => __('Charsets'),
4061
                'plugins'     => __('Plugins'),
4062
                'engine'      => __('Engines')
4063
            ],
4064
            'db'     => [
4065
                'structure'   => __('Structure'),
4066
                'sql'         => __('SQL'),
4067
                'search'      => __('Search'),
4068
                'query'       => __('Query'),
4069
                'export'      => __('Export'),
4070
                'import'      => __('Import'),
4071
                'operation'   => __('Operations'),
4072
                'privileges'  => __('Privileges'),
4073
                'routines'    => __('Routines'),
4074
                'events'      => __('Events'),
4075
                'triggers'    => __('Triggers'),
4076
                'tracking'    => __('Tracking'),
4077
                'designer'    => __('Designer'),
4078
                'central_columns' => __('Central columns')
4079
            ],
4080
            'table'  => [
4081
                'browse'      => __('Browse'),
4082
                'structure'   => __('Structure'),
4083
                'sql'         => __('SQL'),
4084
                'search'      => __('Search'),
4085
                'insert'      => __('Insert'),
4086
                'export'      => __('Export'),
4087
                'import'      => __('Import'),
4088
                'privileges'  => __('Privileges'),
4089
                'operation'   => __('Operations'),
4090
                'tracking'    => __('Tracking'),
4091
                'triggers'    => __('Triggers'),
4092
            ]
4093
        ];
4094
4095
        if ($level == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $level of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
4096
            return $tabList;
4097
        } elseif (array_key_exists($level, $tabList)) {
4098
            return $tabList[$level];
4099
        }
4100
4101
        return null;
4102
    }
4103
4104
    /**
4105
     * Add fractional seconds to time, datetime and timestamp strings.
4106
     * If the string contains fractional seconds,
4107
     * pads it with 0s up to 6 decimal places.
4108
     *
4109
     * @param string $value time, datetime or timestamp strings
4110
     *
4111
     * @return string time, datetime or timestamp strings with fractional seconds
4112
     */
4113
    public static function addMicroseconds($value)
4114
    {
4115
        if (empty($value) || $value == 'CURRENT_TIMESTAMP'
4116
            || $value == 'current_timestamp()') {
4117
            return $value;
4118
        }
4119
4120
        if (mb_strpos($value, '.') === false) {
4121
            return $value . '.000000';
4122
        }
4123
4124
        $value .= '000000';
4125
        return mb_substr(
4126
            $value,
4127
            0,
4128
            mb_strpos($value, '.') + 7
4129
        );
4130
    }
4131
4132
    /**
4133
     * Reads the file, detects the compression MIME type, closes the file
4134
     * and returns the MIME type
4135
     *
4136
     * @param resource $file the file handle
4137
     *
4138
     * @return string the MIME type for compression, or 'none'
4139
     */
4140
    public static function getCompressionMimeType($file)
4141
    {
4142
        $test = fread($file, 4);
4143
        $len = strlen($test);
4144
        fclose($file);
4145
        if ($len >= 2 && $test[0] == chr(31) && $test[1] == chr(139)) {
4146
            return 'application/gzip';
4147
        }
4148
        if ($len >= 3 && substr($test, 0, 3) == 'BZh') {
4149
            return 'application/bzip2';
4150
        }
4151
        if ($len >= 4 && $test == "PK\003\004") {
4152
            return 'application/zip';
4153
        }
4154
        return 'none';
4155
    }
4156
4157
    /**
4158
     * Renders a single link for the top of the navigation panel
4159
     *
4160
     * @param string  $link        The url for the link
4161
     * @param bool    $showText    Whether to show the text or to
4162
     *                             only use it for title attributes
4163
     * @param string  $text        The text to display and use for title attributes
4164
     * @param bool    $showIcon    Whether to show the icon
4165
     * @param string  $icon        The filename of the icon to show
4166
     * @param string  $linkId      Value to use for the ID attribute
4167
     * @param boolean $disableAjax Whether to disable ajax page loading for this link
4168
     * @param string  $linkTarget  The name of the target frame for the link
4169
     * @param array   $classes     HTML classes to apply
4170
     *
4171
     * @return string HTML code for one link
4172
     */
4173
    public static function getNavigationLink(
4174
        $link,
4175
        $showText,
4176
        $text,
4177
        $showIcon,
4178
        $icon,
4179
        $linkId = '',
4180
        $disableAjax = false,
4181
        $linkTarget = '',
4182
        array $classes = []
4183
    ) {
4184
        $retval = '<a href="' . $link . '"';
4185
        if (! empty($linkId)) {
4186
            $retval .= ' id="' . $linkId . '"';
4187
        }
4188
        if (! empty($linkTarget)) {
4189
            $retval .= ' target="' . $linkTarget . '"';
4190
        }
4191
        if ($disableAjax) {
4192
            $classes[] = 'disableAjax';
4193
        }
4194
        if (!empty($classes)) {
4195
            $retval .= ' class="' . join(" ", $classes) . '"';
4196
        }
4197
        $retval .= ' title="' . $text . '">';
4198
        if ($showIcon) {
4199
            $retval .= self::getImage(
4200
                $icon,
4201
                $text
4202
            );
4203
        }
4204
        if ($showText) {
4205
            $retval .= $text;
4206
        }
4207
        $retval .= '</a>';
4208
        if ($showText) {
4209
            $retval .= '<br />';
4210
        }
4211
        return $retval;
4212
    }
4213
4214
    /**
4215
     * Provide COLLATE clause, if required, to perform case sensitive comparisons
4216
     * for queries on information_schema.
4217
     *
4218
     * @return string COLLATE clause if needed or empty string.
4219
     */
4220
    public static function getCollateForIS()
4221
    {
4222
        $names = $GLOBALS['dbi']->getLowerCaseNames();
4223
        if ($names === '0') {
4224
            return "COLLATE utf8_bin";
4225
        } elseif ($names === '2') {
4226
            return "COLLATE utf8_general_ci";
4227
        }
4228
        return "";
4229
    }
4230
4231
    /**
4232
     * Process the index data.
4233
     *
4234
     * @param array $indexes index data
4235
     *
4236
     * @return array processes index data
4237
     */
4238
    public static function processIndexData(array $indexes)
4239
    {
4240
        $lastIndex    = '';
4241
4242
        $primary      = '';
4243
        $pk_array     = []; // will be use to emphasis prim. keys in the table
4244
        $indexes_info = [];
4245
        $indexes_data = [];
4246
4247
        // view
4248
        foreach ($indexes as $row) {
4249
            // Backups the list of primary keys
4250
            if ($row['Key_name'] == 'PRIMARY') {
4251
                $primary   .= $row['Column_name'] . ', ';
4252
                $pk_array[$row['Column_name']] = 1;
4253
            }
4254
            // Retains keys informations
4255
            if ($row['Key_name'] != $lastIndex) {
4256
                $indexes[] = $row['Key_name'];
4257
                $lastIndex = $row['Key_name'];
4258
            }
4259
            $indexes_info[$row['Key_name']]['Sequences'][] = $row['Seq_in_index'];
4260
            $indexes_info[$row['Key_name']]['Non_unique'] = $row['Non_unique'];
4261
            if (isset($row['Cardinality'])) {
4262
                $indexes_info[$row['Key_name']]['Cardinality'] = $row['Cardinality'];
4263
            }
4264
            // I don't know what does following column mean....
4265
            // $indexes_info[$row['Key_name']]['Packed']          = $row['Packed'];
4266
4267
            $indexes_info[$row['Key_name']]['Comment'] = $row['Comment'];
4268
4269
            $indexes_data[$row['Key_name']][$row['Seq_in_index']]['Column_name']
4270
                = $row['Column_name'];
4271
            if (isset($row['Sub_part'])) {
4272
                $indexes_data[$row['Key_name']][$row['Seq_in_index']]['Sub_part']
4273
                    = $row['Sub_part'];
4274
            }
4275
        } // end while
4276
4277
        return [$primary, $pk_array, $indexes_info, $indexes_data];
4278
    }
4279
4280
    /**
4281
     * Function to get html for the start row and number of rows panel
4282
     *
4283
     * @param string $sql_query sql query
4284
     *
4285
     * @return string html
4286
     */
4287
    public static function getStartAndNumberOfRowsPanel($sql_query)
4288
    {
4289
        $template = new Template();
4290
4291
        if (isset($_REQUEST['session_max_rows'])) {
4292
            $rows = $_REQUEST['session_max_rows'];
4293
        } else if (isset($_SESSION['tmpval']['max_rows'])
4294
                    && $_SESSION['tmpval']['max_rows'] != 'all'
4295
        ) {
4296
            $rows = $_SESSION['tmpval']['max_rows'];
4297
        } else {
4298
            $rows = $GLOBALS['cfg']['MaxRows'];
4299
            $_SESSION['tmpval']['max_rows'] = $rows;
4300
        }
4301
4302
        if(isset($_REQUEST['pos'])) {
4303
            $pos = $_REQUEST['pos'];
4304
        } else if(isset($_SESSION['tmpval']['pos'])) {
4305
            $pos = $_SESSION['tmpval']['pos'];
4306
        } else {
4307
            $number_of_line = intval($_REQUEST['unlim_num_rows']);
4308
            $pos = ((ceil($number_of_line / $rows) - 1) * $rows);
4309
            $_SESSION['tmpval']['pos'] = $pos;
4310
        }
4311
4312
        return $template->render('start_and_number_of_rows_panel', [
4313
            'pos' => $pos,
4314
            'unlim_num_rows' => intval($_REQUEST['unlim_num_rows']),
4315
            'rows' => $rows,
4316
            'sql_query' => $sql_query,
4317
        ]);
4318
    }
4319
4320
    /**
4321
     * Returns whether the database server supports virtual columns
4322
     *
4323
     * @return bool
4324
     */
4325
    public static function isVirtualColumnsSupported()
4326
    {
4327
        $serverType = self::getServerType();
4328
        $serverVersion = $GLOBALS['dbi']->getVersion();
4329
        return $serverType == 'MySQL' && $serverVersion >= 50705
4330
             || ($serverType == 'MariaDB' && $serverVersion >= 50200);
4331
    }
4332
4333
    /**
4334
     * Returns the proper class clause according to the column type
4335
     *
4336
     * @param string $type the column type
4337
     *
4338
     * @return string $class_clause the HTML class clause
4339
     */
4340
    public static function getClassForType($type)
4341
    {
4342
        if ('set' == $type
4343
            || 'enum' == $type
4344
        ) {
4345
            $class_clause = '';
4346
        } else {
4347
            $class_clause = ' class="nowrap"';
4348
        }
4349
        return $class_clause;
4350
    }
4351
4352
    /**
4353
     * Gets the list of tables in the current db and information about these
4354
     * tables if possible
4355
     *
4356
     * @param string      $db       database name
4357
     * @param string|null $sub_part part of script name
4358
     *
4359
     * @return array
4360
     *
4361
     */
4362
    public static function getDbInfo($db, ?string $sub_part)
4363
    {
4364
        global $cfg;
4365
4366
        /**
4367
         * limits for table list
4368
         */
4369
        if (! isset($_SESSION['tmpval']['table_limit_offset'])
4370
            || $_SESSION['tmpval']['table_limit_offset_db'] != $db
4371
        ) {
4372
            $_SESSION['tmpval']['table_limit_offset'] = 0;
4373
            $_SESSION['tmpval']['table_limit_offset_db'] = $db;
4374
        }
4375
        if (isset($_REQUEST['pos'])) {
4376
            $_SESSION['tmpval']['table_limit_offset'] = (int) $_REQUEST['pos'];
4377
        }
4378
        $pos = $_SESSION['tmpval']['table_limit_offset'];
4379
4380
        /**
4381
         * whether to display extended stats
4382
         */
4383
        $is_show_stats = $cfg['ShowStats'];
4384
4385
        /**
4386
         * whether selected db is information_schema
4387
         */
4388
        $db_is_system_schema = false;
4389
4390
        if ($GLOBALS['dbi']->isSystemSchema($db)) {
4391
            $is_show_stats = false;
4392
            $db_is_system_schema = true;
4393
        }
4394
4395
        /**
4396
         * information about tables in db
4397
         */
4398
        $tables = [];
4399
4400
        $tooltip_truename = [];
4401
        $tooltip_aliasname = [];
4402
4403
        // Special speedup for newer MySQL Versions (in 4.0 format changed)
4404
        if (true === $cfg['SkipLockedTables']) {
4405
            $db_info_result = $GLOBALS['dbi']->query(
4406
                'SHOW OPEN TABLES FROM ' . self::backquote($db) . ' WHERE In_use > 0;'
4407
            );
4408
4409
            // Blending out tables in use
4410
            if ($db_info_result && $GLOBALS['dbi']->numRows($db_info_result) > 0) {
4411
                $tables = self::getTablesWhenOpen($db, $db_info_result);
4412
            } elseif ($db_info_result) {
4413
                $GLOBALS['dbi']->freeResult($db_info_result);
4414
            }
4415
        }
4416
4417
        if (empty($tables)) {
4418
            // Set some sorting defaults
4419
            $sort = 'Name';
4420
            $sort_order = 'ASC';
4421
4422
            if (isset($_REQUEST['sort'])) {
4423
                $sortable_name_mappings = [
4424
                    'table'       => 'Name',
4425
                    'records'     => 'Rows',
4426
                    'type'        => 'Engine',
4427
                    'collation'   => 'Collation',
4428
                    'size'        => 'Data_length',
4429
                    'overhead'    => 'Data_free',
4430
                    'creation'    => 'Create_time',
4431
                    'last_update' => 'Update_time',
4432
                    'last_check'  => 'Check_time',
4433
                    'comment'     => 'Comment',
4434
                ];
4435
4436
                // Make sure the sort type is implemented
4437
                if (isset($sortable_name_mappings[$_REQUEST['sort']])) {
4438
                    $sort = $sortable_name_mappings[$_REQUEST['sort']];
4439
                    if ($_REQUEST['sort_order'] == 'DESC') {
4440
                        $sort_order = 'DESC';
4441
                    }
4442
                }
4443
            }
4444
4445
            $groupWithSeparator = false;
4446
            $tbl_type = null;
4447
            $limit_offset = 0;
4448
            $limit_count = false;
4449
            $groupTable = [];
4450
4451
            if (! empty($_REQUEST['tbl_group']) || ! empty($_REQUEST['tbl_type'])) {
4452
                if (! empty($_REQUEST['tbl_type'])) {
4453
                    // only tables for selected type
4454
                    $tbl_type = $_REQUEST['tbl_type'];
4455
                }
4456
                if (! empty($_REQUEST['tbl_group'])) {
4457
                    // only tables for selected group
4458
                    $tbl_group = $_REQUEST['tbl_group'];
4459
                    // include the table with the exact name of the group if such
4460
                    // exists
4461
                    $groupTable = $GLOBALS['dbi']->getTablesFull(
4462
                        $db,
4463
                        $tbl_group,
4464
                        false,
4465
                        $limit_offset,
4466
                        $limit_count,
4467
                        $sort,
4468
                        $sort_order,
4469
                        $tbl_type
4470
                    );
4471
                    $groupWithSeparator = $tbl_group
4472
                        . $GLOBALS['cfg']['NavigationTreeTableSeparator'];
4473
                }
4474
            } else {
4475
                // all tables in db
4476
                // - get the total number of tables
4477
                //  (needed for proper working of the MaxTableList feature)
4478
                $tables = $GLOBALS['dbi']->getTables($db);
4479
                $total_num_tables = count($tables);
4480
                if (isset($sub_part) && $sub_part == '_export') {
4481
                    // (don't fetch only a subset if we are coming from
4482
                    // db_export.php, because I think it's too risky to display only
4483
                    // a subset of the table names when exporting a db)
4484
                    /**
4485
                     *
4486
                     * @todo Page selector for table names?
4487
                     */
4488
                } else {
4489
                    // fetch the details for a possible limited subset
4490
                    $limit_offset = $pos;
4491
                    $limit_count = true;
4492
                }
4493
            }
4494
            $tables = array_merge(
4495
                $groupTable,
4496
                $GLOBALS['dbi']->getTablesFull(
4497
                    $db,
4498
                    $groupWithSeparator,
4499
                    ($groupWithSeparator !== false),
4500
                    $limit_offset,
4501
                    $limit_count,
4502
                    $sort,
4503
                    $sort_order,
4504
                    $tbl_type
4505
                )
4506
            );
4507
        }
4508
4509
        $num_tables = count($tables);
4510
        //  (needed for proper working of the MaxTableList feature)
4511
        if (! isset($total_num_tables)) {
4512
            $total_num_tables = $num_tables;
4513
        }
4514
4515
        /**
4516
         * If coming from a Show MySQL link on the home page,
4517
         * put something in $sub_part
4518
         */
4519
        if (empty($sub_part)) {
4520
            $sub_part = '_structure';
4521
        }
4522
4523
        return [
4524
            $tables,
4525
            $num_tables,
4526
            $total_num_tables,
4527
            $sub_part,
4528
            $is_show_stats,
4529
            $db_is_system_schema,
4530
            $tooltip_truename,
4531
            $tooltip_aliasname,
4532
            $pos
4533
        ];
4534
    }
4535
4536
    /**
4537
     * Gets the list of tables in the current db, taking into account
4538
     * that they might be "in use"
4539
     *
4540
     * @param string $db             database name
4541
     * @param object $db_info_result result set
4542
     *
4543
     * @return array $tables list of tables
4544
     *
4545
     */
4546
    public static function getTablesWhenOpen($db, $db_info_result)
4547
    {
4548
        $sot_cache = [];
4549
        $tables = [];
4550
4551
        while ($tmp = $GLOBALS['dbi']->fetchAssoc($db_info_result)) {
4552
            $sot_cache[$tmp['Table']] = true;
4553
        }
4554
        $GLOBALS['dbi']->freeResult($db_info_result);
4555
4556
        // is there at least one "in use" table?
4557
        if (count($sot_cache) > 0) {
4558
            $tblGroupSql = "";
4559
            $whereAdded = false;
4560
            if (Core::isValid($_REQUEST['tbl_group'])) {
4561
                $group = self::escapeMysqlWildcards($_REQUEST['tbl_group']);
4562
                $groupWithSeparator = self::escapeMysqlWildcards(
4563
                    $_REQUEST['tbl_group']
4564
                    . $GLOBALS['cfg']['NavigationTreeTableSeparator']
4565
                );
4566
                $tblGroupSql .= " WHERE ("
4567
                    . self::backquote('Tables_in_' . $db)
4568
                    . " LIKE '" . $groupWithSeparator . "%'"
4569
                    . " OR "
4570
                    . self::backquote('Tables_in_' . $db)
4571
                    . " LIKE '" . $group . "')";
4572
                $whereAdded = true;
4573
            }
4574
            if (Core::isValid($_REQUEST['tbl_type'], ['table', 'view'])) {
4575
                $tblGroupSql .= $whereAdded ? " AND" : " WHERE";
4576
                if ($_REQUEST['tbl_type'] == 'view') {
4577
                    $tblGroupSql .= " `Table_type` != 'BASE TABLE'";
4578
                } else {
4579
                    $tblGroupSql .= " `Table_type` = 'BASE TABLE'";
4580
                }
4581
            }
4582
            $db_info_result = $GLOBALS['dbi']->query(
4583
                'SHOW FULL TABLES FROM ' . self::backquote($db) . $tblGroupSql,
4584
                null,
4585
                DatabaseInterface::QUERY_STORE
4586
            );
4587
            unset($tblGroupSql, $whereAdded);
4588
4589
            if ($db_info_result && $GLOBALS['dbi']->numRows($db_info_result) > 0) {
4590
                $names = [];
4591
                while ($tmp = $GLOBALS['dbi']->fetchRow($db_info_result)) {
4592
                    if (! isset($sot_cache[$tmp[0]])) {
4593
                        $names[] = $tmp[0];
4594
                    } else { // table in use
4595
                        $tables[$tmp[0]] = [
4596
                            'TABLE_NAME' => $tmp[0],
4597
                            'ENGINE' => '',
4598
                            'TABLE_TYPE' => '',
4599
                            'TABLE_ROWS' => 0,
4600
                            'TABLE_COMMENT' => '',
4601
                        ];
4602
                    }
4603
                } // end while
4604
                if (count($names) > 0) {
4605
                    $tables = array_merge(
4606
                        $tables,
4607
                        $GLOBALS['dbi']->getTablesFull($db, $names)
4608
                    );
4609
                }
4610
                if ($GLOBALS['cfg']['NaturalOrder']) {
4611
                    uksort($tables, 'strnatcasecmp');
4612
                }
4613
            } elseif ($db_info_result) {
4614
                $GLOBALS['dbi']->freeResult($db_info_result);
4615
            }
4616
            unset($sot_cache);
4617
        }
4618
        return $tables;
4619
    }
4620
4621
    /**
4622
     * Returs list of used PHP extensions.
4623
     *
4624
     * @return array of strings
4625
     */
4626
    public static function listPHPExtensions()
4627
    {
4628
        $result = [];
4629
        if (DatabaseInterface::checkDbExtension('mysqli')) {
4630
            $result[] = 'mysqli';
4631
        } else {
4632
            $result[] = 'mysql';
4633
        }
4634
4635
        if (extension_loaded('curl')) {
4636
            $result[] = 'curl';
4637
        }
4638
4639
        if (extension_loaded('mbstring')) {
4640
            $result[] = 'mbstring';
4641
        }
4642
4643
        return $result;
4644
    }
4645
4646
    /**
4647
     * Converts given (request) paramter to string
4648
     *
4649
     * @param mixed $value Value to convert
4650
     *
4651
     * @return string
4652
     */
4653
    public static function requestString($value)
4654
    {
4655
        while (is_array($value) || is_object($value)) {
4656
            $value = reset($value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type object; however, parameter $array of reset() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4656
            $value = reset(/** @scrutinizer ignore-type */ $value);
Loading history...
4657
        }
4658
        return trim((string)$value);
4659
    }
4660
4661
    /**
4662
     * Generates random string consisting of ASCII chars
4663
     *
4664
     * @param integer $length Length of string
4665
     *
4666
     * @return string
4667
     */
4668
    public static function generateRandom($length)
4669
    {
4670
        $result = '';
4671
        if (class_exists('phpseclib\\Crypt\\Random')) {
4672
            $random_func = ['phpseclib\\Crypt\\Random', 'string'];
4673
        } else {
4674
            $random_func = 'openssl_random_pseudo_bytes';
4675
        }
4676
        while (strlen($result) < $length) {
4677
            // Get random byte and strip highest bit
4678
            // to get ASCII only range
4679
            $byte = ord($random_func(1)) & 0x7f;
4680
            // We want only ASCII chars
4681
            if ($byte > 32) {
4682
                $result .= chr($byte);
4683
            }
4684
        }
4685
        return $result;
4686
    }
4687
4688
    /**
4689
     * Wraper around PHP date function
4690
     *
4691
     * @param string $format Date format string
4692
     *
4693
     * @return string
4694
     */
4695
    public static function date($format)
4696
    {
4697
        if (defined('TESTSUITE')) {
4698
            return '0000-00-00 00:00:00';
4699
        }
4700
        return date($format);
4701
    }
4702
4703
    /**
4704
     * Wrapper around php's set_time_limit
4705
     *
4706
     * @return void
4707
     */
4708
    public static function setTimeLimit()
4709
    {
4710
        // The function can be disabled in php.ini
4711
        if (function_exists('set_time_limit')) {
4712
            @set_time_limit($GLOBALS['cfg']['ExecTimeLimit']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for set_time_limit(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

4712
            /** @scrutinizer ignore-unhandled */ @set_time_limit($GLOBALS['cfg']['ExecTimeLimit']);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
4713
        }
4714
    }
4715
4716
    /**
4717
     * Access to a multidimensional array by dot notation
4718
     *
4719
     * @param array        $array   List of values
4720
     * @param string|array $path    Path to searched value
4721
     * @param mixed        $default Default value
4722
     *
4723
     * @return mixed Searched value
4724
     */
4725
    public static function getValueByKey(array $array, $path, $default = null)
4726
    {
4727
        if (is_string($path)) {
4728
            $path = explode('.', $path);
4729
        }
4730
        $p = array_shift($path);
4731
        while (isset($p)) {
4732
            if (!isset($array[$p])) {
4733
                return $default;
4734
            }
4735
            $array = $array[$p];
4736
            $p = array_shift($path);
4737
        }
4738
        return $array;
4739
    }
4740
4741
    /**
4742
     * Creates a clickable column header for table information
4743
     *
4744
     * @param string $title            Title to use for the link
4745
     * @param string $sort             Corresponds to sortable data name mapped
4746
     *                                 in Util::getDbInfo
4747
     * @param string $initialSortOrder Initial sort order
4748
     *
4749
     * @return string Link to be displayed in the table header
4750
     */
4751
    public static function sortableTableHeader($title, $sort, $initialSortOrder = 'ASC')
4752
    {
4753
        $requestedSort = 'table';
4754
        $requestedSortOrder = $futureSortOrder = $initialSortOrder;
4755
        // If the user requested a sort
4756
        if (isset($_REQUEST['sort'])) {
4757
            $requestedSort = $_REQUEST['sort'];
4758
            if (isset($_REQUEST['sort_order'])) {
4759
                $requestedSortOrder = $_REQUEST['sort_order'];
4760
            }
4761
        }
4762
        $orderImg = '';
4763
        $orderLinkParams = [];
4764
        $orderLinkParams['title'] = __('Sort');
4765
        // If this column was requested to be sorted.
4766
        if ($requestedSort == $sort) {
4767
            if ($requestedSortOrder == 'ASC') {
4768
                $futureSortOrder = 'DESC';
4769
                // current sort order is ASC
4770
                $orderImg = ' ' . self::getImage(
4771
                    's_asc',
4772
                    __('Ascending'),
4773
                    ['class' => 'sort_arrow', 'title' => '']
4774
                );
4775
                $orderImg .= ' ' . self::getImage(
4776
                    's_desc',
4777
                    __('Descending'),
4778
                    ['class' => 'sort_arrow hide', 'title' => '']
4779
                );
4780
                // but on mouse over, show the reverse order (DESC)
4781
                $orderLinkParams['onmouseover'] = "$('.sort_arrow').toggle();";
4782
                // on mouse out, show current sort order (ASC)
4783
                $orderLinkParams['onmouseout'] = "$('.sort_arrow').toggle();";
4784
            } else {
4785
                $futureSortOrder = 'ASC';
4786
                // current sort order is DESC
4787
                $orderImg = ' ' . self::getImage(
4788
                    's_asc',
4789
                    __('Ascending'),
4790
                    ['class' => 'sort_arrow hide', 'title' => '']
4791
                );
4792
                $orderImg .= ' ' . self::getImage(
4793
                    's_desc',
4794
                    __('Descending'),
4795
                    ['class' => 'sort_arrow', 'title' => '']
4796
                );
4797
                // but on mouse over, show the reverse order (ASC)
4798
                $orderLinkParams['onmouseover'] = "$('.sort_arrow').toggle();";
4799
                // on mouse out, show current sort order (DESC)
4800
                $orderLinkParams['onmouseout'] = "$('.sort_arrow').toggle();";
4801
            }
4802
        }
4803
        $urlParams = [
4804
            'db' => $_REQUEST['db'],
4805
            'pos' => 0, // We set the position back to 0 every time they sort.
4806
            'sort' => $sort,
4807
            'sort_order' => $futureSortOrder,
4808
        ];
4809
4810
        if (Core::isValid($_REQUEST['tbl_type'], ['view', 'table'])) {
4811
            $urlParams['tbl_type'] = $_REQUEST['tbl_type'];
4812
        }
4813
        if (! empty($_REQUEST['tbl_group'])) {
4814
            $urlParams['tbl_group'] = $_REQUEST['tbl_group'];
4815
        }
4816
4817
        $url = 'db_structure.php' . Url::getCommon($urlParams);
4818
4819
        return self::linkOrButton($url, $title . $orderImg, $orderLinkParams);
4820
    }
4821
}
4822