From 352c465640bb561069bbf6916f5a5195cc7bdf99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Meneghini=20Fauth?= Date: Tue, 19 Mar 2024 15:08:33 -0300 Subject: [PATCH 1/9] Initialize Console settings at server side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maurício Meneghini Fauth --- resources/js/src/modules/console.ts | 22 ---------------------- resources/templates/console/display.twig | 12 ++++++------ src/Console.php | 1 + 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/resources/js/src/modules/console.ts b/resources/js/src/modules/console.ts index a0f7acd0a7..645a184485 100644 --- a/resources/js/src/modules/console.ts +++ b/resources/js/src/modules/console.ts @@ -101,28 +101,6 @@ var Console = { // Event binds shouldn't run again if (Console.isInitialized === false) { - // Load config first - if (Config.AlwaysExpand) { - (document.getElementById('consoleOptionsAlwaysExpandCheckbox') as HTMLInputElement).checked = true; - } - - if (Config.StartHistory) { - (document.getElementById('consoleOptionsStartHistoryCheckbox') as HTMLInputElement).checked = true; - } - - if (Config.CurrentQuery) { - (document.getElementById('consoleOptionsCurrentQueryCheckbox') as HTMLInputElement).checked = true; - } - - if (Config.EnterExecutes) { - (document.getElementById('consoleOptionsEnterExecutesCheckbox') as HTMLInputElement).checked = true; - } - - if (Config.DarkTheme) { - (document.getElementById('consoleOptionsDarkThemeCheckbox') as HTMLInputElement).checked = true; - $('#pma_console').find('>.content').addClass('console_dark_theme'); - } - ConsoleResizer.initialize(); ConsoleInput.initialize(); ConsoleMessages.initialize(); diff --git a/resources/templates/console/display.twig b/resources/templates/console/display.twig index 7f3540d40c..25db37f536 100644 --- a/resources/templates/console/display.twig +++ b/resources/templates/console/display.twig @@ -27,7 +27,7 @@ {# Console messages #} -
+
{% trans 'Press Ctrl+Enter to execute query' %} @@ -171,23 +171,23 @@
- +
- +
- +
- +
- +
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, From 775a9a4537fd03cd784fda116c10b375d106e814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Meneghini=20Fauth?= Date: Tue, 19 Mar 2024 18:32:39 -0300 Subject: [PATCH 2/9] Remove getConfigValue() call from Console.initialize() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uses data attributes to store the config values. That removes the need to do a HTTP request to get the values. Signed-off-by: Maurício Meneghini Fauth --- resources/js/src/modules/console.ts | 14 ++++-------- resources/js/src/modules/console/config.ts | 26 ++++++++++------------ resources/templates/console/display.twig | 12 +++++++++- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/resources/js/src/modules/console.ts b/resources/js/src/modules/console.ts index 645a184485..069becfa98 100644 --- a/resources/js/src/modules/console.ts +++ b/resources/js/src/modules/console.ts @@ -5,7 +5,6 @@ import { Functions } from './functions.ts'; import { CommonParams } from './common.ts'; import { Navigation } from './navigation.ts'; import { Config } from './console/config.ts'; -import { getConfigValue } from './functions/config.ts'; import { escapeHtml } from './functions/escape.ts'; /** @@ -58,18 +57,13 @@ var Console = { * Used for console initialize, reinit is ok, just some variable assignment */ initialize: function (): void { - if ($('#pma_console').length === 0) { + const consoleElement = document.getElementById('pma_console'); + if (consoleElement === null) { return; } - getConfigValue('Console', false, (data) => { - Config.init(data); - Console.setupAfterInit(); - }, () => { - Config.init({});// Avoid null pointers in setupAfterInit() - // Fetching data failed, still perform the console init - Console.setupAfterInit(); - }); + Config.init(consoleElement.dataset); + Console.setupAfterInit(); }, /** diff --git a/resources/js/src/modules/console/config.ts b/resources/js/src/modules/console/config.ts index 17b1f604d6..ebe56de2ea 100644 --- a/resources/js/src/modules/console/config.ts +++ b/resources/js/src/modules/console/config.ts @@ -45,20 +45,18 @@ export const Config = { */ Order: 'asc', - /** - * @param {Object} data - */ - init: function (data): void { - this.StartHistory = !! data.StartHistory; - this.AlwaysExpand = !! data.AlwaysExpand; - this.CurrentQuery = data.CurrentQuery !== undefined ? !! data.CurrentQuery : true; - this.EnterExecutes = !! data.EnterExecutes; - this.DarkTheme = !! data.DarkTheme; - this.Mode = data.Mode === 'show' || data.Mode === 'collapse' ? data.Mode : 'info'; - this.Height = data.Height > 0 ? Number(data.Height) : 92; - this.GroupQueries = !! data.GroupQueries; - this.OrderBy = data.OrderBy === 'time' || data.OrderBy === 'count' ? data.OrderBy : 'exec'; - this.Order = data.Order === 'desc' ? 'desc' : 'asc'; + init: function (dataset: DOMStringMap): void { + this.StartHistory = dataset.startHistory === 'true'; + this.AlwaysExpand = dataset.alwaysExpand === 'true'; + this.CurrentQuery = dataset.currentQuery !== undefined ? dataset.currentQuery === 'true' : true; + this.EnterExecutes = dataset.enterExecutes === 'true'; + this.DarkTheme = dataset.darkTheme === 'true'; + this.Mode = dataset.mode === 'show' || dataset.mode === 'collapse' ? dataset.mode : 'info'; + const height = Number(dataset.height); + this.Height = height > 0 ? height : 92; + this.GroupQueries = dataset.groupQueries === 'true'; + this.OrderBy = dataset.orderBy === 'time' || dataset.orderBy === 'count' ? dataset.orderBy : 'exec'; + this.Order = dataset.order === 'desc' ? 'desc' : 'asc'; }, /** diff --git a/resources/templates/console/display.twig b/resources/templates/console/display.twig index 25db37f536..45a66960a9 100644 --- a/resources/templates/console/display.twig +++ b/resources/templates/console/display.twig @@ -1,5 +1,15 @@
-
+
From db5b230ae54c205e072551e3be2ec30a66cfa09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Meneghini=20Fauth?= Date: Thu, 21 Mar 2024 19:14:38 -0300 Subject: [PATCH 6/9] Split Console's Config.set() for each property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows for better type handling. Signed-off-by: Maurício Meneghini Fauth --- resources/js/src/modules/console.ts | 46 ++++++++--------- resources/js/src/modules/console/config.ts | 57 +++++++++++++++++++--- 2 files changed, 72 insertions(+), 31 deletions(-) diff --git a/resources/js/src/modules/console.ts b/resources/js/src/modules/console.ts index e0ea8b9d6c..462528a499 100644 --- a/resources/js/src/modules/console.ts +++ b/resources/js/src/modules/console.ts @@ -148,30 +148,30 @@ var Console = { const consoleOptionsAlwaysExpandCheckbox = document.getElementById('consoleOptionsAlwaysExpandCheckbox') as HTMLInputElement; consoleOptionsAlwaysExpandCheckbox?.addEventListener('change', function (): void { - Config.set('AlwaysExpand', !! consoleOptionsAlwaysExpandCheckbox.checked); + Config.setAlwaysExpand(consoleOptionsAlwaysExpandCheckbox.checked); }); const consoleOptionsStartHistoryCheckbox = document.getElementById('consoleOptionsStartHistoryCheckbox') as HTMLInputElement; consoleOptionsStartHistoryCheckbox?.addEventListener('change', function (): void { - Config.set('StartHistory', !! consoleOptionsStartHistoryCheckbox.checked); + Config.setStartHistory(consoleOptionsStartHistoryCheckbox.checked); }); const consoleOptionsCurrentQueryCheckbox = document.getElementById('consoleOptionsCurrentQueryCheckbox') as HTMLInputElement; consoleOptionsCurrentQueryCheckbox?.addEventListener('change', function (): void { - Config.set('CurrentQuery', !! consoleOptionsCurrentQueryCheckbox.checked); + Config.setCurrentQuery(consoleOptionsCurrentQueryCheckbox.checked); }); const consoleOptionsEnterExecutesCheckbox = document.getElementById('consoleOptionsEnterExecutesCheckbox') as HTMLInputElement; consoleOptionsEnterExecutesCheckbox?.addEventListener('change', function (): void { - const isEnterExecutes = !! consoleOptionsEnterExecutesCheckbox.checked; - Config.set('EnterExecutes', isEnterExecutes); + const isEnterExecutes = consoleOptionsEnterExecutesCheckbox.checked; + Config.setEnterExecutes(isEnterExecutes); ConsoleMessages.showInstructions(isEnterExecutes); }); const consoleOptionsDarkThemeCheckbox = document.getElementById('consoleOptionsDarkThemeCheckbox') as HTMLInputElement; consoleOptionsDarkThemeCheckbox?.addEventListener('change', function (): void { - const isDarkTheme = !! consoleOptionsDarkThemeCheckbox.checked; - Config.set('DarkTheme', isDarkTheme); + const isDarkTheme = consoleOptionsDarkThemeCheckbox.checked; + Config.setDarkTheme(isDarkTheme); const consoleContent = document.getElementById('pma_console').querySelector('.content'); consoleContent.classList.toggle('console_dark_theme', isDarkTheme); }); @@ -180,28 +180,28 @@ var Console = { restoreConsoleOptionsButton?.addEventListener('click', function (): void { if (consoleOptionsAlwaysExpandCheckbox.checked) { consoleOptionsAlwaysExpandCheckbox.checked = false; - Config.set('AlwaysExpand', false); + Config.setAlwaysExpand(false); } if (consoleOptionsStartHistoryCheckbox.checked) { consoleOptionsStartHistoryCheckbox.checked = false; - Config.set('StartHistory', false); + Config.setStartHistory(false); } if (! consoleOptionsCurrentQueryCheckbox.checked) { consoleOptionsCurrentQueryCheckbox.checked = true; - Config.set('CurrentQuery', true); + Config.setCurrentQuery(true); } if (consoleOptionsEnterExecutesCheckbox.checked) { consoleOptionsEnterExecutesCheckbox.checked = false; - Config.set('EnterExecutes', false); + Config.setEnterExecutes(false); ConsoleMessages.showInstructions(false); } if (consoleOptionsDarkThemeCheckbox.checked) { consoleOptionsDarkThemeCheckbox.checked = false; - Config.set('DarkTheme', false); + Config.setDarkTheme(false); const consoleContent = document.getElementById('pma_console').querySelector('.content'); consoleContent.classList.remove('console_dark_theme'); } @@ -243,7 +243,7 @@ var Console = { Console.scrollBottom(); break; default: - Config.set('Mode', 'info'); + Config.setMode('info'); Console.info(); } }, @@ -306,7 +306,7 @@ var Console = { * Change console to collapse mode */ collapse: function (): void { - Config.set('Mode', 'collapse'); + Config.setMode('collapse'); var pmaConsoleHeight = Math.max(92, Config.Height); Console.$consoleToolbar.addClass('collapsed'); @@ -321,7 +321,7 @@ var Console = { * @param {boolean} inputFocus If true, focus the input line after show() */ show: function (inputFocus = undefined): void { - Config.set('Mode', 'show'); + Config.setMode('show'); var pmaConsoleHeight = Math.max(92, Config.Height); // eslint-disable-next-line compat/compat @@ -472,7 +472,7 @@ var ConsoleResizer = { * Mouseup event handler for bind to resizer */ mouseUp: function (): void { - Config.set('Height', Math.round(ConsoleResizer.resultHeight)); + Config.setHeight(Math.round(ConsoleResizer.resultHeight)); Console.show(); $(document).off('mousemove'); $(document).off('mouseup'); @@ -1197,7 +1197,7 @@ var ConsoleDebug = { $('#debug_console').find('.button.group_queries').on('click', function () { $('#debug_console').addClass('grouped'); $('#debug_console').removeClass('ungrouped'); - Config.set('GroupQueries', true); + Config.setGroupQueries(true); ConsoleDebug.refresh(); if (Config.OrderBy === 'count') { $('#debug_console').find('.button.order_by.sort_exec').removeClass('active'); @@ -1207,7 +1207,7 @@ var ConsoleDebug = { $('#debug_console').find('.button.ungroup_queries').on('click', function () { $('#debug_console').addClass('ungrouped'); $('#debug_console').removeClass('grouped'); - Config.set('GroupQueries', false); + Config.setGroupQueries(false); ConsoleDebug.refresh(); if (Config.OrderBy === 'count') { $('#debug_console').find('.button.order_by.sort_exec').addClass('active'); @@ -1219,11 +1219,11 @@ var ConsoleDebug = { $('#debug_console').find('.button.order_by').removeClass('active'); $this.addClass('active'); if ($this.hasClass('sort_time')) { - Config.set('OrderBy', 'time'); + Config.setOrderBy('time'); } else if ($this.hasClass('sort_exec')) { - Config.set('OrderBy', 'exec'); + Config.setOrderBy('exec'); } else if ($this.hasClass('sort_count')) { - Config.set('OrderBy', 'count'); + Config.setOrderBy('count'); } ConsoleDebug.refresh(); @@ -1234,9 +1234,9 @@ var ConsoleDebug = { $('#debug_console').find('.button.order').removeClass('active'); $this.addClass('active'); if ($this.hasClass('order_asc')) { - Config.set('Order', 'asc'); + Config.setOrder('asc'); } else if ($this.hasClass('order_desc')) { - Config.set('Order', 'desc'); + Config.setOrder('desc'); } ConsoleDebug.refresh(); diff --git a/resources/js/src/modules/console/config.ts b/resources/js/src/modules/console/config.ts index df304b4ce6..95462812e4 100644 --- a/resources/js/src/modules/console/config.ts +++ b/resources/js/src/modules/console/config.ts @@ -61,13 +61,54 @@ export const Config = { this.Order = dataset.order === 'desc' ? 'desc' : 'asc'; }, - /** - * @param {'StartHistory'|'AlwaysExpand'|'CurrentQuery'|'EnterExecutes'|'DarkTheme'|'Mode'|'Height'|'GroupQueries'|'OrderBy'|'Order'} key - * @param {boolean|string|number} value - */ - set: function (key, value): void { - this[key] = value; - setConfigValue(key, value); + setStartHistory: function (value: boolean): void { + this.StartHistory = value; + setConfigValue('StartHistory', value); + }, + + setAlwaysExpand: function (value: boolean): void { + this.AlwaysExpand = value; + setConfigValue('AlwaysExpand', value); + }, + + setCurrentQuery: function (value: boolean): void { + this.CurrentQuery = value; + setConfigValue('CurrentQuery', value); + }, + + setEnterExecutes: function (value: boolean): void { + this.EnterExecutes = value; + setConfigValue('EnterExecutes', value); + }, + + setDarkTheme: function (value: boolean): void { + this.DarkTheme = value; + setConfigValue('DarkTheme', value); + }, + + setMode: function (value: 'info'|'show'|'collapse'): void { + this.Mode = value; + setConfigValue('Mode', value); + }, + + setHeight: function (value: number): void { + this.Height = value; + setConfigValue('Height', value); + }, + + setGroupQueries: function (value: boolean): void { + this.GroupQueries = value; + setConfigValue('GroupQueries', value); + }, + + setOrderBy: function (value: 'exec'|'time'|'count'): void { + this.OrderBy = value; + setConfigValue('OrderBy', value); + }, + + setOrder: function (value: 'asc'|'desc'): void { + this.Order = value; + setConfigValue('Order', value); }, }; @@ -75,7 +116,7 @@ export const Config = { * @param {'StartHistory'|'AlwaysExpand'|'CurrentQuery'|'EnterExecutes'|'DarkTheme'|'Mode'|'Height'|'GroupQueries'|'OrderBy'|'Order'} key * @param {boolean|string|number} value */ -function setConfigValue (key, value): void { +function setConfigValue (key: string, value: boolean|number|string): void { // Updating value in local storage. const serialized = JSON.stringify(value); localStorage.setItem('Console/' + key, serialized); From 0e7ea9cb8990d0a65b470e9301bca20240f87226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Meneghini=20Fauth?= Date: Thu, 21 Mar 2024 20:03:23 -0300 Subject: [PATCH 7/9] Convert Console's Config object into a class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maurício Meneghini Fauth --- resources/js/src/modules/console.ts | 92 +++++------ resources/js/src/modules/console/config.ts | 174 +++++++++++---------- 2 files changed, 137 insertions(+), 129 deletions(-) diff --git a/resources/js/src/modules/console.ts b/resources/js/src/modules/console.ts index 462528a499..17a277656c 100644 --- a/resources/js/src/modules/console.ts +++ b/resources/js/src/modules/console.ts @@ -4,9 +4,11 @@ import { AJAX } from './ajax.ts'; import { Functions } from './functions.ts'; import { CommonParams } from './common.ts'; import { Navigation } from './navigation.ts'; -import { Config } from './console/config.ts'; +import Config from './console/config.ts'; import { escapeHtml } from './functions/escape.ts'; +let config: Config; + /** * Console object */ @@ -62,7 +64,7 @@ var Console = { return; } - Config.init(consoleElement.dataset); + config = Config.createFromDataset(consoleElement.dataset); Console.setupAfterInit(); }, @@ -148,30 +150,30 @@ var Console = { const consoleOptionsAlwaysExpandCheckbox = document.getElementById('consoleOptionsAlwaysExpandCheckbox') as HTMLInputElement; consoleOptionsAlwaysExpandCheckbox?.addEventListener('change', function (): void { - Config.setAlwaysExpand(consoleOptionsAlwaysExpandCheckbox.checked); + config.setAlwaysExpand(consoleOptionsAlwaysExpandCheckbox.checked); }); const consoleOptionsStartHistoryCheckbox = document.getElementById('consoleOptionsStartHistoryCheckbox') as HTMLInputElement; consoleOptionsStartHistoryCheckbox?.addEventListener('change', function (): void { - Config.setStartHistory(consoleOptionsStartHistoryCheckbox.checked); + config.setStartHistory(consoleOptionsStartHistoryCheckbox.checked); }); const consoleOptionsCurrentQueryCheckbox = document.getElementById('consoleOptionsCurrentQueryCheckbox') as HTMLInputElement; consoleOptionsCurrentQueryCheckbox?.addEventListener('change', function (): void { - Config.setCurrentQuery(consoleOptionsCurrentQueryCheckbox.checked); + config.setCurrentQuery(consoleOptionsCurrentQueryCheckbox.checked); }); const consoleOptionsEnterExecutesCheckbox = document.getElementById('consoleOptionsEnterExecutesCheckbox') as HTMLInputElement; consoleOptionsEnterExecutesCheckbox?.addEventListener('change', function (): void { const isEnterExecutes = consoleOptionsEnterExecutesCheckbox.checked; - Config.setEnterExecutes(isEnterExecutes); + config.setEnterExecutes(isEnterExecutes); ConsoleMessages.showInstructions(isEnterExecutes); }); const consoleOptionsDarkThemeCheckbox = document.getElementById('consoleOptionsDarkThemeCheckbox') as HTMLInputElement; consoleOptionsDarkThemeCheckbox?.addEventListener('change', function (): void { const isDarkTheme = consoleOptionsDarkThemeCheckbox.checked; - Config.setDarkTheme(isDarkTheme); + config.setDarkTheme(isDarkTheme); const consoleContent = document.getElementById('pma_console').querySelector('.content'); consoleContent.classList.toggle('console_dark_theme', isDarkTheme); }); @@ -180,28 +182,28 @@ var Console = { restoreConsoleOptionsButton?.addEventListener('click', function (): void { if (consoleOptionsAlwaysExpandCheckbox.checked) { consoleOptionsAlwaysExpandCheckbox.checked = false; - Config.setAlwaysExpand(false); + config.setAlwaysExpand(false); } if (consoleOptionsStartHistoryCheckbox.checked) { consoleOptionsStartHistoryCheckbox.checked = false; - Config.setStartHistory(false); + config.setStartHistory(false); } if (! consoleOptionsCurrentQueryCheckbox.checked) { consoleOptionsCurrentQueryCheckbox.checked = true; - Config.setCurrentQuery(true); + config.setCurrentQuery(true); } if (consoleOptionsEnterExecutesCheckbox.checked) { consoleOptionsEnterExecutesCheckbox.checked = false; - Config.setEnterExecutes(false); + config.setEnterExecutes(false); ConsoleMessages.showInstructions(false); } if (consoleOptionsDarkThemeCheckbox.checked) { consoleOptionsDarkThemeCheckbox.checked = false; - Config.setDarkTheme(false); + config.setDarkTheme(false); const consoleContent = document.getElementById('pma_console').querySelector('.content'); consoleContent.classList.remove('console_dark_theme'); } @@ -231,7 +233,7 @@ var Console = { } // Change console mode from cookie - switch (Config.Mode) { + switch (config.mode) { case 'collapse': Console.collapse(); break; @@ -243,7 +245,7 @@ var Console = { Console.scrollBottom(); break; default: - Config.setMode('info'); + config.setMode('info'); Console.info(); } }, @@ -298,7 +300,7 @@ var Console = { if (data.reloadQuerywindow.sql_query.length > 0) { ConsoleMessages.appendQuery(data.reloadQuerywindow, 'successed') // @ts-ignore - .$message.addClass(Config.CurrentQuery ? '' : 'hide'); + .$message.addClass(config.currentQuery ? '' : 'hide'); } } }, @@ -306,8 +308,8 @@ var Console = { * Change console to collapse mode */ collapse: function (): void { - Config.setMode('collapse'); - var pmaConsoleHeight = Math.max(92, Config.Height); + config.setMode('collapse'); + var pmaConsoleHeight = Math.max(92, config.height); Console.$consoleToolbar.addClass('collapsed'); Console.$consoleAllContents.height(pmaConsoleHeight); @@ -321,11 +323,11 @@ var Console = { * @param {boolean} inputFocus If true, focus the input line after show() */ show: function (inputFocus = undefined): void { - Config.setMode('show'); + config.setMode('show'); - var pmaConsoleHeight = Math.max(92, Config.Height); + var pmaConsoleHeight = Math.max(92, config.height); // eslint-disable-next-line compat/compat - pmaConsoleHeight = Math.min(Config.Height, (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight) - 25); + pmaConsoleHeight = Math.min(config.height, (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight) - 25); Console.$consoleContent.css({ display: 'block' }); if (Console.$consoleToolbar.hasClass('collapsed')) { Console.$consoleToolbar.removeClass('collapsed'); @@ -351,7 +353,7 @@ var Console = { * Used for toggle buttons and shortcuts */ toggle: function (): void { - if (Config.Mode === 'show') { + if (config.mode === 'show') { Console.collapse(); } else { Console.show(true); @@ -429,7 +431,7 @@ var ConsoleResizer = { * @param {MouseEvent} event */ mouseDown: function (event): void { - if (Config.Mode !== 'show') { + if (config.mode !== 'show') { return; } @@ -472,7 +474,7 @@ var ConsoleResizer = { * Mouseup event handler for bind to resizer */ mouseUp: function (): void { - Config.setHeight(Math.round(ConsoleResizer.resultHeight)); + config.setHeight(Math.round(ConsoleResizer.resultHeight)); Console.show(); $(document).off('mousemove'); $(document).off('mouseup'); @@ -654,7 +656,7 @@ var ConsoleInput = { */ keyDown: function (event): void { // Execute command - if (Config.EnterExecutes) { + if (config.enterExecutes) { // Enter, but not in combination with Shift (which writes a new line). if (! event.shiftKey && event.keyCode === 13) { ConsoleInput.execute(); @@ -826,7 +828,7 @@ var ConsoleMessages = { var now = new Date(); var $newMessage = $('
'); switch (msgType) { case 'query': @@ -1069,11 +1071,11 @@ var ConsoleMessages = { */ initialize: function (): void { ConsoleMessages.messageEventBinds($('#pma_console').find('.message:not(.binded)')); - if (Config.StartHistory) { + if (config.startHistory) { ConsoleMessages.showHistory(); } - ConsoleMessages.showInstructions(Config.EnterExecutes); + ConsoleMessages.showInstructions(config.enterExecutes); } }; @@ -1179,17 +1181,17 @@ var ConsoleDebug = { } }); - if (Config.GroupQueries) { + if (config.groupQueries) { $('#debug_console').addClass('grouped'); } else { $('#debug_console').addClass('ungrouped'); - if (Config.OrderBy === 'count') { + if (config.orderBy === 'count') { $('#debug_console').find('.button.order_by.sort_exec').addClass('active'); } } - var orderBy = Config.OrderBy; - var order = Config.Order; + var orderBy = config.orderBy; + var order = config.order; $('#debug_console').find('.button.order_by.sort_' + orderBy).addClass('active'); $('#debug_console').find('.button.order.order_' + order).addClass('active'); @@ -1197,9 +1199,9 @@ var ConsoleDebug = { $('#debug_console').find('.button.group_queries').on('click', function () { $('#debug_console').addClass('grouped'); $('#debug_console').removeClass('ungrouped'); - Config.setGroupQueries(true); + config.setGroupQueries(true); ConsoleDebug.refresh(); - if (Config.OrderBy === 'count') { + if (config.orderBy === 'count') { $('#debug_console').find('.button.order_by.sort_exec').removeClass('active'); } }); @@ -1207,9 +1209,9 @@ var ConsoleDebug = { $('#debug_console').find('.button.ungroup_queries').on('click', function () { $('#debug_console').addClass('ungrouped'); $('#debug_console').removeClass('grouped'); - Config.setGroupQueries(false); + config.setGroupQueries(false); ConsoleDebug.refresh(); - if (Config.OrderBy === 'count') { + if (config.orderBy === 'count') { $('#debug_console').find('.button.order_by.sort_exec').addClass('active'); } }); @@ -1219,11 +1221,11 @@ var ConsoleDebug = { $('#debug_console').find('.button.order_by').removeClass('active'); $this.addClass('active'); if ($this.hasClass('sort_time')) { - Config.setOrderBy('time'); + config.setOrderBy('time'); } else if ($this.hasClass('sort_exec')) { - Config.setOrderBy('exec'); + config.setOrderBy('exec'); } else if ($this.hasClass('sort_count')) { - Config.setOrderBy('count'); + config.setOrderBy('count'); } ConsoleDebug.refresh(); @@ -1234,9 +1236,9 @@ var ConsoleDebug = { $('#debug_console').find('.button.order').removeClass('active'); $this.addClass('active'); if ($this.hasClass('order_asc')) { - Config.setOrder('asc'); + config.setOrder('asc'); } else if ($this.hasClass('order_desc')) { - Config.setOrder('desc'); + config.setOrder('desc'); } ConsoleDebug.refresh(); @@ -1523,7 +1525,7 @@ var ConsoleDebug = { // For sorting queries function sortByTime (a, b) { - var order = Config.Order === 'asc' ? 1 : -1; + var order = config.order === 'asc' ? 1 : -1; if (Array.isArray(a) && Array.isArray(b)) { // It is grouped var timeA = 0; @@ -1544,15 +1546,15 @@ var ConsoleDebug = { } function sortByCount (a, b) { - var order = Config.Order === 'asc' ? 1 : -1; + var order = config.order === 'asc' ? 1 : -1; return (a.length - b.length) * order; } - var orderBy = Config.OrderBy; - var order = Config.Order; + var orderBy = config.orderBy; + var order = config.order; - if (Config.GroupQueries) { + if (config.groupQueries) { // Sort queries if (orderBy === 'time') { uniqueQueries.sort(sortByTime); diff --git a/resources/js/src/modules/console/config.ts b/resources/js/src/modules/console/config.ts index 95462812e4..99947a0e95 100644 --- a/resources/js/src/modules/console/config.ts +++ b/resources/js/src/modules/console/config.ts @@ -5,112 +5,118 @@ import { CommonParams } from '../common.ts'; /** * @link https://docs.phpmyadmin.net/en/latest/config.html#console-settings */ -export const Config = { - /** - * @type {boolean} - */ - StartHistory: false, - /** - * @type {boolean} - */ - AlwaysExpand: false, - /** - * @type {boolean} - */ - CurrentQuery: true, - /** - * @type {boolean} - */ - EnterExecutes: false, - /** - * @type {boolean} - */ - DarkTheme: false, - /** - * @type {'info'|'show'|'collapse'} - */ - Mode: 'info', - /** - * @type {number} - */ - Height: 92, - /** - * @type {boolean} - */ - GroupQueries: false, - /** - * @type {'exec'|'time'|'count'} - */ - OrderBy: 'exec', - /** - * @type {'asc'|'desc'} - */ - Order: 'asc', +export default class Config { + startHistory: boolean; - init: function (dataset: DOMStringMap): void { - this.StartHistory = dataset.startHistory === 'true'; - this.AlwaysExpand = dataset.alwaysExpand === 'true'; - this.CurrentQuery = dataset.currentQuery !== undefined ? dataset.currentQuery === 'true' : true; - this.EnterExecutes = dataset.enterExecutes === 'true'; - this.DarkTheme = dataset.darkTheme === 'true'; - this.Mode = dataset.mode === 'show' || dataset.mode === 'collapse' ? dataset.mode : 'info'; + alwaysExpand: boolean; + + currentQuery: boolean; + + enterExecutes: boolean; + + darkTheme: boolean; + + mode: 'info'|'show'|'collapse'; + + height: number; + + groupQueries: boolean; + + orderBy: 'exec'|'time'|'count'; + + order: 'asc'|'desc'; + + constructor ( + startHistory: boolean, + alwaysExpand: boolean, + currentQuery: boolean, + enterExecutes: boolean, + darkTheme: boolean, + mode: 'info'|'show'|'collapse', + height: number, + groupQueries: boolean, + orderBy: 'exec'|'time'|'count', + order: 'asc'|'desc', + ) { + this.startHistory = startHistory; + this.alwaysExpand = alwaysExpand; + this.currentQuery = currentQuery; + this.enterExecutes = enterExecutes; + this.darkTheme = darkTheme; + this.mode = mode; + this.height = height; + this.groupQueries = groupQueries; + this.orderBy = orderBy; + this.order = order; + } + + static createFromDataset (dataset: DOMStringMap): Config { const height = Number(dataset.height); - this.Height = height > 0 ? height : 92; - this.GroupQueries = dataset.groupQueries === 'true'; - this.OrderBy = dataset.orderBy === 'time' || dataset.orderBy === 'count' ? dataset.orderBy : 'exec'; - this.Order = dataset.order === 'desc' ? 'desc' : 'asc'; - }, - setStartHistory: function (value: boolean): void { - this.StartHistory = value; + return new this( + dataset.startHistory === 'true', + dataset.alwaysExpand === 'true', + dataset.currentQuery !== undefined ? dataset.currentQuery === 'true' : true, + dataset.enterExecutes === 'true', + dataset.darkTheme === 'true', + dataset.mode === 'show' || dataset.mode === 'collapse' ? dataset.mode : 'info', + height > 0 ? height : 92, + dataset.groupQueries === 'true', + dataset.orderBy === 'time' || dataset.orderBy === 'count' ? dataset.orderBy : 'exec', + dataset.order === 'desc' ? 'desc' : 'asc', + ); + } + + setStartHistory (value: boolean): void { + this.startHistory = value; setConfigValue('StartHistory', value); - }, + } - setAlwaysExpand: function (value: boolean): void { - this.AlwaysExpand = value; + setAlwaysExpand (value: boolean): void { + this.alwaysExpand = value; setConfigValue('AlwaysExpand', value); - }, + } - setCurrentQuery: function (value: boolean): void { - this.CurrentQuery = value; + setCurrentQuery (value: boolean): void { + this.currentQuery = value; setConfigValue('CurrentQuery', value); - }, + } - setEnterExecutes: function (value: boolean): void { - this.EnterExecutes = value; + setEnterExecutes (value: boolean): void { + this.enterExecutes = value; setConfigValue('EnterExecutes', value); - }, + } - setDarkTheme: function (value: boolean): void { - this.DarkTheme = value; + setDarkTheme (value: boolean): void { + this.darkTheme = value; setConfigValue('DarkTheme', value); - }, + } - setMode: function (value: 'info'|'show'|'collapse'): void { - this.Mode = value; + setMode (value: 'info'|'show'|'collapse'): void { + this.mode = value; setConfigValue('Mode', value); - }, + } - setHeight: function (value: number): void { - this.Height = value; + setHeight (value: number): void { + this.height = value; setConfigValue('Height', value); - }, + } - setGroupQueries: function (value: boolean): void { - this.GroupQueries = value; + setGroupQueries (value: boolean): void { + this.groupQueries = value; setConfigValue('GroupQueries', value); - }, + } - setOrderBy: function (value: 'exec'|'time'|'count'): void { - this.OrderBy = value; + setOrderBy (value: 'exec'|'time'|'count'): void { + this.orderBy = value; setConfigValue('OrderBy', value); - }, + } - setOrder: function (value: 'asc'|'desc'): void { - this.Order = value; + setOrder (value: 'asc'|'desc'): void { + this.order = value; setConfigValue('Order', value); - }, -}; + } +} /** * @param {'StartHistory'|'AlwaysExpand'|'CurrentQuery'|'EnterExecutes'|'DarkTheme'|'Mode'|'Height'|'GroupQueries'|'OrderBy'|'Order'} key From 2c32a6c58332af7f429613ec2a13f6f2ddfc38da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Meneghini=20Fauth?= Date: Wed, 27 Mar 2024 21:33:28 -0300 Subject: [PATCH 8/9] Change Console\UpdateConfigController to validade params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maurício Meneghini Fauth --- psalm-baseline.xml | 11 +- resources/js/src/modules/console/config.ts | 8 +- .../Console/UpdateConfigController.php | 87 ++++++++--- .../Console/UpdateConfigControllerTest.php | 142 ++++++++++++++++++ 4 files changed, 218 insertions(+), 30 deletions(-) create mode 100644 tests/unit/Controllers/Console/UpdateConfigControllerTest.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 18eb1d731a..33160a93c3 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -875,11 +875,6 @@ - - - - - getParsedBodyParam('selectedTable')]]> @@ -12454,6 +12449,12 @@ + + + + + + diff --git a/resources/js/src/modules/console/config.ts b/resources/js/src/modules/console/config.ts index 99947a0e95..ec97334ed9 100644 --- a/resources/js/src/modules/console/config.ts +++ b/resources/js/src/modules/console/config.ts @@ -123,19 +123,15 @@ export default class Config { * @param {boolean|string|number} value */ function setConfigValue (key: string, value: boolean|number|string): void { - // Updating value in local storage. - const serialized = JSON.stringify(value); - localStorage.setItem('Console/' + key, serialized); - $.ajax({ url: 'index.php?route=/console/update-config', type: 'POST', dataType: 'json', data: { 'ajax_request': true, - key: key, server: CommonParams.get('server'), - value: serialized, + key: key, + value: value, }, success: function (data) { if (data.success !== true) { diff --git a/src/Controllers/Console/UpdateConfigController.php b/src/Controllers/Console/UpdateConfigController.php index 8ac75cb58e..c11426b808 100644 --- a/src/Controllers/Console/UpdateConfigController.php +++ b/src/Controllers/Console/UpdateConfigController.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Controllers\Console; +use InvalidArgumentException; use PhpMyAdmin\Config; use PhpMyAdmin\Controllers\AbstractController; use PhpMyAdmin\Http\ServerRequest; @@ -11,9 +12,9 @@ use PhpMyAdmin\Message; use PhpMyAdmin\ResponseRenderer; use PhpMyAdmin\Template; +use function __; use function in_array; -use function is_string; -use function json_decode; +use function is_numeric; final class UpdateConfigController extends AbstractController { @@ -24,28 +25,17 @@ final class UpdateConfigController extends AbstractController public function __invoke(ServerRequest $request): void { - $validKeys = [ - 'StartHistory', - 'AlwaysExpand', - 'CurrentQuery', - 'EnterExecutes', - 'DarkTheme', - 'Mode', - 'Height', - 'GroupQueries', - 'OrderBy', - 'Order', - ]; - $key = $request->getParsedBodyParam('key'); - $value = $request->getParsedBodyParam('value'); - if (! in_array($key, $validKeys, true) || ! is_string($value)) { + try { + $key = $this->parseKeyParam($request->getParsedBodyParam('key')); + $value = $this->parseValueParam($key, $request->getParsedBodyParam('value')); + } catch (InvalidArgumentException $exception) { $this->response->setRequestStatus(false); - $this->response->addJSON(['message' => Message::error()]); + $this->response->addJSON(['message' => Message::error($exception->getMessage())]); return; } - $result = $this->config->setUserValue(null, 'Console/' . $key, json_decode($value)); + $result = $this->config->setUserValue(null, 'Console/' . $key, $value); if ($result === true) { return; } @@ -53,4 +43,63 @@ final class UpdateConfigController extends AbstractController $this->response->setRequestStatus(false); $this->response->addJSON(['message' => $result]); } + + /** @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/tests/unit/Controllers/Console/UpdateConfigControllerTest.php b/tests/unit/Controllers/Console/UpdateConfigControllerTest.php new file mode 100644 index 0000000000..dcc3698a17 --- /dev/null +++ b/tests/unit/Controllers/Console/UpdateConfigControllerTest.php @@ -0,0 +1,142 @@ +createServerRequest('POST', 'http://example.com/') + ->withParsedBody(['key' => $key, 'value' => $value]); + + $config = new Config(); + $responseRenderer = new ResponseRenderer(); + $controller = new UpdateConfigController($responseRenderer, new Template($config), $config); + $controller($request); + + self::assertSame($expected, $config->settings['Console'][$key]); + self::assertSame([], $responseRenderer->getJSONResult()); + self::assertTrue($responseRenderer->hasSuccessState(), 'Should be a successful response.'); + } + + /** @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]); + + $config = new Config(); + $responseRenderer = new ResponseRenderer(); + $controller = new UpdateConfigController($responseRenderer, new Template($config), $config); + $controller($request); + + self::assertSame( + ['message' => Message::error('Unexpected parameter value.')->getDisplay()], + $responseRenderer->getJSONResult(), + ); + self::assertFalse($responseRenderer->hasSuccessState(), 'Should be a failed response.'); + } + + /** @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(); + $controller = new UpdateConfigController($responseRenderer, new Template($config), $config); + $controller($request); + + self::assertSame( + ['message' => Message::error('Could not save configuration')->getDisplay()], + $responseRenderer->getJSONResult(), + ); + self::assertFalse($responseRenderer->hasSuccessState(), 'Should be a failed response.'); + } +} From 774ec8259e755060892f87a2944be8776c1a046b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Meneghini=20Fauth?= Date: Thu, 28 Mar 2024 21:09:48 -0300 Subject: [PATCH 9/9] Change Console\UpdateConfigController to return a Response object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maurício Meneghini Fauth --- psalm-baseline.xml | 1 + resources/js/src/modules/ajax.ts | 2 +- resources/js/src/modules/console/config.ts | 20 ++++------ .../Console/UpdateConfigController.php | 23 ++++++++---- .../Console/UpdateConfigControllerTest.php | 37 ++++++++++++++----- tests/unit/Stubs/ResponseRenderer.php | 23 ++++++++++++ 6 files changed, 74 insertions(+), 32 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 33160a93c3..cc796d4994 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -14488,6 +14488,7 @@ + diff --git a/resources/js/src/modules/ajax.ts b/resources/js/src/modules/ajax.ts index 062f81f95a..d172d2704d 100644 --- a/resources/js/src/modules/ajax.ts +++ b/resources/js/src/modules/ajax.ts @@ -939,7 +939,7 @@ const AJAX = { console.log('AJAX error: status=' + request.status + ', text=' + request.statusText); } - if (settings.url.includes('/git-revision')) { + if (settings.url.includes('/git-revision') || settings.url.includes('/console/update-config')) { return; } diff --git a/resources/js/src/modules/console/config.ts b/resources/js/src/modules/console/config.ts index ec97334ed9..d33cbc7c5a 100644 --- a/resources/js/src/modules/console/config.ts +++ b/resources/js/src/modules/console/config.ts @@ -1,6 +1,7 @@ import $ from 'jquery'; import { ajaxShowMessage } from '../ajax-message.ts'; import { CommonParams } from '../common.ts'; +import { escapeHtml } from '../functions/escape.ts'; /** * @link https://docs.phpmyadmin.net/en/latest/config.html#console-settings @@ -123,23 +124,16 @@ export default class Config { * @param {boolean|string|number} value */ function setConfigValue (key: string, value: boolean|number|string): void { - $.ajax({ - url: 'index.php?route=/console/update-config', - type: 'POST', - dataType: 'json', - data: { + $.post( + 'index.php?route=/console/update-config', + { 'ajax_request': true, server: CommonParams.get('server'), key: key, value: value, }, - success: function (data) { - if (data.success !== true) { - // Try to find a message to display - if (data.error || data.message) { - ajaxShowMessage(data.error || data.message); - } - } - } + ).fail(function (data) { + const message = ''; + ajaxShowMessage(message, false); }); } diff --git a/src/Controllers/Console/UpdateConfigController.php b/src/Controllers/Console/UpdateConfigController.php index c11426b808..e7ac305138 100644 --- a/src/Controllers/Console/UpdateConfigController.php +++ b/src/Controllers/Console/UpdateConfigController.php @@ -4,11 +4,12 @@ declare(strict_types=1); namespace PhpMyAdmin\Controllers\Console; +use Fig\Http\Message\StatusCodeInterface; use InvalidArgumentException; use PhpMyAdmin\Config; use PhpMyAdmin\Controllers\AbstractController; +use PhpMyAdmin\Http\Response; use PhpMyAdmin\Http\ServerRequest; -use PhpMyAdmin\Message; use PhpMyAdmin\ResponseRenderer; use PhpMyAdmin\Template; @@ -23,25 +24,31 @@ final class UpdateConfigController extends AbstractController parent::__construct($response, $template); } - public function __invoke(ServerRequest $request): void + public function __invoke(ServerRequest $request): Response { try { $key = $this->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' => Message::error($exception->getMessage())]); + $this->response->addJSON(['message' => $exception->getMessage()]); - return; + return $this->response->response(); } $result = $this->config->setUserValue(null, 'Console/' . $key, $value); - if ($result === true) { - return; + 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->setRequestStatus(false); - $this->response->addJSON(['message' => $result]); + $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' */ diff --git a/tests/unit/Controllers/Console/UpdateConfigControllerTest.php b/tests/unit/Controllers/Console/UpdateConfigControllerTest.php index dcc3698a17..531b964b0e 100644 --- a/tests/unit/Controllers/Console/UpdateConfigControllerTest.php +++ b/tests/unit/Controllers/Console/UpdateConfigControllerTest.php @@ -6,6 +6,7 @@ namespace PhpMyAdmin\Tests\Controllers\Console; use PhpMyAdmin\Config; use PhpMyAdmin\Controllers\Console\UpdateConfigController; +use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Http\Factory\ServerRequestFactory; use PhpMyAdmin\Message; use PhpMyAdmin\Template; @@ -14,6 +15,8 @@ use PhpMyAdmin\Tests\Stubs\ResponseRenderer; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; +use function json_decode; + #[CoversClass(UpdateConfigController::class)] final class UpdateConfigControllerTest extends AbstractTestCase { @@ -23,14 +26,20 @@ final class UpdateConfigControllerTest extends AbstractTestCase $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); - $controller($request); + $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]); - self::assertSame([], $responseRenderer->getJSONResult()); - self::assertTrue($responseRenderer->hasSuccessState(), 'Should be a successful response.'); } /** @return iterable */ @@ -70,16 +79,19 @@ final class UpdateConfigControllerTest extends AbstractTestCase $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); - $controller($request); + $response = $controller($request); + $responseBody = (string) $response->getBody(); + self::assertJson($responseBody); self::assertSame( - ['message' => Message::error('Unexpected parameter value.')->getDisplay()], - $responseRenderer->getJSONResult(), + ['success' => false, 'error' => 'Unexpected parameter value.'], + json_decode($responseBody, true), ); - self::assertFalse($responseRenderer->hasSuccessState(), 'Should be a failed response.'); } /** @return iterable */ @@ -130,13 +142,18 @@ final class UpdateConfigControllerTest extends AbstractTestCase $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); - $controller($request); + $response = $controller($request); + $responseBody = (string) $response->getBody(); + self::assertJson($responseBody); self::assertSame( - ['message' => Message::error('Could not save configuration')->getDisplay()], - $responseRenderer->getJSONResult(), + ['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/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; + } }