From 8d245ae223fd8fcbd508362dfe7fd04254a066c4 Mon Sep 17 00:00:00 2001 From: Tobias Perschon Date: Sat, 23 May 2026 18:48:42 +0200 Subject: [PATCH] Adds a setting to disable first time wizard for new users (#5938) --- src/Configuration/SystemConfiguration.php | 5 + .../SystemConfigurationController.php | 4 + src/DependencyInjection/Configuration.php | 3 + src/EventSubscriber/WizardSubscriber.php | 18 ++-- .../Configuration/SystemConfigurationTest.php | 16 ++++ tests/Controller/WizardControllerTest.php | 93 +++++++++++++++++++ .../DependencyInjection/ConfigurationTest.php | 1 + translations/system-configuration.de.xlf | 4 + translations/system-configuration.de_CH.xlf | 4 + translations/system-configuration.en.xlf | 4 + 10 files changed, 146 insertions(+), 6 deletions(-) diff --git a/src/Configuration/SystemConfiguration.php b/src/Configuration/SystemConfiguration.php index ed595cd3..2a94ee93 100644 --- a/src/Configuration/SystemConfiguration.php +++ b/src/Configuration/SystemConfiguration.php @@ -370,6 +370,11 @@ final class SystemConfiguration return $this->getDefaultCurrency(); } + public function isUserWizardActive(): bool + { + return (bool) $this->find('user.wizard'); + } + // ========== Timesheet configurations ========== /* public function getTimesheetBreakWarningDuration(): int diff --git a/src/Controller/SystemConfigurationController.php b/src/Controller/SystemConfigurationController.php index 785509b4..b4e20b4f 100644 --- a/src/Controller/SystemConfigurationController.php +++ b/src/Controller/SystemConfigurationController.php @@ -546,6 +546,10 @@ final class SystemConfigurationController extends AbstractController ->setLabel('theme.avatar_url') ->setType(YesNoType::class) ->setTranslationDomain('system-configuration'), + (new Configuration('user.wizard')) + ->setLabel('user_auth_wizard') + ->setType(YesNoType::class) + ->setTranslationDomain('system-configuration'), ]), (new SystemConfigurationModel('theme')) ->setConfiguration([ diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 338f45b4..d3778c3e 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -568,6 +568,9 @@ final class Configuration implements ConfigurationInterface ->booleanNode('registration') ->defaultFalse() ->end() + ->booleanNode('wizard') + ->defaultTrue() + ->end() ->booleanNode('password_reset') ->defaultTrue() ->end() diff --git a/src/EventSubscriber/WizardSubscriber.php b/src/EventSubscriber/WizardSubscriber.php index a92b1484..6802d78f 100644 --- a/src/EventSubscriber/WizardSubscriber.php +++ b/src/EventSubscriber/WizardSubscriber.php @@ -9,6 +9,7 @@ namespace App\EventSubscriber; +use App\Configuration\SystemConfiguration; use App\Entity\User; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -23,7 +24,8 @@ class WizardSubscriber implements EventSubscriberInterface public function __construct( private UrlGeneratorInterface $urlGenerator, private AuthorizationCheckerInterface $security, - private TokenStorageInterface $storage + private TokenStorageInterface $storage, + private SystemConfiguration $systemConfiguration ) { } @@ -63,6 +65,15 @@ class WizardSubscriber implements EventSubscriberInterface return; } + if ($user->requiresPasswordReset()) { + $response = new RedirectResponse($this->urlGenerator->generate('wizard', ['wizard' => 'password'])); + $event->setResponse($response); + } + + if ($user->isRegularUserOnly() && !$this->systemConfiguration->isUserWizardActive()) { + return; + } + foreach (User::WIZARDS as $wizard) { if (!$user->hasSeenWizard($wizard)) { $response = new RedirectResponse($this->urlGenerator->generate('wizard', ['wizard' => $wizard])); @@ -71,10 +82,5 @@ class WizardSubscriber implements EventSubscriberInterface return; } } - - if ($user->requiresPasswordReset()) { - $response = new RedirectResponse($this->urlGenerator->generate('wizard', ['wizard' => 'password'])); - $event->setResponse($response); - } } } diff --git a/tests/Configuration/SystemConfigurationTest.php b/tests/Configuration/SystemConfigurationTest.php index 5463e3c2..f22cc337 100644 --- a/tests/Configuration/SystemConfigurationTest.php +++ b/tests/Configuration/SystemConfigurationTest.php @@ -105,6 +105,9 @@ class SystemConfigurationTest extends TestCase 'company' => 'Acme Corp.', ], ], + 'user' => [ + 'wizard' => true, + ], ]; } @@ -125,6 +128,7 @@ class SystemConfigurationTest extends TestCase (new Configuration())->setName('timesheet.markdown_content')->setValue('1'), (new Configuration())->setName('timesheet.default_begin')->setValue('07:00'), (new Configuration())->setName('timesheet.active_entries.hard_limit')->setValue('7'), + (new Configuration())->setName('user.wizard')->setValue(false), ]; } @@ -226,6 +230,18 @@ class SystemConfigurationTest extends TestCase self::assertEquals('IT', $sut->getUserDefaultLanguage()); } + public function testUserWizardWithoutLoader(): void + { + $sut = $this->getSut($this->getDefaultSettings(), []); + self::assertTrue($sut->isUserWizardActive()); + } + + public function testUserWizardWithLoader(): void + { + $sut = $this->getSut($this->getDefaultSettings(), $this->getDefaultLoaderSettings()); + self::assertFalse($sut->isUserWizardActive()); + } + public function testTimesheetWithoutLoader(): void { $sut = $this->getSut($this->getDefaultSettings(), []); diff --git a/tests/Controller/WizardControllerTest.php b/tests/Controller/WizardControllerTest.php index 34d9ad83..089a114c 100644 --- a/tests/Controller/WizardControllerTest.php +++ b/tests/Controller/WizardControllerTest.php @@ -9,7 +9,9 @@ namespace App\Tests\Controller; +use App\DataFixtures\UserFixtures; use App\Entity\User; +use App\Entity\UserPreference; use PHPUnit\Framework\Attributes\Group; #[Group('integration')] @@ -37,6 +39,97 @@ class WizardControllerTest extends AbstractControllerBaseTestCase $this->assertAccessIsGranted($client, '/wizard/profile'); } + public function testWizardDoesNotAppearOnFirstLoginIfDisabled(): void + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + + $this->setSystemConfiguration('user.wizard', false); + + $user = $this->loadUserFromDatabase(UserFixtures::USERNAME_USER); + $user->setPreferenceValue('__wizards__', null); + $user->setRequiresPasswordReset(false); + $this->getEntityManager()->persist($user); + $this->getEntityManager()->flush(); + + $this->request($client, '/timesheet/'); + + self::assertTrue($client->getResponse()->isSuccessful()); + self::assertFalse($client->getResponse()->isRedirect()); + } + + public function testWizardAppearsOnFirstLoginWithDefaultConfiguration(): void + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + + $user = $this->loadUserFromDatabase(UserFixtures::USERNAME_USER); + $user->setPreferenceValue('__wizards__', null); + $user->setRequiresPasswordReset(false); + $this->getEntityManager()->persist($user); + $this->getEntityManager()->flush(); + + $this->request($client, '/timesheet/'); + + $this->assertIsRedirect($client, '/wizard/intro'); + } + + public function testProfileWizardSubmitRedirectsToDoneAndMarksSeen(): void + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + + $user = $this->loadUserFromDatabase(UserFixtures::USERNAME_USER); + $user->setPreferenceValue('__wizards__', null); + $user->setRequiresPasswordReset(false); + $this->getEntityManager()->persist($user); + $this->getEntityManager()->flush(); + + $crawler = $this->request($client, '/wizard/profile'); + $form = $crawler->filter('form[name=form]')->form(); + $values = $form->getPhpValues(); + $values['form']['reload'] = '0'; + $values['form'][UserPreference::LANGUAGE] = 'en'; + $values['form'][UserPreference::LOCALE] = 'en'; + $values['form'][UserPreference::TIMEZONE] = 'Europe/Berlin'; + $values['form'][UserPreference::SKIN] = 'auto'; + $client->submit($form, $values); + + $this->assertIsRedirect($client, '/wizard/done'); + + $this->getEntityManager()->clear(); + $user = $this->loadUserFromDatabase(UserFixtures::USERNAME_USER); + self::assertTrue($user->hasSeenWizard('profile')); + } + + public function testProfileWizardSubmitReloadsProfileWhenRequested(): void + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + + $crawler = $this->request($client, '/wizard/profile'); + $form = $crawler->filter('form[name=form]')->form(); + $values = $form->getPhpValues(); + $values['form']['reload'] = '1'; + $client->submit($form, $values); + + $this->assertIsRedirect($client, '/wizard/profile'); + } + + public function testProfileWizardSubmitRedirectsToPasswordIfResetRequired(): void + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + + $user = $this->loadUserFromDatabase(UserFixtures::USERNAME_USER); + $user->setRequiresPasswordReset(true); + $this->getEntityManager()->persist($user); + $this->getEntityManager()->flush(); + + $crawler = $this->request($client, '/wizard/profile'); + $form = $crawler->filter('form[name=form]')->form(); + $values = $form->getPhpValues(); + $values['form']['reload'] = '0'; + $client->submit($form, $values); + + $this->assertIsRedirect($client, '/wizard/password'); + } + public function testDoneWizard(): void { $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php index 138a39cc..836a0fb0 100644 --- a/tests/DependencyInjection/ConfigurationTest.php +++ b/tests/DependencyInjection/ConfigurationTest.php @@ -412,6 +412,7 @@ class ConfigurationTest extends TestCase 'user' => [ 'registration' => false, 'password_reset' => true, + 'wizard' => true, 'login' => true, 'password_reset_retry_ttl' => 3600, 'password_reset_token_ttl' => 86400, diff --git a/translations/system-configuration.de.xlf b/translations/system-configuration.de.xlf index 01325cd5..e30c531a 100644 --- a/translations/system-configuration.de.xlf +++ b/translations/system-configuration.de.xlf @@ -14,6 +14,10 @@ theme.avatar_url Erlaube die Nutzung von URLs für Avatarbilder + + user_auth_wizard + Einrichtungsassistent für neue Benutzer anzeigen + timesheet.mode Zeiterfassungs-Modus diff --git a/translations/system-configuration.de_CH.xlf b/translations/system-configuration.de_CH.xlf index 2d5dad80..a85ab10f 100644 --- a/translations/system-configuration.de_CH.xlf +++ b/translations/system-configuration.de_CH.xlf @@ -254,6 +254,10 @@ authentication Authentifizierung + + user_auth_wizard + Einrichtungsassistent für neue Benutzer anzeigen + timesheet.mode Zeiterfassungsmodus diff --git a/translations/system-configuration.en.xlf b/translations/system-configuration.en.xlf index d4c43093..e806a094 100644 --- a/translations/system-configuration.en.xlf +++ b/translations/system-configuration.en.xlf @@ -14,6 +14,10 @@ theme.avatar_url Allow the use of URLs for avatar images + + user_auth_wizard + Show setup wizard for new users + timesheet.mode Timetracking mode