3 declare(strict_types
=1);
5 namespace PhpMyAdmin\Tests
;
8 use PhpMyAdmin\DatabaseInterface
;
9 use PhpMyAdmin\FieldMetadata
;
10 use PhpMyAdmin\MoTranslator\Loader
;
11 use PhpMyAdmin\ResponseRenderer
;
12 use PhpMyAdmin\SqlParser\Context
;
13 use PhpMyAdmin\SqlParser\Token
;
15 use PhpMyAdmin\Utils\SessionCache
;
16 use PhpMyAdmin\Version
;
19 use function _setlocale
;
21 use function date_default_timezone_get
;
22 use function date_default_timezone_set
;
23 use function file_exists
;
24 use function floatval
;
25 use function htmlspecialchars
;
28 use function str_repeat
;
29 use function str_replace
;
34 use const MYSQLI_NUM_FLAG
;
35 use const MYSQLI_PRI_KEY_FLAG
;
36 use const MYSQLI_TYPE_BIT
;
37 use const MYSQLI_TYPE_GEOMETRY
;
38 use const MYSQLI_TYPE_LONG
;
39 use const MYSQLI_TYPE_SHORT
;
40 use const MYSQLI_TYPE_STRING
;
41 use const MYSQLI_UNIQUE_KEY_FLAG
;
44 * @covers \PhpMyAdmin\Util
46 class UtilTest
extends AbstractTestCase
49 * init data for the test
51 protected function setUp(): void
54 parent
::setLanguage();
59 * Test for listPHPExtensions
61 * @requires extension mysqli
62 * @requires extension curl
63 * @requires extension mbstring
65 public function testListPHPExtensions(): void
73 Util
::listPHPExtensions()
77 public function testGetUniqueCondition(): void
79 $GLOBALS['db'] = 'db';
80 $GLOBALS['cfg']['Server']['DisableIS'] = false;
82 $actual = Util
::getUniqueCondition(0, [], []);
83 $this->assertEquals(['', false, []], $actual);
85 $actual = Util
::getUniqueCondition(0, [], [], true);
86 $this->assertEquals(['', true, []], $actual);
89 public function testGetUniqueConditionWithMultipleFields(): void
92 new FieldMetadata(MYSQLI_TYPE_STRING
, 0, (object) [
95 'orgtable' => 'table',
97 new FieldMetadata(MYSQLI_TYPE_STRING
, 0, (object) [
100 'orgtable' => 'table',
102 new FieldMetadata(MYSQLI_TYPE_SHORT
, MYSQLI_NUM_FLAG
, (object) [
105 'orgtable' => 'table',
107 new FieldMetadata(MYSQLI_TYPE_LONG
, MYSQLI_NUM_FLAG
, (object) [
110 'orgtable' => 'table',
112 new FieldMetadata(MYSQLI_TYPE_STRING
, 0, (object) [
115 'orgtable' => 'table',
116 'charsetnr' => 63, // binary
118 new FieldMetadata(MYSQLI_TYPE_STRING
, 0, (object) [
121 'orgtable' => 'table',
122 'charsetnr' => 63, // binary
124 new FieldMetadata(MYSQLI_TYPE_STRING
, 0, (object) [
127 'orgtable' => 'table',
130 'charsetnr' => 32, // armscii8_general_ci
132 new FieldMetadata(MYSQLI_TYPE_STRING
, 0, (object) [
135 'orgtable' => 'table',
138 'charsetnr' => 48, // latin1_general_ci
140 new FieldMetadata(MYSQLI_TYPE_STRING
, 0, (object) [
143 'orgtable' => 'table',
146 'charsetnr' => 63, // binary
148 new FieldMetadata(MYSQLI_TYPE_GEOMETRY
, 0, (object) [
151 'orgtable' => 'table',
153 new FieldMetadata(MYSQLI_TYPE_STRING
, 0, (object) [
156 'orgtable' => 'table2',
158 new FieldMetadata(MYSQLI_TYPE_BIT
, 0, (object) [
161 'orgtable' => 'table',
166 $actual = Util
::getUniqueCondition(count($meta), $meta, [
172 str_repeat('*', 1001),
182 '`table`.`field1` IS NULL AND `table`.`field2` = \'value\\\'s\' AND `table`.`field3` = 123456'
183 . ' AND `table`.`field4` = 123.456 AND `table`.`field5` = CAST(0x76616c7565 AS BINARY)'
184 . ' AND `table`.`field7` = \'value\' AND `table`.`field8` = \'value\''
185 . ' AND `table`.`field9` = CAST(0x76616c7565 AS BINARY)'
186 . ' AND `table`.`field10` =0x76616c7565 AND'
187 . ' AND `table`.`field12` = b\'0001\'',
190 '`table`.`field1`' => 'IS NULL',
191 '`table`.`field2`' => '= \'value\\\'s\'',
192 '`table`.`field3`' => '= 123456',
193 '`table`.`field4`' => '= 123.456',
194 '`table`.`field5`' => '= CAST(0x76616c7565 AS BINARY)',
195 '`table`.`field7`' => '= \'value\'',
196 '`table`.`field8`' => '= \'value\'',
197 '`table`.`field9`' => '= CAST(0x76616c7565 AS BINARY)',
198 '`table`.`field10`' => '',
199 '`table`.`field12`' => '= b\'0001\'',
206 public function testGetUniqueConditionWithSingleBigBinaryField(): void
209 new FieldMetadata(MYSQLI_TYPE_STRING
, 0, (object) [
212 'orgtable' => 'table',
213 'charsetnr' => 63, // binary
217 $actual = Util
::getUniqueCondition(1, $meta, [str_repeat('*', 1001)]);
219 ['CHAR_LENGTH(`table`.`field`) = 1001', false, ['`table`.`field`' => ' = 1001']],
224 public function testGetUniqueConditionWithPrimaryKey(): void
227 new FieldMetadata(MYSQLI_TYPE_LONG
, MYSQLI_PRI_KEY_FLAG | MYSQLI_NUM_FLAG
, (object) [
230 'orgtable' => 'table',
232 new FieldMetadata(MYSQLI_TYPE_STRING
, 0, (object) [
235 'orgtable' => 'table',
239 $actual = Util
::getUniqueCondition(count($meta), $meta, [1, 'value']);
240 $this->assertEquals(['`table`.`id` = 1', true, ['`table`.`id`' => '= 1']], $actual);
243 public function testGetUniqueConditionWithUniqueKey(): void
246 new FieldMetadata(MYSQLI_TYPE_STRING
, MYSQLI_UNIQUE_KEY_FLAG
, (object) [
249 'orgtable' => 'table',
251 new FieldMetadata(MYSQLI_TYPE_STRING
, 0, (object) [
254 'orgtable' => 'table',
258 $actual = Util
::getUniqueCondition(count($meta), $meta, ['unique', 'value']);
259 $this->assertEquals(['`table`.`id` = \'unique\'', true, ['`table`.`id`' => '= \'unique\'']], $actual);
263 * Test for Page Selector
265 public function testPageSelector(): void
267 $this->assertStringContainsString(
268 '<select class="pageselector ajax" name="pma" >',
269 Util
::pageselector('pma', 3)
272 // If pageNow > nbTotalPage, show the pageNow number to avoid confusion
273 $this->assertStringContainsString(
274 '<option selected="selected" style="font-weight: bold" value="297">100</option>',
275 Util
::pageselector('pma', 3, 100, 50)
280 * Test for getCharsetQueryPart
282 * @param string $collation Collation
283 * @param string $expected Expected Charset Query
285 * @dataProvider charsetQueryData
287 public function testGenerateCharsetQueryPart(string $collation, string $expected): void
291 Util
::getCharsetQueryPart($collation)
296 * Data Provider for testgetCharsetQueryPart
298 * @return array test data
300 public function charsetQueryData(): array
305 ' CHARSET=a COLLATE a_b_c_d',
309 ' CHARSET=a COLLATE a_',
319 * Test for random generation
321 public function testGenerateRandom(): void
323 $this->assertEquals(32, strlen(Util
::generateRandom(32)));
324 $this->assertEquals(16, strlen(Util
::generateRandom(16)));
327 public function testClearUserCache(): void
329 $GLOBALS['server'] = 'server';
330 SessionCache
::set('is_superuser', 'yes');
331 $this->assertEquals('yes', $_SESSION['cache']['server_server']['is_superuser']);
333 Util
::clearUserCache();
334 $this->assertArrayNotHasKey('is_superuser', $_SESSION['cache']['server_server']);
337 public function testCheckParameterMissing(): void
339 parent
::setGlobalConfig();
341 $GLOBALS['text_dir'] = 'ltr';
342 $GLOBALS['PMA_PHP_SELF'] = Core
::getenv('PHP_SELF');
343 $GLOBALS['db'] = 'db';
344 $GLOBALS['table'] = 'table';
345 $GLOBALS['server'] = 1;
346 $GLOBALS['cfg']['ServerDefault'] = 1;
347 $GLOBALS['cfg']['AllowThirdPartyFraming'] = false;
348 ResponseRenderer
::getInstance()->setAjax(false);
350 $this->expectOutputRegex('/Missing parameter: field/');
352 Util
::checkParameters(
361 public function testCheckParameter(): void
363 parent
::setGlobalConfig();
364 $GLOBALS['cfg'] = ['ServerDefault' => 1];
365 $GLOBALS['text_dir'] = 'ltr';
366 $GLOBALS['PMA_PHP_SELF'] = Core
::getenv('PHP_SELF');
367 $GLOBALS['db'] = 'dbDatabase';
368 $GLOBALS['table'] = 'tblTable';
369 $GLOBALS['field'] = 'test_field';
370 $GLOBALS['sql_query'] = 'SELECT * FROM tblTable;';
372 $this->expectOutputString('');
373 Util
::checkParameters(
384 * Test for Util::convertBitDefaultValue
386 * @param string|null $bit Value
387 * @param string $val Expected value
389 * @dataProvider providerConvertBitDefaultValue
391 public function testConvertBitDefaultValue(?
string $bit, string $val): void
395 Util
::convertBitDefaultValue($bit)
400 * Provider for testConvertBitDefaultValue
404 public function providerConvertBitDefaultValue(): array
423 'database name starting with b' => [
427 "database name containing b'" => [
431 'database name in single quotes' => [
435 "database name with multiple b'" => [
443 * data provider for testEscapeMysqlWildcards and testUnescapeMysqlWildcards
447 public function providerUnEscapeMysqlWildcards(): array
486 * PhpMyAdmin\Util::escapeMysqlWildcards tests
488 * @param string $a Expected value
489 * @param string $b String to escape
491 * @dataProvider providerUnEscapeMysqlWildcards
493 public function testEscapeMysqlWildcards(string $a, string $b): void
497 Util
::escapeMysqlWildcards($b)
502 * PhpMyAdmin\Util::unescapeMysqlWildcards tests
504 * @param string $a String to unescape
505 * @param string $b Expected value
507 * @dataProvider providerUnEscapeMysqlWildcards
509 public function testUnescapeMysqlWildcards(string $a, string $b): void
513 Util
::unescapeMysqlWildcards($a)
518 * Test case for expanding strings
520 * @param string $in string to evaluate
521 * @param string $out expected output
523 * @dataProvider providerExpandUserString
525 public function testExpandUserString(string $in, string $out): void
527 parent
::setGlobalConfig();
531 'verbose' => 'verbose',
534 $GLOBALS['db'] = 'database';
535 $GLOBALS['table'] = 'table';
539 Util
::expandUserString($in)
543 htmlspecialchars($out),
544 Util
::expandUserString(
552 * Data provider for testExpandUserString
556 public function providerExpandUserString(): array
581 'phpMyAdmin ' . Version
::VERSION
,
587 * Test case for parsing SHOW COLUMNS output
589 * @param string $in Column specification
590 * @param array $out Expected value
592 * @dataProvider providerExtractColumnSpec
594 public function testExtractColumnSpec(string $in, array $out): void
596 $GLOBALS['cfg']['LimitChars'] = 1000;
600 Util
::extractColumnSpec($in)
605 * Data provider for testExtractColumnSpec
609 public function providerExtractColumnSpec(): array
616 'print_type' => "set('a', 'b')",
620 'spec_in_brackets' => "'a','b'",
621 'enum_set_values' => [
626 'can_contain_collation' => true,
627 'displayed_type' => "set('a', 'b')",
634 'print_type' => "set('\'a', 'b')",
638 'spec_in_brackets' => "'\'a','b'",
639 'enum_set_values' => [
644 'can_contain_collation' => true,
645 'displayed_type' => "set('\'a', 'b')",
652 'print_type' => "set('''a', 'b')",
656 'spec_in_brackets' => "'''a','b'",
657 'enum_set_values' => [
662 'can_contain_collation' => true,
663 'displayed_type' => "set('''a', 'b')",
667 "ENUM('a&b', 'b''c\\'d', 'e\\\\f')",
670 'print_type' => "enum('a&b', 'b''c\\'d', 'e\\\\f')",
674 'spec_in_brackets' => "'a&b', 'b''c\\'d', 'e\\\\f'",
675 'enum_set_values' => [
681 'can_contain_collation' => true,
682 'displayed_type' => "enum('a&b', 'b''c\\'d', 'e\\\\f')",
686 'INT UNSIGNED zerofill',
689 'print_type' => 'int',
693 'spec_in_brackets' => '',
694 'enum_set_values' => [],
695 'attribute' => 'UNSIGNED ZEROFILL',
696 'can_contain_collation' => false,
697 'displayed_type' => 'int',
704 'print_type' => 'varchar(255)',
708 'spec_in_brackets' => '255',
709 'enum_set_values' => [],
711 'can_contain_collation' => true,
712 'displayed_type' => 'varchar(255)',
718 'type' => 'varbinary',
719 'print_type' => 'varbinary(255)',
723 'spec_in_brackets' => '255',
724 'enum_set_values' => [],
726 'can_contain_collation' => false,
727 'displayed_type' => 'varbinary(255)',
734 * Test for Util::extractValueFromFormattedSize
736 * @param int|string $size Size
737 * @param int|float $expected Expected value (float on some cpu architectures)
739 * @dataProvider providerExtractValueFromFormattedSize
741 public function testExtractValueFromFormattedSize($size, $expected): void
745 Util
::extractValueFromFormattedSize($size)
750 * Data provider for testExtractValueFromFormattedSize
754 public function providerExtractValueFromFormattedSize(): array
777 * format byte test, globals are defined
779 * @param float|int|string $a Value to format
780 * @param int $b Sensitiveness
781 * @param int $c Number of decimals to retain
782 * @param array $e Expected value
784 * @dataProvider providerFormatByteDown
786 public function testFormatByteDown($a, int $b, int $c, array $e): void
788 $result = Util
::formatByteDown($a, $b, $c);
789 $this->assertIsArray($result);
790 $result[0] = trim($result[0]);
791 $this->assertSame($e, $result);
795 * format byte down data provider
799 public function providerFormatByteDown(): array
947 floatval(52) +
floatval(2048),
956 '' . (floatval(52) +
floatval(2048)),
968 * Core test for formatNumber
970 * @param float|int|string $a Value to format
971 * @param int $b Sensitiveness
972 * @param int $c Number of decimals to retain
973 * @param string $d Expected value
975 private function assertFormatNumber($a, int $b, int $c, string $d): void
979 (string) Util
::formatNumber(
989 * format number test, globals are defined
991 * @param float|int|string $a Value to format
992 * @param int $b Sensitiveness
993 * @param int $c Number of decimals to retain
994 * @param string $d Expected value
996 * @dataProvider providerFormatNumber
998 public function testFormatNumber($a, int $b, int $c, string $d): void
1000 $this->assertFormatNumber($a, $b, $c, $d);
1002 // Test with various precisions
1003 $old_precision = (string) ini_get('precision');
1005 ini_set('precision', '20');
1006 $this->assertFormatNumber($a, $b, $c, $d);
1007 ini_set('precision', '14');
1008 $this->assertFormatNumber($a, $b, $c, $d);
1009 ini_set('precision', '10');
1010 $this->assertFormatNumber($a, $b, $c, $d);
1011 ini_set('precision', '5');
1012 $this->assertFormatNumber($a, $b, $c, $d);
1013 ini_set('precision', '-1');
1014 $this->assertFormatNumber($a, $b, $c, $d);
1016 ini_set('precision', $old_precision);
1019 // Test with different translations
1020 $translator = Loader
::getInstance()->getTranslator();
1024 $translator->setTranslation(',', '.');
1025 $translator->setTranslation('.', ',');
1026 $expected = str_replace([',', 'X'], ['.', ','], str_replace('.', 'X', $d));
1027 $this->assertFormatNumber($a, $b, $c, $expected);
1030 $translator->setTranslation(',', ' ');
1031 $translator->setTranslation('.', ',');
1032 $expected = str_replace([',', 'X'], [' ', ','], str_replace('.', 'X', $d));
1033 $this->assertFormatNumber($a, $b, $c, $expected);
1036 $translator->setTranslation(',', ',');
1037 $translator->setTranslation('.', '.');
1042 * format number data provider
1046 public function providerFormatNumber(): array
1161 * Test for Util::getFormattedMaximumUploadSize
1163 * @param int|float $size Size (float on some cpu architectures)
1164 * @param string $unit Unit
1165 * @param string $res Result
1167 * @dataProvider providerGetFormattedMaximumUploadSize
1169 public function testGetFormattedMaximumUploadSize($size, string $unit, string $res): void
1171 $this->assertEquals(
1172 '(' . __('Max: ') . $res . $unit . ')',
1173 Util
::getFormattedMaximumUploadSize($size)
1178 * Data provider for testGetFormattedMaximumUploadSize
1182 public function providerGetFormattedMaximumUploadSize(): array
1231 // Equals to Core::getRealSize of '102400K'
1232 // according to PHP FAQ on "shorthandbytes"
1241 * Test for Util::getTitleForTarget
1243 * @param string $target Target
1244 * @param string $result Expected value
1246 * @dataProvider providerGetTitleForTarget
1248 public function testGetTitleForTarget(string $target, string $result): void
1250 $this->assertEquals(
1252 Util
::getTitleForTarget($target)
1257 * Data provider for testGetTitleForTarget
1261 public function providerGetTitleForTarget(): array
1292 * localised date test, globals are defined
1294 * @param int $a Current timestamp
1295 * @param string $b Format
1296 * @param string $e Expected output
1297 * @param string $tz Timezone to set
1298 * @param string $locale Locale to set
1300 * @dataProvider providerLocalisedDate
1302 public function testLocalisedDate(int $a, string $b, string $e, string $tz, string $locale): void
1304 // A test case for #15830 could be added for using the php setlocale on a Windows CI
1305 // See https://github.com/phpmyadmin/phpmyadmin/issues/15830
1306 _setlocale(LC_ALL
, $locale);
1307 $tmpTimezone = date_default_timezone_get();
1308 date_default_timezone_set($tz);
1310 $this->assertEquals(
1312 Util
::localisedDate($a, $b)
1315 date_default_timezone_set($tmpTimezone);
1316 _setlocale(LC_ALL
, 'en');
1320 * data provider for localised date test
1324 public function providerLocalisedDate(): array
1326 $hasJaTranslations = file_exists(LOCALE_PATH
. '/cs/LC_MESSAGES/phpmyadmin.mo');
1332 'Nov 23, 2008 at 03:52 PM',
1338 '%Y-%m-%d %H:%M:%S %a',
1339 '2008-11-23 15:52:38 Sun',
1345 '%Y-%m-%d %H:%M:%S %a',
1346 '2008-11-23 16:52:38 Sun',
1352 '%Y-%m-%d %H:%M:%S %a',
1353 '2008-11-24 00:52:38 Mon',
1367 'Mon Mon Nov Nov AM',
1373 '%Y-%m-%d %H:%M:%S %a',
1374 $hasJaTranslations ?
'2008-11-24 00:52:38 月' : '2008-11-24 00:52:38 Mon',
1381 $hasJaTranslations ?
'月 月 11 月 11 月' : 'Mon Mon Nov Nov',
1388 $hasJaTranslations ?
'月 月 11 月 11 月 午前' : 'Mon Mon Nov Nov AM',
1401 '%Y 年 2 月 %d 日 %H:%M',
1402 '2008 年 2 月 24 日 00:52',
1408 '%Y 年 2 � %d 日 %H:%M',
1409 '2008 年 2 � 24 日 00:52',
1416 'H:i:s Y-d-m',// Not a valid strftime format
1423 'mer. 31 mars 2021 à 03:25',// No format uses format "%B %d, %Y at %I:%M %p"
1431 * localised timestamp test, globals are defined
1433 * @param int $a Timespan in seconds
1434 * @param string $e Expected output
1436 * @dataProvider providerTimespanFormat
1438 public function testTimespanFormat(int $a, string $e): void
1440 $GLOBALS['timespanfmt'] = '%s days, %s hours, %s minutes and %s seconds';
1441 $tmpTimezone = date_default_timezone_get();
1442 date_default_timezone_set('Europe/London');
1444 $this->assertEquals(
1446 Util
::timespanFormat($a)
1449 date_default_timezone_set($tmpTimezone);
1453 * data provider for localised timestamp test
1457 public function providerTimespanFormat(): array
1462 '0 days, 0 hours, 20 minutes and 58 seconds',
1466 '9 days, 12 hours, 19 minutes and 18 seconds',
1472 * test for generating string contains printable bit value of selected data
1474 * @param int $a Value
1475 * @param int $b Length
1476 * @param string $e Expected output
1478 * @dataProvider providerPrintableBitValue
1480 public function testPrintableBitValue(int $a, int $b, string $e): void
1482 $this->assertEquals(
1484 Util
::printableBitValue($a, $b)
1489 * data provider for printable bit value test
1493 public function providerPrintableBitValue(): array
1499 '0000000000000000000000000000000000000001001100110010110011000001',
1504 '00000000000000000000000000000101',
1510 * PhpMyAdmin\Util::unQuote test
1512 * @param string $param String
1513 * @param string $expected Expected output
1515 * @dataProvider providerUnQuote
1517 public function testUnQuote(string $param, string $expected): void
1519 $this->assertEquals(
1521 Util
::unQuote($param)
1526 * data provider for PhpMyAdmin\Util::unQuote test
1530 public function providerUnQuote(): array
1553 * PhpMyAdmin\Util::unQuote test with chosen quote
1555 * @param string $param String
1556 * @param string $expected Expected output
1558 * @dataProvider providerUnQuoteSelectedChar
1560 public function testUnQuoteSelectedChar(string $param, string $expected): void
1562 $this->assertEquals(
1564 Util
::unQuote($param, '"')
1569 * data provider for PhpMyAdmin\Util::unQuote test with chosen quote
1573 public function providerUnQuoteSelectedChar(): array
1596 * @dataProvider providerForTestBackquote
1598 public function testBackquote(?
string $entry, string $expectedNoneOutput, string $expectedMssqlOutput): void
1600 $this->assertSame($expectedNoneOutput, Util
::backquote($entry));
1601 $this->assertEquals($entry, Util
::backquoteCompat($entry, 'NONE', false));
1602 $this->assertEquals($entry, Util
::backquoteCompat($entry, 'MSSQL', false));
1603 $this->assertSame($expectedNoneOutput, Util
::backquoteCompat($entry, 'NONE'));
1604 $this->assertSame($expectedMssqlOutput, Util
::backquoteCompat($entry, 'MSSQL'));
1608 * @return array<int|string, string|null>[]
1610 public function providerForTestBackquote(): array
1652 * backquoteCompat test with forbidden words
1654 public function testBackquoteForbidenWords(): void
1656 foreach (Context
::$KEYWORDS as $keyword => $type) {
1657 if ($type & Token
::FLAG_KEYWORD_RESERVED
) {
1658 $this->assertEquals(
1659 '`' . $keyword . '`',
1660 Util
::backquoteCompat($keyword, 'NONE', false)
1663 $this->assertEquals(
1665 Util
::backquoteCompat($keyword, 'NONE', false)
1672 * test of generating user dir, globals are defined
1674 * @param string $a String
1675 * @param string $e Expected output
1677 * @dataProvider providerUserDir
1679 public function testUserDir(string $a, string $e): void
1681 $GLOBALS['cfg']['Server']['user'] = 'root';
1683 $this->assertEquals($e, Util
::userDir($a));
1687 * data provider for PhpMyAdmin\Util::userDir test
1691 public function providerUserDir(): array
1696 '/var/pma_tmp/root/',
1706 * duplicate first newline test
1708 * @param string $a String
1709 * @param string $e Expected output
1711 * @dataProvider providerDuplicateFirstNewline
1713 public function testDuplicateFirstNewline(string $a, string $e): void
1715 $this->assertEquals(
1717 Util
::duplicateFirstNewline($a)
1722 * data provider for duplicate first newline test
1726 public function providerDuplicateFirstNewline(): array
1748 public function testUnsupportedDatatypes(): void
1750 $no_support_types = [];
1751 $this->assertEquals(
1753 Util
::unsupportedDatatypes()
1757 public function testGetPageFromPosition(): void
1759 $this->assertEquals(Util
::getPageFromPosition(0, 1), 1);
1760 $this->assertEquals(Util
::getPageFromPosition(1, 1), 2);
1761 $this->assertEquals(Util
::getPageFromPosition(1, 2), 1);
1762 $this->assertEquals(Util
::getPageFromPosition(1, 6), 1);
1766 * Test for Util::isInteger
1768 * @param bool $expected Expected result for a given input
1769 * @param mixed $input Input data to check
1771 * @dataProvider providerIsInteger
1773 public function testIsInteger(bool $expected, $input): void
1775 $isInteger = Util
::isInteger($input);
1776 $this->assertEquals($expected, $isInteger);
1780 * Data provider for Util::isInteger test
1784 public function providerIsInteger(): array
1811 * Test for Util::getProtoFromForwardedHeader
1813 * @param string $header The http Forwarded header
1814 * @param string $proto The protocol http/https
1816 * @dataProvider providerForwardedHeaders
1818 public function testGetProtoFromForwardedHeader(string $header, string $proto): void
1820 $protocolDetected = Util
::getProtoFromForwardedHeader($header);
1821 $this->assertEquals($proto, $protocolDetected);
1825 * Data provider for Util::getProtoFromForwardedHeader test
1829 * @source https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded MDN docs
1830 * @source https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/ Nginx docs
1832 public function providerForwardedHeaders(): array
1860 'For="[2001:db8:cafe::17]:4711"',
1864 'for=192.0.2.60;proto=http;by=203.0.113.43',
1868 'for=192.0.2.43, for=198.51.100.17',
1872 'for=123.34.567.89',
1876 'for=192.0.2.43, for="[2001:db8:cafe::17]"',
1880 'for=12.34.56.78;host=example.com;proto=https, for=23.45.67.89',
1884 'for=12.34.56.78, for=23.45.67.89;secret=egah2CGj55fSJFs, for=10.1.2.3',
1888 'for=injected;by="',
1892 'for=injected;by=", for=real',
1896 'for=192.0.2.60;proto=http;by=203.0.113.43',
1900 'for=192.0.2.60;proto=htTp;by=203.0.113.43',
1904 'for=192.0.2.60;proto=HTTP;by=203.0.113.43',
1908 'for=192.0.2.60;proto= http;by=203.0.113.43',
1912 'for=12.34.45.67;secret="special;proto=abc;test=1";proto=http,for=23.45.67.89',
1916 'for=12.34.45.67;secret="special;proto=abc;test=1";proto=418,for=23.45.67.89',
1919 /*[ // this test case is very special and would need a different implementation
1920 'for=12.34.45.67;secret="special;proto=http;test=1";proto=https,for=23.45.67.89',
1926 public function testCurrentUserHasPrivilegeSkipGrantTables(): void
1928 $dbi = $this->getMockBuilder(DatabaseInterface
::class)
1929 ->disableOriginalConstructor()
1931 $dbi->expects($this->once())
1932 ->method('getCurrentUserAndHost')
1933 ->will($this->returnValue(['', '']));
1935 $oldDbi = $GLOBALS['dbi'];
1936 $GLOBALS['dbi'] = $dbi;
1937 $this->assertTrue(Util
::currentUserHasPrivilege('EVENT'));
1938 $GLOBALS['dbi'] = $oldDbi;
1941 public function testCurrentUserHasUserPrivilege(): void
1943 $dbi = $this->getMockBuilder(DatabaseInterface
::class)
1944 ->disableOriginalConstructor()
1946 $dbi->expects($this->once())
1947 ->method('getCurrentUserAndHost')
1948 ->will($this->returnValue(['groot_%', '%']));
1949 $dbi->expects($this->once())
1950 ->method('fetchValue')
1952 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES`'
1953 . " WHERE GRANTEE='''groot_%''@''%''' AND PRIVILEGE_TYPE='EVENT'"
1955 ->will($this->returnValue('EVENT'));
1957 $oldDbi = $GLOBALS['dbi'];
1958 $GLOBALS['dbi'] = $dbi;
1959 $this->assertTrue(Util
::currentUserHasPrivilege('EVENT'));
1960 $GLOBALS['dbi'] = $oldDbi;
1963 public function testCurrentUserHasNotUserPrivilege(): void
1965 $dbi = $this->getMockBuilder(DatabaseInterface
::class)
1966 ->disableOriginalConstructor()
1968 $dbi->expects($this->once())
1969 ->method('getCurrentUserAndHost')
1970 ->will($this->returnValue(['groot_%', '%']));
1971 $dbi->expects($this->once())
1972 ->method('fetchValue')
1974 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES`'
1975 . " WHERE GRANTEE='''groot_%''@''%''' AND PRIVILEGE_TYPE='EVENT'"
1977 ->will($this->returnValue(false));
1979 $oldDbi = $GLOBALS['dbi'];
1980 $GLOBALS['dbi'] = $dbi;
1981 $this->assertFalse(Util
::currentUserHasPrivilege('EVENT'));
1982 $GLOBALS['dbi'] = $oldDbi;
1985 public function testCurrentUserHasNotUserPrivilegeButDbPrivilege(): void
1987 $dbi = $this->getMockBuilder(DatabaseInterface
::class)
1988 ->onlyMethods(['getCurrentUserAndHost', 'fetchValue'])
1989 ->disableOriginalConstructor()
1992 $dbi->expects($this->once())
1993 ->method('getCurrentUserAndHost')
1994 ->will($this->returnValue(['groot_%', '%']));
1995 $dbi->expects($this->exactly(2))
1996 ->method('fetchValue')
1999 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES`'
2000 . " WHERE GRANTEE='''groot_%''@''%''' AND PRIVILEGE_TYPE='EVENT'",
2003 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES`'
2004 . " WHERE GRANTEE='''groot_%''@''%''' AND PRIVILEGE_TYPE='EVENT'"
2005 . " AND 'my_data_base' LIKE `TABLE_SCHEMA`",
2008 ->willReturnOnConsecutiveCalls(false, 'EVENT');
2010 $oldDbi = $GLOBALS['dbi'];
2011 $GLOBALS['dbi'] = $dbi;
2012 $this->assertTrue(Util
::currentUserHasPrivilege('EVENT', 'my_data_base'));
2013 $GLOBALS['dbi'] = $oldDbi;
2016 public function testCurrentUserHasNotUserPrivilegeAndNotDbPrivilege(): void
2018 $dbi = $this->getMockBuilder(DatabaseInterface
::class)
2019 ->onlyMethods(['getCurrentUserAndHost', 'fetchValue'])
2020 ->disableOriginalConstructor()
2023 $dbi->expects($this->once())
2024 ->method('getCurrentUserAndHost')
2025 ->will($this->returnValue(['groot_%', '%']));
2026 $dbi->expects($this->exactly(2))
2027 ->method('fetchValue')
2030 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES`'
2031 . " WHERE GRANTEE='''groot_%''@''%''' AND PRIVILEGE_TYPE='EVENT'",
2034 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES`'
2035 . " WHERE GRANTEE='''groot_%''@''%''' AND PRIVILEGE_TYPE='EVENT'"
2036 . " AND 'my_data_base' LIKE `TABLE_SCHEMA`",
2039 ->willReturnOnConsecutiveCalls(false, false);
2041 $oldDbi = $GLOBALS['dbi'];
2042 $GLOBALS['dbi'] = $dbi;
2043 $this->assertFalse(Util
::currentUserHasPrivilege('EVENT', 'my_data_base'));
2044 $GLOBALS['dbi'] = $oldDbi;
2047 public function testCurrentUserHasNotUserPrivilegeAndNotDbPrivilegeButTablePrivilege(): void
2049 $dbi = $this->getMockBuilder(DatabaseInterface
::class)
2050 ->onlyMethods(['getCurrentUserAndHost', 'fetchValue'])
2051 ->disableOriginalConstructor()
2054 $dbi->expects($this->once())
2055 ->method('getCurrentUserAndHost')
2056 ->will($this->returnValue(['groot_%', '%']));
2057 $dbi->expects($this->exactly(3))
2058 ->method('fetchValue')
2061 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES`'
2062 . " WHERE GRANTEE='''groot_%''@''%''' AND PRIVILEGE_TYPE='EVENT'",
2065 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES`'
2066 . " WHERE GRANTEE='''groot_%''@''%''' AND PRIVILEGE_TYPE='EVENT'"
2067 . " AND 'my_data_base' LIKE `TABLE_SCHEMA`",
2070 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`TABLE_PRIVILEGES`'
2071 . " WHERE GRANTEE='''groot_%''@''%''' AND PRIVILEGE_TYPE='EVENT'"
2072 . " AND 'my_data_base' LIKE `TABLE_SCHEMA` AND TABLE_NAME='my_data_table'",
2075 ->willReturnOnConsecutiveCalls(false, false, 'EVENT');
2077 $oldDbi = $GLOBALS['dbi'];
2078 $GLOBALS['dbi'] = $dbi;
2079 $this->assertTrue(Util
::currentUserHasPrivilege('EVENT', 'my_data_base', 'my_data_table'));
2080 $GLOBALS['dbi'] = $oldDbi;
2083 public function testCurrentUserHasNotUserPrivilegeAndNotDbPrivilegeAndNotTablePrivilege(): void
2085 $dbi = $this->getMockBuilder(DatabaseInterface
::class)
2086 ->onlyMethods(['getCurrentUserAndHost', 'fetchValue'])
2087 ->disableOriginalConstructor()
2090 $dbi->expects($this->once())
2091 ->method('getCurrentUserAndHost')
2092 ->will($this->returnValue(['groot_%', '%']));
2093 $dbi->expects($this->exactly(3))
2094 ->method('fetchValue')
2097 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES`'
2098 . " WHERE GRANTEE='''groot_%''@''%''' AND PRIVILEGE_TYPE='EVENT'",
2101 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`SCHEMA_PRIVILEGES`'
2102 . " WHERE GRANTEE='''groot_%''@''%''' AND PRIVILEGE_TYPE='EVENT'"
2103 . " AND 'my_data_base' LIKE `TABLE_SCHEMA`",
2106 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`TABLE_PRIVILEGES`'
2107 . " WHERE GRANTEE='''groot_%''@''%''' AND PRIVILEGE_TYPE='EVENT'"
2108 . " AND 'my_data_base' LIKE `TABLE_SCHEMA` AND TABLE_NAME='my_data_table'",
2111 ->willReturnOnConsecutiveCalls(false, false, false);
2113 $oldDbi = $GLOBALS['dbi'];
2114 $GLOBALS['dbi'] = $dbi;
2115 $this->assertFalse(Util
::currentUserHasPrivilege('EVENT', 'my_data_base', 'my_data_table'));
2116 $GLOBALS['dbi'] = $oldDbi;
2122 public function dataProviderScriptNames(): array
2129 'structure', // Notice the typo on db_structure.php
2131 'index.php?route=/&lang=en', // Fallback to the base route
2134 'db_structures.php', // Notice the typo on databases
2136 'index.php?route=/&lang=en', // Fallback to the base route
2139 'tbl_structure.php', // Support the legacy value
2141 'index.php?route=/table/structure&lang=en',
2146 'index.php?route=/table/structure&lang=en',
2149 'tbl_sql.php', // Support the legacy value
2151 'index.php?route=/table/sql&lang=en',
2156 'index.php?route=/table/sql&lang=en',
2159 'tbl_select.php', // Support the legacy value
2161 'index.php?route=/table/search&lang=en',
2166 'index.php?route=/table/search&lang=en',
2169 'tbl_change.php', // Support the legacy value
2171 'index.php?route=/table/change&lang=en',
2176 'index.php?route=/table/change&lang=en',
2179 'sql.php', // Support the legacy value
2181 'index.php?route=/sql&lang=en',
2186 'index.php?route=/sql&lang=en',
2189 'db_structure.php', // Support the legacy value
2191 'index.php?route=/database/structure&lang=en',
2196 'index.php?route=/database/structure&lang=en',
2199 'db_sql.php', // Support the legacy value
2201 'index.php?route=/database/sql&lang=en',
2206 'index.php?route=/database/sql&lang=en',
2209 'db_search.php', // Support the legacy value
2211 'index.php?route=/database/search&lang=en',
2216 'index.php?route=/database/search&lang=en',
2219 'db_operations.php', // Support the legacy value
2221 'index.php?route=/database/operations&lang=en',
2226 'index.php?route=/database/operations&lang=en',
2229 'index.php', // Support the legacy value
2231 'index.php?route=/&lang=en',
2236 'index.php?route=/&lang=en',
2239 'server_databases.php', // Support the legacy value
2241 'index.php?route=/server/databases&lang=en',
2246 'index.php?route=/server/databases&lang=en',
2249 'server_status.php', // Support the legacy value
2251 'index.php?route=/server/status&lang=en',
2256 'index.php?route=/server/status&lang=en',
2259 'server_variables.php', // Support the legacy value
2261 'index.php?route=/server/variables&lang=en',
2266 'index.php?route=/server/variables&lang=en',
2269 'server_privileges.php', // Support the legacy value
2271 'index.php?route=/server/privileges&lang=en',
2276 'index.php?route=/server/privileges&lang=en',
2282 * @dataProvider dataProviderScriptNames
2284 public function testGetScriptNameForOption(string $target, string $location, string $finalLink): void
2288 Util
::getScriptNameForOption($target, $location)