diff --git a/js/src/ajax.js b/js/src/ajax.js index 61b6c8f0d7..f1a123830f 100644 --- a/js/src/ajax.js +++ b/js/src/ajax.js @@ -895,6 +895,23 @@ $(document).on('ajaxError', function (event, request) { var details = ''; var state = request.state(); + if ( + 'responseJSON' in request && + 'isErrorResponse' in request.responseJSON && + request.responseJSON.isErrorResponse + ) { + Functions.ajaxShowMessage( + '', + false + ); + AJAX.active = false; + AJAX.xhr = null; + + return; + } + if (request.status !== 0) { details += '
' + Functions.escapeHtml(Functions.sprintf(Messages.strErrorCode, request.status)) + '
'; } diff --git a/libraries/classes/Controllers/AbstractController.php b/libraries/classes/Controllers/AbstractController.php index 158c735ef4..50d8ed644a 100644 --- a/libraries/classes/Controllers/AbstractController.php +++ b/libraries/classes/Controllers/AbstractController.php @@ -146,4 +146,22 @@ abstract class AbstractController $this->response->setHttpResponseCode(400); Core::fatalError($errorMessage); } + + /** + * @psalm-param int<400,599> $statusCode + */ + protected function sendErrorResponse(string $message, int $statusCode = 400): void + { + $this->response->setHttpResponseCode($statusCode); + $this->response->setRequestStatus(false); + + if ($this->response->isAjax()) { + $this->response->addJSON('isErrorResponse', true); + $this->response->addJSON('message', $message); + + return; + } + + $this->response->addHTML(Message::error($message)->getDisplay()); + } } diff --git a/libraries/classes/Controllers/Table/DropColumnConfirmationController.php b/libraries/classes/Controllers/Table/DropColumnConfirmationController.php index d402d4b980..8d8f30a0f9 100644 --- a/libraries/classes/Controllers/Table/DropColumnConfirmationController.php +++ b/libraries/classes/Controllers/Table/DropColumnConfirmationController.php @@ -25,15 +25,11 @@ final class DropColumnConfirmationController extends AbstractController $table = TableName::fromValue($request->getParsedBodyParam('table')); Assert::allStringNotEmpty($fields); } catch (InvalidIdentifierName $exception) { - $this->response->setHttpResponseCode(400); - $this->response->setRequestStatus(false); - $this->response->addJSON('message', $exception->getMessage()); + $this->sendErrorResponse($exception->getMessage()); return; } catch (InvalidArgumentException $exception) { - $this->response->setHttpResponseCode(400); - $this->response->setRequestStatus(false); - $this->response->addJSON('message', __('No column selected.')); + $this->sendErrorResponse(__('No column selected.')); return; } diff --git a/test/classes/Controllers/AbstractControllerTest.php b/test/classes/Controllers/AbstractControllerTest.php index b56b9822bb..daa1908266 100644 --- a/test/classes/Controllers/AbstractControllerTest.php +++ b/test/classes/Controllers/AbstractControllerTest.php @@ -6,6 +6,7 @@ namespace PhpMyAdmin\Tests\Controllers; use PhpMyAdmin\Controllers\AbstractController; use PhpMyAdmin\Html\MySQLDocumentation; +use PhpMyAdmin\Message; use PhpMyAdmin\Sanitize; use PhpMyAdmin\Template; use PhpMyAdmin\Tests\AbstractTestCase; @@ -58,8 +59,7 @@ class AbstractControllerTest extends AbstractTestCase $_REQUEST = []; $response = new ResponseRenderer(); - $template = new Template(); - $controller = new class ($response, $template) extends AbstractController { + $controller = new class ($response, new Template()) extends AbstractController { /** * @psalm-param non-empty-list $params */ @@ -80,4 +80,56 @@ class AbstractControllerTest extends AbstractTestCase $this->assertSame(200, $response->getHttpResponseCode()); } + + public function testSendErrorResponseWithJson(): void + { + $response = new ResponseRenderer(); + $response->setAjax(true); + + $controller = new class ($response, new Template()) extends AbstractController { + /** + * @psalm-param int<400,599> $statusCode + */ + public function testSendErrorResponse(string $message, int $statusCode = 400): void + { + parent::sendErrorResponse($message, $statusCode); + } + }; + + $controller->testSendErrorResponse('Error message.', 404); + + $this->assertSame(404, $response->getHttpResponseCode()); + $this->assertFalse($response->hasSuccessState()); + $this->assertSame('', $response->getHTMLResult()); + $this->assertSame([ + 'isErrorResponse' => true, + 'message' => 'Error message.', + ], $response->getJSONResult()); + } + + public function testSendErrorResponseWithHtml(): void + { + $response = new ResponseRenderer(); + $response->setAjax(false); + + $controller = new class ($response, new Template()) extends AbstractController { + /** + * @psalm-param int<400,599> $statusCode + */ + public function testSendErrorResponse(string $message, int $statusCode = 400): void + { + parent::sendErrorResponse($message, $statusCode); + } + }; + + $controller->testSendErrorResponse('Error message.', 404); + + $this->assertSame(404, $response->getHttpResponseCode()); + $this->assertFalse($response->hasSuccessState()); + $this->assertSame( + Message::error('Error message.')->getDisplay(), + $response->getHTMLResult() + ); + $this->assertSame([], $response->getJSONResult()); + } } diff --git a/test/classes/Controllers/Table/DropColumnConfirmationControllerTest.php b/test/classes/Controllers/Table/DropColumnConfirmationControllerTest.php index b9358433ea..f8aad39dfa 100644 --- a/test/classes/Controllers/Table/DropColumnConfirmationControllerTest.php +++ b/test/classes/Controllers/Table/DropColumnConfirmationControllerTest.php @@ -28,6 +28,8 @@ class DropColumnConfirmationControllerTest extends AbstractTestCase $this->dummyDbi->addResult('SHOW TABLES LIKE \'test_table\';', [['test_table']]); $response = new ResponseRenderer(); + $response->setAjax(true); + $template = new Template(); $expected = $template->render('table/structure/drop_confirm', [ 'db' => 'test_db', @@ -53,11 +55,16 @@ class DropColumnConfirmationControllerTest extends AbstractTestCase ]); $response = new ResponseRenderer(); + $response->setAjax(true); + (new DropColumnConfirmationController($response, new Template()))($request); $this->assertSame(400, $response->getHttpResponseCode()); $this->assertFalse($response->hasSuccessState()); - $this->assertSame(['message' => 'No column selected.'], $response->getJSONResult()); + $this->assertSame([ + 'isErrorResponse' => true, + 'message' => 'No column selected.' + ], $response->getJSONResult()); $this->assertSame('', $response->getHTMLResult()); } @@ -71,11 +78,16 @@ class DropColumnConfirmationControllerTest extends AbstractTestCase ]); $response = new ResponseRenderer(); + $response->setAjax(true); + (new DropColumnConfirmationController($response, new Template()))($request); $this->assertSame(400, $response->getHttpResponseCode()); $this->assertFalse($response->hasSuccessState()); - $this->assertSame(['message' => 'The database name must be a non-empty string.'], $response->getJSONResult()); + $this->assertSame([ + 'isErrorResponse' => true, + 'message' => 'The database name must be a non-empty string.' + ], $response->getJSONResult()); $this->assertSame('', $response->getHTMLResult()); } }