setLanguage(); Current::$server = 2; Current::$database = 'db'; Current::$table = 'table'; $config = Config::$instance = new Config(); $config->selectedServer['DisableIS'] = false; $this->dummyDbi = $this->createDbiDummy(); $this->dbi = $this->createDatabaseInterface($this->dummyDbi, $config); DatabaseInterface::$instance = $this->dbi; $this->object = new DisplayResults($this->dbi, $config, 'as', '', 2, '', ''); $_SESSION[' HMAC_secret '] = 'test'; } /** * Test for isSelect function */ public function testisSelect(): void { self::assertTrue( (new ReflectionMethod(DisplayResults::class, 'isSelect')) ->invokeArgs($this->object, [Query::getAll('SELECT * FROM pma')]), ); } public function testGetClassForDateTimeRelatedFieldsCase1(): void { self::assertSame( 'datetimefield', (new ReflectionMethod(DisplayResults::class, 'getClassForDateTimeRelatedFields')) ->invokeArgs($this->object, [FieldHelper::fromArray(['type' => MYSQLI_TYPE_TIMESTAMP])]), ); } public function testGetClassForDateTimeRelatedFieldsCase2(): void { self::assertSame( 'datefield', (new ReflectionMethod(DisplayResults::class, 'getClassForDateTimeRelatedFields')) ->invokeArgs($this->object, [FieldHelper::fromArray(['type' => MYSQLI_TYPE_DATE])]), ); } public function testGetClassForDateTimeRelatedFieldsCase3(): void { self::assertSame( 'text', (new ReflectionMethod(DisplayResults::class, 'getClassForDateTimeRelatedFields')) ->invokeArgs($this->object, [FieldHelper::fromArray(['type' => MYSQLI_TYPE_STRING])]), ); } /** * Test for getOffsets - case 1 */ public function testGetOffsetsCase1(): void { $_SESSION['tmpval']['max_rows'] = DisplayResults::ALL_ROWS; self::assertSame( [0, 0], (new ReflectionMethod(DisplayResults::class, 'getOffsets'))->invokeArgs($this->object, []), ); } /** * Test for getOffsets - case 2 */ public function testGetOffsetsCase2(): void { $_SESSION['tmpval']['max_rows'] = 5; $_SESSION['tmpval']['pos'] = 4; self::assertSame( [9, 0], (new ReflectionMethod(DisplayResults::class, 'getOffsets'))->invokeArgs($this->object, []), ); } /** * Data provider for testGetSpecialLinkUrl * * @return array, string, string}> */ public static function dataProviderForTestGetSpecialLinkUrl(): array { return [ [ 'information_schema', 'routines', 'circumference', ['routine_name' => 'circumference', 'routine_schema' => 'data', 'routine_type' => 'FUNCTION'], 'routine_name', 'index.php?route=/database/routines&item_name=circumference&db=data' . '&item_type=FUNCTION&server=2&lang=en', ], [ 'information_schema', 'routines', 'area', ['routine_name' => 'area', 'routine_schema' => 'data', 'routine_type' => 'PROCEDURE'], 'routine_name', 'index.php?route=/database/routines&item_name=area&db=data&item_type=PROCEDURE&server=2&lang=en', ], ]; } /** * Test getSpecialLinkUrl * * @param string $db the database name * @param string $table the table name * @param string $columnValue column value * @param array $rowInfo information about row * @param string $fieldName column name * @param string $output output of getSpecialLinkUrl */ #[DataProvider('dataProviderForTestGetSpecialLinkUrl')] public function testGetSpecialLinkUrl( string $db, string $table, string $columnValue, array $rowInfo, string $fieldName, string $output, ): void { $specialSchemaLinks = [ 'information_schema' => [ 'routines' => [ 'routine_name' => [ 'link_param' => 'item_name', 'link_dependancy_params' => [ ['param_info' => 'db', 'column_name' => 'routine_schema'], ['param_info' => 'item_type', 'column_name' => 'routine_type'], ], 'default_page' => 'index.php?route=/database/routines', ], ], 'columns' => [ 'column_name' => [ 'link_param' => 'table_schema', 'link_dependancy_params' => [ ['param_info' => 'db', 'column_name' => 'table_schema'], ['param_info' => 'db2', 'column_name' => 'table_schema'], ], 'default_page' => 'index.php', ], ], ], ]; self::assertSame( $output, (new ReflectionMethod(DisplayResults::class, 'getSpecialLinkUrl')) ->invokeArgs($this->object, [$specialSchemaLinks[$db][$table][$fieldName], $columnValue, $rowInfo]), ); } /** * Data provider for testGetRowInfoForSpecialLinks * * @return mixed[] parameters and output */ public static function dataProviderForTestGetRowInfoForSpecialLinks(): array { $columnNames = ['host', 'db', 'user', 'select_privilages']; $fieldsMeta = []; foreach ($columnNames as $columnName) { $fieldMeta = new stdClass(); $fieldMeta->orgname = $columnName; $fieldsMeta[] = $fieldMeta; } return [ [ $fieldsMeta, ['localhost', 'phpmyadmin', 'pmauser', 'Y'], ['host' => 'localhost', 'select_privilages' => 'Y', 'db' => 'phpmyadmin', 'user' => 'pmauser'], ], ]; } /** * Test getRowInfoForSpecialLinks * * @param FieldMetadata[] $fieldsMeta meta information about fields * @param string[] $row current row data * @param array $output output of getRowInfoForSpecialLinks */ #[DataProvider('dataProviderForTestGetRowInfoForSpecialLinks')] public function testGetRowInfoForSpecialLinks( array $fieldsMeta, array $row, array $output, ): void { (new ReflectionProperty(DisplayResults::class, 'fieldsMeta'))->setValue($this->object, $fieldsMeta); self::assertEquals( $output, (new ReflectionMethod(DisplayResults::class, 'getRowInfoForSpecialLinks')) ->invokeArgs($this->object, [$row]), ); } public function testSetHighlightedColumnGlobalField(): void { $query = 'SELECT * FROM db_name WHERE `db_name`.`tbl`.id > 0 AND `id` < 10'; (new ReflectionMethod(DisplayResults::class, 'setHighlightedColumnGlobalField')) ->invokeArgs($this->object, [Query::getAll($query)]); self::assertSame([ 'db_name' => true, 'tbl' => true, 'id' => true, ], (new ReflectionProperty(DisplayResults::class, 'highlightColumns'))->getValue($this->object)); } /** * Data provider for testGetPartialText * * @return array */ public static function dataProviderForTestGetPartialText(): array { return [ ['P', 10, 'foo', 'foo'], ['P', 1, 'foo', 'f...'], ['F', 10, 'foo', 'foo'], ['F', 1, 'foo', 'foo'], ]; } /** * Test getPartialText * * @param string $pftext Partial or Full text * @param int $limitChars Partial or Full text * @param string $str the string to be tested * @param string $output return value of getPartialText */ #[DataProvider('dataProviderForTestGetPartialText')] public function testGetPartialText(string $pftext, int $limitChars, string $str, string $output): void { $_SESSION['tmpval']['pftext'] = $pftext; Config::getInstance()->set('LimitChars', $limitChars); self::assertSame( $output, (new ReflectionMethod(DisplayResults::class, 'getPartialText'))->invokeArgs($this->object, [$str]), ); } /** * @return mixed[][] * @psalm-return array, * bool, * string * }> */ public static function dataProviderForTestHandleNonPrintableContents(): array { $transformationPlugin = new Text_Plain_Link(); $meta = FieldHelper::fromArray(['type' => MYSQLI_TYPE_BLOB, 'orgtable' => 'bar']); $urlParams = ['db' => 'foo', 'table' => 'bar', 'where_clause' => 'where_clause']; return [ [true, true, 'BLOB', '1001', null, [], $meta, $urlParams, false, 'class="disableAjax">1001'], [ true, true, 'BLOB', (string) hex2bin('123456'), null, [], $meta, $urlParams, false, 'class="disableAjax">0x123456', ], [true, false, 'BLOB', '1001', null, [], $meta, $urlParams, false, 'class="disableAjax">[BLOB - 4 B]'], [false, false, 'BINARY', '1001', $transformationPlugin, [], $meta, $urlParams, false, '1001'], [false, true, 'GEOMETRY', null, null, [], $meta, $urlParams, false, '[GEOMETRY - NULL]'], ]; } /** * @param bool $displayBinary show binary contents? * @param bool $displayBlob show blob contents? * @param string $category BLOB|BINARY|GEOMETRY * @param string|null $content the binary content * @param mixed[]|object $transformOptions transformation parameters * @param object $meta the meta-information about the field * @param array $urlParams parameters that should go to the download link * @param bool $isTruncated the result is truncated or not * @param string $output the output of this function */ #[DataProvider('dataProviderForTestHandleNonPrintableContents')] public function testHandleNonPrintableContents( bool $displayBinary, bool $displayBlob, string $category, string|null $content, TransformationsPlugin|null $transformationPlugin, array|object $transformOptions, object $meta, array $urlParams, bool $isTruncated, string $output, ): void { $_SESSION['tmpval']['display_binary'] = $displayBinary; $_SESSION['tmpval']['display_blob'] = $displayBlob; $actual = (new ReflectionMethod(DisplayResults::class, 'handleNonPrintableContents'))->invokeArgs( $this->object, [$category, $content, $transformationPlugin, $transformOptions, $meta, $urlParams, &$isTruncated], ); self::assertIsString($actual); self::assertStringContainsString($output, $actual); } /** * @return array */ public static function dataProviderForTestGetDataCellForNonNumericColumns(): array { $transformationPlugin = new Text_Plain_Link(); $transformationPluginExternal = new Text_Plain_External(); $meta = FieldHelper::fromArray([ 'type' => MYSQLI_TYPE_BLOB, 'table' => 'tbl', 'orgtable' => 'tbl', 'name' => 'tblob', 'orgname' => 'tblob', 'charsetnr' => 63, ]); $meta2 = FieldHelper::fromArray([ 'type' => MYSQLI_TYPE_STRING, 'table' => 'tbl', 'orgtable' => 'tbl', 'name' => 'varchar', 'orgname' => 'varchar', ]); $meta3 = FieldHelper::fromArray([ 'type' => MYSQLI_TYPE_DATETIME, 'table' => 'tbl', 'orgtable' => 'tbl', 'name' => 'datetime', 'orgname' => 'datetime', ]); $urlParams = ['db' => 'foo', 'table' => 'tbl', 'where_clause' => 'where_clause']; return [ [ 'all', '1001', 'grid_edit', $meta, [], $urlParams, false, null, ['https://www.example.com/'], 'class="disableAjax">[BLOB - 4 B]' . '' . "\n", ], [ 'noblob', '1001', 'grid_edit', $meta, [], $urlParams, false, $transformationPlugin, [], '' . '1001' . '' . "\n", ], [ 'noblob', null, 'grid_edit', $meta2, [], $urlParams, false, $transformationPlugin, [], '' . "\n" . ' NULL' . "\n" . '' . "\n", ], [ 'all', 'foo bar baz', 'grid_edit', $meta2, [], $urlParams, false, null, [], 'foo bar baz' . "\n", ], [ 'all', 'foo bar baz', 'grid_edit', $meta2, [], $urlParams, false, $transformationPluginExternal, [], 'foo bar baz' . "\n", ], [ 'all', '2020-09-20 16:35:00', 'grid_edit', $meta3, [], $urlParams, false, null, [], '2020-09-20 16:35:00' . "\n", ], ]; } /** * @param 'blob'|'noblob'|'all' $protectBinary all|blob|noblob|no * @param string|null $column the relevant column in data row * @param string $class the html class for column * @param object $meta the meta-information about the field * @param mixed[] $map the list of relations * @param mixed[] $urlParams the parameters for generate url * @param bool $conditionField the column should highlighted or not * @param string[] $transformOptions the transformation parameters * @param string $output the output of this function */ #[DataProvider('dataProviderForTestGetDataCellForNonNumericColumns')] public function testGetDataCellForNonNumericColumns( string $protectBinary, string|null $column, string $class, object $meta, array $map, array $urlParams, bool $conditionField, TransformationsPlugin|null $transformationPlugin, array $transformOptions, string $output, ): void { $_SESSION['tmpval']['display_binary'] = true; $_SESSION['tmpval']['display_blob'] = false; $_SESSION['tmpval']['relational_display'] = false; $config = Config::getInstance(); $config->set('ProtectBinary', $protectBinary); $statementInfo = new StatementInfo(new Parser(), null, new StatementFlags(), [], []); $actual = (new ReflectionMethod(DisplayResults::class, 'getDataCellForNonNumericColumns'))->invokeArgs( $this->object, [ $column, $class, $meta, $map, $urlParams, $conditionField, $transformationPlugin, $transformOptions, $statementInfo, ], ); self::assertIsString($actual); self::assertStringContainsString($output, $actual); } /** * Simple output transformation test * * It mocks data needed to display two transformations and asserts * they are rendered. */ public function testOutputTransformations(): void { // Fake relation settings $_SESSION['tmpval']['relational_display'] = 'K'; $relationParameters = RelationParameters::fromArray([ RelationParameters::DATABASE => 'db', RelationParameters::MIME_WORK => true, RelationParameters::COLUMN_INFO => 'column_info', ]); (new ReflectionProperty(Relation::class, 'cache'))->setValue(null, $relationParameters); $config = Config::getInstance(); // Basic data $query = 'SELECT 1'; $this->object = new DisplayResults($this->dbi, $config, 'db', '', 2, '', ''); // Field meta information (new ReflectionProperty(DisplayResults::class, 'fieldsMeta'))->setValue($this->object, [ FieldHelper::fromArray([ 'type' => MYSQLI_TYPE_LONG, 'flags' => MYSQLI_NUM_FLAG | MYSQLI_NOT_NULL_FLAG, 'table' => 'table', 'orgtable' => 'table', 'name' => '1', 'orgname' => '1', ]), FieldHelper::fromArray([ 'type' => MYSQLI_TYPE_LONG, 'flags' => MYSQLI_NUM_FLAG | MYSQLI_NOT_NULL_FLAG, 'table' => 'table', 'orgtable' => 'table', 'name' => '2', 'orgname' => '2', ]), ]); $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); // MIME transformations $dbi->expects(self::exactly(1)) ->method('fetchResult') ->willReturn( [ 'db.table.1' => ['mimetype' => '', 'transformation' => 'output/text_plain_dateformat.php'], 'db.table.2' => ['mimetype' => '', 'transformation' => 'output/text_plain_bool2text.php'], ], ); DatabaseInterface::$instance = $dbi; $transformations = new Transformations($dbi, new Relation($dbi)); (new ReflectionProperty(DisplayResults::class, 'mediaTypeMap'))->setValue( $this->object, $transformations->getMime('db', 'table'), ); // Actually invoke tested method $output = (new ReflectionMethod(DisplayResults::class, 'getRowValues'))->invokeArgs($this->object, [ ['3600', 'true'], 0, false, [], 'disabled', false, $query, Query::getAll($query), ]); // Dateformat self::assertStringContainsString('Jan 01, 1970 at 01:00 AM', $output); // Bool2Text self::assertStringContainsString('>T<', $output); } /** @return mixed[][] */ public static function dataProviderGetSortOrderHiddenInputs(): array { // SQL to add the column // SQL to remove the column // The URL params // The column name return [ ['', '', ['sql_query' => ''], 'colname', ''], [ 'SELECT * FROM `gis_all` ORDER BY `gis_all`.`shape` DESC, `gis_all`.`name` ASC', 'SELECT * FROM `gis_all` ORDER BY `gis_all`.`name` ASC', ['sql_query' => 'SELECT * FROM `gis_all` ORDER BY `gis_all`.`shape` DESC, `gis_all`.`name` ASC'], 'shape', '', ], [ 'SELECT * FROM `gis_all` ORDER BY `gis_all`.`shape` DESC, `gis_all`.`name` ASC', 'SELECT * FROM `gis_all` ORDER BY `gis_all`.`shape` DESC', ['sql_query' => 'SELECT * FROM `gis_all` ORDER BY `gis_all`.`shape` DESC, `gis_all`.`name` ASC'], 'name', '', ], [ 'SELECT * FROM `gis_all`', 'SELECT * FROM `gis_all`', ['sql_query' => 'SELECT * FROM `gis_all`'], 'name', '', ], [ 'SELECT * FROM `gd_cities` ORDER BY `gd_cities`.`region_slug` DESC, ' . '`gd_cities`.`country_slug` ASC, `gd_cities`.`city_id` ASC, `gd_cities`.`city` ASC', 'SELECT * FROM `gd_cities` ORDER BY `gd_cities`.`region_slug` DESC, ' . '`gd_cities`.`country_slug` ASC, `gd_cities`.`city_id` ASC, `gd_cities`.`city` ASC', [ 'sql_query' => 'SELECT * FROM `gd_cities` ORDER BY `gd_cities`.`region_slug` DESC, ' . '`gd_cities`.`country_slug` ASC, `gd_cities`.`city_id` ASC, `gd_cities`.`city` ASC', ], '', '', ], [ 'SELECT * FROM `gd_cities` ORDER BY `gd_cities`.`region_slug` DESC, ' . '`gd_cities`.`country_slug` ASC, `gd_cities`.`city_id` ASC, `gd_cities`.`city` ASC', 'SELECT * FROM `gd_cities` ORDER BY `gd_cities`.`country_slug` ASC, `gd_cities`.`city_id`' . ' ASC, `gd_cities`.`city` ASC', [ 'sql_query' => 'SELECT * FROM `gd_cities` ORDER BY `gd_cities`.`region_slug` DESC, ' . '`gd_cities`.`country_slug` ASC, `gd_cities`.`city_id` ASC, `gd_cities`.`city` ASC', ], 'region_slug', '', ], [ 'SELECT * FROM `gis_all` ORDER BY `gis_all`.`shape` DESC', 'SELECT * FROM `gis_all`', ['sql_query' => 'SELECT * FROM `gis_all` ORDER BY `gis_all`.`shape` DESC'], 'shape', '&discard_remembered_sort=1', ], ]; } /** @param array $urlParams */ #[DataProvider('dataProviderGetSortOrderHiddenInputs')] public function testGetSortOrderHiddenInputs( string $sqlAdd, string $sqlRemove, array $urlParams, string $colName, string $urlParamsRemove, ): void { $output = (new ReflectionMethod(DisplayResults::class, 'getSortOrderHiddenInputs')) ->invokeArgs($this->object, [$urlParams, $colName]); $out = urldecode(htmlspecialchars_decode($output)); self::assertStringContainsString( 'name="url-remove-order" value="index.php?route=/sql&sql_query=' . $sqlRemove, $out, 'The remove query should be found', ); self::assertStringContainsString( 'name="url-add-order" value="index.php?route=/sql&sql_query=' . $sqlAdd, $out, 'The add query should be found', ); $firstLine = explode("\n", $out)[0]; self::assertStringContainsString( 'url-remove-order', $firstLine, 'The first line should contain url-remove-order input', ); self::assertStringNotContainsString( 'url-add-order', $firstLine, 'The first line should contain NOT url-add-order input', ); self::assertStringContainsString($urlParamsRemove, $firstLine, 'The first line should contain the URL params'); } /** @see https://github.com/phpmyadmin/phpmyadmin/issues/16836 */ public function testBuildValueDisplayNoTrainlingSpaces(): void { $output = (new ReflectionMethod(DisplayResults::class, 'buildValueDisplay')) ->invokeArgs($this->object, ['my_class', false, ' special value ']); self::assertSame(' special value ' . "\n", $output); $output = (new ReflectionMethod(DisplayResults::class, 'buildValueDisplay')) ->invokeArgs($this->object, ['my_class', false, '0x11e6ac0cfb1e8bf3bf48b827ebdafb0b']); self::assertSame('0x11e6ac0cfb1e8bf3bf48b827ebdafb0b' . "\n", $output); $output = (new ReflectionMethod(DisplayResults::class, 'buildValueDisplay'))->invokeArgs($this->object, [ 'my_class', true,// condition mode '0x11e6ac0cfb1e8bf3bf48b827ebdafb0b', ]); self::assertSame( '0x11e6ac0cfb1e8bf3bf48b827ebdafb0b' . "\n", $output, ); } public function testPftextConfigParam(): void { $db = 'test_db'; $table = 'test_table'; $config = Config::getInstance(); $query = 'ANALYZE FORMAT=JSON SELECT * FROM test_table'; [$statementInfo] = ParseAnalyze::sqlQuery($query, $db, false); $request = ServerRequestFactory::create()->createServerRequest('POST', 'https://example.com'); $object = new DisplayResults($this->dbi, $config, $db, $table, 2, '', $query); $object->setConfigParamsForDisplayTable($request, $statementInfo); self::assertSame('F', $_SESSION['tmpval']['pftext']); $query = 'ANALYZE NO_WRITE_TO_BINLOG TABLE test_table'; [$statementInfo] = ParseAnalyze::sqlQuery($query, $db, false); $object = new DisplayResults($this->dbi, $config, $db, $table, 2, '', $query); $object->setConfigParamsForDisplayTable($request, $statementInfo); self::assertSame('P', $_SESSION['tmpval']['pftext']); } /** * @param array>>|string> $session * @param array $queryParams * @param array $parsedBody * @param array>|string|int> $expected */ #[DataProvider('providerSetConfigParamsForDisplayTable')] public function testSetConfigParamsForDisplayTable( array $session, array $queryParams, array $parsedBody, array $expected, ): void { $_SESSION = $session; $db = 'test_db'; $table = 'test_table'; $query = 'SELECT * FROM `test_db`.`test_table`;'; [$statementInfo] = ParseAnalyze::sqlQuery($query, $db, false); $request = ServerRequestFactory::create()->createServerRequest('POST', 'https://example.com') ->withQueryParams($queryParams) ->withParsedBody($parsedBody); $object = new DisplayResults($this->dbi, Config::getInstance(), $db, $table, 2, '', $query); $object->setConfigParamsForDisplayTable($request, $statementInfo); self::assertArrayHasKey('tmpval', $_SESSION); self::assertIsArray($_SESSION['tmpval']); self::assertSame($expected, $_SESSION['tmpval']); } /** @return mixed[][] */ public static function providerSetConfigParamsForDisplayTable(): array { $cfg = ['RelationalDisplay' => DisplayResults::RELATIONAL_KEY, 'MaxRows' => 25, 'RepeatCells' => 100]; return [ 'default values' => [ [' PMA_token ' => 'token'], [], [], [ 'query' => [ '2b7b3faf4b48255e47876f6d5bd2da35' => [ 'sql' => 'SELECT * FROM `test_db`.`test_table`;', 'repeat_cells' => $cfg['RepeatCells'], 'max_rows' => $cfg['MaxRows'], 'pos' => 0, 'pftext' => DisplayResults::DISPLAY_PARTIAL_TEXT, 'relational_display' => $cfg['RelationalDisplay'], 'geoOption' => DisplayResults::GEOMETRY_DISP_GEOM, 'display_binary' => true, ], ], 'pftext' => DisplayResults::DISPLAY_PARTIAL_TEXT, 'relational_display' => $cfg['RelationalDisplay'], 'geoOption' => DisplayResults::GEOMETRY_DISP_GEOM, 'display_binary' => true, 'display_blob' => false, 'hide_transformation' => false, 'pos' => 0, 'max_rows' => $cfg['MaxRows'], 'repeat_cells' => $cfg['RepeatCells'], ], ], 'cached values' => [ [ 'tmpval' => [ 'query' => [ '2b7b3faf4b48255e47876f6d5bd2da35' => [ 'sql' => 'SELECT * FROM `test_db`.`test_table`;', 'repeat_cells' => 90, 'max_rows' => 26, 'pos' => 1, 'pftext' => DisplayResults::DISPLAY_FULL_TEXT, 'relational_display' => DisplayResults::RELATIONAL_DISPLAY_COLUMN, 'geoOption' => DisplayResults::GEOMETRY_DISP_WKB, 'display_binary' => false, ], 'a' => [], 'b' => [], 'c' => [], 'd' => [], 'e' => [], 'f' => [], 'g' => [], 'h' => [], 'i' => [], 'j' => [], ], ], ' PMA_token ' => 'token', ], [], [], [ 'query' => [ 'b' => [], 'c' => [], 'd' => [], 'e' => [], 'f' => [], 'g' => [], 'h' => [], 'i' => [], 'j' => [], '2b7b3faf4b48255e47876f6d5bd2da35' => [ 'sql' => 'SELECT * FROM `test_db`.`test_table`;', 'repeat_cells' => 90, 'max_rows' => 26, 'pos' => 1, 'pftext' => DisplayResults::DISPLAY_FULL_TEXT, 'relational_display' => DisplayResults::RELATIONAL_DISPLAY_COLUMN, 'geoOption' => DisplayResults::GEOMETRY_DISP_WKB, 'display_binary' => true, ], ], 'pftext' => DisplayResults::DISPLAY_FULL_TEXT, 'relational_display' => DisplayResults::RELATIONAL_DISPLAY_COLUMN, 'geoOption' => DisplayResults::GEOMETRY_DISP_WKB, 'display_binary' => true, 'display_blob' => false, 'hide_transformation' => false, 'pos' => 1, 'max_rows' => 26, 'repeat_cells' => 90, ], ], 'default and request values' => [ [' PMA_token ' => 'token'], ['session_max_rows' => '28'], [ 'session_max_rows' => '27', 'pos' => '2', 'pftext' => DisplayResults::DISPLAY_FULL_TEXT, 'relational_display' => DisplayResults::RELATIONAL_DISPLAY_COLUMN, 'geoOption' => DisplayResults::GEOMETRY_DISP_WKT, 'display_binary' => '0', 'display_blob' => '0', 'hide_transformation' => '0', ], [ 'query' => [ '2b7b3faf4b48255e47876f6d5bd2da35' => [ 'sql' => 'SELECT * FROM `test_db`.`test_table`;', 'repeat_cells' => $cfg['RepeatCells'], 'max_rows' => 27, 'pos' => 2, 'pftext' => DisplayResults::DISPLAY_FULL_TEXT, 'relational_display' => DisplayResults::RELATIONAL_DISPLAY_COLUMN, 'geoOption' => DisplayResults::GEOMETRY_DISP_WKT, 'display_binary' => true, 'display_blob' => true, 'hide_transformation' => true, ], ], 'pftext' => DisplayResults::DISPLAY_FULL_TEXT, 'relational_display' => DisplayResults::RELATIONAL_DISPLAY_COLUMN, 'geoOption' => DisplayResults::GEOMETRY_DISP_WKT, 'display_binary' => true, 'display_blob' => true, 'hide_transformation' => true, 'pos' => 2, 'max_rows' => 27, 'repeat_cells' => $cfg['RepeatCells'], ], ], 'cached and request values' => [ [ 'tmpval' => [ 'query' => [ '2b7b3faf4b48255e47876f6d5bd2da35' => [ 'sql' => 'SELECT * FROM `test_db`.`test_table`;', 'repeat_cells' => $cfg['RepeatCells'], 'max_rows' => $cfg['MaxRows'], 'pos' => 0, 'pftext' => DisplayResults::DISPLAY_FULL_TEXT, 'relational_display' => DisplayResults::RELATIONAL_DISPLAY_COLUMN, 'geoOption' => DisplayResults::GEOMETRY_DISP_GEOM, 'display_binary' => true, ], 'a' => [], 'b' => [], 'c' => [], 'd' => [], 'e' => [], 'f' => [], 'g' => [], 'h' => [], 'i' => [], ], ], ' PMA_token ' => 'token', ], ['session_max_rows' => DisplayResults::ALL_ROWS], [ 'pos' => 'NaN', 'pftext' => DisplayResults::DISPLAY_PARTIAL_TEXT, 'relational_display' => DisplayResults::RELATIONAL_KEY, 'geoOption' => DisplayResults::GEOMETRY_DISP_WKB, 'display_options_form' => '0', ], [ 'query' => [ 'a' => [], 'b' => [], 'c' => [], 'd' => [], 'e' => [], 'f' => [], 'g' => [], 'h' => [], 'i' => [], '2b7b3faf4b48255e47876f6d5bd2da35' => [ 'sql' => 'SELECT * FROM `test_db`.`test_table`;', 'repeat_cells' => $cfg['RepeatCells'], 'max_rows' => DisplayResults::ALL_ROWS, 'pos' => 0, 'pftext' => DisplayResults::DISPLAY_PARTIAL_TEXT, 'relational_display' => DisplayResults::RELATIONAL_KEY, 'geoOption' => DisplayResults::GEOMETRY_DISP_WKB, ], ], 'pftext' => DisplayResults::DISPLAY_PARTIAL_TEXT, 'relational_display' => DisplayResults::RELATIONAL_KEY, 'geoOption' => DisplayResults::GEOMETRY_DISP_WKB, 'display_binary' => false, 'display_blob' => false, 'hide_transformation' => false, 'pos' => 0, 'max_rows' => DisplayResults::ALL_ROWS, 'repeat_cells' => $cfg['RepeatCells'], ], ], ]; } public function testGetTable(): void { $config = Config::$instance = new Config(); $config->selectedServer['DisableIS'] = true; $this->dbi = $this->createDatabaseInterface($this->dummyDbi, $config); Current::$server = 2; Current::$database = 'test_db'; Current::$table = 'test_table'; $query = 'SELECT * FROM `test_db`.`test_table`;'; $object = new DisplayResults( $this->dbi, $config, Current::$database, Current::$table, Current::$server, '', $query, ); (new ReflectionProperty(DisplayResults::class, 'uniqueId'))->setValue($object, 1234567890); [$statementInfo] = ParseAnalyze::sqlQuery($query, Current::$database, false); $fieldsMeta = [ FieldHelper::fromArray([ 'type' => MYSQLI_TYPE_DECIMAL, 'flags' => MYSQLI_PRI_KEY_FLAG | MYSQLI_NUM_FLAG | MYSQLI_NOT_NULL_FLAG, 'name' => 'id', ]), FieldHelper::fromArray([ 'type' => MYSQLI_TYPE_STRING, 'flags' => MYSQLI_NOT_NULL_FLAG, 'name' => 'name', ]), FieldHelper::fromArray([ 'type' => MYSQLI_TYPE_DATETIME, 'flags' => MYSQLI_NOT_NULL_FLAG, 'name' => 'datetimefield', ]), ]; $object->setProperties( 3, $fieldsMeta, $statementInfo->flags->isCount, $statementInfo->flags->isExport, $statementInfo->flags->isFunc, $statementInfo->flags->isAnalyse, 3, 1.234, $statementInfo->flags->isMaint, $statementInfo->flags->queryType === StatementType::Explain, $statementInfo->flags->queryType === StatementType::Show, false, true, false, ); $_SESSION = ['tmpval' => [], ' PMA_token ' => 'token']; $_SESSION['tmpval']['geoOption'] = ''; $_SESSION['tmpval']['hide_transformation'] = ''; $_SESSION['tmpval']['display_blob'] = ''; $_SESSION['tmpval']['display_binary'] = ''; $_SESSION['tmpval']['relational_display'] = ''; $_SESSION['tmpval']['possible_as_geometry'] = ''; $_SESSION['tmpval']['pftext'] = ''; $_SESSION['tmpval']['max_rows'] = 25; $_SESSION['tmpval']['pos'] = 0; $_SESSION['tmpval']['repeat_cells'] = 0; $_SESSION['tmpval']['query']['2b7b3faf4b48255e47876f6d5bd2da35']['max_rows'] = 25; $dtResult = $this->dbi->tryQuery($query); $displayParts = DisplayParts::fromArray([ 'hasEditLink' => true, 'deleteLink' => DeleteLinkEnum::DELETE_ROW, 'hasSortLink' => true, 'hasNavigationBar' => true, 'hasBookmarkForm' => true, 'hasTextButton' => false, 'hasPrintLink' => true, ]); self::assertNotFalse($dtResult); $actual = $object->getTable($dtResult, $displayParts, $statementInfo, false); $template = new Template($config); $tableHeadersForColumns = $template->render('display/results/table_headers_for_columns', [ 'is_sortable' => true, 'columns' => [ [ 'column_name' => 'id', 'order_link' => 'id' . '' . '' . "\n" . '', 'comments' => '', 'is_browse_pointer_enabled' => true, 'is_browse_marker_enabled' => true, 'is_column_hidden' => false, 'is_column_numeric' => true, ], [ 'column_name' => 'name', 'order_link' => 'name' . '' . '' . "\n" . '', 'comments' => '', 'is_browse_pointer_enabled' => true, 'is_browse_marker_enabled' => true, 'is_column_hidden' => false, 'is_column_numeric' => false, ], [ 'column_name' => 'datetimefield', 'order_link' => 'datetimefield' . '' . '' . "\n" . '', 'comments' => '', 'is_browse_pointer_enabled' => true, 'is_browse_marker_enabled' => true, 'is_column_hidden' => false, 'is_column_numeric' => false, ], ], ]); $tableTemplate = $template->render('display/results/table', [ 'sql_query_message' => Generator::getMessage( Message::success('Showing rows 0 - 2 (3 total, Query took 1.2340 seconds.)'), $query, MessageType::Success, ), 'navigation' => [ 'page_selector' => '', 'number_total_page' => 1, 'has_show_all' => true, 'hidden_fields' => [ 'db' => Current::$database, 'table' => Current::$table, 'server' => 2, 'sql_query' => $query, 'is_browse_distinct' => false, 'goto' => '', ], 'session_max_rows' => 'all', 'is_showing_all' => false, 'max_rows' => 25, 'pos' => 0, 'sort_by_key' => [ 'hidden_fields' => [ 'db' => Current::$database, 'table' => Current::$table, 'server' => 2, 'sort_by_key' => '1', 'session_max_rows' => 25, ], 'options' => [ [ 'value' => 'SELECT * FROM `test_db`.`test_table` ORDER BY `id` ASC', 'content' => 'PRIMARY (ASC)', 'is_selected' => false, ], [ 'value' => 'SELECT * FROM `test_db`.`test_table` ORDER BY `id` DESC', 'content' => 'PRIMARY (DESC)', 'is_selected' => false, ], [ 'value' => 'SELECT * FROM `test_db`.`test_table`', 'content' => 'None', 'is_selected' => true, ], ], ], 'is_last_page' => true, ], 'headers' => [ 'column_order' => [ 'order' => false, 'visibility' => false, 'is_view' => false, 'table_create_time' => '', ], 'options' => [ 'geo_option' => null, 'hide_transformation' => null, 'display_blob' => null, 'display_binary' => null, 'relational_display' => null, 'possible_as_geometry' => null, 'pftext' => null, ], 'has_bulk_actions_form' => false, 'button' => '', 'table_headers_for_columns' => $tableHeadersForColumns, 'column_at_right_side' => "\n" . '', ], 'body' => '1' . "\n" . 'abcd' . "\n" . '2011-01-20 02:00:02' . "\n" . '' . "\n" . '2' . "\n" . 'foo' . "\n" . '2010-01-20 02:00:02' . "\n" . '' . "\n" . '3' . "\n" . 'Abcd' . "\n" . '2012-01-20 02:00:02' . "\n" . '' . "\n", 'bulk_links' => [], 'operations' => [ 'has_procedure' => false, 'has_geometry' => false, 'has_print_link' => true, 'has_export_link' => true, 'url_params' => [ 'db' => Current::$database, 'table' => Current::$table, 'printview' => '1', 'sql_query' => $query, 'single_table' => 'true', 'unlim_num_rows' => 3, ], ], 'db' => Current::$database, 'table' => Current::$table, 'unique_id' => 1234567890, 'sql_query' => $query, 'goto' => '', 'unlim_num_rows' => 3, 'displaywork' => false, 'relwork' => false, 'save_cells_at_once' => false, 'default_sliders_state' => 'closed', ]); self::assertSame($tableTemplate, $actual); } public function testGetTable2(): void { $config = Config::getInstance(); $config->selectedServer['DisableIS'] = true; Current::$server = 2; Current::$database = 'test_db'; Current::$table = 'test_table'; $query = 'SELECT COUNT(*) AS `Rows`, `name` FROM `test_table` GROUP BY `name` ORDER BY `name`'; $dummyDbi = $this->createDbiDummy(); $dbi = $this->createDatabaseInterface($dummyDbi); $object = new DisplayResults($dbi, $config, Current::$database, Current::$table, 2, '', $query); (new ReflectionProperty(DisplayResults::class, 'uniqueId'))->setValue($object, 1234567890); [$statementInfo] = ParseAnalyze::sqlQuery($query, Current::$database, false); $fieldsMeta = [ FieldHelper::fromArray([ 'type' => MYSQLI_TYPE_LONG, 'flags' => MYSQLI_NUM_FLAG | MYSQLI_NOT_NULL_FLAG, 'name' => 'Rows', ]), FieldHelper::fromArray([ 'type' => MYSQLI_TYPE_STRING, 'flags' => MYSQLI_NOT_NULL_FLAG, 'name' => 'name', ]), ]; $dummyDbi->addResult($query, [['2', 'abcd'], ['1', 'foo']], ['Rows', 'name'], $fieldsMeta); $object->setProperties( 2, $fieldsMeta, $statementInfo->flags->isCount, $statementInfo->flags->isExport, $statementInfo->flags->isFunc, $statementInfo->flags->isAnalyse, 2, 1.234, $statementInfo->flags->isMaint, $statementInfo->flags->queryType === StatementType::Explain, $statementInfo->flags->queryType === StatementType::Show, false, true, true, ); $_SESSION = ['tmpval' => [], ' PMA_token ' => 'token']; $_SESSION['tmpval']['geoOption'] = ''; $_SESSION['tmpval']['hide_transformation'] = false; $_SESSION['tmpval']['display_blob'] = ''; $_SESSION['tmpval']['display_binary'] = ''; $_SESSION['tmpval']['relational_display'] = ''; $_SESSION['tmpval']['possible_as_geometry'] = ''; $_SESSION['tmpval']['pftext'] = ''; $_SESSION['tmpval']['max_rows'] = 25; $_SESSION['tmpval']['pos'] = 0; $_SESSION['tmpval']['repeat_cells'] = 0; $_SESSION['tmpval']['query']['5ce1ef88afb4e13d3b8c0a55c2c9657a']['max_rows'] = 25; $dtResult = $dbi->tryQuery($query); $displayParts = DisplayParts::fromArray([ 'hasEditLink' => false, 'deleteLink' => DeleteLinkEnum::NO_DELETE, 'hasSortLink' => true, 'hasNavigationBar' => true, 'hasBookmarkForm' => true, 'hasTextButton' => false, 'hasPrintLink' => true, ]); self::assertNotFalse($dtResult); $actual = $object->getTable($dtResult, $displayParts, $statementInfo, false); $template = new Template($config); $tableHeadersForColumns = $template->render('display/results/table_headers_for_columns', [ 'is_sortable' => true, 'columns' => [ [ 'column_name' => 'Rows', 'order_link' => 'Rows' . "\n" . '', 'comments' => '', 'is_browse_pointer_enabled' => true, 'is_browse_marker_enabled' => true, 'is_column_hidden' => false, 'is_column_numeric' => true, ], [ 'column_name' => 'name', 'order_link' => 'name Ascending Descending 1' . "\n" . '', 'comments' => '', 'is_browse_pointer_enabled' => true, 'is_browse_marker_enabled' => true, 'is_column_hidden' => false, 'is_column_numeric' => false, ], ], ]); $tableTemplate = $template->render('display/results/table', [ 'sql_query_message' => Generator::getMessage( Message::success('Showing rows 0 - 1 (2 total, Query took 1.2340 seconds.)'), $query, MessageType::Success, ), 'navigation' => [ 'page_selector' => '', 'number_total_page' => 1, 'has_show_all' => true, 'hidden_fields' => [ 'db' => Current::$database, 'table' => Current::$table, 'server' => 2, 'sql_query' => $query, 'is_browse_distinct' => true, 'goto' => '', ], 'session_max_rows' => 'all', 'is_showing_all' => false, 'max_rows' => 25, 'pos' => 0, 'sort_by_key' => [], 'is_last_page' => true, ], 'headers' => [ 'column_order' => [], 'options' => [ 'geo_option' => null, 'hide_transformation' => null, 'display_blob' => null, 'display_binary' => null, 'relational_display' => null, 'possible_as_geometry' => null, 'pftext' => null, ], 'has_bulk_actions_form' => false, 'button' => '', 'table_headers_for_columns' => $tableHeadersForColumns, 'column_at_right_side' => "\n" . '', ], 'body' => '2' . "\n" . 'abcd' . "\n" . '' . "\n" . '1' . "\n" . 'foo' . "\n" . '' . "\n", 'bulk_links' => [], 'operations' => [ 'has_procedure' => false, 'has_geometry' => false, 'has_print_link' => true, 'has_export_link' => true, 'url_params' => [ 'db' => Current::$database, 'table' => Current::$table, 'printview' => '1', 'sql_query' => $query, 'single_table' => 'true', 'unlim_num_rows' => 2, ], ], 'db' => Current::$database, 'table' => Current::$table, 'unique_id' => 1234567890, 'sql_query' => $query, 'goto' => '', 'unlim_num_rows' => 2, 'displaywork' => false, 'relwork' => false, 'save_cells_at_once' => false, 'default_sliders_state' => 'closed', ]); self::assertSame($tableTemplate, $actual); } /** @return array */ public static function dataProviderSortOrder(): array { return [ 'Default date' => [ 'SMART', 'DESC',// date types are DESC in SMART mode MYSQLI_TYPE_DATE, ], 'ASC date' => [ 'ASC', 'ASC',// do as config says MYSQLI_TYPE_DATE, ], 'DESC date' => [ 'DESC', 'DESC',// do as config says MYSQLI_TYPE_DATE, ], 'Default date-time' => [ 'SMART', 'DESC',// date time types are DESC in SMART mode MYSQLI_TYPE_DATETIME, ], 'ASC date-time' => [ 'ASC', 'ASC',// do as config says MYSQLI_TYPE_DATETIME, ], 'DESC date-time' => [ 'DESC', 'DESC',// do as config says MYSQLI_TYPE_DATETIME, ], 'Default time' => [ 'SMART', 'DESC',// time types are DESC in SMART mode MYSQLI_TYPE_TIME, ], 'ASC time' => [ 'ASC', 'ASC',// do as config says MYSQLI_TYPE_TIME, ], 'DESC time' => [ 'DESC', 'DESC',// do as config says MYSQLI_TYPE_TIME, ], 'Default timestamp' => [ 'SMART', 'DESC',// timestamp types are DESC in SMART mode MYSQLI_TYPE_TIMESTAMP, ], 'ASC timestamp' => [ 'ASC', 'ASC',// do as config says MYSQLI_TYPE_TIMESTAMP, ], 'DESC timestamp' => [ 'DESC', 'DESC',// do as config says MYSQLI_TYPE_TIMESTAMP, ], 'Default string' => [ 'SMART', 'ASC',// string types are ASC in SMART mode MYSQLI_TYPE_STRING, ], 'ASC string' => [ 'ASC', 'ASC',// do as config says MYSQLI_TYPE_STRING, ], 'DESC string' => [ 'DESC', 'DESC',// do as config says MYSQLI_TYPE_STRING, ], ]; } /** * @param 'ASC'|'DESC'|'SMART' $orderSetting * @param 'ASC'|'DESC' $querySortDirection */ #[DataProvider('dataProviderSortOrder')] public function testGetSingleAndMultiSortUrls( string $orderSetting, string $querySortDirection, int $metaType, ): void { Config::getInstance()->set('Order', $orderSetting); $data = (new ReflectionMethod(DisplayResults::class, 'getSingleAndMultiSortUrls'))->invokeArgs($this->object, [ [new SortExpression('Country', 'Code', 'ASC', '`Country`.`Code`')], 'Country', 'FoundedIn', FieldHelper::fromArray(['type' => $metaType]), ]); self::assertSame([ 'ORDER BY `Country`.`FoundedIn` ' . $querySortDirection, // singleSortOrder 'ORDER BY `Country`.`Code` ASC, `Country`.`FoundedIn` ' . $querySortDirection, // sortOrderColumns '', // orderImg ], $data); $data = (new ReflectionMethod(DisplayResults::class, 'getSingleAndMultiSortUrls'))->invokeArgs($this->object, [ [new SortExpression('Country', 'Code', 'ASC', '`Country`.`Code`')], 'Country', 'Code2', FieldHelper::fromArray(['type' => $metaType]), ]); self::assertSame([ 'ORDER BY `Country`.`Code2` ' . $querySortDirection, // singleSortOrder 'ORDER BY `Country`.`Code` ASC, `Country`.`Code2` ' . $querySortDirection, // sortOrderColumns '', // orderImg ], $data); $data = (new ReflectionMethod(DisplayResults::class, 'getSingleAndMultiSortUrls'))->invokeArgs($this->object, [ [ new SortExpression('Country', 'Continent', 'DESC', '`Country`.`Continent`'), new SortExpression('Country', 'Region', 'ASC', '`Country`.`Region`'), new SortExpression('Country', 'Population', 'ASC', '`Country`.`Population`'), ], 'Country', 'Code2', FieldHelper::fromArray(['type' => $metaType]), ]); self::assertSame([ 'ORDER BY `Country`.`Code2` ' . $querySortDirection, // singleSortOrder 'ORDER BY `Country`.`Continent` DESC, `Country`.`Region` ASC' . ', `Country`.`Population` ASC, `Country`.`Code2` ' . $querySortDirection, // sortOrderColumns '', // orderImg ], $data); } }