{% trans 'Press Ctrl+Enter to execute query' %}
@@ -165,29 +175,29 @@
{{ 'Options'|trans }}
- {{ 'Set default'|trans }}
+ {{ 'Restore default values'|trans }}
diff --git a/src/Console.php b/src/Console.php
index e5197c509f..d4be6fcbaf 100644
--- a/src/Console.php
+++ b/src/Console.php
@@ -113,6 +113,7 @@ class Console
$bookmarkContent = $this->getBookmarkContent();
return $this->template->render('console/display', [
+ 'settings' => $this->config->config->Console->asArray(),
'has_bookmark_feature' => $bookmarkFeature !== null,
'sql_history' => $sqlHistory,
'bookmark_content' => $bookmarkContent,
diff --git a/src/Controllers/Console/UpdateConfigController.php b/src/Controllers/Console/UpdateConfigController.php
new file mode 100644
index 0000000000..e7ac305138
--- /dev/null
+++ b/src/Controllers/Console/UpdateConfigController.php
@@ -0,0 +1,112 @@
+parseKeyParam($request->getParsedBodyParam('key'));
+ $value = $this->parseValueParam($key, $request->getParsedBodyParam('value'));
+ } catch (InvalidArgumentException $exception) {
+ $this->response->setStatusCode(StatusCodeInterface::STATUS_BAD_REQUEST);
+ $this->response->setRequestStatus(false);
+ $this->response->addJSON(['message' => $exception->getMessage()]);
+
+ return $this->response->response();
+ }
+
+ $result = $this->config->setUserValue(null, 'Console/' . $key, $value);
+ if ($result !== true) {
+ $this->response->setStatusCode(StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR);
+ $this->response->setRequestStatus(false);
+ $this->response->addJSON(['message' => $result->getMessage()]);
+
+ return $this->response->response();
+ }
+
+ $this->response->addJSON('message', __('Console settings has been updated successfully.'));
+
+ return $this->response->response();
+ }
+
+ /** @psalm-return 'StartHistory'|'AlwaysExpand'|'CurrentQuery'|'EnterExecutes'|'DarkTheme'|'Mode'|'Height'|'GroupQueries'|'OrderBy'|'Order' */
+ private function parseKeyParam(mixed $key): string
+ {
+ if (
+ ! in_array($key, [
+ 'StartHistory',
+ 'AlwaysExpand',
+ 'CurrentQuery',
+ 'EnterExecutes',
+ 'DarkTheme',
+ 'Mode',
+ 'Height',
+ 'GroupQueries',
+ 'OrderBy',
+ 'Order',
+ ], true)
+ ) {
+ throw new InvalidArgumentException(__('Unexpected parameter value.'));
+ }
+
+ return $key;
+ }
+
+ /** @psalm-param 'StartHistory'|'AlwaysExpand'|'CurrentQuery'|'EnterExecutes'|'DarkTheme'|'Mode'|'Height'|'GroupQueries'|'OrderBy'|'Order' $key */
+ private function parseValueParam(string $key, mixed $value): bool|int|string
+ {
+ if (
+ in_array($key, [
+ 'StartHistory',
+ 'AlwaysExpand',
+ 'CurrentQuery',
+ 'EnterExecutes',
+ 'DarkTheme',
+ 'GroupQueries',
+ ], true)
+ && in_array($value, ['true', 'false'], true)
+ ) {
+ return $value === 'true';
+ }
+
+ if ($key === 'Mode' && in_array($value, ['show', 'collapse', 'info'], true)) {
+ return $value;
+ }
+
+ if ($key === 'Height' && is_numeric($value) && $value > 0) {
+ return (int) $value;
+ }
+
+ if ($key === 'OrderBy' && in_array($value, ['exec', 'time', 'count'], true)) {
+ return $value;
+ }
+
+ if ($key === 'Order' && in_array($value, ['asc', 'desc'], true)) {
+ return $value;
+ }
+
+ throw new InvalidArgumentException(__('Unexpected parameter value.'));
+ }
+}
diff --git a/src/Routing/Routes.php b/src/Routing/Routes.php
index c433d5897e..159f554640 100644
--- a/src/Routing/Routes.php
+++ b/src/Routing/Routes.php
@@ -12,6 +12,7 @@ use PhpMyAdmin\Controllers\CollationConnectionController;
use PhpMyAdmin\Controllers\ColumnController;
use PhpMyAdmin\Controllers\Config;
use PhpMyAdmin\Controllers\Console\Bookmark;
+use PhpMyAdmin\Controllers\Console\UpdateConfigController;
use PhpMyAdmin\Controllers\Database;
use PhpMyAdmin\Controllers\DatabaseController;
use PhpMyAdmin\Controllers\ErrorReportController;
@@ -57,9 +58,12 @@ final class Routes
$routes->post('/get', Config\GetConfigController::class);
$routes->post('/set', Config\SetConfigController::class);
});
- $routes->addGroup('/console/bookmark', static function (RouteCollector $routes): void {
- $routes->post('/add', Bookmark\AddController::class);
- $routes->get('/refresh', Bookmark\RefreshController::class);
+ $routes->addGroup('/console', static function (RouteCollector $routes): void {
+ $routes->addGroup('/bookmark', static function (RouteCollector $routes): void {
+ $routes->post('/add', Bookmark\AddController::class);
+ $routes->get('/refresh', Bookmark\RefreshController::class);
+ });
+ $routes->post('/update-config', UpdateConfigController::class);
});
$routes->addGroup('/database', static function (RouteCollector $routes): void {
$routes->addGroup('/central-columns', static function (RouteCollector $routes): void {
diff --git a/tests/unit/Controllers/Console/UpdateConfigControllerTest.php b/tests/unit/Controllers/Console/UpdateConfigControllerTest.php
new file mode 100644
index 0000000000..531b964b0e
--- /dev/null
+++ b/tests/unit/Controllers/Console/UpdateConfigControllerTest.php
@@ -0,0 +1,159 @@
+createServerRequest('POST', 'http://example.com/')
+ ->withParsedBody(['key' => $key, 'value' => $value]);
+
+ DatabaseInterface::$instance = $this->createDatabaseInterface();
+ $config = new Config();
+ $responseRenderer = new ResponseRenderer();
+ $responseRenderer->setAjax(true);
+ $controller = new UpdateConfigController($responseRenderer, new Template($config), $config);
+ $response = $controller($request);
+
+ $responseBody = (string) $response->getBody();
+ self::assertJson($responseBody);
+ self::assertSame(
+ ['message' => 'Console settings has been updated successfully.', 'success' => true],
+ json_decode($responseBody, true),
+ );
+ self::assertSame($expected, $config->settings['Console'][$key]);
+ }
+
+ /** @return iterable
*/
+ public static function validParamsProvider(): iterable
+ {
+ yield ['StartHistory', 'true', true];
+ yield ['StartHistory', 'false', false];
+ yield ['AlwaysExpand', 'true', true];
+ yield ['AlwaysExpand', 'false', false];
+ yield ['CurrentQuery', 'true', true];
+ yield ['CurrentQuery', 'false', false];
+ yield ['EnterExecutes', 'true', true];
+ yield ['EnterExecutes', 'false', false];
+ yield ['DarkTheme', 'true', true];
+ yield ['DarkTheme', 'false', false];
+ yield ['Mode', 'show', 'show'];
+ yield ['Mode', 'collapse', 'collapse'];
+ yield ['Mode', 'info', 'info'];
+ yield ['Height', '1', 1];
+ yield ['Height', '92', 92];
+ yield ['GroupQueries', 'true', true];
+ yield ['GroupQueries', 'false', false];
+ yield ['OrderBy', 'exec', 'exec'];
+ yield ['OrderBy', 'time', 'time'];
+ yield ['OrderBy', 'count', 'count'];
+ yield ['Order', 'asc', 'asc'];
+ yield ['Order', 'desc', 'desc'];
+ }
+
+ /**
+ * @param string|string[] $key
+ * @param string|string[] $value
+ */
+ #[DataProvider('invalidParamsProvider')]
+ public function testInvalidParams(array|string $key, array|string $value): void
+ {
+ $request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
+ ->withParsedBody(['key' => $key, 'value' => $value]);
+
+ DatabaseInterface::$instance = $this->createDatabaseInterface();
+ $config = new Config();
+ $responseRenderer = new ResponseRenderer();
+ $responseRenderer->setAjax(true);
+ $controller = new UpdateConfigController($responseRenderer, new Template($config), $config);
+ $response = $controller($request);
+
+ $responseBody = (string) $response->getBody();
+ self::assertJson($responseBody);
+ self::assertSame(
+ ['success' => false, 'error' => 'Unexpected parameter value.'],
+ json_decode($responseBody, true),
+ );
+ }
+
+ /** @return iterable */
+ public static function invalidParamsProvider(): iterable
+ {
+ yield ['StartHistory', ''];
+ yield ['StartHistory', 'invalid'];
+ yield ['StartHistory', ['invalid']];
+ yield ['AlwaysExpand', ''];
+ yield ['AlwaysExpand', 'invalid'];
+ yield ['AlwaysExpand', ['invalid']];
+ yield ['CurrentQuery', ''];
+ yield ['CurrentQuery', 'invalid'];
+ yield ['CurrentQuery', ['invalid']];
+ yield ['EnterExecutes', ''];
+ yield ['EnterExecutes', 'invalid'];
+ yield ['EnterExecutes', ['invalid']];
+ yield ['DarkTheme', ''];
+ yield ['DarkTheme', 'invalid'];
+ yield ['DarkTheme', ['invalid']];
+ yield ['Mode', ''];
+ yield ['Mode', 'invalid'];
+ yield ['Mode', ['invalid']];
+ yield ['Height', ''];
+ yield ['Height', 'invalid'];
+ yield ['Height', ['invalid']];
+ yield ['Height', '0'];
+ yield ['Height', '-1'];
+ yield ['GroupQueries', ''];
+ yield ['GroupQueries', 'invalid'];
+ yield ['GroupQueries', ['invalid']];
+ yield ['OrderBy', ''];
+ yield ['OrderBy', 'invalid'];
+ yield ['OrderBy', ['invalid']];
+ yield ['Order', ''];
+ yield ['Order', 'invalid'];
+ yield ['Order', ['invalid']];
+ yield ['', 'invalid'];
+ yield ['invalid', 'invalid'];
+ yield [['invalid'], 'invalid'];
+ }
+
+ public function testFailedConfigSaving(): void
+ {
+ $request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
+ ->withParsedBody(['key' => 'StartHistory', 'value' => 'true']);
+
+ $config = self::createStub(Config::class);
+ $config->method('setUserValue')->willReturn(Message::error('Could not save configuration'));
+ $responseRenderer = new ResponseRenderer();
+ $responseRenderer->setAjax(true);
+ $controller = new UpdateConfigController($responseRenderer, new Template($config), $config);
+ $response = $controller($request);
+
+ $responseBody = (string) $response->getBody();
+ self::assertJson($responseBody);
+ self::assertSame(
+ ['success' => false, 'error' => 'Could not save configuration'],
+ json_decode($responseBody, true),
+ );
+
+ self::assertSame(['message' => 'Could not save configuration'], $responseRenderer->getJSONResult());
+ self::assertFalse($responseRenderer->hasSuccessState(), 'Should be a failed response.');
+ }
+}
diff --git a/tests/unit/Routing/RoutesTest.php b/tests/unit/Routing/RoutesTest.php
index f3f3f96330..709552977a 100644
--- a/tests/unit/Routing/RoutesTest.php
+++ b/tests/unit/Routing/RoutesTest.php
@@ -14,6 +14,7 @@ use PhpMyAdmin\Controllers\CollationConnectionController;
use PhpMyAdmin\Controllers\ColumnController;
use PhpMyAdmin\Controllers\Config;
use PhpMyAdmin\Controllers\Console\Bookmark;
+use PhpMyAdmin\Controllers\Console\UpdateConfigController;
use PhpMyAdmin\Controllers\Database;
use PhpMyAdmin\Controllers\Database\Structure\CentralColumns;
use PhpMyAdmin\Controllers\DatabaseController;
@@ -164,6 +165,7 @@ final class RoutesTest extends TestCase
'/config/get' => Config\GetConfigController::class,
'/config/set' => Config\SetConfigController::class,
'/console/bookmark/add' => Bookmark\AddController::class,
+ '/console/update-config' => UpdateConfigController::class,
'/database/central-columns' => Database\CentralColumnsController::class,
'/database/central-columns/populate' => Database\CentralColumns\PopulateColumnsController::class,
'/database/designer' => Database\DesignerController::class,
diff --git a/tests/unit/Stubs/ResponseRenderer.php b/tests/unit/Stubs/ResponseRenderer.php
index 1faab2d41a..8f86d8d62e 100644
--- a/tests/unit/Stubs/ResponseRenderer.php
+++ b/tests/unit/Stubs/ResponseRenderer.php
@@ -25,6 +25,7 @@ use PhpMyAdmin\Message;
use PhpMyAdmin\Template;
use function is_array;
+use function json_encode;
class ResponseRenderer extends \PhpMyAdmin\ResponseRenderer
{
@@ -186,4 +187,26 @@ class ResponseRenderer extends \PhpMyAdmin\ResponseRenderer
{
return $this->response;
}
+
+ public function response(): Response
+ {
+ if ($this->isAjax()) {
+ $json = $this->getJSONResult();
+ if ($this->isSuccess) {
+ $json['success'] = true;
+ } else {
+ $json['success'] = false;
+ $json['error'] = $json['message'];
+ unset($json['message']);
+ }
+
+ $output = (string) json_encode($json);
+ } else {
+ $output = $this->getHTMLResult();
+ }
+
+ $this->response->getBody()->write($output);
+
+ return $this->response;
+ }
}