From 3c31c3ed85e8bfb64d54aabb03f0fb70bc72eff3 Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Mon, 20 Jul 2026 16:16:07 +0100 Subject: [PATCH 1/3] Refactor uploadProgressSetup Signed-off-by: Kamil Tekiela --- psalm-baseline.xml | 3 --- src/Controllers/Database/ImportController.php | 2 +- src/Controllers/Server/ImportController.php | 2 +- src/Controllers/Table/ImportController.php | 2 +- src/Import/Ajax.php | 25 ++++++++----------- 5 files changed, 13 insertions(+), 21 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index f86744684ec4..af2f8729ce20 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -6312,9 +6312,6 @@ - - - diff --git a/src/Controllers/Database/ImportController.php b/src/Controllers/Database/ImportController.php index 0d60875109b3..be054651f646 100644 --- a/src/Controllers/Database/ImportController.php +++ b/src/Controllers/Database/ImportController.php @@ -66,7 +66,7 @@ public function __invoke(ServerRequest $request): Response return $this->response->redirectToRoute('/', ['reload' => true, 'message' => __('No databases selected.')]); } - [$uploadId] = Ajax::uploadProgressSetup(); + $uploadId = Ajax::uploadProgressSetup(); ImportSettings::$importType = 'database'; $importList = Plugins::getImport(); diff --git a/src/Controllers/Server/ImportController.php b/src/Controllers/Server/ImportController.php index 71a4735e3ba7..82f143bf26b2 100644 --- a/src/Controllers/Server/ImportController.php +++ b/src/Controllers/Server/ImportController.php @@ -51,7 +51,7 @@ public function __invoke(ServerRequest $request): Response $this->dbi->selectDb('mysql'); } - [$uploadId] = Ajax::uploadProgressSetup(); + $uploadId = Ajax::uploadProgressSetup(); ImportSettings::$importType = 'server'; $importList = Plugins::getImport(); diff --git a/src/Controllers/Table/ImportController.php b/src/Controllers/Table/ImportController.php index eccdc2cd8948..74e00c800f67 100644 --- a/src/Controllers/Table/ImportController.php +++ b/src/Controllers/Table/ImportController.php @@ -90,7 +90,7 @@ public function __invoke(ServerRequest $request): Response UrlParams::$params['goto'] = Url::getFromRoute('/table/import'); UrlParams::$params['back'] = Url::getFromRoute('/table/import'); - [$uploadId] = Ajax::uploadProgressSetup(); + $uploadId = Ajax::uploadProgressSetup(); ImportSettings::$importType = 'table'; $importList = Plugins::getImport(); diff --git a/src/Import/Ajax.php b/src/Import/Ajax.php index 04c9d818e532..3f8927ea545d 100644 --- a/src/Import/Ajax.php +++ b/src/Import/Ajax.php @@ -5,6 +5,8 @@ namespace PhpMyAdmin\Import; use PhpMyAdmin\Core; +use PhpMyAdmin\Plugins\Import\Upload\UploadNoplugin; +use PhpMyAdmin\Plugins\Import\Upload\UploadProgress; use function defined; use function function_exists; @@ -12,7 +14,6 @@ use function ini_get; use function json_encode; use function sprintf; -use function ucwords; use function uniqid; /** @@ -23,12 +24,7 @@ final class Ajax /** For differentiating array in $_SESSION variable */ public const SESSION_KEY = '__upload_status'; - /** - * Sets up some variables for upload progress - * - * @return array{string, string[]} - */ - public static function uploadProgressSetup(): array + public static function uploadProgressSetup(): string { /** * sets default plugin for handling the import process @@ -45,23 +41,22 @@ public static function uploadProgressSetup(): array */ $plugins = [ // in PHP 5.4 session-based upload progress was problematic, see closed bug 3964 - //"session", - 'progress', - 'noplugin', + // [UploadSession::class, 'sessionCheck'], + [UploadProgress::class, 'progressCheck'], + [UploadNoplugin::class, 'nopluginCheck'], ]; // select available plugin foreach ($plugins as $plugin) { - $check = $plugin . 'Check'; + [$class, $method] = $plugin; - if (self::$check()) { - $uploadClass = 'PhpMyAdmin\Plugins\Import\Upload\Upload' . ucwords($plugin); - $_SESSION[self::SESSION_KEY]['handler'] = $uploadClass; + if (self::$method()) { + $_SESSION[self::SESSION_KEY]['handler'] = $class; break; } } - return [$uploadId, $plugins]; + return $uploadId; } /** From a743ab7eead501efe5496b23444f7116ae90d15a Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Mon, 20 Jul 2026 17:02:26 +0100 Subject: [PATCH 2/3] Refactor TwoFactor Signed-off-by: Kamil Tekiela --- .../preferences/two_factor/main.twig | 4 +- .../Preferences/TwoFactorController.php | 2 +- src/TwoFactor.php | 49 ++++++------------- tests/unit/TwoFactorTest.php | 10 ++-- 4 files changed, 23 insertions(+), 42 deletions(-) diff --git a/resources/templates/preferences/two_factor/main.twig b/resources/templates/preferences/two_factor/main.twig index 969d52cf726f..a8478ce380dc 100644 --- a/resources/templates/preferences/two_factor/main.twig +++ b/resources/templates/preferences/two_factor/main.twig @@ -7,7 +7,7 @@
{% if enabled %} - {% if num_backends == 0 %} + {% if not has_backends %}

{{ t('Two-factor authentication is not available, please install optional dependencies to enable authentication backends.') }}

{{ t('Following composer packages are missing:') }}

    @@ -69,7 +69,7 @@
-{% elseif num_backends > 0 %} +{% elseif has_backends %}
diff --git a/src/Controllers/Preferences/TwoFactorController.php b/src/Controllers/Preferences/TwoFactorController.php index 7c85484daf32..4b5906bc0275 100644 --- a/src/Controllers/Preferences/TwoFactorController.php +++ b/src/Controllers/Preferences/TwoFactorController.php @@ -66,7 +66,7 @@ public function __invoke(ServerRequest $request): Response $backend = $twoFactor->getBackend(); $this->response->render('preferences/two_factor/main', [ 'enabled' => $twoFactor->isWritable(), - 'num_backends' => count($twoFactor->getAvailable()), + 'has_backends' => count($twoFactor->getAvailable()) > 1, 'backend_id' => $backend::$id, 'backend_name' => $backend::getName(), 'backend_description' => $backend::getDescription(), diff --git a/src/TwoFactor.php b/src/TwoFactor.php index a11c73c65896..9597cf55e09a 100644 --- a/src/TwoFactor.php +++ b/src/TwoFactor.php @@ -17,18 +17,17 @@ use PhpMyAdmin\Plugins\TwoFactor\Application; use PhpMyAdmin\Plugins\TwoFactor\Invalid; use PhpMyAdmin\Plugins\TwoFactor\Key; +use PhpMyAdmin\Plugins\TwoFactor\Simple; +use PhpMyAdmin\Plugins\TwoFactor\WebAuthn; use PhpMyAdmin\Plugins\TwoFactorPlugin; use PragmaRX\Google2FAQRCode\Google2FA; use XMLWriter; -use function array_merge; use function class_exists; use function extension_loaded; -use function in_array; use function is_array; use function is_bool; use function is_string; -use function ucfirst; /** * Two factor authentication wrapper class @@ -45,9 +44,6 @@ class TwoFactor private TwoFactorPlugin $backend; - /** @var string[] */ - private array $available; - private UserPreferences $userPreferences; /** @@ -67,7 +63,6 @@ public function __construct(public string $user) $config, new Clock(), ); - $this->available = $this->getAvailableBackends(); $this->config = $this->readConfig(); $this->writable = $this->config['type'] === 'db'; $this->backend = $this->getBackendForCurrentUser(); @@ -109,10 +104,10 @@ public function getBackend(): TwoFactorPlugin return $this->backend; } - /** @return string[] */ + /** @return array> */ public function getAvailable(): array { - return $this->available; + return $this->getAvailableBackends(); } public function showSubmit(): bool @@ -120,17 +115,13 @@ public function showSubmit(): bool return $this->backend::$showSubmit; } - /** - * Returns list of available backends - * - * @return string[] - */ - public function getAvailableBackends(): array + /** @return array> */ + private function getAvailableBackends(): array { - $result = []; + $result = ['' => TwoFactorPlugin::class]; $config = Config::getInstance(); if ($config->config->debug->simple2fa) { - $result[] = 'simple'; + $result['simple'] = Simple::class; } if ( @@ -138,13 +129,13 @@ class_exists(Google2FA::class) && class_exists(ImageRenderer::class) && (class_exists(XMLWriter::class) || extension_loaded('imagick')) ) { - $result[] = 'application'; + $result['application'] = Application::class; } - $result[] = 'WebAuthn'; + $result['WebAuthn'] = WebAuthn::class; if (class_exists(U2FServer::class)) { - $result[] = 'key'; + $result['key'] = Key::class; } return $result; @@ -182,15 +173,7 @@ public function getMissingDeps(): array */ public function getBackendClass(string $name): string { - $result = TwoFactorPlugin::class; - if (in_array($name, $this->available, true)) { - /** @psalm-var class-string $result */ - $result = 'PhpMyAdmin\\Plugins\\TwoFactor\\' . ucfirst($name); - } elseif ($name !== '') { - $result = Invalid::class; - } - - return $result; + return $this->getAvailableBackends()[$name] ?? Invalid::class; } /** @@ -268,11 +251,11 @@ public function configure(ServerRequest $request, string $name): bool $cls = $this->getBackendClass($name); $this->backend = new $cls($this); } else { - if (! in_array($name, $this->available, true)) { + $cls = $this->getBackendClass($name); + if ($cls === Invalid::class) { return false; } - $cls = $this->getBackendClass($name); $this->backend = new $cls($this); if (! $this->backend->configure($request)) { return false; @@ -294,10 +277,8 @@ public function configure(ServerRequest $request, string $name): bool */ public function getAllBackends(): array { - $all = array_merge([''], $this->available); $backends = []; - foreach ($all as $name) { - $cls = $this->getBackendClass($name); + foreach ($this->getAvailableBackends() as $cls) { $backends[] = ['id' => $cls::$id, 'name' => $cls::getName(), 'description' => $cls::getDescription()]; } diff --git a/tests/unit/TwoFactorTest.php b/tests/unit/TwoFactorTest.php index f78ae41f6f1f..ec71fe7bbe6d 100644 --- a/tests/unit/TwoFactorTest.php +++ b/tests/unit/TwoFactorTest.php @@ -24,8 +24,8 @@ use Psr\Http\Message\ServerRequestInterface; use ReflectionProperty; +use function array_key_exists; use function count; -use function in_array; use function json_encode; use function str_replace; @@ -266,7 +266,7 @@ public function testApplication(): void $request = new ServerRequest(self::createStub(ServerRequestInterface::class)); $object = $this->getTwoFactorAndLoadConfig('user', null); - if (! in_array('application', $object->getAvailable(), true)) { + if (! array_key_exists('application', $object->getAvailable())) { self::markTestSkipped('google2fa not available'); } @@ -320,7 +320,7 @@ public function testKey(): void $request = new ServerRequest(self::createStub(ServerRequestInterface::class)); $object = $this->getTwoFactorAndLoadConfig('user', null); - if (! in_array('key', $object->getAvailable(), true)) { + if (! array_key_exists('key', $object->getAvailable())) { self::markTestSkipped('u2f-php-server not available'); } @@ -386,7 +386,7 @@ public function testKeyAuthentication(): void $request = new ServerRequest(self::createStub(ServerRequestInterface::class)); $object = $this->getTwoFactorAndLoadConfig('user', null); - if (! in_array('key', $object->getAvailable(), true)) { + if (! array_key_exists('key', $object->getAvailable())) { self::markTestSkipped('u2f-php-server not available'); } @@ -509,7 +509,7 @@ public function testBackends(): void $object = $this->getTwoFactorAndLoadConfig('user', null); $backends = $object->getAllBackends(); self::assertCount( - count($object->getAvailable()) + 1, + count($object->getAvailable()), $backends, ); $config->config->debug->simple2fa = false; From 87a19cf2eb67568a4100f0c2908d5c6b0dfdccc3 Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Mon, 20 Jul 2026 17:15:51 +0100 Subject: [PATCH 3/3] Refactor AuthenticationPluginFactory Adding new authentication plugin requires changing the codebase anyway. The available plugins are now hardcoded just like the config values. Signed-off-by: Kamil Tekiela --- src/Plugins/AuthenticationPluginFactory.php | 21 +++++++++++-------- .../AuthenticationPluginFactoryTest.php | 6 +++--- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Plugins/AuthenticationPluginFactory.php b/src/Plugins/AuthenticationPluginFactory.php index 9117b191ecff..f786bd29cc76 100644 --- a/src/Plugins/AuthenticationPluginFactory.php +++ b/src/Plugins/AuthenticationPluginFactory.php @@ -6,13 +6,13 @@ use PhpMyAdmin\Config; use PhpMyAdmin\Exceptions\AuthenticationPluginException; +use PhpMyAdmin\Plugins\Auth\AuthenticationConfig; +use PhpMyAdmin\Plugins\Auth\AuthenticationCookie; +use PhpMyAdmin\Plugins\Auth\AuthenticationHttp; +use PhpMyAdmin\Plugins\Auth\AuthenticationSignon; use PhpMyAdmin\ResponseRenderer; use function __; -use function class_exists; -use function is_subclass_of; -use function strtolower; -use function ucfirst; class AuthenticationPluginFactory { @@ -30,12 +30,15 @@ public function create(): AuthenticationPlugin } $authType = Config::getInstance()->selectedServer['auth_type']; - $class = 'PhpMyAdmin\\Plugins\\Auth\\Authentication' . ucfirst(strtolower($authType)); - if (! class_exists($class) || ! is_subclass_of($class, AuthenticationPlugin::class)) { - throw new AuthenticationPluginException( + $class = match ($authType) { + 'config' => AuthenticationConfig::class, + 'cookie' => AuthenticationCookie::class, + 'http' => AuthenticationHttp::class, + 'signon' => AuthenticationSignon::class, + default => throw new AuthenticationPluginException( __('Invalid authentication method set in configuration:') . ' ' . $authType, - ); - } + ), + }; $this->plugin = new $class($this->responseRenderer); diff --git a/tests/unit/Plugins/AuthenticationPluginFactoryTest.php b/tests/unit/Plugins/AuthenticationPluginFactoryTest.php index beb43625d68d..48d9c902bd0c 100644 --- a/tests/unit/Plugins/AuthenticationPluginFactoryTest.php +++ b/tests/unit/Plugins/AuthenticationPluginFactoryTest.php @@ -35,9 +35,9 @@ public function testValidPlugins(string $type, string $class): void public static function providerForTestValidPlugins(): iterable { yield 'config plugin' => ['config', AuthenticationConfig::class]; - yield 'cookie plugin' => ['Cookie', AuthenticationCookie::class]; - yield 'http plugin' => ['HTTP', AuthenticationHttp::class]; - yield 'sign on plugin' => ['signOn', AuthenticationSignon::class]; + yield 'cookie plugin' => ['cookie', AuthenticationCookie::class]; + yield 'http plugin' => ['http', AuthenticationHttp::class]; + yield 'sign on plugin' => ['signon', AuthenticationSignon::class]; } public function testInvalidPlugin(): void