diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 39035224..009a6ef9 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -89,12 +89,12 @@ security: access_control: - { path: '^/auth/2fa', role: IS_AUTHENTICATED_2FA_IN_PROGRESS } - { path: '^/auth', roles: PUBLIC_ACCESS } - - { path: '^/(%app_locales%)$', role: PUBLIC_ACCESS } - - { path: '^/(%app_locales%)/auth', role: PUBLIC_ACCESS } - - { path: '^/(%app_locales%)/login', role: PUBLIC_ACCESS } - - { path: '^/(%app_locales%)/register', role: PUBLIC_ACCESS } - - { path: '^/(%app_locales%)/resetting', role: PUBLIC_ACCESS } - - { path: '^/(%app_locales%)/', roles: ROLE_USER } + - { path: '^/{_locale}$', role: PUBLIC_ACCESS } + - { path: '^/{_locale}/auth', role: PUBLIC_ACCESS } + - { path: '^/{_locale}/login', role: PUBLIC_ACCESS } + - { path: '^/{_locale}/register', role: PUBLIC_ACCESS } + - { path: '^/{_locale}/resetting', role: PUBLIC_ACCESS } + - { path: '^/{_locale}/', roles: ROLE_USER } - { path: '^/api', roles: IS_AUTHENTICATED } when@test: diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml index 516422fe..8f88c638 100644 --- a/config/packages/translation.yaml +++ b/config/packages/translation.yaml @@ -1,4 +1,6 @@ framework: + # https://symfony.com/doc/current/reference/configuration/framework.html#enabled-locales + enabled_locales: '%kimai_locales%' default_locale: '%locale%' translator: default_path: '%kernel.project_dir%/translations' diff --git a/config/routes.yaml b/config/routes.yaml index af7c31f3..ef0a2123 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -2,8 +2,6 @@ controllers: resource: ../src/Controller/ type: attribute prefix: /{_locale} - requirements: - _locale: '%app_locales%' defaults: _locale: '%locale%' @@ -31,8 +29,6 @@ security: resource: ../src/Controller/Security/ type: attribute prefix: /{_locale} - requirements: - _locale: '%app_locales%' defaults: _locale: '%locale%' @@ -49,8 +45,6 @@ home: homeLocale: path: /{_locale} - requirements: - _locale: '%app_locales%' defaults: _controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction _locale: '%locale%' diff --git a/config/routes/dev/twig.yaml b/config/routes/dev/twig.yaml index 3694650a..104f99d0 100644 --- a/config/routes/dev/twig.yaml +++ b/config/routes/dev/twig.yaml @@ -1,7 +1,5 @@ _errors: resource: '@FrameworkBundle/Resources/config/routing/errors.xml' prefix: /{_locale}/_error - requirements: - _locale: '%app_locales%' defaults: _locale: '%locale%' diff --git a/config/routes/test/test_routes.yaml b/config/routes/test/test_routes.yaml index 2b87c97b..636ffea5 100644 --- a/config/routes/test/test_routes.yaml +++ b/config/routes/test/test_routes.yaml @@ -1,7 +1,5 @@ app.test.active_entries: path: /{_locale}/layou/active_entries controller: App\Controller\LayoutController::activeEntries - requirements: - _locale: '%app_locales%' defaults: _locale: '%locale%' diff --git a/config/services.yaml b/config/services.yaml index a20855ff..57f52764 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -1,7 +1,7 @@ parameters: locale: en - # can be regenerated with "bin/console kimai:reset:locales" - app_locales: ar|cs|da|de|de_CH|el|en|eo|es|eu|fa|fi|fo|fr|he|hr|hu|id|it|ja|ko|nb_NO|nl|pa|pl|pt|pt_BR|ro|ru|sk|sl|sv|tr|uk|vi|zh_CN|zh_Hant|zh_Hant_TW|cs_CZ|da_DK|da_GL|de_AT|de_BE|de_CH|de_DE|de_IT|de_LI|de_LU|el_CY|el_GR|en_AE|en_AG|en_AI|en_AS|en_AT|en_AU|en_BB|en_BE|en_BI|en_BM|en_BS|en_BW|en_BZ|en_CA|en_CC|en_CH|en_CK|en_CM|en_CX|en_CY|en_DE|en_DG|en_DK|en_DM|en_ER|en_FI|en_FJ|en_FK|en_FM|en_GB|en_GD|en_GG|en_GH|en_GI|en_GM|en_GU|en_GY|en_HK|en_ID|en_IE|en_IL|en_IM|en_IN|en_IO|en_JE|en_JM|en_KE|en_KI|en_KN|en_KY|en_LC|en_LR|en_LS|en_MG|en_MH|en_MO|en_MP|en_MS|en_MT|en_MU|en_MV|en_MW|en_MY|en_NA|en_NF|en_NG|en_NH|en_NL|en_NR|en_NU|en_NZ|en_PG|en_PH|en_PK|en_PN|en_PR|en_PW|en_RH|en_RW|en_SB|en_SC|en_SD|en_SE|en_SG|en_SH|en_SI|en_SL|en_SS|en_SX|en_SZ|en_TC|en_TK|en_TO|en_TT|en_TV|en_TZ|en_UG|en_UM|en_US|en_VC|en_VG|en_VI|en_VU|en_WS|en_ZA|en_ZM|en_ZW|es_AR|es_BO|es_BR|es_BZ|es_CL|es_CO|es_CR|es_CU|es_DO|es_EA|es_EC|es_ES|es_GQ|es_GT|es_HN|es_IC|es_MX|es_NI|es_PA|es_PE|es_PH|es_PR|es_PY|es_SV|es_US|es_UY|es_VE|eu_ES|fa_AF|fa_IR|fi_FI|fo_DK|fo_FO|fr_BE|fr_BF|fr_BI|fr_BJ|fr_BL|fr_CA|fr_CD|fr_CF|fr_CG|fr_CH|fr_CI|fr_CM|fr_DJ|fr_DZ|fr_FR|fr_GA|fr_GF|fr_GN|fr_GP|fr_GQ|fr_HT|fr_KM|fr_LU|fr_MA|fr_MC|fr_MF|fr_MG|fr_ML|fr_MQ|fr_MR|fr_MU|fr_NC|fr_NE|fr_PF|fr_PM|fr_RE|fr_RW|fr_SC|fr_SN|fr_SY|fr_TD|fr_TG|fr_TN|fr_VU|fr_WF|fr_YT|he_IL|hr_BA|hr_HR|hu_HU|it_CH|it_IT|it_SM|it_VA|ja_JP|ko_CN|ko_KP|ko_KR|nl_AW|nl_BE|nl_BQ|nl_CW|nl_NL|nl_SR|nl_SX|pl_PL|pt_AO|pt_BR|pt_CH|pt_CV|pt_GQ|pt_GW|pt_LU|pt_MO|pt_MZ|pt_PT|pt_ST|pt_TL|ro_MD|ro_RO|ru_BY|ru_KG|ru_KZ|ru_MD|ru_RU|ru_UA|sk_SK|sv_AX|sv_FI|sv_SE|tr_CY|tr_TR|uk_UA|vi_VN + # can be regenerated with: bin/console kimai:reset:locales + kimai_locales: ['ar', 'ca', 'cs', 'da', 'de', 'de_CH', 'el', 'en', 'eo', 'es', 'eu', 'fa', 'fi', 'fo', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'nb_NO', 'nl', 'pa', 'pl', 'pt', 'pt_BR', 'ro', 'ru', 'sk', 'sl', 'sv', 'tr', 'uk', 'vi', 'zh_CN', 'zh_Hant', 'zh_Hant_TW', 'ca_AD', 'ca_ES', 'ca_FR', 'ca_IT', 'cs_CZ', 'da_DK', 'da_GL', 'de_AT', 'de_BE', 'de_CH', 'de_DE', 'de_IT', 'de_LI', 'de_LU', 'el_CY', 'el_GR', 'en_AE', 'en_AG', 'en_AI', 'en_AS', 'en_AT', 'en_AU', 'en_BB', 'en_BE', 'en_BI', 'en_BM', 'en_BS', 'en_BW', 'en_BZ', 'en_CA', 'en_CC', 'en_CH', 'en_CK', 'en_CM', 'en_CX', 'en_CY', 'en_DE', 'en_DG', 'en_DK', 'en_DM', 'en_ER', 'en_FI', 'en_FJ', 'en_FK', 'en_FM', 'en_GB', 'en_GD', 'en_GG', 'en_GH', 'en_GI', 'en_GM', 'en_GU', 'en_GY', 'en_HK', 'en_ID', 'en_IE', 'en_IL', 'en_IM', 'en_IN', 'en_IO', 'en_JE', 'en_JM', 'en_KE', 'en_KI', 'en_KN', 'en_KY', 'en_LC', 'en_LR', 'en_LS', 'en_MG', 'en_MH', 'en_MO', 'en_MP', 'en_MS', 'en_MT', 'en_MU', 'en_MV', 'en_MW', 'en_MY', 'en_NA', 'en_NF', 'en_NG', 'en_NH', 'en_NL', 'en_NR', 'en_NU', 'en_NZ', 'en_PG', 'en_PH', 'en_PK', 'en_PN', 'en_PR', 'en_PW', 'en_RH', 'en_RW', 'en_SB', 'en_SC', 'en_SD', 'en_SE', 'en_SG', 'en_SH', 'en_SI', 'en_SL', 'en_SS', 'en_SX', 'en_SZ', 'en_TC', 'en_TK', 'en_TO', 'en_TT', 'en_TV', 'en_TZ', 'en_UG', 'en_UM', 'en_US', 'en_VC', 'en_VG', 'en_VI', 'en_VU', 'en_WS', 'en_ZA', 'en_ZM', 'en_ZW', 'es_AR', 'es_BO', 'es_BR', 'es_BZ', 'es_CL', 'es_CO', 'es_CR', 'es_CU', 'es_DO', 'es_EA', 'es_EC', 'es_ES', 'es_GQ', 'es_GT', 'es_HN', 'es_IC', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PH', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'eu_ES', 'fa_AF', 'fa_IR', 'fi_FI', 'fo_DK', 'fo_FO', 'fr_BE', 'fr_BF', 'fr_BI', 'fr_BJ', 'fr_BL', 'fr_CA', 'fr_CD', 'fr_CF', 'fr_CG', 'fr_CH', 'fr_CI', 'fr_CM', 'fr_DJ', 'fr_DZ', 'fr_FR', 'fr_GA', 'fr_GF', 'fr_GN', 'fr_GP', 'fr_GQ', 'fr_HT', 'fr_KM', 'fr_LU', 'fr_MA', 'fr_MC', 'fr_MF', 'fr_MG', 'fr_ML', 'fr_MQ', 'fr_MR', 'fr_MU', 'fr_NC', 'fr_NE', 'fr_PF', 'fr_PM', 'fr_RE', 'fr_RW', 'fr_SC', 'fr_SN', 'fr_SY', 'fr_TD', 'fr_TG', 'fr_TN', 'fr_VU', 'fr_WF', 'fr_YT', 'he_IL', 'hr_BA', 'hr_HR', 'hu_HU', 'it_CH', 'it_IT', 'it_SM', 'it_VA', 'ja_JP', 'ko_CN', 'ko_KP', 'ko_KR', 'nl_AW', 'nl_BE', 'nl_BQ', 'nl_CW', 'nl_NL', 'nl_SR', 'nl_SX', 'pl_PL', 'pt_AO', 'pt_BR', 'pt_CH', 'pt_CV', 'pt_GQ', 'pt_GW', 'pt_LU', 'pt_MO', 'pt_MZ', 'pt_PT', 'pt_ST', 'pt_TL', 'ro_MD', 'ro_RO', 'ru_BY', 'ru_KG', 'ru_KZ', 'ru_MD', 'ru_RU', 'ru_UA', 'sk_SK', 'sv_AX', 'sv_FI', 'sv_SE', 'tr_CY', 'tr_TR', 'uk_UA', 'vi_VN'] services: # default configuration for services in *this* file diff --git a/config/services_test.yaml b/config/services_test.yaml index ba0ae9f7..52fe715f 100644 --- a/config/services_test.yaml +++ b/config/services_test.yaml @@ -1,6 +1,6 @@ parameters: locale: en - app_locales: ar|cs|de|de_CH|da|en|eo|es|eu|fr|he|hu|it|ja|ko|nl|pl|pt_BR|ro|ru|sk|sv|tr|vi|zh_CN + kimai_locales: ['ar', 'cs', 'de', 'de_CH', 'da', 'en', 'eo', 'es', 'eu', 'fr', 'he', 'hu', 'it', 'ja', 'ko', 'nl', 'pl', 'pt_BR', 'ro', 'ru', 'sk', 'sv', 'tr', 'vi', 'zh_CN'] services: _defaults: diff --git a/src/Command/RegenerateLocalesCommand.php b/src/Command/RegenerateLocalesCommand.php index 1d2a30cf..46e6e4f2 100644 --- a/src/Command/RegenerateLocalesCommand.php +++ b/src/Command/RegenerateLocalesCommand.php @@ -39,13 +39,19 @@ final class RegenerateLocalesCommand extends Command * * @var string[] */ - private array $noRegionCode = ['ar', 'id', 'pa', 'sl']; + private array $noRegionCode = ['ar', 'id', 'pa', 'sl', 'ca']; /** * A list of locales that will be activated, no matter if translation files exist for them. * * @var string[] */ private array $addLocaleToList = ['zh_Hant_TW']; + /** + * A list of locales that will NOT be activated, as no translations exist by now. + * + * @var string[] + */ + private array $skipLocale = ['ca']; public function __construct( private readonly string $projectDirectory, @@ -78,7 +84,11 @@ final class RegenerateLocalesCommand extends Command } $firstLevelLocales = []; foreach ($translationFilenames as $file) { - $firstLevelLocales[] = explode('.', basename($file))[1]; + $l = explode('.', basename($file))[1]; + if (\in_array($l, $this->skipLocale, true)) { + continue; + } + $firstLevelLocales[] = $l; } $firstLevelLocales = array_unique(array_merge($firstLevelLocales, $this->addLocaleToList)); $io->title('First level locales found'); @@ -185,8 +195,8 @@ final class RegenerateLocalesCommand extends Command // in the future this list should be reduced to the list of available translations, but for a long time users // could choose from the entire list of all locales, so we likely have to keep that forever ... - $io->title('List of app_locales for services.yaml'); - $io->writeln(implode('|', $locales)); + $io->title('List of "kimai_locales" for services.yaml'); + $io->writeln("['" . implode("', '", $locales) . "']"); ksort($appLocales); diff --git a/src/DependencyInjection/AppExtension.php b/src/DependencyInjection/AppExtension.php index 8dc8ba5b..1d49cce6 100644 --- a/src/DependencyInjection/AppExtension.php +++ b/src/DependencyInjection/AppExtension.php @@ -107,7 +107,9 @@ final class AppExtension extends Extension private function setLanguageFormats(ContainerBuilder $container): void { - $locales = explode('|', $container->getParameter('app_locales')); + $locales = $container->getParameter('kimai_locales'); + // @deprecated since 2.21.0 + $container->setParameter('app_locales', implode('|', $locales)); $directory = $container->getParameter('kernel.project_dir'); $settings = include $directory . DIRECTORY_SEPARATOR . 'config/locales.php'; diff --git a/tests/Controller/PermissionControllerTest.php b/tests/Controller/PermissionControllerTest.php index c4b89708..363c78ee 100644 --- a/tests/Controller/PermissionControllerTest.php +++ b/tests/Controller/PermissionControllerTest.php @@ -81,7 +81,16 @@ class PermissionControllerTest extends ControllerBaseTest public function testDeleteRoleIsSecured(): void { - $this->assertUrlIsSecured('/admin/permissions/roles/1/delete/sdfsdfsdfsd'); + $client = self::createClient(); + + $role = new Role(); + $role->setName('TEST_ROLE'); + + $em = $this->getEntityManager(); + $em->persist($role); + $em->flush(); + + $this->assertRequestIsSecured($client, '/admin/permissions/roles/' . $role->getId() . '/delete/sdfsdfsdfsd'); } public function testDeleteRoleIsSecuredForRole(): void @@ -150,7 +159,16 @@ class PermissionControllerTest extends ControllerBaseTest public function testSavePermissionIsSecured(): void { - $this->assertUrlIsSecured('/admin/permissions/roles/1/view_user/1/asdfasdf', 'POST'); + $client = self::createClient(); + + $role = new Role(); + $role->setName('TEST_ROLE'); + + $em = $this->getEntityManager(); + $em->persist($role); + $em->flush(); + + $this->assertRequestIsSecured($client, '/admin/permissions/roles/' . $role->getId() . '/view_user/1/asdfasdf', 'POST'); } public function testSavePermissionIsSecuredForRole(): void diff --git a/tests/Controller/QuickEntryControllerTest.php b/tests/Controller/QuickEntryControllerTest.php index 005f0f53..b3b581e1 100644 --- a/tests/Controller/QuickEntryControllerTest.php +++ b/tests/Controller/QuickEntryControllerTest.php @@ -18,7 +18,7 @@ class QuickEntryControllerTest extends ControllerBaseTest { public function testIsSecure(): void { - $this->assertUrlIsSecured('/quick_entry'); + $this->assertUrlIsSecured('/quick_entry/'); } public function testIndexAction(): void diff --git a/tests/Controller/ReportingControllerTest.php b/tests/Controller/ReportingControllerTest.php index 508168a5..0a3fe074 100644 --- a/tests/Controller/ReportingControllerTest.php +++ b/tests/Controller/ReportingControllerTest.php @@ -18,7 +18,7 @@ class ReportingControllerTest extends ControllerBaseTest { public function testIsSecure(): void { - $this->assertUrlIsSecured('/reporting'); + $this->assertUrlIsSecured('/reporting/'); } public function testOverviewPage(): void @@ -29,6 +29,24 @@ class ReportingControllerTest extends ControllerBaseTest $this->assertCount(11, $nodes); } + public function testAllReports(): void + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN); + $this->request($client, '/reporting/'); + $nodes = $client->getCrawler()->filter('section.content div.row-cards a.card-link'); + $this->assertCount(11, $nodes); + foreach ($nodes as $node) { + self::assertNotNull($node->attributes); + $link = $node->attributes->getNamedItem('href'); + self::assertNotNull($link); + $url = $link->nodeValue; + self::assertNotNull($url); + self::assertNotEmpty($url); + self::assertStringStartsWith('/en/reporting/', $url); + $this->request($client, $url); + } + } + public function testOverviewPageAsUser(): void { $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); diff --git a/tests/DependencyInjection/AppExtensionTest.php b/tests/DependencyInjection/AppExtensionTest.php index 6be3f093..ce4eab0f 100644 --- a/tests/DependencyInjection/AppExtensionTest.php +++ b/tests/DependencyInjection/AppExtensionTest.php @@ -30,7 +30,7 @@ class AppExtensionTest extends TestCase private function getContainer(): ContainerBuilder { $container = new ContainerBuilder(); - $container->setParameter('app_locales', 'de|en|he|tr|zh_CN'); + $container->setParameter('kimai_locales', ['de', 'en', 'he', 'tr', 'zh_CN']); $container->setParameter('kernel.project_dir', realpath(__DIR__ . '/../../')); $container->setParameter('security.role_hierarchy.roles', [ 'ROLE_TEAMLEAD' => ['ROLE_USER'],