diff --git a/apps/dashboard/lib/Controller/DashboardApiController.php b/apps/dashboard/lib/Controller/DashboardApiController.php index d31cede85b790..f920c384ba234 100644 --- a/apps/dashboard/lib/Controller/DashboardApiController.php +++ b/apps/dashboard/lib/Controller/DashboardApiController.php @@ -200,6 +200,7 @@ public function getLayout(): DataResponse { #[NoAdminRequired] #[ApiRoute(verb: 'POST', url: '/api/v3/layout')] public function updateLayout(array $layout): DataResponse { + $layout = $this->service->sanitizeLayout($layout); $this->config->setUserValue($this->userId, 'dashboard', 'layout', implode(',', $layout)); return new DataResponse(['layout' => $layout]); } diff --git a/apps/dashboard/lib/Service/DashboardService.php b/apps/dashboard/lib/Service/DashboardService.php index bb5333c2cc77b..a0bbc475bc647 100644 --- a/apps/dashboard/lib/Service/DashboardService.php +++ b/apps/dashboard/lib/Service/DashboardService.php @@ -29,7 +29,28 @@ public function __construct( */ public function getLayout(): array { $systemDefault = $this->config->getAppValue('dashboard', 'layout', 'recommendations,spreed,mail,calendar'); - return array_values(array_filter(explode(',', $this->config->getUserValue($this->userId, 'dashboard', 'layout', $systemDefault)), fn (string $value) => $value !== '')); + return $this->sanitizeLayout( + explode(',', $this->config->getUserValue($this->userId, 'dashboard', 'layout', $systemDefault)), + ); + } + + /** + * @param list $layout + * @return list + */ + public function sanitizeLayout(array $layout): array { + $seen = []; + $result = []; + foreach ($layout as $value) { + if ($value === '' || isset($seen[$value])) { + continue; + } + + $seen[$value] = true; + $result[] = $value; + } + + return $result; } /** diff --git a/apps/dashboard/tests/DashboardServiceTest.php b/apps/dashboard/tests/DashboardServiceTest.php index ebcd06cdf033a..73a7906ec7fbd 100644 --- a/apps/dashboard/tests/DashboardServiceTest.php +++ b/apps/dashboard/tests/DashboardServiceTest.php @@ -40,6 +40,25 @@ protected function setUp(): void { ); } + public function testGetLayoutRemovesEmptyAndDuplicateEntries(): void { + $this->appConfig->method('getAppValueString') + ->with('layout', 'recommendations,spreed,mail,calendar') + ->willReturn('recommendations,spreed,mail,calendar'); + $this->userConfig->method('getValueString') + ->with('alice', 'dashboard', 'layout', 'recommendations,spreed,mail,calendar') + ->willReturn('spreed,,mail,mail,calendar,spreed'); + + $layout = $this->service->getLayout(); + + $this->assertSame(['spreed', 'mail', 'calendar'], $layout); + } + + public function testSanitizeLayoutRemovesEmptyAndDuplicateEntries(): void { + $layout = $this->service->sanitizeLayout(['files', 'calendar', 'files', '', 'mail', 'calendar']); + + $this->assertSame(['files', 'calendar', 'mail'], $layout); + } + public function testGetBirthdate(): void { $user = $this->createMock(IUser::class); $this->userManager->method('get')