From 51d89d5d51e0dd2f34c9be3e23d9487b8bf94b23 Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Fri, 20 Feb 2026 16:54:06 +0000 Subject: [PATCH 1/5] Make $cookieName optional Signed-off-by: Kamil Tekiela --- phpstan-baseline.neon | 6 ------ psalm-baseline.xml | 3 --- src/Config/UserPreferencesHandler.php | 12 +++--------- src/Controllers/CollationConnectionController.php | 1 - src/Controllers/Console/UpdateConfigController.php | 2 +- .../Navigation/UpdateNavWidthConfigController.php | 2 +- src/Export/Export.php | 8 ++++---- tests/unit/Config/UserPreferencesHandlerTest.php | 6 +++--- .../CollationConnectionControllerTest.php | 2 +- 9 files changed, 13 insertions(+), 29 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 76010b510f..abc19e6a95 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -969,12 +969,6 @@ parameters: count: 1 path: src/Config/UserPreferencesHandler.php - - - message: '#^Only booleans are allowed in &&, string\|null given on the right side\.$#' - identifier: booleanAnd.rightNotBoolean - count: 1 - path: src/Config/UserPreferencesHandler.php - - message: '#^Parameter \#1 \$code of method PhpMyAdmin\\I18n\\LanguageManager\:\:getLanguage\(\) expects string, mixed given\.$#' identifier: argument.type diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 356839ef0c..fd578699d6 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -439,9 +439,6 @@ - - - diff --git a/src/Config/UserPreferencesHandler.php b/src/Config/UserPreferencesHandler.php index e524e6473f..461c776457 100644 --- a/src/Config/UserPreferencesHandler.php +++ b/src/Config/UserPreferencesHandler.php @@ -88,7 +88,6 @@ class UserPreferencesHandler && $configData['ThemeDefault'] !== $this->themeManager->theme->getId() ) { $this->setUserValue( - null, 'ThemeDefault', $this->themeManager->theme->getId(), 'original', @@ -111,7 +110,7 @@ class UserPreferencesHandler || isset($configData['lang']) && Current::$lang !== $configData['lang'] ) { - $this->setUserValue(null, 'lang', Current::$lang, 'en'); + $this->setUserValue('lang', Current::$lang, 'en'); } } elseif (isset($configData['lang'])) { // read language from settings @@ -151,17 +150,12 @@ class UserPreferencesHandler * If user preferences are not yet initialized, option is applied to * global config and added to a update queue, which is processed * by {@link loadUserPreferences()} - * - * @param string|null $cookieName can be null - * @param string $cfgPath configuration path - * @param mixed $newCfgValue new value - * @param string|null $defaultValue default value */ public function setUserValue( - string|null $cookieName, string $cfgPath, mixed $newCfgValue, string|null $defaultValue = null, + string $cookieName = '', ): true|Message { $result = true; // use permanent user preferences if possible @@ -173,7 +167,7 @@ class UserPreferencesHandler $result = $this->userPreferences->persistOption($cfgPath, $newCfgValue, $defaultValue); } - if ($this->storageType !== 'db' && $cookieName) { + if ($this->storageType !== 'db' && $cookieName !== '') { // fall back to cookies if ($defaultValue === null) { $defaultValue = Core::arrayRead($cfgPath, $this->config->settings); diff --git a/src/Controllers/CollationConnectionController.php b/src/Controllers/CollationConnectionController.php index e252abbb61..a1264bf883 100644 --- a/src/Controllers/CollationConnectionController.php +++ b/src/Controllers/CollationConnectionController.php @@ -23,7 +23,6 @@ final readonly class CollationConnectionController implements InvocableControlle public function __invoke(ServerRequest $request): Response { $this->userPreferencesHandler->setUserValue( - null, 'DefaultConnectionCollation', $request->getParsedBodyParam('collation_connection'), 'utf8mb4_unicode_ci', diff --git a/src/Controllers/Console/UpdateConfigController.php b/src/Controllers/Console/UpdateConfigController.php index 8cd9f83e66..2e9c25cd26 100644 --- a/src/Controllers/Console/UpdateConfigController.php +++ b/src/Controllers/Console/UpdateConfigController.php @@ -39,7 +39,7 @@ final readonly class UpdateConfigController implements InvocableController return $this->response->response(); } - $result = $this->userPreferencesHandler->setUserValue(null, 'Console/' . $key, $value); + $result = $this->userPreferencesHandler->setUserValue('Console/' . $key, $value); if ($result !== true) { $this->response->setStatusCode(StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR); $this->response->setRequestStatus(false); diff --git a/src/Controllers/Navigation/UpdateNavWidthConfigController.php b/src/Controllers/Navigation/UpdateNavWidthConfigController.php index b1e0ab53a4..0e35512bc7 100644 --- a/src/Controllers/Navigation/UpdateNavWidthConfigController.php +++ b/src/Controllers/Navigation/UpdateNavWidthConfigController.php @@ -34,7 +34,7 @@ final readonly class UpdateNavWidthConfigController implements InvocableControll return $this->response->response(); } - $result = $this->userPreferencesHandler->setUserValue(null, 'NavigationWidth', (int) $value); + $result = $this->userPreferencesHandler->setUserValue('NavigationWidth', (int) $value); if ($result === true) { return $this->response->response(); } diff --git a/src/Export/Export.php b/src/Export/Export.php index c15dca2d81..2e95861548 100644 --- a/src/Export/Export.php +++ b/src/Export/Export.php @@ -169,27 +169,27 @@ class Export ): void { if ($exportType === ExportType::Server) { $userPreferencesHandler->setUserValue( - 'pma_server_filename_template', 'Export/file_template_server', $filenameTemplate, + cookieName: 'pma_server_filename_template', ); } elseif ($exportType === ExportType::Database) { $userPreferencesHandler->setUserValue( - 'pma_db_filename_template', 'Export/file_template_database', $filenameTemplate, + cookieName: 'pma_db_filename_template', ); } elseif ($exportType === ExportType::Raw) { $userPreferencesHandler->setUserValue( - 'pma_raw_filename_template', 'Export/file_template_raw', $filenameTemplate, + cookieName: 'pma_raw_filename_template', ); } else { $userPreferencesHandler->setUserValue( - 'pma_table_filename_template', 'Export/file_template_table', $filenameTemplate, + cookieName: 'pma_table_filename_template', ); } } diff --git a/tests/unit/Config/UserPreferencesHandlerTest.php b/tests/unit/Config/UserPreferencesHandlerTest.php index a8fe693999..d2e4643664 100644 --- a/tests/unit/Config/UserPreferencesHandlerTest.php +++ b/tests/unit/Config/UserPreferencesHandlerTest.php @@ -29,10 +29,10 @@ final class UserPreferencesHandlerTest extends AbstractTestCase new LanguageManager($config), new ThemeManager(), ); - $userPreferencesHandler->setUserValue(null, 'Lang', 'cs', 'en'); - $userPreferencesHandler->setUserValue('TEST_COOKIE_USER_VAL', 'Servers/1/hide_db', 'cfg_val_1'); + $userPreferencesHandler->setUserValue('Lang', 'cs', 'en'); + $userPreferencesHandler->setUserValue('Servers/1/hide_db', 'cfg_val_1', cookieName: 'TEST_COOKIE_USER_VAL'); self::assertSame('cfg_val_1', $userPreferencesHandler->getUserValue('TEST_COOKIE_USER_VAL', 'fail')); - $userPreferencesHandler->setUserValue(null, 'NavigationWidth', 300); + $userPreferencesHandler->setUserValue('NavigationWidth', 300); self::assertSame(300, $config->settings['NavigationWidth']); } diff --git a/tests/unit/Controllers/CollationConnectionControllerTest.php b/tests/unit/Controllers/CollationConnectionControllerTest.php index 4d9d9dbc83..284f1d440a 100644 --- a/tests/unit/Controllers/CollationConnectionControllerTest.php +++ b/tests/unit/Controllers/CollationConnectionControllerTest.php @@ -28,7 +28,7 @@ class CollationConnectionControllerTest extends AbstractTestCase $userPreferencesHandler = self::createMock(UserPreferencesHandler::class); $userPreferencesHandler->expects(self::once())->method('setUserValue') - ->with(null, 'DefaultConnectionCollation', 'utf8mb4_general_ci', 'utf8mb4_unicode_ci'); + ->with('DefaultConnectionCollation', 'utf8mb4_general_ci', 'utf8mb4_unicode_ci'); (new CollationConnectionController($response, $userPreferencesHandler))($request); } From b70e0f7bb7fd7db0cac5765217b2b91efbb8a072 Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Fri, 20 Feb 2026 17:05:25 +0000 Subject: [PATCH 2/5] Remove file_template_raw Signed-off-by: Kamil Tekiela --- src/Export/Export.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Export/Export.php b/src/Export/Export.php index 2e95861548..887f535ba9 100644 --- a/src/Export/Export.php +++ b/src/Export/Export.php @@ -179,13 +179,7 @@ class Export $filenameTemplate, cookieName: 'pma_db_filename_template', ); - } elseif ($exportType === ExportType::Raw) { - $userPreferencesHandler->setUserValue( - 'Export/file_template_raw', - $filenameTemplate, - cookieName: 'pma_raw_filename_template', - ); - } else { + } elseif ($exportType === ExportType::Table) { $userPreferencesHandler->setUserValue( 'Export/file_template_table', $filenameTemplate, From e578b8729df31e9e69140dd72e228ec9f0d7b9c7 Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Fri, 20 Feb 2026 17:28:42 +0000 Subject: [PATCH 3/5] Create updateConfigValue() Signed-off-by: Kamil Tekiela --- src/Config/UserPreferencesHandler.php | 8 ++++++-- .../CollationConnectionController.php | 4 ++++ .../Console/UpdateConfigController.php | 2 ++ .../UpdateNavWidthConfigController.php | 1 + .../unit/Config/UserPreferencesHandlerTest.php | 17 +++++++++++++++-- .../CollationConnectionControllerTest.php | 2 ++ 6 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/Config/UserPreferencesHandler.php b/src/Config/UserPreferencesHandler.php index 461c776457..504ddf0197 100644 --- a/src/Config/UserPreferencesHandler.php +++ b/src/Config/UserPreferencesHandler.php @@ -92,6 +92,7 @@ class UserPreferencesHandler $this->themeManager->theme->getId(), 'original', ); + $this->updateConfigValue('ThemeDefault', $this->themeManager->theme->getId()); } } elseif ( $this->config->config->ThemeDefault !== $this->themeManager->theme->getId() @@ -176,11 +177,14 @@ class UserPreferencesHandler $this->config->setCookie($cookieName, (string) $newCfgValue, $defaultValue); } - $this->config->set($cfgPath, $newCfgValue); - return $result; } + public function updateConfigValue(string $cfgPath, mixed $newCfgValue): void + { + $this->config->set($cfgPath, $newCfgValue); + } + /** * Reads value stored by {@link setUserValue()} * diff --git a/src/Controllers/CollationConnectionController.php b/src/Controllers/CollationConnectionController.php index a1264bf883..631c1cd7aa 100644 --- a/src/Controllers/CollationConnectionController.php +++ b/src/Controllers/CollationConnectionController.php @@ -27,6 +27,10 @@ final readonly class CollationConnectionController implements InvocableControlle $request->getParsedBodyParam('collation_connection'), 'utf8mb4_unicode_ci', ); + $this->userPreferencesHandler->updateConfigValue( + 'DefaultConnectionCollation', + $request->getParsedBodyParam('collation_connection'), + ); $this->response->redirect('index.php?route=/' . Url::getCommonRaw([], '&')); diff --git a/src/Controllers/Console/UpdateConfigController.php b/src/Controllers/Console/UpdateConfigController.php index 2e9c25cd26..88348c1beb 100644 --- a/src/Controllers/Console/UpdateConfigController.php +++ b/src/Controllers/Console/UpdateConfigController.php @@ -48,6 +48,8 @@ final readonly class UpdateConfigController implements InvocableController return $this->response->response(); } + $this->userPreferencesHandler->updateConfigValue('Console/' . $key, $value); + $this->response->addJSON('message', __('Console settings has been updated successfully.')); return $this->response->response(); diff --git a/src/Controllers/Navigation/UpdateNavWidthConfigController.php b/src/Controllers/Navigation/UpdateNavWidthConfigController.php index 0e35512bc7..e1dfc14886 100644 --- a/src/Controllers/Navigation/UpdateNavWidthConfigController.php +++ b/src/Controllers/Navigation/UpdateNavWidthConfigController.php @@ -35,6 +35,7 @@ final readonly class UpdateNavWidthConfigController implements InvocableControll } $result = $this->userPreferencesHandler->setUserValue('NavigationWidth', (int) $value); + $this->userPreferencesHandler->updateConfigValue('NavigationWidth', (int) $value); if ($result === true) { return $this->response->response(); } diff --git a/tests/unit/Config/UserPreferencesHandlerTest.php b/tests/unit/Config/UserPreferencesHandlerTest.php index d2e4643664..9c2bf5438f 100644 --- a/tests/unit/Config/UserPreferencesHandlerTest.php +++ b/tests/unit/Config/UserPreferencesHandlerTest.php @@ -32,8 +32,21 @@ final class UserPreferencesHandlerTest extends AbstractTestCase $userPreferencesHandler->setUserValue('Lang', 'cs', 'en'); $userPreferencesHandler->setUserValue('Servers/1/hide_db', 'cfg_val_1', cookieName: 'TEST_COOKIE_USER_VAL'); self::assertSame('cfg_val_1', $userPreferencesHandler->getUserValue('TEST_COOKIE_USER_VAL', 'fail')); - $userPreferencesHandler->setUserValue('NavigationWidth', 300); - self::assertSame(300, $config->settings['NavigationWidth']); + } + + public function testUpdateConfigValue(): void + { + $config = new Config(); + $dbi = $this->createDatabaseInterface(); + $userPreferencesHandler = new UserPreferencesHandler( + $config, + $dbi, + new UserPreferences($dbi, new Relation($dbi, $config), new Template($config), $config, new Clock()), + new LanguageManager($config), + new ThemeManager(), + ); + $userPreferencesHandler->updateConfigValue('NavigationWidth', 300); + self::assertSame(300, $config->config->NavigationWidth); } public function testGetUserValue(): void diff --git a/tests/unit/Controllers/CollationConnectionControllerTest.php b/tests/unit/Controllers/CollationConnectionControllerTest.php index 284f1d440a..a80c1e0dbc 100644 --- a/tests/unit/Controllers/CollationConnectionControllerTest.php +++ b/tests/unit/Controllers/CollationConnectionControllerTest.php @@ -29,6 +29,8 @@ class CollationConnectionControllerTest extends AbstractTestCase $userPreferencesHandler = self::createMock(UserPreferencesHandler::class); $userPreferencesHandler->expects(self::once())->method('setUserValue') ->with('DefaultConnectionCollation', 'utf8mb4_general_ci', 'utf8mb4_unicode_ci'); + $userPreferencesHandler->expects(self::once())->method('updateConfigValue') + ->with('DefaultConnectionCollation', 'utf8mb4_general_ci'); (new CollationConnectionController($response, $userPreferencesHandler))($request); } From b8f7a6ec7bf6ea3c987de9761f403bf907b69a4e Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Fri, 20 Feb 2026 17:33:25 +0000 Subject: [PATCH 4/5] Improve guard in setValueRecursive Signed-off-by: Kamil Tekiela --- src/Config.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Config.php b/src/Config.php index cd4ec35b46..81d487fed1 100644 --- a/src/Config.php +++ b/src/Config.php @@ -307,8 +307,12 @@ class Config private function setValueRecursive(array &$array, array $parts, mixed $value): bool { $part = array_shift($parts); + if (! isset($array[$part])) { + throw new ConfigException('Configuration option "' . $part . '" does not exist.'); + } + if ($parts === []) { - if (isset($array[$part]) && $array[$part] === $value) { + if ($array[$part] === $value) { return false; } @@ -318,7 +322,7 @@ class Config } if (! is_array($array[$part])) { - throw new ConfigException('Failed to set configuration value.'); + throw new ConfigException('Configuration option "' . $part . '" is not an array.'); } return $this->setValueRecursive($array[$part], $parts, $value); From 2dc32a96b1c67a77c6ce642ed0141a2d9910e202 Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Fri, 20 Feb 2026 17:46:43 +0000 Subject: [PATCH 5/5] Add type hint Signed-off-by: Kamil Tekiela --- phpstan-baseline.neon | 24 ------------------------ psalm-baseline.xml | 3 --- src/Config/UserPreferencesHandler.php | 1 + 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index abc19e6a95..78488d125f 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -915,30 +915,12 @@ parameters: count: 1 path: src/Config/UserPreferencesHandler.php - - - message: '#^Cannot access offset ''Server'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/Config/UserPreferencesHandler.php - - - - message: '#^Cannot access offset ''ThemeDefault'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 2 - path: src/Config/UserPreferencesHandler.php - - message: '#^Cannot access offset ''config_mtime'' on mixed\.$#' identifier: offsetAccess.nonOffsetAccessible count: 2 path: src/Config/UserPreferencesHandler.php - - - message: '#^Cannot access offset ''lang'' on mixed\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 3 - path: src/Config/UserPreferencesHandler.php - - message: '#^Cannot access offset ''userprefs'' on mixed\.$#' identifier: offsetAccess.nonOffsetAccessible @@ -975,12 +957,6 @@ parameters: count: 1 path: src/Config/UserPreferencesHandler.php - - - message: '#^Parameter \#2 \.\.\.\$replacements of function array_replace_recursive expects array, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/Config/UserPreferencesHandler.php - - message: '#^Parameter \#3 \$default of method PhpMyAdmin\\Config\:\:setCookie\(\) expects string\|null, mixed given\.$#' identifier: argument.type diff --git a/psalm-baseline.xml b/psalm-baseline.xml index fd578699d6..c109ac1b80 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -408,7 +408,6 @@ - @@ -416,7 +415,6 @@ - @@ -427,7 +425,6 @@ - storageType]]> diff --git a/src/Config/UserPreferencesHandler.php b/src/Config/UserPreferencesHandler.php index 504ddf0197..3bcc87d41c 100644 --- a/src/Config/UserPreferencesHandler.php +++ b/src/Config/UserPreferencesHandler.php @@ -58,6 +58,7 @@ class UserPreferencesHandler return; } + /** @var mixed[] $configData */ $configData = $_SESSION['cache'][$cacheKey]['userprefs']; // type is 'db' or 'session' $this->storageType = $_SESSION['cache'][$cacheKey]['userprefs_type'];