diff --git a/ChangeLog b/ChangeLog index ba7afa0875..eff0b13e05 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,11 @@ phpMyAdmin - ChangeLog - issue #17842 Change js.cookie.js to js.cookie.min.js - issue #17632 Improve tab keypress to text fields on the login form +5.2.2 (not yet released) +- issue #17028 Fix total count of rows in not accurate +- issue #17683 Fix total count of rows in not accurate +- issue #17766 Allow to open in a new tab copy and edit row actions + 5.2.1 (2023-02-07) - issue #17522 Fix case where the routes cache file is invalid - issue #17506 Fix error when configuring 2FA without XMLWriter or Imagick diff --git a/js/src/drag_drop_import.js b/js/src/drag_drop_import.js index e9d405b3d8..5cf8758c4d 100644 --- a/js/src/drag_drop_import.js +++ b/js/src/drag_drop_import.js @@ -134,7 +134,7 @@ var DragDropImport = { var filename = $this.parent('span').attr('data-filename'); $('body').append('

' + window.Messages.dropImportImportResultHeader + ' - ' + - Functions.escapeHtml(filename) + 'x

' + value.message + '
'); + escapeHtml(filename) + 'x' + value.message + ''); $('.pma_drop_result').draggable(); // to make this dialog draggable } }); diff --git a/libraries/classes/Controllers/Table/ChangeController.php b/libraries/classes/Controllers/Table/ChangeController.php index 0ee8d6e7c2..c3d617364c 100644 --- a/libraries/classes/Controllers/Table/ChangeController.php +++ b/libraries/classes/Controllers/Table/ChangeController.php @@ -7,6 +7,7 @@ namespace PhpMyAdmin\Controllers\Table; use PhpMyAdmin\Config\PageSettings; use PhpMyAdmin\ConfigStorage\Relation; use PhpMyAdmin\Controllers\AbstractController; +use PhpMyAdmin\Core; use PhpMyAdmin\DbTableExists; use PhpMyAdmin\Html\Generator; use PhpMyAdmin\Http\ServerRequest; @@ -82,6 +83,12 @@ class ChangeController extends AbstractController DbTableExists::check($GLOBALS['db'], $GLOBALS['table']); + if (isset($_GET['where_clause'], $_GET['where_clause_signature'])) { + if (Core::checkSqlQuerySignature($_GET['where_clause'], $_GET['where_clause_signature'])) { + $where_clause = $_GET['where_clause']; + } + } + /** * Determine whether Insert or Edit and set global variables */ diff --git a/libraries/classes/Controllers/Table/ReplaceController.php b/libraries/classes/Controllers/Table/ReplaceController.php index 37da83c735..6dc5a4a511 100644 --- a/libraries/classes/Controllers/Table/ReplaceController.php +++ b/libraries/classes/Controllers/Table/ReplaceController.php @@ -290,7 +290,7 @@ final class ReplaceController extends AbstractController $valueSets[] = implode(', ', $queryValues); } else { // build update query - $clauseIsUnique = $_POST['clause_is_unique'] ?? '';// Should contain 0 or 1 + $clauseIsUnique = $_POST['clause_is_unique'] ?? $_GET['clause_is_unique'] ?? '';// Should contain 0 or 1 $GLOBALS['query'][] = 'UPDATE ' . Util::backquote($GLOBALS['table']) . ' SET ' . implode(', ', $queryValues) . ' WHERE ' . $where_clause diff --git a/libraries/classes/Display/Results.php b/libraries/classes/Display/Results.php index cb37fa5732..103dfb6b27 100644 --- a/libraries/classes/Display/Results.php +++ b/libraries/classes/Display/Results.php @@ -2735,8 +2735,10 @@ class Results 'db' => $this->properties['db'], 'table' => $this->properties['table'], 'where_clause' => $whereClause, + 'where_clause_signature' => Core::signSqlQuery($whereClause), 'clause_is_unique' => $clauseIsUnique, 'sql_query' => $urlSqlQuery, + 'sql_signature' => Core::signSqlQuery($urlSqlQuery), 'goto' => Url::getFromRoute('/sql'), ]; diff --git a/libraries/classes/InsertEdit.php b/libraries/classes/InsertEdit.php index aeee5f84bc..5917da306d 100644 --- a/libraries/classes/InsertEdit.php +++ b/libraries/classes/InsertEdit.php @@ -135,6 +135,13 @@ class InsertEdit 'err_url' => $errorUrl, 'sql_query' => $_POST['sql_query'] ?? '', ]; + + if ($formParams['sql_query'] === '' && isset($_GET['sql_query'], $_GET['sql_signature'])) { + if (Core::checkSqlQuerySignature($_GET['sql_query'], $_GET['sql_signature'])) { + $formParams['sql_query'] = $_GET['sql_query']; + } + } + if (isset($whereClauses)) { foreach ($whereClauseArray as $keyId => $whereClause) { $formParams['where_clause[' . $keyId . ']'] = trim($whereClause); @@ -143,6 +150,8 @@ class InsertEdit if (isset($_POST['clause_is_unique'])) { $formParams['clause_is_unique'] = $_POST['clause_is_unique']; + } elseif (isset($_GET['clause_is_unique'])) { + $formParams['clause_is_unique'] = $_GET['clause_is_unique']; } return $formParams; diff --git a/libraries/classes/Sql.php b/libraries/classes/Sql.php index 951ed97e2d..ee4fb08385 100644 --- a/libraries/classes/Sql.php +++ b/libraries/classes/Sql.php @@ -15,6 +15,7 @@ use PhpMyAdmin\Html\Generator; use PhpMyAdmin\Html\MySQLDocumentation; use PhpMyAdmin\Query\Generator as QueryGenerator; use PhpMyAdmin\Query\Utilities; +use PhpMyAdmin\SqlParser\Components\Expression; use PhpMyAdmin\SqlParser\Statements\AlterStatement; use PhpMyAdmin\SqlParser\Statements\DropStatement; use PhpMyAdmin\SqlParser\Statements\SelectStatement; @@ -707,24 +708,24 @@ class Sql } } else { $statement = $statementInfo->statement; - $tokenList = $statementInfo->parser->list; - $replaces = [ - // Remove ORDER BY to decrease unnecessary sorting time - [ - 'ORDER BY', - '', - ], - // Removes LIMIT clause that might have been added - [ - 'LIMIT', - '', - ], - ]; - $countQuery = 'SELECT COUNT(*) FROM (' . Query::replaceClauses( - $statement, - $tokenList, - $replaces - ) . ') as cnt'; + + // Remove ORDER BY to decrease unnecessary sorting time + if ($statementInfo->order !== false) { + $statement->order = null; + } + + // Removes LIMIT clause that might have been added + if ($statementInfo->limit !== null) { + $statement->limit = null; + } + + if ($statementInfo->isGroup === false && count($statement->expr) === 1) { + $statement->expr[0] = new Expression(); + $statement->expr[0]->expr = '1'; + } + + $countQuery = 'SELECT COUNT(*) FROM (' . $statement->build() . ' ) as cnt'; + $unlimNumRows = $this->dbi->fetchValue($countQuery); if ($unlimNumRows === false) { $unlimNumRows = 0; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d151b26f6a..268ae07aa5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -7960,6 +7960,21 @@ parameters: count: 1 path: libraries/classes/Setup/Index.php + - + message: "#^Access to an undefined property PhpMyAdmin\\\\SqlParser\\\\Statement\\:\\:\\$expr\\.$#" + count: 2 + path: libraries/classes/Sql.php + + - + message: "#^Access to an undefined property PhpMyAdmin\\\\SqlParser\\\\Statement\\:\\:\\$limit\\.$#" + count: 1 + path: libraries/classes/Sql.php + + - + message: "#^Access to an undefined property PhpMyAdmin\\\\SqlParser\\\\Statement\\:\\:\\$order\\.$#" + count: 1 + path: libraries/classes/Sql.php + - message: "#^Method PhpMyAdmin\\\\Sql\\:\\:countQueryResults\\(\\) should return int\\|numeric\\-string but returns mixed\\.$#" count: 1 @@ -8100,11 +8115,6 @@ parameters: count: 2 path: libraries/classes/Sql.php - - - message: "#^Parameter \\#2 \\$list of static method PhpMyAdmin\\\\SqlParser\\\\Utils\\\\Query\\:\\:replaceClauses\\(\\) expects PhpMyAdmin\\\\SqlParser\\\\TokensList, PhpMyAdmin\\\\SqlParser\\\\TokensList\\|null given\\.$#" - count: 1 - path: libraries/classes/Sql.php - - message: "#^Parameter \\#2 \\$table of method PhpMyAdmin\\\\Sql\\:\\:handleSortOrder\\(\\) expects string, string\\|null given\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index cbdadc63e6..4c2eb55626 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -2874,9 +2874,15 @@ $GLOBALS['biggest_max_file_size'] - + $GLOBALS['current_result'] + $_GET['where_clause'] + $_GET['where_clause_signature'] + + $_GET['where_clause'] + $_GET['where_clause_signature'] + $GLOBALS['text_dir'] $isUpload @@ -2884,6 +2890,9 @@ $GLOBALS['biggest_max_file_size'] > 0 + + $where_clause + @@ -7594,14 +7603,18 @@ - + + $_GET['sql_query'] + $_GET['sql_signature'] $whereClause $whereClause $_POST['fields']['multi_edit'] - + + $_GET['sql_query'] + $_GET['sql_signature'] $whereClause @@ -12175,6 +12188,11 @@ $row['#'] + + + 0 !== --$sortableColCount + + $config->getCookie('phpMyAdmin') @@ -12276,7 +12294,7 @@ $unlimNumRows - + $columns[$indexColumnName]['Extra'] $extraData['error'] $fieldInfoResult[0]['Type'] @@ -12285,6 +12303,7 @@ $oneResult['Duration'] $oneResult['Status'] $sortCol + $statement->expr $unlimNumRows $unlimNumRows Message::sanitize($warning) @@ -12311,9 +12330,10 @@ $oneResult['Status'] $statementInfo->statement->where[0] - + $_SESSION['tmpval']['pos'] $_SESSION['tmpval']['possible_as_geometry'] + $statement->expr[0] $maxRows @@ -12384,18 +12404,28 @@ $statement->altered[0]->field->column - + $result $row[0] $statementInfo->parser->list $statementInfo->parser->list $statementInfo->parser->list $table - $tokenList (bool) $GLOBALS['cfg']['ShowSQL'] + + $statementInfo->limit !== null + + + $statement->expr + $statement->limit + $statement->order + + + $statement->expr + diff --git a/test/classes/Command/TwigLintCommandTest.php b/test/classes/Command/TwigLintCommandTest.php index b97d2cd703..a02ab14837 100644 --- a/test/classes/Command/TwigLintCommandTest.php +++ b/test/classes/Command/TwigLintCommandTest.php @@ -14,9 +14,9 @@ use function class_exists; use function sort; use const DIRECTORY_SEPARATOR; -use const ROOT_PATH; use const SORT_NATURAL; use const SORT_REGULAR; +use const TEST_PATH; /** * @covers \PhpMyAdmin\Command\TwigLintCommand @@ -42,7 +42,7 @@ class TwigLintCommandTest extends AbstractTestCase public function testGetTemplateContents(): void { $contents = $this->callFunction($this->command, TwigLintCommand::class, 'getTemplateContents', [ - ROOT_PATH . 'test/classes/_data/file_listing/subfolder/one.ini', + TEST_PATH . 'test/classes/_data/file_listing/subfolder/one.ini', ]); $this->assertSame('key=value' . "\n", $contents); @@ -50,7 +50,7 @@ class TwigLintCommandTest extends AbstractTestCase public function testFindFiles(): void { - $path = ROOT_PATH . 'test/classes/_data/file_listing'; + $path = TEST_PATH . 'test/classes/_data/file_listing'; $filesFound = $this->callFunction($this->command, TwigLintCommand::class, 'findFiles', [$path]); // Sort results to avoid file system test specific failures @@ -66,7 +66,7 @@ class TwigLintCommandTest extends AbstractTestCase public function testGetFilesInfo(): void { - $path = ROOT_PATH . 'test/classes/_data/file_listing'; + $path = TEST_PATH . 'test/classes/_data/file_listing'; $filesInfos = $this->callFunction($this->command, TwigLintCommand::class, 'getFilesInfo', [$path]); // Sort results to avoid file system test specific failures @@ -120,7 +120,7 @@ class TwigLintCommandTest extends AbstractTestCase ->willReturnOnConsecutiveCalls('{{ file }}', '{{ file }'); $filesFound = $this->callFunction($command, TwigLintCommand::class, 'getFilesInfo', [ - ROOT_PATH . 'test/classes/_data/file_listing', + TEST_PATH . 'test/classes/_data/file_listing', ]); $this->assertEquals([ diff --git a/test/classes/InsertEditTest.php b/test/classes/InsertEditTest.php index 162647a59f..c9abc7a111 100644 --- a/test/classes/InsertEditTest.php +++ b/test/classes/InsertEditTest.php @@ -153,6 +153,43 @@ class InsertEditTest extends AbstractTestCase ); } + /** + * Test for getFormParametersForInsertForm + */ + public function testGetFormParametersForInsertFormGet(): void + { + $where_clause = [ + 'foo' => 'bar ', + '1' => ' test', + ]; + $_GET['clause_is_unique'] = false; + $_GET['sql_query'] = 'SELECT a'; + $_GET['sql_signature'] = Core::signSqlQuery($_GET['sql_query']); + $GLOBALS['goto'] = 'index.php'; + + $result = $this->insertEdit->getFormParametersForInsertForm( + 'dbname', + 'tablename', + [], + $where_clause, + 'localhost' + ); + + $this->assertEquals( + [ + 'db' => 'dbname', + 'table' => 'tablename', + 'goto' => 'index.php', + 'err_url' => 'localhost', + 'sql_query' => 'SELECT a', + 'where_clause[foo]' => 'bar', + 'where_clause[1]' => 'test', + 'clause_is_unique' => false, + ], + $result + ); + } + /** * Test for getWhereClauseArray */ diff --git a/test/classes/SqlTest.php b/test/classes/SqlTest.php index cbe24d7d47..c72be2e63c 100644 --- a/test/classes/SqlTest.php +++ b/test/classes/SqlTest.php @@ -371,9 +371,9 @@ class SqlTest extends AbstractTestCase 164056, 50, false, - 'SELECT COUNT(*) FROM (select * from game_auth_logs l join (' + 'SELECT COUNT(*) FROM (SELECT 1 FROM game_auth_logs AS `l` JOIN (' . ' select al.user_id, max(al.id) as id from game_auth_logs al ' - . 'where al.successfull = 1 group by al.user_id ) last_log on last_log.id = l.id' + . 'where al.successfull = 1 group by al.user_id ) AS `last_log` ON last_log.id = l.id' . ' ) as cnt', ], 'join on SELECT results with alias.*' => [ @@ -385,9 +385,9 @@ class SqlTest extends AbstractTestCase 267, 50, false, - 'SELECT COUNT(*) FROM (select l.* from game_auth_logs l join (' + 'SELECT COUNT(*) FROM (SELECT 1 FROM game_auth_logs AS `l` JOIN (' . ' select al.user_id, max(al.id) as id from game_auth_logs al ' - . 'where al.successfull = 1 group by al.user_id ) last_log on last_log.id = l.id' + . 'where al.successfull = 1 group by al.user_id ) AS `last_log` ON last_log.id = l.id' . ' ) as cnt', ], [ diff --git a/test/classes/Stubs/DbiDummy.php b/test/classes/Stubs/DbiDummy.php index 362993ec11..58d2140bbe 100644 --- a/test/classes/Stubs/DbiDummy.php +++ b/test/classes/Stubs/DbiDummy.php @@ -2742,17 +2742,17 @@ class DbiDummy implements DbiExtension 'result' => [['hostname', 'username', 'password']], ], [ - 'query' => 'SELECT COUNT(*) FROM (SELECT * FROM company_users WHERE not_working_count != 0 ) as cnt', + 'query' => 'SELECT COUNT(*) FROM (SELECT 1 FROM company_users WHERE not_working_count != 0 ) as cnt', 'result' => false, ], [ - 'query' => 'SELECT COUNT(*) FROM (SELECT * FROM company_users ) as cnt', + 'query' => 'SELECT COUNT(*) FROM (SELECT 1 FROM company_users ) as cnt', 'result' => [ [4], ], ], [ - 'query' => 'SELECT COUNT(*) FROM (SELECT * FROM company_users WHERE working_count = 0 ) as cnt', + 'query' => 'SELECT COUNT(*) FROM (SELECT 1 FROM company_users WHERE working_count = 0 ) as cnt', 'result' => [ [15], ], @@ -2765,8 +2765,8 @@ class DbiDummy implements DbiExtension ], [ 'query' => 'SELECT COUNT(*) FROM (' - . 'SELECT *, 1, (SELECT COUNT(*) FROM tbl1) as c1, ' - . '(SELECT 1 FROM tbl2) as c2 FROM company_users WHERE subquery_case = 0 ) as cnt', + . 'SELECT *, 1, (SELECT COUNT(*) FROM tbl1) AS `c1`, ' + . '(SELECT 1 FROM tbl2) AS `c2` FROM company_users WHERE subquery_case = 0 ) as cnt', 'result' => [ [42], ], diff --git a/test/classes/ThemeTest.php b/test/classes/ThemeTest.php index 10a371e3f4..35c8491811 100644 --- a/test/classes/ThemeTest.php +++ b/test/classes/ThemeTest.php @@ -65,7 +65,7 @@ class ThemeTest extends AbstractTestCase */ public function testCheckImgPathIncorrect(): void { - $this->object->setPath(ROOT_PATH . 'test/classes/_data/incorrect_theme'); + $this->object->setPath(TEST_PATH . 'test/classes/_data/incorrect_theme'); $this->assertFalse( $this->object->loadInfo(), 'Theme name is not properly set'