1<?php
2
3declare(strict_types=1);
4
5namespace PhpMyAdmin\Query;
6
7use PhpMyAdmin\Error;
8use PhpMyAdmin\Url;
9use function array_slice;
10use function debug_backtrace;
11use function explode;
12use function htmlspecialchars;
13use function intval;
14use function md5;
15use function sprintf;
16use function strcasecmp;
17use function strnatcasecmp;
18use function strpos;
19use function strtolower;
20
21/**
22 * Some helfull functions for common tasks related to SQL results
23 */
24class Utilities
25{
26    /**
27     * Get the list of system schemas
28     *
29     * @return string[] list of system schemas
30     */
31    public static function getSystemSchemas(): array
32    {
33        $schemas = [
34            'information_schema',
35            'performance_schema',
36            'mysql',
37            'sys',
38        ];
39        $systemSchemas = [];
40        foreach ($schemas as $schema) {
41            if (! self::isSystemSchema($schema, true)) {
42                continue;
43            }
44
45            $systemSchemas[] = $schema;
46        }
47
48        return $systemSchemas;
49    }
50
51    /**
52     * Checks whether given schema is a system schema
53     *
54     * @param string $schema_name        Name of schema (database) to test
55     * @param bool   $testForMysqlSchema Whether 'mysql' schema should
56     *                                   be treated the same as IS and DD
57     */
58    public static function isSystemSchema(
59        string $schema_name,
60        bool $testForMysqlSchema = false
61    ): bool {
62        $schema_name = strtolower($schema_name);
63
64        $isMySqlSystemSchema = $schema_name === 'mysql' && $testForMysqlSchema;
65
66        return $schema_name === 'information_schema'
67            || $schema_name === 'performance_schema'
68            || $isMySqlSystemSchema
69            || $schema_name === 'sys';
70    }
71
72    /**
73     * Formats database error message in a friendly way.
74     * This is needed because some errors messages cannot
75     * be obtained by mysql_error().
76     *
77     * @param int    $error_number  Error code
78     * @param string $error_message Error message as returned by server
79     *
80     * @return string HML text with error details
81     */
82    public static function formatError(int $error_number, string $error_message): string
83    {
84        $error_message = htmlspecialchars($error_message);
85
86        $error = '#' . ((string) $error_number);
87        $separator = ' &mdash; ';
88
89        if ($error_number == 2002) {
90            $error .= ' - ' . $error_message;
91            $error .= $separator;
92            $error .= __(
93                'The server is not responding (or the local server\'s socket'
94                . ' is not correctly configured).'
95            );
96        } elseif ($error_number == 2003) {
97            $error .= ' - ' . $error_message;
98            $error .= $separator . __('The server is not responding.');
99        } elseif ($error_number == 1698) {
100            $error .= ' - ' . $error_message;
101            $error .= $separator . '<a href="' . Url::getFromRoute('/logout') . '" class="disableAjax">';
102            $error .= __('Logout and try as another user.') . '</a>';
103        } elseif ($error_number == 1005) {
104            if (strpos($error_message, 'errno: 13') !== false) {
105                $error .= ' - ' . $error_message;
106                $error .= $separator
107                    . __(
108                        'Please check privileges of directory containing database.'
109                    );
110            } else {
111                /**
112                 * InnoDB constraints, see
113                 * https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html
114                 */
115                $error .= ' - ' . $error_message .
116                    ' (<a href="' .
117                    Url::getFromRoute('/server/engines/InnoDB/Status') .
118                    '">' . __('Details…') . '</a>)';
119            }
120        } else {
121            $error .= ' - ' . $error_message;
122        }
123
124        return $error;
125    }
126
127    /**
128     * usort comparison callback
129     *
130     * @param array  $a         first argument to sort
131     * @param array  $b         second argument to sort
132     * @param string $sortBy    Key to sort by
133     * @param string $sortOrder The order (ASC/DESC)
134     *
135     * @return int  a value representing whether $a should be before $b in the
136     *              sorted array or not
137     */
138    public static function usortComparisonCallback(array $a, array $b, string $sortBy, string $sortOrder): int
139    {
140        global $cfg;
141
142        /* No sorting when key is not present */
143        if (! isset($a[$sortBy], $b[$sortBy])
144        ) {
145            return 0;
146        }
147
148        // produces f.e.:
149        // return -1 * strnatcasecmp($a['SCHEMA_TABLES'], $b['SCHEMA_TABLES'])
150        $compare = $cfg['NaturalOrder'] ? strnatcasecmp(
151            $a[$sortBy],
152            $b[$sortBy]
153        ) : strcasecmp(
154            $a[$sortBy],
155            $b[$sortBy]
156        );
157
158        return ($sortOrder === 'ASC' ? 1 : -1) * $compare;
159    }
160
161    /**
162     * Convert version string to integer.
163     *
164     * @param string $version MySQL server version
165     */
166    public static function versionToInt(string $version): int
167    {
168        $match = explode('.', $version);
169
170        return (int) sprintf('%d%02d%02d', $match[0], $match[1], intval($match[2]));
171    }
172
173    /**
174     * Stores query data into session data for debugging purposes
175     *
176     * @param string      $query        Query text
177     * @param string|null $errorMessage Error message from getError()
178     * @param object|bool $result       Query result
179     * @param int|float   $time         Time to execute query
180     */
181    public static function debugLogQueryIntoSession(string $query, ?string $errorMessage, $result, $time): void
182    {
183        $dbgInfo = [];
184
185        if ($result === false && $errorMessage !== null) {
186            $dbgInfo['error']
187                = '<span class="color_red">'
188                . htmlspecialchars($errorMessage) . '</span>';
189        }
190        $dbgInfo['query'] = htmlspecialchars($query);
191        $dbgInfo['time'] = $time;
192        // Get and slightly format backtrace, this is used
193        // in the javascript console.
194        // Strip call to debugLogQueryIntoSession
195        $dbgInfo['trace'] = Error::processBacktrace(
196            array_slice(debug_backtrace(), 1)
197        );
198        $dbgInfo['hash'] = md5($query);
199
200        $_SESSION['debug']['queries'][] = $dbgInfo;
201    }
202}
203