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 .= ' '; |
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; |
|
|
|
|
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
|
|
|
[' ', ' '], |
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; |
|
|
|
|
713
|
|
|
} |
714
|
|
|
|
715
|
|
|
if (!empty($back_url)) { |
716
|
|
|
if (mb_strstr($back_url, '?')) { |
717
|
|
|
$back_url .= '&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); |
|
|
|
|
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" . ' . "'; |
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 = ' [ ' |
1097
|
|
|
. self::linkOrButton( |
1098
|
|
|
'import.php' . Url::getCommon($explain_params), |
1099
|
|
|
__('Explain SQL') |
1100
|
|
|
) . ' ]'; |
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 = ' [ ' |
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
|
|
|
) . ' ]'; |
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 = ' [ ' |
1135
|
|
|
. self::linkOrButton($edit_link, __('Edit')) |
1136
|
|
|
. ' ]'; |
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 = ' [ ' |
1146
|
|
|
. self::linkOrButton( |
1147
|
|
|
'import.php' . Url::getCommon($url_params), |
1148
|
|
|
__('Without PHP code') |
1149
|
|
|
) |
1150
|
|
|
. ' ]'; |
1151
|
|
|
|
1152
|
|
|
$php_link .= ' [ ' |
1153
|
|
|
. self::linkOrButton( |
1154
|
|
|
'import.php' . Url::getCommon($url_params), |
1155
|
|
|
__('Submit query') |
1156
|
|
|
) |
1157
|
|
|
. ' ]'; |
1158
|
|
|
} else { |
1159
|
|
|
$php_params = $url_params; |
1160
|
|
|
$php_params['show_as_php'] = 1; |
1161
|
|
|
$php_link = ' [ ' |
1162
|
|
|
. self::linkOrButton( |
1163
|
|
|
'import.php' . Url::getCommon($php_params), |
1164
|
|
|
__('Create PHP code') |
1165
|
|
|
) |
1166
|
|
|
. ' ]'; |
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 = ' [ ' |
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) { |
|
|
|
|
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 => 'µ', |
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) . '.' |
|
|
|
|
2036
|
|
|
. self::backquote($meta->orgname) . ')'; |
|
|
|
|
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) { |
|
|
|
|
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 |
|
|
|
|
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 .= '<< '; |
2381
|
|
|
$caption2 .= '< '; |
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, |
|
|
|
|
2409
|
|
|
ceil($count / $max_count) |
|
|
|
|
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 .= ' >'; |
2422
|
|
|
$caption4 .= ' >>'; |
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'], |
|
|
|
|
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&" . 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('–','“') |
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); |
|
|
|
|
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; |
|
|
|
|
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); |
|
|
|
|
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" |
|
|
|
|
3332
|
|
|
// some browsers should respect this :) |
3333
|
|
|
. self::generateHiddenMaxFileSize($max_upload_size) . "\n"; |
|
|
|
|
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) { |
|
|
|
|
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=""> </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 .= "\"; |
3984
|
|
|
$i++; |
3985
|
|
|
} elseif (($in_string && $next == "'") |
3986
|
|
|
&& ($curr == "'" || $curr == "\\") |
3987
|
|
|
) { |
3988
|
|
|
$buffer .= "'"; |
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) { |
|
|
|
|
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); |
|
|
|
|
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']); |
|
|
|
|
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
|
|
|
|