Completed
Push — master ( 105398...75e577 )
by
unknown
27:06
created
apps/theming/lib/Settings/Personal.php 1 patch
Indentation   +76 added lines, -76 removed lines patch added patch discarded remove patch
@@ -20,90 +20,90 @@
 block discarded – undo
20 20
 
21 21
 class Personal implements ISettings {
22 22
 
23
-	public function __construct(
24
-		private string $userId,
25
-		private IConfig $config,
26
-		private ThemesService $themesService,
27
-		private IInitialState $initialStateService,
28
-		private ThemingDefaults $themingDefaults,
29
-		private INavigationManager $navigationManager,
30
-	) {
31
-	}
23
+    public function __construct(
24
+        private string $userId,
25
+        private IConfig $config,
26
+        private ThemesService $themesService,
27
+        private IInitialState $initialStateService,
28
+        private ThemingDefaults $themingDefaults,
29
+        private INavigationManager $navigationManager,
30
+    ) {
31
+    }
32 32
 
33
-	public function getForm(): TemplateResponse {
34
-		$enforcedTheme = $this->config->getSystemValueString('enforce_theme', '');
33
+    public function getForm(): TemplateResponse {
34
+        $enforcedTheme = $this->config->getSystemValueString('enforce_theme', '');
35 35
 
36
-		$themes = array_map(function ($theme) {
37
-			return [
38
-				'id' => $theme->getId(),
39
-				'type' => $theme->getType(),
40
-				'title' => $theme->getTitle(),
41
-				'enableLabel' => $theme->getEnableLabel(),
42
-				'description' => $theme->getDescription(),
43
-				'enabled' => $this->themesService->isEnabled($theme),
44
-			];
45
-		}, $this->themesService->getThemes());
36
+        $themes = array_map(function ($theme) {
37
+            return [
38
+                'id' => $theme->getId(),
39
+                'type' => $theme->getType(),
40
+                'title' => $theme->getTitle(),
41
+                'enableLabel' => $theme->getEnableLabel(),
42
+                'description' => $theme->getDescription(),
43
+                'enabled' => $this->themesService->isEnabled($theme),
44
+            ];
45
+        }, $this->themesService->getThemes());
46 46
 
47
-		if ($enforcedTheme !== '') {
48
-			$themes = array_filter($themes, function ($theme) use ($enforcedTheme) {
49
-				return $theme['type'] !== ITheme::TYPE_THEME || $theme['id'] === $enforcedTheme;
50
-			});
51
-		}
47
+        if ($enforcedTheme !== '') {
48
+            $themes = array_filter($themes, function ($theme) use ($enforcedTheme) {
49
+                return $theme['type'] !== ITheme::TYPE_THEME || $theme['id'] === $enforcedTheme;
50
+            });
51
+        }
52 52
 
53
-		// Get the default entry enforced by admin
54
-		$forcedDefaultEntry = $this->navigationManager->getDefaultEntryIdForUser(null, false);
53
+        // Get the default entry enforced by admin
54
+        $forcedDefaultEntry = $this->navigationManager->getDefaultEntryIdForUser(null, false);
55 55
 
56
-		/** List of all shipped backgrounds */
57
-		$this->initialStateService->provideInitialState('shippedBackgrounds', BackgroundService::SHIPPED_BACKGROUNDS);
56
+        /** List of all shipped backgrounds */
57
+        $this->initialStateService->provideInitialState('shippedBackgrounds', BackgroundService::SHIPPED_BACKGROUNDS);
58 58
 
59
-		/**
60
-		 * Admin theming
61
-		 */
62
-		$this->initialStateService->provideInitialState('themingDefaults', [
63
-			/** URL of admin configured background image */
64
-			'backgroundImage' => $this->themingDefaults->getBackground(),
65
-			/** `backgroundColor` if disabled, mime type if defined and empty by default */
66
-			'backgroundMime' => $this->config->getAppValue('theming', 'backgroundMime', ''),
67
-			/** Admin configured background color */
68
-			'backgroundColor' => $this->themingDefaults->getDefaultColorBackground(),
69
-			/** Admin configured primary color */
70
-			'primaryColor' => $this->themingDefaults->getDefaultColorPrimary(),
71
-			/** Nextcloud default background image */
72
-			'defaultShippedBackground' => BackgroundService::DEFAULT_BACKGROUND_IMAGE,
73
-		]);
59
+        /**
60
+         * Admin theming
61
+         */
62
+        $this->initialStateService->provideInitialState('themingDefaults', [
63
+            /** URL of admin configured background image */
64
+            'backgroundImage' => $this->themingDefaults->getBackground(),
65
+            /** `backgroundColor` if disabled, mime type if defined and empty by default */
66
+            'backgroundMime' => $this->config->getAppValue('theming', 'backgroundMime', ''),
67
+            /** Admin configured background color */
68
+            'backgroundColor' => $this->themingDefaults->getDefaultColorBackground(),
69
+            /** Admin configured primary color */
70
+            'primaryColor' => $this->themingDefaults->getDefaultColorPrimary(),
71
+            /** Nextcloud default background image */
72
+            'defaultShippedBackground' => BackgroundService::DEFAULT_BACKGROUND_IMAGE,
73
+        ]);
74 74
 
75
-		$this->initialStateService->provideInitialState('userBackgroundImage', $this->config->getUserValue($this->userId, 'theming', 'background_image', BackgroundService::BACKGROUND_DEFAULT));
76
-		$this->initialStateService->provideInitialState('themes', array_values($themes));
77
-		$this->initialStateService->provideInitialState('enforceTheme', $enforcedTheme);
78
-		$this->initialStateService->provideInitialState('isUserThemingDisabled', $this->themingDefaults->isUserThemingDisabled());
79
-		$this->initialStateService->provideInitialState('enableBlurFilter', $this->config->getUserValue($this->userId, 'theming', 'force_enable_blur_filter', ''));
80
-		$this->initialStateService->provideInitialState('navigationBar', [
81
-			'userAppOrder' => json_decode($this->config->getUserValue($this->userId, 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR),
82
-			'enforcedDefaultApp' => $forcedDefaultEntry
83
-		]);
75
+        $this->initialStateService->provideInitialState('userBackgroundImage', $this->config->getUserValue($this->userId, 'theming', 'background_image', BackgroundService::BACKGROUND_DEFAULT));
76
+        $this->initialStateService->provideInitialState('themes', array_values($themes));
77
+        $this->initialStateService->provideInitialState('enforceTheme', $enforcedTheme);
78
+        $this->initialStateService->provideInitialState('isUserThemingDisabled', $this->themingDefaults->isUserThemingDisabled());
79
+        $this->initialStateService->provideInitialState('enableBlurFilter', $this->config->getUserValue($this->userId, 'theming', 'force_enable_blur_filter', ''));
80
+        $this->initialStateService->provideInitialState('navigationBar', [
81
+            'userAppOrder' => json_decode($this->config->getUserValue($this->userId, 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR),
82
+            'enforcedDefaultApp' => $forcedDefaultEntry
83
+        ]);
84 84
 
85
-		Util::addStyle(Application::APP_ID, 'settings-personal');
86
-		Util::addScript(Application::APP_ID, 'settings-personal');
87
-		return new TemplateResponse(Application::APP_ID, 'settings-personal');
88
-	}
85
+        Util::addStyle(Application::APP_ID, 'settings-personal');
86
+        Util::addScript(Application::APP_ID, 'settings-personal');
87
+        return new TemplateResponse(Application::APP_ID, 'settings-personal');
88
+    }
89 89
 
90
-	/**
91
-	 * @return string the section ID, e.g. 'sharing'
92
-	 * @since 9.1
93
-	 */
94
-	public function getSection(): string {
95
-		return Application::APP_ID;
96
-	}
90
+    /**
91
+     * @return string the section ID, e.g. 'sharing'
92
+     * @since 9.1
93
+     */
94
+    public function getSection(): string {
95
+        return Application::APP_ID;
96
+    }
97 97
 
98
-	/**
99
-	 * @return int whether the form should be rather on the top or bottom of
100
-	 *             the admin section. The forms are arranged in ascending order of the
101
-	 *             priority values. It is required to return a value between 0 and 100.
102
-	 *
103
-	 * E.g.: 70
104
-	 * @since 9.1
105
-	 */
106
-	public function getPriority(): int {
107
-		return 40;
108
-	}
98
+    /**
99
+     * @return int whether the form should be rather on the top or bottom of
100
+     *             the admin section. The forms are arranged in ascending order of the
101
+     *             priority values. It is required to return a value between 0 and 100.
102
+     *
103
+     * E.g.: 70
104
+     * @since 9.1
105
+     */
106
+    public function getPriority(): int {
107
+        return 40;
108
+    }
109 109
 }
Please login to merge, or discard this patch.
apps/theming/lib/Settings/PersonalSection.php 1 patch
Indentation   +53 added lines, -53 removed lines patch added patch discarded remove patch
@@ -13,61 +13,61 @@
 block discarded – undo
13 13
 
14 14
 class PersonalSection implements IIconSection {
15 15
 
16
-	/**
17
-	 * Personal Section constructor.
18
-	 *
19
-	 * @param string $appName
20
-	 * @param IURLGenerator $urlGenerator
21
-	 * @param IL10N $l
22
-	 */
23
-	public function __construct(
24
-		private IURLGenerator $urlGenerator,
25
-		private IL10N $l,
26
-	) {
27
-	}
16
+    /**
17
+     * Personal Section constructor.
18
+     *
19
+     * @param string $appName
20
+     * @param IURLGenerator $urlGenerator
21
+     * @param IL10N $l
22
+     */
23
+    public function __construct(
24
+        private IURLGenerator $urlGenerator,
25
+        private IL10N $l,
26
+    ) {
27
+    }
28 28
 
29
-	/**
30
-	 * returns the relative path to an 16*16 icon describing the section.
31
-	 * e.g. '/core/img/places/files.svg'
32
-	 *
33
-	 * @returns string
34
-	 * @since 13.0.0
35
-	 */
36
-	public function getIcon() {
37
-		return $this->urlGenerator->imagePath(Application::APP_ID, 'accessibility-dark.svg');
38
-	}
29
+    /**
30
+     * returns the relative path to an 16*16 icon describing the section.
31
+     * e.g. '/core/img/places/files.svg'
32
+     *
33
+     * @returns string
34
+     * @since 13.0.0
35
+     */
36
+    public function getIcon() {
37
+        return $this->urlGenerator->imagePath(Application::APP_ID, 'accessibility-dark.svg');
38
+    }
39 39
 
40
-	/**
41
-	 * returns the ID of the section. It is supposed to be a lower case string,
42
-	 * e.g. 'ldap'
43
-	 *
44
-	 * @returns string
45
-	 * @since 9.1
46
-	 */
47
-	public function getID() {
48
-		return Application::APP_ID;
49
-	}
40
+    /**
41
+     * returns the ID of the section. It is supposed to be a lower case string,
42
+     * e.g. 'ldap'
43
+     *
44
+     * @returns string
45
+     * @since 9.1
46
+     */
47
+    public function getID() {
48
+        return Application::APP_ID;
49
+    }
50 50
 
51
-	/**
52
-	 * returns the translated name as it should be displayed, e.g. 'LDAP / AD
53
-	 * integration'. Use the L10N service to translate it.
54
-	 *
55
-	 * @return string
56
-	 * @since 9.1
57
-	 */
58
-	public function getName() {
59
-		return $this->l->t('Appearance and accessibility');
60
-	}
51
+    /**
52
+     * returns the translated name as it should be displayed, e.g. 'LDAP / AD
53
+     * integration'. Use the L10N service to translate it.
54
+     *
55
+     * @return string
56
+     * @since 9.1
57
+     */
58
+    public function getName() {
59
+        return $this->l->t('Appearance and accessibility');
60
+    }
61 61
 
62
-	/**
63
-	 * @return int whether the form should be rather on the top or bottom of
64
-	 *             the settings navigation. The sections are arranged in ascending order of
65
-	 *             the priority values. It is required to return a value between 0 and 99.
66
-	 *
67
-	 * E.g.: 70
68
-	 * @since 9.1
69
-	 */
70
-	public function getPriority() {
71
-		return 15;
72
-	}
62
+    /**
63
+     * @return int whether the form should be rather on the top or bottom of
64
+     *             the settings navigation. The sections are arranged in ascending order of
65
+     *             the priority values. It is required to return a value between 0 and 99.
66
+     *
67
+     * E.g.: 70
68
+     * @since 9.1
69
+     */
70
+    public function getPriority() {
71
+        return 15;
72
+    }
73 73
 }
Please login to merge, or discard this patch.
apps/theming/lib/Settings/Admin.php 2 patches
Indentation   +79 added lines, -79 removed lines patch added patch discarded remove patch
@@ -22,92 +22,92 @@
 block discarded – undo
22 22
 
23 23
 class Admin implements IDelegatedSettings {
24 24
 
25
-	public function __construct(
26
-		private IConfig $config,
27
-		private IL10N $l,
28
-		private ThemingDefaults $themingDefaults,
29
-		private IInitialState $initialState,
30
-		private IURLGenerator $urlGenerator,
31
-		private ImageManager $imageManager,
32
-		private INavigationManager $navigationManager,
33
-	) {
34
-	}
25
+    public function __construct(
26
+        private IConfig $config,
27
+        private IL10N $l,
28
+        private ThemingDefaults $themingDefaults,
29
+        private IInitialState $initialState,
30
+        private IURLGenerator $urlGenerator,
31
+        private ImageManager $imageManager,
32
+        private INavigationManager $navigationManager,
33
+    ) {
34
+    }
35 35
 
36
-	/**
37
-	 * @return TemplateResponse
38
-	 */
39
-	public function getForm(): TemplateResponse {
40
-		$themeable = true;
41
-		$errorMessage = '';
42
-		$theme = $this->config->getSystemValue('theme', '');
43
-		if ($theme !== '') {
44
-			$themeable = false;
45
-			$errorMessage = $this->l->t('You are already using a custom theme. Theming app settings might be overwritten by that.');
46
-		}
36
+    /**
37
+     * @return TemplateResponse
38
+     */
39
+    public function getForm(): TemplateResponse {
40
+        $themeable = true;
41
+        $errorMessage = '';
42
+        $theme = $this->config->getSystemValue('theme', '');
43
+        if ($theme !== '') {
44
+            $themeable = false;
45
+            $errorMessage = $this->l->t('You are already using a custom theme. Theming app settings might be overwritten by that.');
46
+        }
47 47
 
48
-		$allowedMimeTypes = array_reduce(ThemingController::VALID_UPLOAD_KEYS, function ($carry, $key) {
49
-			$carry[$key] = $this->imageManager->getSupportedUploadImageFormats($key);
50
-			return $carry;
51
-		}, []);
48
+        $allowedMimeTypes = array_reduce(ThemingController::VALID_UPLOAD_KEYS, function ($carry, $key) {
49
+            $carry[$key] = $this->imageManager->getSupportedUploadImageFormats($key);
50
+            return $carry;
51
+        }, []);
52 52
 
53
-		$this->initialState->provideInitialState('adminThemingInfo', [
54
-			'isThemeable' => $themeable,
55
-			'notThemeableErrorMessage' => $errorMessage,
56
-			'defaultBackgroundURL' => $this->urlGenerator->linkTo(Application::APP_ID, 'img/background/' . BackgroundService::DEFAULT_BACKGROUND_IMAGE),
57
-			'defaultBackgroundColor' => BackgroundService::DEFAULT_BACKGROUND_COLOR,
58
-			'docUrl' => $this->urlGenerator->linkToDocs('admin-theming'),
59
-			'docUrlIcons' => $this->urlGenerator->linkToDocs('admin-theming-icons'),
60
-			'canThemeIcons' => $this->imageManager->shouldReplaceIcons(),
61
-		]);
53
+        $this->initialState->provideInitialState('adminThemingInfo', [
54
+            'isThemeable' => $themeable,
55
+            'notThemeableErrorMessage' => $errorMessage,
56
+            'defaultBackgroundURL' => $this->urlGenerator->linkTo(Application::APP_ID, 'img/background/' . BackgroundService::DEFAULT_BACKGROUND_IMAGE),
57
+            'defaultBackgroundColor' => BackgroundService::DEFAULT_BACKGROUND_COLOR,
58
+            'docUrl' => $this->urlGenerator->linkToDocs('admin-theming'),
59
+            'docUrlIcons' => $this->urlGenerator->linkToDocs('admin-theming-icons'),
60
+            'canThemeIcons' => $this->imageManager->shouldReplaceIcons(),
61
+        ]);
62 62
 
63
-		$this->initialState->provideInitialState('adminThemingParameters', [
64
-			'name' => $this->themingDefaults->getEntity(),
65
-			'url' => $this->themingDefaults->getBaseUrl(),
66
-			'slogan' => $this->themingDefaults->getSlogan(),
67
-			'primaryColor' => $this->themingDefaults->getDefaultColorPrimary(),
68
-			'backgroundColor' => $this->themingDefaults->getDefaultColorBackground(),
69
-			'logoMime' => $this->config->getAppValue(Application::APP_ID, 'logoMime', ''),
70
-			'allowedMimeTypes' => $allowedMimeTypes,
71
-			'backgroundURL' => $this->imageManager->getImageUrl('background'),
72
-			'backgroundMime' => $this->config->getAppValue(Application::APP_ID, 'backgroundMime', ''),
73
-			'logoheaderMime' => $this->config->getAppValue(Application::APP_ID, 'logoheaderMime', ''),
74
-			'faviconMime' => $this->config->getAppValue(Application::APP_ID, 'faviconMime', ''),
75
-			'legalNoticeUrl' => $this->themingDefaults->getImprintUrl(),
76
-			'privacyPolicyUrl' => $this->themingDefaults->getPrivacyUrl(),
77
-			'disableUserTheming' => $this->themingDefaults->isUserThemingDisabled(),
78
-			'defaultApps' => $this->navigationManager->getDefaultEntryIds(),
79
-		]);
63
+        $this->initialState->provideInitialState('adminThemingParameters', [
64
+            'name' => $this->themingDefaults->getEntity(),
65
+            'url' => $this->themingDefaults->getBaseUrl(),
66
+            'slogan' => $this->themingDefaults->getSlogan(),
67
+            'primaryColor' => $this->themingDefaults->getDefaultColorPrimary(),
68
+            'backgroundColor' => $this->themingDefaults->getDefaultColorBackground(),
69
+            'logoMime' => $this->config->getAppValue(Application::APP_ID, 'logoMime', ''),
70
+            'allowedMimeTypes' => $allowedMimeTypes,
71
+            'backgroundURL' => $this->imageManager->getImageUrl('background'),
72
+            'backgroundMime' => $this->config->getAppValue(Application::APP_ID, 'backgroundMime', ''),
73
+            'logoheaderMime' => $this->config->getAppValue(Application::APP_ID, 'logoheaderMime', ''),
74
+            'faviconMime' => $this->config->getAppValue(Application::APP_ID, 'faviconMime', ''),
75
+            'legalNoticeUrl' => $this->themingDefaults->getImprintUrl(),
76
+            'privacyPolicyUrl' => $this->themingDefaults->getPrivacyUrl(),
77
+            'disableUserTheming' => $this->themingDefaults->isUserThemingDisabled(),
78
+            'defaultApps' => $this->navigationManager->getDefaultEntryIds(),
79
+        ]);
80 80
 
81
-		Util::addStyle(Application::APP_ID, 'settings-admin');
82
-		Util::addScript(Application::APP_ID, 'settings-admin');
83
-		return new TemplateResponse(Application::APP_ID, 'settings-admin');
84
-	}
81
+        Util::addStyle(Application::APP_ID, 'settings-admin');
82
+        Util::addScript(Application::APP_ID, 'settings-admin');
83
+        return new TemplateResponse(Application::APP_ID, 'settings-admin');
84
+    }
85 85
 
86
-	/**
87
-	 * @return string the section ID, e.g. 'sharing'
88
-	 */
89
-	public function getSection(): string {
90
-		return Application::APP_ID;
91
-	}
86
+    /**
87
+     * @return string the section ID, e.g. 'sharing'
88
+     */
89
+    public function getSection(): string {
90
+        return Application::APP_ID;
91
+    }
92 92
 
93
-	/**
94
-	 * @return int whether the form should be rather on the top or bottom of
95
-	 *             the admin section. The forms are arranged in ascending order of the
96
-	 *             priority values. It is required to return a value between 0 and 100.
97
-	 *
98
-	 * E.g.: 70
99
-	 */
100
-	public function getPriority(): int {
101
-		return 5;
102
-	}
93
+    /**
94
+     * @return int whether the form should be rather on the top or bottom of
95
+     *             the admin section. The forms are arranged in ascending order of the
96
+     *             priority values. It is required to return a value between 0 and 100.
97
+     *
98
+     * E.g.: 70
99
+     */
100
+    public function getPriority(): int {
101
+        return 5;
102
+    }
103 103
 
104
-	public function getName(): ?string {
105
-		return null; // Only one setting in this section
106
-	}
104
+    public function getName(): ?string {
105
+        return null; // Only one setting in this section
106
+    }
107 107
 
108
-	public function getAuthorizedAppConfig(): array {
109
-		return [
110
-			Application::APP_ID => '/.*/',
111
-		];
112
-	}
108
+    public function getAuthorizedAppConfig(): array {
109
+        return [
110
+            Application::APP_ID => '/.*/',
111
+        ];
112
+    }
113 113
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -45,7 +45,7 @@  discard block
 block discarded – undo
45 45
 			$errorMessage = $this->l->t('You are already using a custom theme. Theming app settings might be overwritten by that.');
46 46
 		}
47 47
 
48
-		$allowedMimeTypes = array_reduce(ThemingController::VALID_UPLOAD_KEYS, function ($carry, $key) {
48
+		$allowedMimeTypes = array_reduce(ThemingController::VALID_UPLOAD_KEYS, function($carry, $key) {
49 49
 			$carry[$key] = $this->imageManager->getSupportedUploadImageFormats($key);
50 50
 			return $carry;
51 51
 		}, []);
@@ -53,7 +53,7 @@  discard block
 block discarded – undo
53 53
 		$this->initialState->provideInitialState('adminThemingInfo', [
54 54
 			'isThemeable' => $themeable,
55 55
 			'notThemeableErrorMessage' => $errorMessage,
56
-			'defaultBackgroundURL' => $this->urlGenerator->linkTo(Application::APP_ID, 'img/background/' . BackgroundService::DEFAULT_BACKGROUND_IMAGE),
56
+			'defaultBackgroundURL' => $this->urlGenerator->linkTo(Application::APP_ID, 'img/background/'.BackgroundService::DEFAULT_BACKGROUND_IMAGE),
57 57
 			'defaultBackgroundColor' => BackgroundService::DEFAULT_BACKGROUND_COLOR,
58 58
 			'docUrl' => $this->urlGenerator->linkToDocs('admin-theming'),
59 59
 			'docUrlIcons' => $this->urlGenerator->linkToDocs('admin-theming-icons'),
Please login to merge, or discard this patch.
apps/theming/lib/Controller/ThemingController.php 1 patch
Indentation   +446 added lines, -446 removed lines patch added patch discarded remove patch
@@ -42,476 +42,476 @@
 block discarded – undo
42 42
  * @package OCA\Theming\Controller
43 43
  */
44 44
 class ThemingController extends Controller {
45
-	public const VALID_UPLOAD_KEYS = ['header', 'logo', 'logoheader', 'background', 'favicon'];
45
+    public const VALID_UPLOAD_KEYS = ['header', 'logo', 'logoheader', 'background', 'favicon'];
46 46
 
47
-	public function __construct(
48
-		string $appName,
49
-		IRequest $request,
50
-		private IConfig $config,
51
-		private IAppConfig $appConfig,
52
-		private ThemingDefaults $themingDefaults,
53
-		private IL10N $l10n,
54
-		private IURLGenerator $urlGenerator,
55
-		private IAppManager $appManager,
56
-		private ImageManager $imageManager,
57
-		private ThemesService $themesService,
58
-		private INavigationManager $navigationManager,
59
-	) {
60
-		parent::__construct($appName, $request);
61
-	}
47
+    public function __construct(
48
+        string $appName,
49
+        IRequest $request,
50
+        private IConfig $config,
51
+        private IAppConfig $appConfig,
52
+        private ThemingDefaults $themingDefaults,
53
+        private IL10N $l10n,
54
+        private IURLGenerator $urlGenerator,
55
+        private IAppManager $appManager,
56
+        private ImageManager $imageManager,
57
+        private ThemesService $themesService,
58
+        private INavigationManager $navigationManager,
59
+    ) {
60
+        parent::__construct($appName, $request);
61
+    }
62 62
 
63
-	/**
64
-	 * @param string $setting
65
-	 * @param string $value
66
-	 * @return DataResponse
67
-	 * @throws NotPermittedException
68
-	 */
69
-	#[AuthorizedAdminSetting(settings: Admin::class)]
70
-	public function updateStylesheet($setting, $value) {
71
-		$value = trim($value);
72
-		$error = null;
73
-		$saved = false;
74
-		switch ($setting) {
75
-			case 'name':
76
-				if (strlen($value) > 250) {
77
-					$error = $this->l10n->t('The given name is too long');
78
-				}
79
-				break;
80
-			case 'url':
81
-				if (strlen($value) > 500) {
82
-					$error = $this->l10n->t('The given web address is too long');
83
-				}
84
-				if ($value !== '' && !$this->isValidUrl($value)) {
85
-					$error = $this->l10n->t('The given web address is not a valid URL');
86
-				}
87
-				break;
88
-			case 'legalNoticeUrl':
89
-				$setting = 'imprintUrl';
90
-				// no break
91
-			case 'imprintUrl':
92
-				if (strlen($value) > 500) {
93
-					$error = $this->l10n->t('The given legal notice address is too long');
94
-				}
95
-				if (!$this->isValidUrl($value)) {
96
-					$error = $this->l10n->t('The given legal notice address is not a valid URL');
97
-				}
98
-				break;
99
-			case 'privacyPolicyUrl':
100
-				$setting = 'privacyUrl';
101
-				// no break
102
-			case 'privacyUrl':
103
-				if (strlen($value) > 500) {
104
-					$error = $this->l10n->t('The given privacy policy address is too long');
105
-				}
106
-				if (!$this->isValidUrl($value)) {
107
-					$error = $this->l10n->t('The given privacy policy address is not a valid URL');
108
-				}
109
-				break;
110
-			case 'slogan':
111
-				if (strlen($value) > 500) {
112
-					$error = $this->l10n->t('The given slogan is too long');
113
-				}
114
-				break;
115
-			case 'primaryColor':
116
-				$setting = 'primary_color';
117
-				// no break
118
-			case 'primary_color':
119
-				if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) {
120
-					$error = $this->l10n->t('The given color is invalid');
121
-				}
122
-				break;
123
-			case 'backgroundColor':
124
-				$setting = 'background_color';
125
-				// no break
126
-			case 'background_color':
127
-				if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) {
128
-					$error = $this->l10n->t('The given color is invalid');
129
-				}
130
-				break;
131
-			case 'disableUserTheming':
132
-			case 'disable-user-theming':
133
-				if (!in_array($value, ['yes', 'true', 'no', 'false'])) {
134
-					$error = $this->l10n->t('%1$s should be true or false', ['disable-user-theming']);
135
-				} else {
136
-					$this->appConfig->setAppValueBool('disable-user-theming', $value === 'yes' || $value === 'true');
137
-					$saved = true;
138
-				}
139
-				break;
140
-			case 'backgroundMime':
141
-				if ($value !== 'backgroundColor') {
142
-					$error = $this->l10n->t('%1$s can only be set to %2$s through the API', ['backgroundMime', 'backgroundColor']);
143
-				}
144
-				break;
145
-			default:
146
-				$error = $this->l10n->t('Invalid setting key');
147
-		}
148
-		if ($error !== null) {
149
-			return new DataResponse([
150
-				'data' => [
151
-					'message' => $error,
152
-				],
153
-				'status' => 'error'
154
-			], Http::STATUS_BAD_REQUEST);
155
-		}
63
+    /**
64
+     * @param string $setting
65
+     * @param string $value
66
+     * @return DataResponse
67
+     * @throws NotPermittedException
68
+     */
69
+    #[AuthorizedAdminSetting(settings: Admin::class)]
70
+    public function updateStylesheet($setting, $value) {
71
+        $value = trim($value);
72
+        $error = null;
73
+        $saved = false;
74
+        switch ($setting) {
75
+            case 'name':
76
+                if (strlen($value) > 250) {
77
+                    $error = $this->l10n->t('The given name is too long');
78
+                }
79
+                break;
80
+            case 'url':
81
+                if (strlen($value) > 500) {
82
+                    $error = $this->l10n->t('The given web address is too long');
83
+                }
84
+                if ($value !== '' && !$this->isValidUrl($value)) {
85
+                    $error = $this->l10n->t('The given web address is not a valid URL');
86
+                }
87
+                break;
88
+            case 'legalNoticeUrl':
89
+                $setting = 'imprintUrl';
90
+                // no break
91
+            case 'imprintUrl':
92
+                if (strlen($value) > 500) {
93
+                    $error = $this->l10n->t('The given legal notice address is too long');
94
+                }
95
+                if (!$this->isValidUrl($value)) {
96
+                    $error = $this->l10n->t('The given legal notice address is not a valid URL');
97
+                }
98
+                break;
99
+            case 'privacyPolicyUrl':
100
+                $setting = 'privacyUrl';
101
+                // no break
102
+            case 'privacyUrl':
103
+                if (strlen($value) > 500) {
104
+                    $error = $this->l10n->t('The given privacy policy address is too long');
105
+                }
106
+                if (!$this->isValidUrl($value)) {
107
+                    $error = $this->l10n->t('The given privacy policy address is not a valid URL');
108
+                }
109
+                break;
110
+            case 'slogan':
111
+                if (strlen($value) > 500) {
112
+                    $error = $this->l10n->t('The given slogan is too long');
113
+                }
114
+                break;
115
+            case 'primaryColor':
116
+                $setting = 'primary_color';
117
+                // no break
118
+            case 'primary_color':
119
+                if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) {
120
+                    $error = $this->l10n->t('The given color is invalid');
121
+                }
122
+                break;
123
+            case 'backgroundColor':
124
+                $setting = 'background_color';
125
+                // no break
126
+            case 'background_color':
127
+                if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) {
128
+                    $error = $this->l10n->t('The given color is invalid');
129
+                }
130
+                break;
131
+            case 'disableUserTheming':
132
+            case 'disable-user-theming':
133
+                if (!in_array($value, ['yes', 'true', 'no', 'false'])) {
134
+                    $error = $this->l10n->t('%1$s should be true or false', ['disable-user-theming']);
135
+                } else {
136
+                    $this->appConfig->setAppValueBool('disable-user-theming', $value === 'yes' || $value === 'true');
137
+                    $saved = true;
138
+                }
139
+                break;
140
+            case 'backgroundMime':
141
+                if ($value !== 'backgroundColor') {
142
+                    $error = $this->l10n->t('%1$s can only be set to %2$s through the API', ['backgroundMime', 'backgroundColor']);
143
+                }
144
+                break;
145
+            default:
146
+                $error = $this->l10n->t('Invalid setting key');
147
+        }
148
+        if ($error !== null) {
149
+            return new DataResponse([
150
+                'data' => [
151
+                    'message' => $error,
152
+                ],
153
+                'status' => 'error'
154
+            ], Http::STATUS_BAD_REQUEST);
155
+        }
156 156
 
157
-		if (!$saved) {
158
-			$this->themingDefaults->set($setting, $value);
159
-		}
157
+        if (!$saved) {
158
+            $this->themingDefaults->set($setting, $value);
159
+        }
160 160
 
161
-		return new DataResponse([
162
-			'data' => [
163
-				'message' => $this->l10n->t('Saved'),
164
-			],
165
-			'status' => 'success'
166
-		]);
167
-	}
161
+        return new DataResponse([
162
+            'data' => [
163
+                'message' => $this->l10n->t('Saved'),
164
+            ],
165
+            'status' => 'success'
166
+        ]);
167
+    }
168 168
 
169
-	/**
170
-	 * @param string $setting
171
-	 * @param mixed $value
172
-	 * @return DataResponse
173
-	 * @throws NotPermittedException
174
-	 */
175
-	#[AuthorizedAdminSetting(settings: Admin::class)]
176
-	public function updateAppMenu($setting, $value) {
177
-		$error = null;
178
-		switch ($setting) {
179
-			case 'defaultApps':
180
-				if (is_array($value)) {
181
-					try {
182
-						$this->navigationManager->setDefaultEntryIds($value);
183
-					} catch (InvalidArgumentException $e) {
184
-						$error = $this->l10n->t('Invalid app given');
185
-					}
186
-				} else {
187
-					$error = $this->l10n->t('Invalid type for setting "defaultApp" given');
188
-				}
189
-				break;
190
-			default:
191
-				$error = $this->l10n->t('Invalid setting key');
192
-		}
193
-		if ($error !== null) {
194
-			return new DataResponse([
195
-				'data' => [
196
-					'message' => $error,
197
-				],
198
-				'status' => 'error'
199
-			], Http::STATUS_BAD_REQUEST);
200
-		}
169
+    /**
170
+     * @param string $setting
171
+     * @param mixed $value
172
+     * @return DataResponse
173
+     * @throws NotPermittedException
174
+     */
175
+    #[AuthorizedAdminSetting(settings: Admin::class)]
176
+    public function updateAppMenu($setting, $value) {
177
+        $error = null;
178
+        switch ($setting) {
179
+            case 'defaultApps':
180
+                if (is_array($value)) {
181
+                    try {
182
+                        $this->navigationManager->setDefaultEntryIds($value);
183
+                    } catch (InvalidArgumentException $e) {
184
+                        $error = $this->l10n->t('Invalid app given');
185
+                    }
186
+                } else {
187
+                    $error = $this->l10n->t('Invalid type for setting "defaultApp" given');
188
+                }
189
+                break;
190
+            default:
191
+                $error = $this->l10n->t('Invalid setting key');
192
+        }
193
+        if ($error !== null) {
194
+            return new DataResponse([
195
+                'data' => [
196
+                    'message' => $error,
197
+                ],
198
+                'status' => 'error'
199
+            ], Http::STATUS_BAD_REQUEST);
200
+        }
201 201
 
202
-		return new DataResponse([
203
-			'data' => [
204
-				'message' => $this->l10n->t('Saved'),
205
-			],
206
-			'status' => 'success'
207
-		]);
208
-	}
202
+        return new DataResponse([
203
+            'data' => [
204
+                'message' => $this->l10n->t('Saved'),
205
+            ],
206
+            'status' => 'success'
207
+        ]);
208
+    }
209 209
 
210
-	/**
211
-	 * Check that a string is a valid http/https url.
212
-	 * Also validates that there is no way for XSS through HTML
213
-	 */
214
-	private function isValidUrl(string $url): bool {
215
-		return ((str_starts_with($url, 'http://') || str_starts_with($url, 'https://'))
216
-			&& filter_var($url, FILTER_VALIDATE_URL) !== false)
217
-			&& !str_contains($url, '"');
218
-	}
210
+    /**
211
+     * Check that a string is a valid http/https url.
212
+     * Also validates that there is no way for XSS through HTML
213
+     */
214
+    private function isValidUrl(string $url): bool {
215
+        return ((str_starts_with($url, 'http://') || str_starts_with($url, 'https://'))
216
+            && filter_var($url, FILTER_VALIDATE_URL) !== false)
217
+            && !str_contains($url, '"');
218
+    }
219 219
 
220
-	/**
221
-	 * @return DataResponse
222
-	 * @throws NotPermittedException
223
-	 */
224
-	#[AuthorizedAdminSetting(settings: Admin::class)]
225
-	public function uploadImage(): DataResponse {
226
-		$key = $this->request->getParam('key');
227
-		if (!in_array($key, self::VALID_UPLOAD_KEYS, true)) {
228
-			return new DataResponse(
229
-				[
230
-					'data' => [
231
-						'message' => 'Invalid key'
232
-					],
233
-					'status' => 'failure',
234
-				],
235
-				Http::STATUS_BAD_REQUEST
236
-			);
237
-		}
238
-		$image = $this->request->getUploadedFile('image');
239
-		$error = null;
240
-		$phpFileUploadErrors = [
241
-			UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'),
242
-			UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
243
-			UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
244
-			UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'),
245
-			UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'),
246
-			UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'),
247
-			UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'),
248
-			UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'),
249
-		];
250
-		if (empty($image)) {
251
-			$error = $this->l10n->t('No file uploaded');
252
-		}
253
-		if (!empty($image) && array_key_exists('error', $image) && $image['error'] !== UPLOAD_ERR_OK) {
254
-			$error = $phpFileUploadErrors[$image['error']];
255
-		}
220
+    /**
221
+     * @return DataResponse
222
+     * @throws NotPermittedException
223
+     */
224
+    #[AuthorizedAdminSetting(settings: Admin::class)]
225
+    public function uploadImage(): DataResponse {
226
+        $key = $this->request->getParam('key');
227
+        if (!in_array($key, self::VALID_UPLOAD_KEYS, true)) {
228
+            return new DataResponse(
229
+                [
230
+                    'data' => [
231
+                        'message' => 'Invalid key'
232
+                    ],
233
+                    'status' => 'failure',
234
+                ],
235
+                Http::STATUS_BAD_REQUEST
236
+            );
237
+        }
238
+        $image = $this->request->getUploadedFile('image');
239
+        $error = null;
240
+        $phpFileUploadErrors = [
241
+            UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'),
242
+            UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
243
+            UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
244
+            UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'),
245
+            UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'),
246
+            UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'),
247
+            UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'),
248
+            UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'),
249
+        ];
250
+        if (empty($image)) {
251
+            $error = $this->l10n->t('No file uploaded');
252
+        }
253
+        if (!empty($image) && array_key_exists('error', $image) && $image['error'] !== UPLOAD_ERR_OK) {
254
+            $error = $phpFileUploadErrors[$image['error']];
255
+        }
256 256
 
257
-		if ($error !== null) {
258
-			return new DataResponse(
259
-				[
260
-					'data' => [
261
-						'message' => $error
262
-					],
263
-					'status' => 'failure',
264
-				],
265
-				Http::STATUS_UNPROCESSABLE_ENTITY
266
-			);
267
-		}
257
+        if ($error !== null) {
258
+            return new DataResponse(
259
+                [
260
+                    'data' => [
261
+                        'message' => $error
262
+                    ],
263
+                    'status' => 'failure',
264
+                ],
265
+                Http::STATUS_UNPROCESSABLE_ENTITY
266
+            );
267
+        }
268 268
 
269
-		try {
270
-			$mime = $this->imageManager->updateImage($key, $image['tmp_name']);
271
-			$this->themingDefaults->set($key . 'Mime', $mime);
272
-		} catch (\Exception $e) {
273
-			return new DataResponse(
274
-				[
275
-					'data' => [
276
-						'message' => $e->getMessage()
277
-					],
278
-					'status' => 'failure',
279
-				],
280
-				Http::STATUS_UNPROCESSABLE_ENTITY
281
-			);
282
-		}
269
+        try {
270
+            $mime = $this->imageManager->updateImage($key, $image['tmp_name']);
271
+            $this->themingDefaults->set($key . 'Mime', $mime);
272
+        } catch (\Exception $e) {
273
+            return new DataResponse(
274
+                [
275
+                    'data' => [
276
+                        'message' => $e->getMessage()
277
+                    ],
278
+                    'status' => 'failure',
279
+                ],
280
+                Http::STATUS_UNPROCESSABLE_ENTITY
281
+            );
282
+        }
283 283
 
284
-		$name = $image['name'];
284
+        $name = $image['name'];
285 285
 
286
-		return new DataResponse(
287
-			[
288
-				'data'
289
-					=> [
290
-						'name' => $name,
291
-						'url' => $this->imageManager->getImageUrl($key),
292
-						'message' => $this->l10n->t('Saved'),
293
-					],
294
-				'status' => 'success'
295
-			]
296
-		);
297
-	}
286
+        return new DataResponse(
287
+            [
288
+                'data'
289
+                    => [
290
+                        'name' => $name,
291
+                        'url' => $this->imageManager->getImageUrl($key),
292
+                        'message' => $this->l10n->t('Saved'),
293
+                    ],
294
+                'status' => 'success'
295
+            ]
296
+        );
297
+    }
298 298
 
299
-	/**
300
-	 * Revert setting to default value
301
-	 *
302
-	 * @param string $setting setting which should be reverted
303
-	 * @return DataResponse
304
-	 * @throws NotPermittedException
305
-	 */
306
-	#[AuthorizedAdminSetting(settings: Admin::class)]
307
-	public function undo(string $setting): DataResponse {
308
-		$setting = match ($setting) {
309
-			'primaryColor' => 'primary_color',
310
-			'backgroundColor' => 'background_color',
311
-			default => $setting,
312
-		};
313
-		$value = $this->themingDefaults->undo($setting);
299
+    /**
300
+     * Revert setting to default value
301
+     *
302
+     * @param string $setting setting which should be reverted
303
+     * @return DataResponse
304
+     * @throws NotPermittedException
305
+     */
306
+    #[AuthorizedAdminSetting(settings: Admin::class)]
307
+    public function undo(string $setting): DataResponse {
308
+        $setting = match ($setting) {
309
+            'primaryColor' => 'primary_color',
310
+            'backgroundColor' => 'background_color',
311
+            default => $setting,
312
+        };
313
+        $value = $this->themingDefaults->undo($setting);
314 314
 
315
-		return new DataResponse(
316
-			[
317
-				'data'
318
-					=> [
319
-						'value' => $value,
320
-						'message' => $this->l10n->t('Saved'),
321
-					],
322
-				'status' => 'success'
323
-			]
324
-		);
325
-	}
315
+        return new DataResponse(
316
+            [
317
+                'data'
318
+                    => [
319
+                        'value' => $value,
320
+                        'message' => $this->l10n->t('Saved'),
321
+                    ],
322
+                'status' => 'success'
323
+            ]
324
+        );
325
+    }
326 326
 
327
-	/**
328
-	 * Revert all theming settings to their default values
329
-	 *
330
-	 * @return DataResponse
331
-	 * @throws NotPermittedException
332
-	 */
333
-	#[AuthorizedAdminSetting(settings: Admin::class)]
334
-	public function undoAll(): DataResponse {
335
-		$this->themingDefaults->undoAll();
336
-		$this->navigationManager->setDefaultEntryIds([]);
327
+    /**
328
+     * Revert all theming settings to their default values
329
+     *
330
+     * @return DataResponse
331
+     * @throws NotPermittedException
332
+     */
333
+    #[AuthorizedAdminSetting(settings: Admin::class)]
334
+    public function undoAll(): DataResponse {
335
+        $this->themingDefaults->undoAll();
336
+        $this->navigationManager->setDefaultEntryIds([]);
337 337
 
338
-		return new DataResponse(
339
-			[
340
-				'data'
341
-					=> [
342
-						'message' => $this->l10n->t('Saved'),
343
-					],
344
-				'status' => 'success'
345
-			]
346
-		);
347
-	}
338
+        return new DataResponse(
339
+            [
340
+                'data'
341
+                    => [
342
+                        'message' => $this->l10n->t('Saved'),
343
+                    ],
344
+                'status' => 'success'
345
+            ]
346
+        );
347
+    }
348 348
 
349
-	/**
350
-	 * @NoSameSiteCookieRequired
351
-	 *
352
-	 * Get an image
353
-	 *
354
-	 * @param string $key Key of the image
355
-	 * @param bool $useSvg Return image as SVG
356
-	 * @return FileDisplayResponse<Http::STATUS_OK, array{}>|NotFoundResponse<Http::STATUS_NOT_FOUND, array{}>
357
-	 * @throws NotPermittedException
358
-	 *
359
-	 * 200: Image returned
360
-	 * 404: Image not found
361
-	 */
362
-	#[PublicPage]
363
-	#[NoCSRFRequired]
364
-	#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
365
-	public function getImage(string $key, bool $useSvg = true) {
366
-		try {
367
-			$file = $this->imageManager->getImage($key, $useSvg);
368
-		} catch (NotFoundException $e) {
369
-			return new NotFoundResponse();
370
-		}
349
+    /**
350
+     * @NoSameSiteCookieRequired
351
+     *
352
+     * Get an image
353
+     *
354
+     * @param string $key Key of the image
355
+     * @param bool $useSvg Return image as SVG
356
+     * @return FileDisplayResponse<Http::STATUS_OK, array{}>|NotFoundResponse<Http::STATUS_NOT_FOUND, array{}>
357
+     * @throws NotPermittedException
358
+     *
359
+     * 200: Image returned
360
+     * 404: Image not found
361
+     */
362
+    #[PublicPage]
363
+    #[NoCSRFRequired]
364
+    #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
365
+    public function getImage(string $key, bool $useSvg = true) {
366
+        try {
367
+            $file = $this->imageManager->getImage($key, $useSvg);
368
+        } catch (NotFoundException $e) {
369
+            return new NotFoundResponse();
370
+        }
371 371
 
372
-		$response = new FileDisplayResponse($file);
373
-		$csp = new ContentSecurityPolicy();
374
-		$csp->allowInlineStyle();
375
-		$response->setContentSecurityPolicy($csp);
376
-		$response->cacheFor(3600);
377
-		$response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
378
-		$response->addHeader('Content-Disposition', 'attachment; filename="' . $key . '"');
379
-		if (!$useSvg) {
380
-			$response->addHeader('Content-Type', 'image/png');
381
-		} else {
382
-			$response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
383
-		}
384
-		return $response;
385
-	}
372
+        $response = new FileDisplayResponse($file);
373
+        $csp = new ContentSecurityPolicy();
374
+        $csp->allowInlineStyle();
375
+        $response->setContentSecurityPolicy($csp);
376
+        $response->cacheFor(3600);
377
+        $response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
378
+        $response->addHeader('Content-Disposition', 'attachment; filename="' . $key . '"');
379
+        if (!$useSvg) {
380
+            $response->addHeader('Content-Type', 'image/png');
381
+        } else {
382
+            $response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
383
+        }
384
+        return $response;
385
+    }
386 386
 
387
-	/**
388
-	 * @NoSameSiteCookieRequired
389
-	 * @NoTwoFactorRequired
390
-	 *
391
-	 * Get the CSS stylesheet for a theme
392
-	 *
393
-	 * @param string $themeId ID of the theme
394
-	 * @param bool $plain Let the browser decide the CSS priority
395
-	 * @param bool $withCustomCss Include custom CSS
396
-	 * @return DataDisplayResponse<Http::STATUS_OK, array{Content-Type: 'text/css'}>|NotFoundResponse<Http::STATUS_NOT_FOUND, array{}>
397
-	 *
398
-	 * 200: Stylesheet returned
399
-	 * 404: Theme not found
400
-	 */
401
-	#[PublicPage]
402
-	#[NoCSRFRequired]
403
-	#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
404
-	public function getThemeStylesheet(string $themeId, bool $plain = false, bool $withCustomCss = false) {
405
-		$themes = $this->themesService->getThemes();
406
-		if (!in_array($themeId, array_keys($themes))) {
407
-			return new NotFoundResponse();
408
-		}
387
+    /**
388
+     * @NoSameSiteCookieRequired
389
+     * @NoTwoFactorRequired
390
+     *
391
+     * Get the CSS stylesheet for a theme
392
+     *
393
+     * @param string $themeId ID of the theme
394
+     * @param bool $plain Let the browser decide the CSS priority
395
+     * @param bool $withCustomCss Include custom CSS
396
+     * @return DataDisplayResponse<Http::STATUS_OK, array{Content-Type: 'text/css'}>|NotFoundResponse<Http::STATUS_NOT_FOUND, array{}>
397
+     *
398
+     * 200: Stylesheet returned
399
+     * 404: Theme not found
400
+     */
401
+    #[PublicPage]
402
+    #[NoCSRFRequired]
403
+    #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
404
+    public function getThemeStylesheet(string $themeId, bool $plain = false, bool $withCustomCss = false) {
405
+        $themes = $this->themesService->getThemes();
406
+        if (!in_array($themeId, array_keys($themes))) {
407
+            return new NotFoundResponse();
408
+        }
409 409
 
410
-		$theme = $themes[$themeId];
411
-		$customCss = $theme->getCustomCss();
410
+        $theme = $themes[$themeId];
411
+        $customCss = $theme->getCustomCss();
412 412
 
413
-		// Generate variables
414
-		$variables = '';
415
-		foreach ($theme->getCSSVariables() as $variable => $value) {
416
-			$variables .= "$variable:$value; ";
417
-		};
413
+        // Generate variables
414
+        $variables = '';
415
+        foreach ($theme->getCSSVariables() as $variable => $value) {
416
+            $variables .= "$variable:$value; ";
417
+        };
418 418
 
419
-		// If plain is set, the browser decides of the css priority
420
-		if ($plain) {
421
-			$css = ":root { $variables } " . $customCss;
422
-		} else {
423
-			// If not set, we'll rely on the body class
424
-			// We need to separate @-rules from normal selectors, as they can't be nested
425
-			// This is a replacement for the SCSS compiler that did this automatically before f1448fcf0777db7d4254cb0a3ef94d63be9f7a24
426
-			// We need a better way to handle this, but for now we just remove comments and split the at-rules
427
-			// from the rest of the CSS.
428
-			$customCssWithoutComments = preg_replace('!/\*.*?\*/!s', '', $customCss);
429
-			$customCssWithoutComments = preg_replace('!//.*!', '', $customCssWithoutComments);
430
-			preg_match_all('/(@[^{]+{(?:[^{}]*|(?R))*})/', $customCssWithoutComments, $atRules);
431
-			$atRulesCss = implode('', $atRules[0]);
432
-			$scopedCss = preg_replace('/(@[^{]+{(?:[^{}]*|(?R))*})/', '', $customCssWithoutComments);
419
+        // If plain is set, the browser decides of the css priority
420
+        if ($plain) {
421
+            $css = ":root { $variables } " . $customCss;
422
+        } else {
423
+            // If not set, we'll rely on the body class
424
+            // We need to separate @-rules from normal selectors, as they can't be nested
425
+            // This is a replacement for the SCSS compiler that did this automatically before f1448fcf0777db7d4254cb0a3ef94d63be9f7a24
426
+            // We need a better way to handle this, but for now we just remove comments and split the at-rules
427
+            // from the rest of the CSS.
428
+            $customCssWithoutComments = preg_replace('!/\*.*?\*/!s', '', $customCss);
429
+            $customCssWithoutComments = preg_replace('!//.*!', '', $customCssWithoutComments);
430
+            preg_match_all('/(@[^{]+{(?:[^{}]*|(?R))*})/', $customCssWithoutComments, $atRules);
431
+            $atRulesCss = implode('', $atRules[0]);
432
+            $scopedCss = preg_replace('/(@[^{]+{(?:[^{}]*|(?R))*})/', '', $customCssWithoutComments);
433 433
 
434
-			$css = "$atRulesCss [data-theme-$themeId] { $variables $scopedCss }";
435
-		}
434
+            $css = "$atRulesCss [data-theme-$themeId] { $variables $scopedCss }";
435
+        }
436 436
 
437
-		try {
438
-			$response = new DataDisplayResponse($css, Http::STATUS_OK, ['Content-Type' => 'text/css']);
439
-			$response->cacheFor(86400);
440
-			return $response;
441
-		} catch (NotFoundException $e) {
442
-			return new NotFoundResponse();
443
-		}
444
-	}
437
+        try {
438
+            $response = new DataDisplayResponse($css, Http::STATUS_OK, ['Content-Type' => 'text/css']);
439
+            $response->cacheFor(86400);
440
+            return $response;
441
+        } catch (NotFoundException $e) {
442
+            return new NotFoundResponse();
443
+        }
444
+    }
445 445
 
446
-	/**
447
-	 * Get the manifest for an app
448
-	 *
449
-	 * @param string $app ID of the app
450
-	 * @psalm-suppress LessSpecificReturnStatement The content of the Manifest doesn't need to be described in the return type
451
-	 * @return JSONResponse<Http::STATUS_OK, array{name: string, short_name: string, start_url: string, theme_color: string, background_color: string, description: string, icons: list<array{src: non-empty-string, type: string, sizes: string}>, display_override: list<string>, display: string}, array{}>|JSONResponse<Http::STATUS_NOT_FOUND, list<empty>, array{}>
452
-	 *
453
-	 * 200: Manifest returned
454
-	 * 404: App not found
455
-	 */
456
-	#[PublicPage]
457
-	#[NoCSRFRequired]
458
-	#[BruteForceProtection(action: 'manifest')]
459
-	#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
460
-	public function getManifest(string $app): JSONResponse {
461
-		$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
462
-		if ($app === 'core' || $app === 'settings') {
463
-			$name = $this->themingDefaults->getName();
464
-			$shortName = $this->themingDefaults->getName();
465
-			$startUrl = $this->urlGenerator->getBaseUrl();
466
-			$description = $this->themingDefaults->getSlogan();
467
-		} else {
468
-			if (!$this->appManager->isEnabledForUser($app)) {
469
-				$response = new JSONResponse([], Http::STATUS_NOT_FOUND);
470
-				$response->throttle(['action' => 'manifest', 'app' => $app]);
471
-				return $response;
472
-			}
446
+    /**
447
+     * Get the manifest for an app
448
+     *
449
+     * @param string $app ID of the app
450
+     * @psalm-suppress LessSpecificReturnStatement The content of the Manifest doesn't need to be described in the return type
451
+     * @return JSONResponse<Http::STATUS_OK, array{name: string, short_name: string, start_url: string, theme_color: string, background_color: string, description: string, icons: list<array{src: non-empty-string, type: string, sizes: string}>, display_override: list<string>, display: string}, array{}>|JSONResponse<Http::STATUS_NOT_FOUND, list<empty>, array{}>
452
+     *
453
+     * 200: Manifest returned
454
+     * 404: App not found
455
+     */
456
+    #[PublicPage]
457
+    #[NoCSRFRequired]
458
+    #[BruteForceProtection(action: 'manifest')]
459
+    #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
460
+    public function getManifest(string $app): JSONResponse {
461
+        $cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
462
+        if ($app === 'core' || $app === 'settings') {
463
+            $name = $this->themingDefaults->getName();
464
+            $shortName = $this->themingDefaults->getName();
465
+            $startUrl = $this->urlGenerator->getBaseUrl();
466
+            $description = $this->themingDefaults->getSlogan();
467
+        } else {
468
+            if (!$this->appManager->isEnabledForUser($app)) {
469
+                $response = new JSONResponse([], Http::STATUS_NOT_FOUND);
470
+                $response->throttle(['action' => 'manifest', 'app' => $app]);
471
+                return $response;
472
+            }
473 473
 
474
-			$info = $this->appManager->getAppInfo($app, false, $this->l10n->getLanguageCode());
475
-			$name = $info['name'] . ' - ' . $this->themingDefaults->getName();
476
-			$shortName = $info['name'];
477
-			if (str_contains($this->request->getRequestUri(), '/index.php/')) {
478
-				$startUrl = $this->urlGenerator->getBaseUrl() . '/index.php/apps/' . $app . '/';
479
-			} else {
480
-				$startUrl = $this->urlGenerator->getBaseUrl() . '/apps/' . $app . '/';
481
-			}
482
-			$description = $info['summary'] ?? '';
483
-		}
484
-		/**
485
-		 * @var string $description
486
-		 * @var string $shortName
487
-		 */
488
-		$responseJS = [
489
-			'name' => $name,
490
-			'short_name' => $shortName,
491
-			'start_url' => $startUrl,
492
-			'theme_color' => $this->themingDefaults->getColorPrimary(),
493
-			'background_color' => $this->themingDefaults->getColorPrimary(),
494
-			'description' => $description,
495
-			'icons'
496
-				=> [
497
-					[
498
-						'src' => $this->urlGenerator->linkToRoute('theming.Icon.getTouchIcon',
499
-							['app' => $app]) . '?v=' . $cacheBusterValue,
500
-						'type' => 'image/png',
501
-						'sizes' => '512x512'
502
-					],
503
-					[
504
-						'src' => $this->urlGenerator->linkToRoute('theming.Icon.getFavicon',
505
-							['app' => $app]) . '?v=' . $cacheBusterValue,
506
-						'type' => 'image/svg+xml',
507
-						'sizes' => '16x16'
508
-					]
509
-				],
510
-			'display_override' => [$this->config->getSystemValueBool('theming.standalone_window.enabled', true) ? 'minimal-ui' : ''],
511
-			'display' => $this->config->getSystemValueBool('theming.standalone_window.enabled', true) ? 'standalone' : 'browser'
512
-		];
513
-		$response = new JSONResponse($responseJS);
514
-		$response->cacheFor(3600);
515
-		return $response;
516
-	}
474
+            $info = $this->appManager->getAppInfo($app, false, $this->l10n->getLanguageCode());
475
+            $name = $info['name'] . ' - ' . $this->themingDefaults->getName();
476
+            $shortName = $info['name'];
477
+            if (str_contains($this->request->getRequestUri(), '/index.php/')) {
478
+                $startUrl = $this->urlGenerator->getBaseUrl() . '/index.php/apps/' . $app . '/';
479
+            } else {
480
+                $startUrl = $this->urlGenerator->getBaseUrl() . '/apps/' . $app . '/';
481
+            }
482
+            $description = $info['summary'] ?? '';
483
+        }
484
+        /**
485
+         * @var string $description
486
+         * @var string $shortName
487
+         */
488
+        $responseJS = [
489
+            'name' => $name,
490
+            'short_name' => $shortName,
491
+            'start_url' => $startUrl,
492
+            'theme_color' => $this->themingDefaults->getColorPrimary(),
493
+            'background_color' => $this->themingDefaults->getColorPrimary(),
494
+            'description' => $description,
495
+            'icons'
496
+                => [
497
+                    [
498
+                        'src' => $this->urlGenerator->linkToRoute('theming.Icon.getTouchIcon',
499
+                            ['app' => $app]) . '?v=' . $cacheBusterValue,
500
+                        'type' => 'image/png',
501
+                        'sizes' => '512x512'
502
+                    ],
503
+                    [
504
+                        'src' => $this->urlGenerator->linkToRoute('theming.Icon.getFavicon',
505
+                            ['app' => $app]) . '?v=' . $cacheBusterValue,
506
+                        'type' => 'image/svg+xml',
507
+                        'sizes' => '16x16'
508
+                    ]
509
+                ],
510
+            'display_override' => [$this->config->getSystemValueBool('theming.standalone_window.enabled', true) ? 'minimal-ui' : ''],
511
+            'display' => $this->config->getSystemValueBool('theming.standalone_window.enabled', true) ? 'standalone' : 'browser'
512
+        ];
513
+        $response = new JSONResponse($responseJS);
514
+        $response->cacheFor(3600);
515
+        return $response;
516
+    }
517 517
 }
Please login to merge, or discard this patch.
apps/theming/tests/Settings/AdminTest.php 1 patch
Indentation   +104 added lines, -104 removed lines patch added patch discarded remove patch
@@ -20,116 +20,116 @@
 block discarded – undo
20 20
 use Test\TestCase;
21 21
 
22 22
 class AdminTest extends TestCase {
23
-	private Admin $admin;
24
-	private IConfig&MockObject $config;
25
-	private ThemingDefaults&MockObject $themingDefaults;
26
-	private IInitialState&MockObject $initialState;
27
-	private IURLGenerator&MockObject $urlGenerator;
28
-	private ImageManager&MockObject $imageManager;
29
-	private IL10N&MockObject $l10n;
30
-	private INavigationManager&MockObject $navigationManager;
23
+    private Admin $admin;
24
+    private IConfig&MockObject $config;
25
+    private ThemingDefaults&MockObject $themingDefaults;
26
+    private IInitialState&MockObject $initialState;
27
+    private IURLGenerator&MockObject $urlGenerator;
28
+    private ImageManager&MockObject $imageManager;
29
+    private IL10N&MockObject $l10n;
30
+    private INavigationManager&MockObject $navigationManager;
31 31
 
32
-	protected function setUp(): void {
33
-		parent::setUp();
34
-		$this->config = $this->createMock(IConfig::class);
35
-		$this->l10n = $this->createMock(IL10N::class);
36
-		$this->themingDefaults = $this->createMock(ThemingDefaults::class);
37
-		$this->initialState = $this->createMock(IInitialState::class);
38
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
39
-		$this->imageManager = $this->createMock(ImageManager::class);
40
-		$this->navigationManager = $this->createMock(INavigationManager::class);
32
+    protected function setUp(): void {
33
+        parent::setUp();
34
+        $this->config = $this->createMock(IConfig::class);
35
+        $this->l10n = $this->createMock(IL10N::class);
36
+        $this->themingDefaults = $this->createMock(ThemingDefaults::class);
37
+        $this->initialState = $this->createMock(IInitialState::class);
38
+        $this->urlGenerator = $this->createMock(IURLGenerator::class);
39
+        $this->imageManager = $this->createMock(ImageManager::class);
40
+        $this->navigationManager = $this->createMock(INavigationManager::class);
41 41
 
42
-		$this->admin = new Admin(
43
-			$this->config,
44
-			$this->l10n,
45
-			$this->themingDefaults,
46
-			$this->initialState,
47
-			$this->urlGenerator,
48
-			$this->imageManager,
49
-			$this->navigationManager,
50
-		);
51
-	}
42
+        $this->admin = new Admin(
43
+            $this->config,
44
+            $this->l10n,
45
+            $this->themingDefaults,
46
+            $this->initialState,
47
+            $this->urlGenerator,
48
+            $this->imageManager,
49
+            $this->navigationManager,
50
+        );
51
+    }
52 52
 
53
-	public function testGetFormNoErrors(): void {
54
-		$this->config
55
-			->expects($this->once())
56
-			->method('getSystemValue')
57
-			->with('theme', '')
58
-			->willReturn('');
59
-		$this->themingDefaults
60
-			->expects($this->once())
61
-			->method('getEntity')
62
-			->willReturn('MyEntity');
63
-		$this->themingDefaults
64
-			->expects($this->once())
65
-			->method('getBaseUrl')
66
-			->willReturn('https://example.com');
67
-		$this->themingDefaults
68
-			->expects($this->once())
69
-			->method('getImprintUrl')
70
-			->willReturn('');
71
-		$this->themingDefaults
72
-			->expects($this->once())
73
-			->method('getPrivacyUrl')
74
-			->willReturn('');
75
-		$this->themingDefaults
76
-			->expects($this->once())
77
-			->method('getSlogan')
78
-			->willReturn('MySlogan');
79
-		$this->themingDefaults
80
-			->expects($this->once())
81
-			->method('getDefaultColorPrimary')
82
-			->willReturn('#fff');
53
+    public function testGetFormNoErrors(): void {
54
+        $this->config
55
+            ->expects($this->once())
56
+            ->method('getSystemValue')
57
+            ->with('theme', '')
58
+            ->willReturn('');
59
+        $this->themingDefaults
60
+            ->expects($this->once())
61
+            ->method('getEntity')
62
+            ->willReturn('MyEntity');
63
+        $this->themingDefaults
64
+            ->expects($this->once())
65
+            ->method('getBaseUrl')
66
+            ->willReturn('https://example.com');
67
+        $this->themingDefaults
68
+            ->expects($this->once())
69
+            ->method('getImprintUrl')
70
+            ->willReturn('');
71
+        $this->themingDefaults
72
+            ->expects($this->once())
73
+            ->method('getPrivacyUrl')
74
+            ->willReturn('');
75
+        $this->themingDefaults
76
+            ->expects($this->once())
77
+            ->method('getSlogan')
78
+            ->willReturn('MySlogan');
79
+        $this->themingDefaults
80
+            ->expects($this->once())
81
+            ->method('getDefaultColorPrimary')
82
+            ->willReturn('#fff');
83 83
 
84
-		$expected = new TemplateResponse('theming', 'settings-admin');
85
-		$this->assertEquals($expected, $this->admin->getForm());
86
-	}
84
+        $expected = new TemplateResponse('theming', 'settings-admin');
85
+        $this->assertEquals($expected, $this->admin->getForm());
86
+    }
87 87
 
88
-	public function testGetFormWithErrors(): void {
89
-		$this->config
90
-			->expects($this->once())
91
-			->method('getSystemValue')
92
-			->with('theme', '')
93
-			->willReturn('MyCustomTheme');
94
-		$this->l10n
95
-			->expects($this->once())
96
-			->method('t')
97
-			->with('You are already using a custom theme. Theming app settings might be overwritten by that.')
98
-			->willReturn('You are already using a custom theme. Theming app settings might be overwritten by that.');
99
-		$this->themingDefaults
100
-			->expects($this->once())
101
-			->method('getEntity')
102
-			->willReturn('MyEntity');
103
-		$this->themingDefaults
104
-			->expects($this->once())
105
-			->method('getBaseUrl')
106
-			->willReturn('https://example.com');
107
-		$this->themingDefaults
108
-			->expects($this->once())
109
-			->method('getImprintUrl')
110
-			->willReturn('');
111
-		$this->themingDefaults
112
-			->expects($this->once())
113
-			->method('getPrivacyUrl')
114
-			->willReturn('');
115
-		$this->themingDefaults
116
-			->expects($this->once())
117
-			->method('getSlogan')
118
-			->willReturn('MySlogan');
119
-		$this->themingDefaults
120
-			->expects($this->once())
121
-			->method('getDefaultColorPrimary')
122
-			->willReturn('#fff');
88
+    public function testGetFormWithErrors(): void {
89
+        $this->config
90
+            ->expects($this->once())
91
+            ->method('getSystemValue')
92
+            ->with('theme', '')
93
+            ->willReturn('MyCustomTheme');
94
+        $this->l10n
95
+            ->expects($this->once())
96
+            ->method('t')
97
+            ->with('You are already using a custom theme. Theming app settings might be overwritten by that.')
98
+            ->willReturn('You are already using a custom theme. Theming app settings might be overwritten by that.');
99
+        $this->themingDefaults
100
+            ->expects($this->once())
101
+            ->method('getEntity')
102
+            ->willReturn('MyEntity');
103
+        $this->themingDefaults
104
+            ->expects($this->once())
105
+            ->method('getBaseUrl')
106
+            ->willReturn('https://example.com');
107
+        $this->themingDefaults
108
+            ->expects($this->once())
109
+            ->method('getImprintUrl')
110
+            ->willReturn('');
111
+        $this->themingDefaults
112
+            ->expects($this->once())
113
+            ->method('getPrivacyUrl')
114
+            ->willReturn('');
115
+        $this->themingDefaults
116
+            ->expects($this->once())
117
+            ->method('getSlogan')
118
+            ->willReturn('MySlogan');
119
+        $this->themingDefaults
120
+            ->expects($this->once())
121
+            ->method('getDefaultColorPrimary')
122
+            ->willReturn('#fff');
123 123
 
124
-		$expected = new TemplateResponse('theming', 'settings-admin');
125
-		$this->assertEquals($expected, $this->admin->getForm());
126
-	}
124
+        $expected = new TemplateResponse('theming', 'settings-admin');
125
+        $this->assertEquals($expected, $this->admin->getForm());
126
+    }
127 127
 
128
-	public function testGetSection(): void {
129
-		$this->assertSame('theming', $this->admin->getSection());
130
-	}
128
+    public function testGetSection(): void {
129
+        $this->assertSame('theming', $this->admin->getSection());
130
+    }
131 131
 
132
-	public function testGetPriority(): void {
133
-		$this->assertSame(5, $this->admin->getPriority());
134
-	}
132
+    public function testGetPriority(): void {
133
+        $this->assertSame(5, $this->admin->getPriority());
134
+    }
135 135
 }
Please login to merge, or discard this patch.
apps/theming/tests/Settings/PersonalTest.php 1 patch
Indentation   +201 added lines, -201 removed lines patch added patch discarded remove patch
@@ -33,205 +33,205 @@
 block discarded – undo
33 33
 use Test\TestCase;
34 34
 
35 35
 class PersonalTest extends TestCase {
36
-	private IConfig&MockObject $config;
37
-	private ThemesService&MockObject $themesService;
38
-	private IInitialState&MockObject $initialStateService;
39
-	private ThemingDefaults&MockObject $themingDefaults;
40
-	private INavigationManager&MockObject $navigationManager;
41
-	private Personal $admin;
42
-
43
-	/** @var ITheme[] */
44
-	private array $themes;
45
-
46
-	protected function setUp(): void {
47
-		parent::setUp();
48
-		$this->config = $this->createMock(IConfig::class);
49
-		$this->themesService = $this->createMock(ThemesService::class);
50
-		$this->initialStateService = $this->createMock(IInitialState::class);
51
-		$this->themingDefaults = $this->createMock(ThemingDefaults::class);
52
-		$this->navigationManager = $this->createMock(INavigationManager::class);
53
-
54
-		$this->initThemes();
55
-
56
-		$this->themesService
57
-			->expects($this->any())
58
-			->method('getThemes')
59
-			->willReturn($this->themes);
60
-
61
-		$this->admin = new Personal(
62
-			'admin',
63
-			$this->config,
64
-			$this->themesService,
65
-			$this->initialStateService,
66
-			$this->themingDefaults,
67
-			$this->navigationManager,
68
-		);
69
-	}
70
-
71
-	public static function dataTestGetForm(): array {
72
-		return [
73
-			['', [
74
-				'default',
75
-				'light',
76
-				'dark',
77
-				'light-highcontrast',
78
-				'dark-highcontrast',
79
-				'opendyslexic',
80
-			]],
81
-			['dark', [
82
-				'dark',
83
-				'opendyslexic',
84
-			]],
85
-		];
86
-	}
87
-
88
-	#[DataProvider('dataTestGetForm')]
89
-	public function testGetForm(string $enforcedTheme, array $themesState): void {
90
-		$themesState = array_map(
91
-			$this->formatThemeForm(...),
92
-			$themesState
93
-		);
94
-
95
-		$this->config->expects($this->once())
96
-			->method('getSystemValueString')
97
-			->with('enforce_theme', '')
98
-			->willReturn($enforcedTheme);
99
-
100
-		$this->config->expects($this->any())
101
-			->method('getUserValue')
102
-			->willReturnMap([
103
-				['admin', 'core', 'apporder', '[]', '[]'],
104
-				['admin', 'theming', 'background_image', BackgroundService::BACKGROUND_DEFAULT],
105
-			]);
106
-
107
-		$this->navigationManager->expects($this->once())
108
-			->method('getDefaultEntryIdForUser')
109
-			->willReturn('forced_id');
110
-
111
-		$this->initialStateService->expects($this->exactly(8))
112
-			->method('provideInitialState')
113
-			->willReturnMap([
114
-				['shippedBackgrounds', BackgroundService::SHIPPED_BACKGROUNDS],
115
-				['themingDefaults'],
116
-				['enableBlurFilter', ''],
117
-				['userBackgroundImage'],
118
-				['themes', $themesState],
119
-				['enforceTheme', $enforcedTheme],
120
-				['isUserThemingDisabled', false],
121
-				['navigationBar', ['userAppOrder' => [], 'enforcedDefaultApp' => 'forced_id']],
122
-			]);
123
-
124
-		$expected = new TemplateResponse('theming', 'settings-personal');
125
-		$this->assertEquals($expected, $this->admin->getForm());
126
-	}
127
-
128
-	public function testGetSection(): void {
129
-		$this->assertSame('theming', $this->admin->getSection());
130
-	}
131
-
132
-	public function testGetPriority(): void {
133
-		$this->assertSame(40, $this->admin->getPriority());
134
-	}
135
-
136
-	private function initThemes() {
137
-		$util = $this->createMock(Util::class);
138
-		$themingDefaults = $this->createMock(ThemingDefaults::class);
139
-		$userSession = $this->createMock(IUserSession::class);
140
-		$urlGenerator = $this->createMock(IURLGenerator::class);
141
-		$imageManager = $this->createMock(ImageManager::class);
142
-		$config = $this->createMock(IConfig::class);
143
-		$l10n = $this->createMock(IL10N::class);
144
-		$appManager = $this->createMock(IAppManager::class);
145
-
146
-		$themingDefaults->expects($this->any())
147
-			->method('getColorPrimary')
148
-			->willReturn('#0082c9');
149
-
150
-		$themingDefaults->expects($this->any())
151
-			->method('getDefaultColorPrimary')
152
-			->willReturn('#0082c9');
153
-
154
-		$this->themes = [
155
-			'default' => new DefaultTheme(
156
-				$util,
157
-				$themingDefaults,
158
-				$userSession,
159
-				$urlGenerator,
160
-				$imageManager,
161
-				$config,
162
-				$l10n,
163
-				$appManager,
164
-				null,
165
-			),
166
-			'light' => new LightTheme(
167
-				$util,
168
-				$themingDefaults,
169
-				$userSession,
170
-				$urlGenerator,
171
-				$imageManager,
172
-				$config,
173
-				$l10n,
174
-				$appManager,
175
-				null,
176
-			),
177
-			'dark' => new DarkTheme(
178
-				$util,
179
-				$themingDefaults,
180
-				$userSession,
181
-				$urlGenerator,
182
-				$imageManager,
183
-				$config,
184
-				$l10n,
185
-				$appManager,
186
-				null,
187
-			),
188
-			'light-highcontrast' => new HighContrastTheme(
189
-				$util,
190
-				$themingDefaults,
191
-				$userSession,
192
-				$urlGenerator,
193
-				$imageManager,
194
-				$config,
195
-				$l10n,
196
-				$appManager,
197
-				null,
198
-			),
199
-			'dark-highcontrast' => new DarkHighContrastTheme(
200
-				$util,
201
-				$themingDefaults,
202
-				$userSession,
203
-				$urlGenerator,
204
-				$imageManager,
205
-				$config,
206
-				$l10n,
207
-				$appManager,
208
-				null,
209
-			),
210
-			'opendyslexic' => new DyslexiaFont(
211
-				$util,
212
-				$themingDefaults,
213
-				$userSession,
214
-				$urlGenerator,
215
-				$imageManager,
216
-				$config,
217
-				$l10n,
218
-				$appManager,
219
-				null,
220
-			),
221
-		];
222
-	}
223
-
224
-	private function formatThemeForm(string $themeId): array {
225
-		$this->initThemes();
226
-
227
-		$theme = $this->themes[$themeId];
228
-		return [
229
-			'id' => $theme->getId(),
230
-			'type' => $theme->getType(),
231
-			'title' => $theme->getTitle(),
232
-			'enableLabel' => $theme->getEnableLabel(),
233
-			'description' => $theme->getDescription(),
234
-			'enabled' => false,
235
-		];
236
-	}
36
+    private IConfig&MockObject $config;
37
+    private ThemesService&MockObject $themesService;
38
+    private IInitialState&MockObject $initialStateService;
39
+    private ThemingDefaults&MockObject $themingDefaults;
40
+    private INavigationManager&MockObject $navigationManager;
41
+    private Personal $admin;
42
+
43
+    /** @var ITheme[] */
44
+    private array $themes;
45
+
46
+    protected function setUp(): void {
47
+        parent::setUp();
48
+        $this->config = $this->createMock(IConfig::class);
49
+        $this->themesService = $this->createMock(ThemesService::class);
50
+        $this->initialStateService = $this->createMock(IInitialState::class);
51
+        $this->themingDefaults = $this->createMock(ThemingDefaults::class);
52
+        $this->navigationManager = $this->createMock(INavigationManager::class);
53
+
54
+        $this->initThemes();
55
+
56
+        $this->themesService
57
+            ->expects($this->any())
58
+            ->method('getThemes')
59
+            ->willReturn($this->themes);
60
+
61
+        $this->admin = new Personal(
62
+            'admin',
63
+            $this->config,
64
+            $this->themesService,
65
+            $this->initialStateService,
66
+            $this->themingDefaults,
67
+            $this->navigationManager,
68
+        );
69
+    }
70
+
71
+    public static function dataTestGetForm(): array {
72
+        return [
73
+            ['', [
74
+                'default',
75
+                'light',
76
+                'dark',
77
+                'light-highcontrast',
78
+                'dark-highcontrast',
79
+                'opendyslexic',
80
+            ]],
81
+            ['dark', [
82
+                'dark',
83
+                'opendyslexic',
84
+            ]],
85
+        ];
86
+    }
87
+
88
+    #[DataProvider('dataTestGetForm')]
89
+    public function testGetForm(string $enforcedTheme, array $themesState): void {
90
+        $themesState = array_map(
91
+            $this->formatThemeForm(...),
92
+            $themesState
93
+        );
94
+
95
+        $this->config->expects($this->once())
96
+            ->method('getSystemValueString')
97
+            ->with('enforce_theme', '')
98
+            ->willReturn($enforcedTheme);
99
+
100
+        $this->config->expects($this->any())
101
+            ->method('getUserValue')
102
+            ->willReturnMap([
103
+                ['admin', 'core', 'apporder', '[]', '[]'],
104
+                ['admin', 'theming', 'background_image', BackgroundService::BACKGROUND_DEFAULT],
105
+            ]);
106
+
107
+        $this->navigationManager->expects($this->once())
108
+            ->method('getDefaultEntryIdForUser')
109
+            ->willReturn('forced_id');
110
+
111
+        $this->initialStateService->expects($this->exactly(8))
112
+            ->method('provideInitialState')
113
+            ->willReturnMap([
114
+                ['shippedBackgrounds', BackgroundService::SHIPPED_BACKGROUNDS],
115
+                ['themingDefaults'],
116
+                ['enableBlurFilter', ''],
117
+                ['userBackgroundImage'],
118
+                ['themes', $themesState],
119
+                ['enforceTheme', $enforcedTheme],
120
+                ['isUserThemingDisabled', false],
121
+                ['navigationBar', ['userAppOrder' => [], 'enforcedDefaultApp' => 'forced_id']],
122
+            ]);
123
+
124
+        $expected = new TemplateResponse('theming', 'settings-personal');
125
+        $this->assertEquals($expected, $this->admin->getForm());
126
+    }
127
+
128
+    public function testGetSection(): void {
129
+        $this->assertSame('theming', $this->admin->getSection());
130
+    }
131
+
132
+    public function testGetPriority(): void {
133
+        $this->assertSame(40, $this->admin->getPriority());
134
+    }
135
+
136
+    private function initThemes() {
137
+        $util = $this->createMock(Util::class);
138
+        $themingDefaults = $this->createMock(ThemingDefaults::class);
139
+        $userSession = $this->createMock(IUserSession::class);
140
+        $urlGenerator = $this->createMock(IURLGenerator::class);
141
+        $imageManager = $this->createMock(ImageManager::class);
142
+        $config = $this->createMock(IConfig::class);
143
+        $l10n = $this->createMock(IL10N::class);
144
+        $appManager = $this->createMock(IAppManager::class);
145
+
146
+        $themingDefaults->expects($this->any())
147
+            ->method('getColorPrimary')
148
+            ->willReturn('#0082c9');
149
+
150
+        $themingDefaults->expects($this->any())
151
+            ->method('getDefaultColorPrimary')
152
+            ->willReturn('#0082c9');
153
+
154
+        $this->themes = [
155
+            'default' => new DefaultTheme(
156
+                $util,
157
+                $themingDefaults,
158
+                $userSession,
159
+                $urlGenerator,
160
+                $imageManager,
161
+                $config,
162
+                $l10n,
163
+                $appManager,
164
+                null,
165
+            ),
166
+            'light' => new LightTheme(
167
+                $util,
168
+                $themingDefaults,
169
+                $userSession,
170
+                $urlGenerator,
171
+                $imageManager,
172
+                $config,
173
+                $l10n,
174
+                $appManager,
175
+                null,
176
+            ),
177
+            'dark' => new DarkTheme(
178
+                $util,
179
+                $themingDefaults,
180
+                $userSession,
181
+                $urlGenerator,
182
+                $imageManager,
183
+                $config,
184
+                $l10n,
185
+                $appManager,
186
+                null,
187
+            ),
188
+            'light-highcontrast' => new HighContrastTheme(
189
+                $util,
190
+                $themingDefaults,
191
+                $userSession,
192
+                $urlGenerator,
193
+                $imageManager,
194
+                $config,
195
+                $l10n,
196
+                $appManager,
197
+                null,
198
+            ),
199
+            'dark-highcontrast' => new DarkHighContrastTheme(
200
+                $util,
201
+                $themingDefaults,
202
+                $userSession,
203
+                $urlGenerator,
204
+                $imageManager,
205
+                $config,
206
+                $l10n,
207
+                $appManager,
208
+                null,
209
+            ),
210
+            'opendyslexic' => new DyslexiaFont(
211
+                $util,
212
+                $themingDefaults,
213
+                $userSession,
214
+                $urlGenerator,
215
+                $imageManager,
216
+                $config,
217
+                $l10n,
218
+                $appManager,
219
+                null,
220
+            ),
221
+        ];
222
+    }
223
+
224
+    private function formatThemeForm(string $themeId): array {
225
+        $this->initThemes();
226
+
227
+        $theme = $this->themes[$themeId];
228
+        return [
229
+            'id' => $theme->getId(),
230
+            'type' => $theme->getType(),
231
+            'title' => $theme->getTitle(),
232
+            'enableLabel' => $theme->getEnableLabel(),
233
+            'description' => $theme->getDescription(),
234
+            'enabled' => false,
235
+        ];
236
+    }
237 237
 }
Please login to merge, or discard this patch.
apps/theming/tests/Controller/ThemingControllerTest.php 2 patches
Indentation   +725 added lines, -725 removed lines patch added patch discarded remove patch
@@ -35,729 +35,729 @@
 block discarded – undo
35 35
 
36 36
 class ThemingControllerTest extends TestCase {
37 37
 
38
-	private IRequest&MockObject $request;
39
-	private IConfig&MockObject $config;
40
-	private IAppConfig&MockObject $appConfig;
41
-	private ThemingDefaults&MockObject $themingDefaults;
42
-	private IL10N&MockObject $l10n;
43
-	private IAppManager&MockObject $appManager;
44
-	private ImageManager&MockObject $imageManager;
45
-	private IURLGenerator&MockObject $urlGenerator;
46
-	private ThemesService&MockObject $themesService;
47
-	private INavigationManager&MockObject $navigationManager;
48
-
49
-	private ThemingController $themingController;
50
-
51
-	protected function setUp(): void {
52
-		$this->request = $this->createMock(IRequest::class);
53
-		$this->config = $this->createMock(IConfig::class);
54
-		$this->appConfig = $this->createMock(IAppConfig::class);
55
-		$this->themingDefaults = $this->createMock(ThemingDefaults::class);
56
-		$this->l10n = $this->createMock(L10N::class);
57
-		$this->appManager = $this->createMock(IAppManager::class);
58
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
59
-		$this->imageManager = $this->createMock(ImageManager::class);
60
-		$this->themesService = $this->createMock(ThemesService::class);
61
-		$this->navigationManager = $this->createMock(INavigationManager::class);
62
-
63
-		$timeFactory = $this->createMock(ITimeFactory::class);
64
-		$timeFactory->expects($this->any())
65
-			->method('getTime')
66
-			->willReturn(123);
67
-
68
-		$this->overwriteService(ITimeFactory::class, $timeFactory);
69
-
70
-		$this->themingController = new ThemingController(
71
-			'theming',
72
-			$this->request,
73
-			$this->config,
74
-			$this->appConfig,
75
-			$this->themingDefaults,
76
-			$this->l10n,
77
-			$this->urlGenerator,
78
-			$this->appManager,
79
-			$this->imageManager,
80
-			$this->themesService,
81
-			$this->navigationManager,
82
-		);
83
-
84
-		parent::setUp();
85
-	}
86
-
87
-	public static function dataUpdateStylesheetSuccess(): array {
88
-		return [
89
-			['name', str_repeat('a', 250), 'Saved'],
90
-			['url', 'https://nextcloud.com/' . str_repeat('a', 478), 'Saved'],
91
-			['slogan', str_repeat('a', 500), 'Saved'],
92
-			['primaryColor', '#0082c9', 'Saved', 'primary_color'],
93
-			['primary_color', '#0082C9', 'Saved'],
94
-			['backgroundColor', '#0082C9', 'Saved', 'background_color'],
95
-			['background_color', '#0082C9', 'Saved'],
96
-			['imprintUrl', 'https://nextcloud.com/' . str_repeat('a', 478), 'Saved'],
97
-			['privacyUrl', 'https://nextcloud.com/' . str_repeat('a', 478), 'Saved'],
98
-		];
99
-	}
100
-
101
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateStylesheetSuccess')]
102
-	public function testUpdateStylesheetSuccess(string $setting, string $value, string $message, ?string $realSetting = null): void {
103
-		$this->themingDefaults
104
-			->expects($this->once())
105
-			->method('set')
106
-			->with($realSetting ?? $setting, $value);
107
-		$this->l10n
108
-			->expects($this->once())
109
-			->method('t')
110
-			->willReturnCallback(function ($str) {
111
-				return $str;
112
-			});
113
-
114
-		$expected = new DataResponse(
115
-			[
116
-				'data'
117
-					=> [
118
-						'message' => $message,
119
-					],
120
-				'status' => 'success',
121
-			]
122
-		);
123
-		$this->assertEquals($expected, $this->themingController->updateStylesheet($setting, $value));
124
-	}
125
-
126
-	public static function dataUpdateStylesheetError(): array {
127
-		$urls = [
128
-			'url' => 'web address',
129
-			'imprintUrl' => 'legal notice address',
130
-			'privacyUrl' => 'privacy policy address',
131
-		];
132
-
133
-		$urlTests = [];
134
-		foreach ($urls as $urlKey => $urlName) {
135
-			// Check length limit
136
-			$urlTests[] = [$urlKey, 'http://example.com/' . str_repeat('a', 501), "The given {$urlName} is too long"];
137
-			// Check potential evil javascript
138
-			$urlTests[] = [$urlKey, 'javascript:alert(1)', "The given {$urlName} is not a valid URL"];
139
-			// Check XSS
140
-			$urlTests[] = [$urlKey, 'https://example.com/"><script/src="alert(\'1\')"><a/href/="', "The given {$urlName} is not a valid URL"];
141
-		}
142
-
143
-		return [
144
-			['name', str_repeat('a', 251), 'The given name is too long'],
145
-			['slogan', str_repeat('a', 501), 'The given slogan is too long'],
146
-			['primary_color', '0082C9', 'The given color is invalid'],
147
-			['primary_color', '#0082Z9', 'The given color is invalid'],
148
-			['primary_color', 'Nextcloud', 'The given color is invalid'],
149
-			['background_color', '0082C9', 'The given color is invalid'],
150
-			['background_color', '#0082Z9', 'The given color is invalid'],
151
-			['background_color', 'Nextcloud', 'The given color is invalid'],
152
-
153
-			['doesnotexist', 'value', 'Invalid setting key'],
154
-
155
-			...$urlTests,
156
-		];
157
-	}
158
-
159
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateStylesheetError')]
160
-	public function testUpdateStylesheetError(string $setting, string $value, string $message): void {
161
-		$this->themingDefaults
162
-			->expects($this->never())
163
-			->method('set')
164
-			->with($setting, $value);
165
-		$this->l10n
166
-			->expects($this->any())
167
-			->method('t')
168
-			->willReturnCallback(function ($str) {
169
-				return $str;
170
-			});
171
-
172
-		$expected = new DataResponse(
173
-			[
174
-				'data'
175
-					=> [
176
-						'message' => $message,
177
-					],
178
-				'status' => 'error',
179
-			],
180
-			Http::STATUS_BAD_REQUEST
181
-		);
182
-		$this->assertEquals($expected, $this->themingController->updateStylesheet($setting, $value));
183
-	}
184
-
185
-	public function testUpdateLogoNoData(): void {
186
-		$this->request
187
-			->expects($this->once())
188
-			->method('getParam')
189
-			->with('key')
190
-			->willReturn('logo');
191
-		$this->request
192
-			->expects($this->once())
193
-			->method('getUploadedFile')
194
-			->with('image')
195
-			->willReturn(null);
196
-		$this->l10n
197
-			->expects($this->any())
198
-			->method('t')
199
-			->willReturnCallback(function ($str) {
200
-				return $str;
201
-			});
202
-
203
-		$expected = new DataResponse(
204
-			[
205
-				'data'
206
-					=> [
207
-						'message' => 'No file uploaded',
208
-					],
209
-				'status' => 'failure',
210
-			],
211
-			Http::STATUS_UNPROCESSABLE_ENTITY
212
-		);
213
-
214
-		$this->assertEquals($expected, $this->themingController->uploadImage());
215
-	}
216
-
217
-	public function testUploadInvalidUploadKey(): void {
218
-		$this->request
219
-			->expects($this->once())
220
-			->method('getParam')
221
-			->with('key')
222
-			->willReturn('invalid');
223
-		$this->request
224
-			->expects($this->never())
225
-			->method('getUploadedFile');
226
-		$this->l10n
227
-			->expects($this->any())
228
-			->method('t')
229
-			->willReturnCallback(function ($str) {
230
-				return $str;
231
-			});
232
-
233
-		$expected = new DataResponse(
234
-			[
235
-				'data'
236
-					=> [
237
-						'message' => 'Invalid key',
238
-					],
239
-				'status' => 'failure',
240
-			],
241
-			Http::STATUS_BAD_REQUEST
242
-		);
243
-
244
-		$this->assertEquals($expected, $this->themingController->uploadImage());
245
-	}
246
-
247
-	/**
248
-	 * Checks that trying to upload an SVG favicon without imagemagick
249
-	 * results in an unsupported media type response.
250
-	 */
251
-	public function testUploadSVGFaviconWithoutImagemagick(): void {
252
-		$this->imageManager
253
-			->method('shouldReplaceIcons')
254
-			->willReturn(false);
255
-
256
-		$this->request
257
-			->expects($this->once())
258
-			->method('getParam')
259
-			->with('key')
260
-			->willReturn('favicon');
261
-		$this->request
262
-			->expects($this->once())
263
-			->method('getUploadedFile')
264
-			->with('image')
265
-			->willReturn([
266
-				'tmp_name' => __DIR__ . '/../../../../tests/data/testimagelarge.svg',
267
-				'type' => 'image/svg',
268
-				'name' => 'testimagelarge.svg',
269
-				'error' => 0,
270
-			]);
271
-		$this->l10n
272
-			->expects($this->any())
273
-			->method('t')
274
-			->willReturnCallback(function ($str) {
275
-				return $str;
276
-			});
277
-
278
-		$this->imageManager->expects($this->once())
279
-			->method('updateImage')
280
-			->willThrowException(new \Exception('Unsupported image type'));
281
-
282
-		$expected = new DataResponse(
283
-			[
284
-				'data'
285
-					=> [
286
-						'message' => 'Unsupported image type',
287
-					],
288
-				'status' => 'failure'
289
-			],
290
-			Http::STATUS_UNPROCESSABLE_ENTITY
291
-		);
292
-
293
-		$this->assertEquals($expected, $this->themingController->uploadImage());
294
-	}
295
-
296
-	public function testUpdateLogoInvalidMimeType(): void {
297
-		$this->request
298
-			->expects($this->once())
299
-			->method('getParam')
300
-			->with('key')
301
-			->willReturn('logo');
302
-		$this->request
303
-			->expects($this->once())
304
-			->method('getUploadedFile')
305
-			->with('image')
306
-			->willReturn([
307
-				'tmp_name' => __DIR__ . '/../../../../tests/data/lorem.txt',
308
-				'type' => 'application/pdf',
309
-				'name' => 'logo.pdf',
310
-				'error' => 0,
311
-			]);
312
-		$this->l10n
313
-			->expects($this->any())
314
-			->method('t')
315
-			->willReturnCallback(function ($str) {
316
-				return $str;
317
-			});
318
-
319
-		$this->imageManager->expects($this->once())
320
-			->method('updateImage')
321
-			->willThrowException(new \Exception('Unsupported image type'));
322
-
323
-		$expected = new DataResponse(
324
-			[
325
-				'data'
326
-					=> [
327
-						'message' => 'Unsupported image type',
328
-					],
329
-				'status' => 'failure'
330
-			],
331
-			Http::STATUS_UNPROCESSABLE_ENTITY
332
-		);
333
-
334
-		$this->assertEquals($expected, $this->themingController->uploadImage());
335
-	}
336
-
337
-	public static function dataUpdateImages(): array {
338
-		return [
339
-			['image/jpeg', false],
340
-			['image/jpeg', true],
341
-			['image/gif'],
342
-			['image/png'],
343
-			['image/svg+xml'],
344
-			['image/svg']
345
-		];
346
-	}
347
-
348
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateImages')]
349
-	public function testUpdateLogoNormalLogoUpload(string $mimeType, bool $folderExists = true): void {
350
-		$tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder() . '/logo.svg';
351
-		$destination = Server::get(ITempManager::class)->getTemporaryFolder();
352
-
353
-		touch($tmpLogo);
354
-		copy(__DIR__ . '/../../../../tests/data/testimage.png', $tmpLogo);
355
-		$this->request
356
-			->expects($this->once())
357
-			->method('getParam')
358
-			->with('key')
359
-			->willReturn('logo');
360
-		$this->request
361
-			->expects($this->once())
362
-			->method('getUploadedFile')
363
-			->with('image')
364
-			->willReturn([
365
-				'tmp_name' => $tmpLogo,
366
-				'type' => $mimeType,
367
-				'name' => 'logo.svg',
368
-				'error' => 0,
369
-			]);
370
-		$this->l10n
371
-			->expects($this->any())
372
-			->method('t')
373
-			->willReturnCallback(function ($str) {
374
-				return $str;
375
-			});
376
-
377
-		$this->imageManager->expects($this->once())
378
-			->method('getImageUrl')
379
-			->with('logo')
380
-			->willReturn('imageUrl');
381
-
382
-		$this->imageManager->expects($this->once())
383
-			->method('updateImage');
384
-
385
-		$expected = new DataResponse(
386
-			[
387
-				'data'
388
-					=> [
389
-						'name' => 'logo.svg',
390
-						'message' => 'Saved',
391
-						'url' => 'imageUrl',
392
-					],
393
-				'status' => 'success'
394
-			]
395
-		);
396
-
397
-		$this->assertEquals($expected, $this->themingController->uploadImage());
398
-	}
399
-
400
-	public function testUpdateLogoLoginScreenUpload(): void {
401
-		$tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder() . 'logo.png';
402
-
403
-		touch($tmpLogo);
404
-		copy(__DIR__ . '/../../../../tests/data/desktopapp.png', $tmpLogo);
405
-		$this->request
406
-			->expects($this->once())
407
-			->method('getParam')
408
-			->with('key')
409
-			->willReturn('background');
410
-		$this->request
411
-			->expects($this->once())
412
-			->method('getUploadedFile')
413
-			->with('image')
414
-			->willReturn([
415
-				'tmp_name' => $tmpLogo,
416
-				'type' => 'image/svg+xml',
417
-				'name' => 'logo.svg',
418
-				'error' => 0,
419
-			]);
420
-		$this->l10n
421
-			->expects($this->any())
422
-			->method('t')
423
-			->willReturnCallback(function ($str) {
424
-				return $str;
425
-			});
426
-
427
-		$this->imageManager->expects($this->once())
428
-			->method('updateImage');
429
-
430
-		$this->imageManager->expects($this->once())
431
-			->method('getImageUrl')
432
-			->with('background')
433
-			->willReturn('imageUrl');
434
-		$expected = new DataResponse(
435
-			[
436
-				'data'
437
-					=> [
438
-						'name' => 'logo.svg',
439
-						'message' => 'Saved',
440
-						'url' => 'imageUrl',
441
-					],
442
-				'status' => 'success'
443
-			]
444
-		);
445
-		$this->assertEquals($expected, $this->themingController->uploadImage());
446
-	}
447
-
448
-	public function testUpdateLogoLoginScreenUploadWithInvalidImage(): void {
449
-		$tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder() . '/logo.svg';
450
-
451
-		touch($tmpLogo);
452
-		file_put_contents($tmpLogo, file_get_contents(__DIR__ . '/../../../../tests/data/data.zip'));
453
-		$this->request
454
-			->expects($this->once())
455
-			->method('getParam')
456
-			->with('key')
457
-			->willReturn('logo');
458
-		$this->request
459
-			->expects($this->once())
460
-			->method('getUploadedFile')
461
-			->with('image')
462
-			->willReturn([
463
-				'tmp_name' => $tmpLogo,
464
-				'type' => 'foobar',
465
-				'name' => 'logo.svg',
466
-				'error' => 0,
467
-			]);
468
-		$this->l10n
469
-			->expects($this->any())
470
-			->method('t')
471
-			->willReturnCallback(function ($str) {
472
-				return $str;
473
-			});
474
-
475
-		$this->imageManager->expects($this->once())
476
-			->method('updateImage')
477
-			->willThrowException(new \Exception('Unsupported image type'));
478
-
479
-		$expected = new DataResponse(
480
-			[
481
-				'data'
482
-					=> [
483
-						'message' => 'Unsupported image type',
484
-					],
485
-				'status' => 'failure'
486
-			],
487
-			Http::STATUS_UNPROCESSABLE_ENTITY
488
-		);
489
-		$this->assertEquals($expected, $this->themingController->uploadImage());
490
-	}
491
-
492
-	public static function dataPhpUploadErrors(): array {
493
-		return [
494
-			[UPLOAD_ERR_INI_SIZE, 'The uploaded file exceeds the upload_max_filesize directive in php.ini'],
495
-			[UPLOAD_ERR_FORM_SIZE, 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'],
496
-			[UPLOAD_ERR_PARTIAL, 'The file was only partially uploaded'],
497
-			[UPLOAD_ERR_NO_FILE, 'No file was uploaded'],
498
-			[UPLOAD_ERR_NO_TMP_DIR, 'Missing a temporary folder'],
499
-			[UPLOAD_ERR_CANT_WRITE, 'Could not write file to disk'],
500
-			[UPLOAD_ERR_EXTENSION, 'A PHP extension stopped the file upload'],
501
-		];
502
-	}
503
-
504
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataPhpUploadErrors')]
505
-	public function testUpdateLogoLoginScreenUploadWithInvalidImageUpload(int $error, string $expectedErrorMessage): void {
506
-		$this->request
507
-			->expects($this->once())
508
-			->method('getParam')
509
-			->with('key')
510
-			->willReturn('background');
511
-		$this->request
512
-			->expects($this->once())
513
-			->method('getUploadedFile')
514
-			->with('image')
515
-			->willReturn([
516
-				'tmp_name' => '',
517
-				'type' => 'image/svg+xml',
518
-				'name' => 'logo.svg',
519
-				'error' => $error,
520
-			]);
521
-		$this->l10n
522
-			->expects($this->any())
523
-			->method('t')
524
-			->willReturnCallback(function ($str) {
525
-				return $str;
526
-			});
527
-
528
-		$expected = new DataResponse(
529
-			[
530
-				'data'
531
-					=> [
532
-						'message' => $expectedErrorMessage,
533
-					],
534
-				'status' => 'failure'
535
-			],
536
-			Http::STATUS_UNPROCESSABLE_ENTITY
537
-		);
538
-		$this->assertEquals($expected, $this->themingController->uploadImage());
539
-	}
540
-
541
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataPhpUploadErrors')]
542
-	public function testUpdateLogoUploadWithInvalidImageUpload($error, $expectedErrorMessage): void {
543
-		$this->request
544
-			->expects($this->once())
545
-			->method('getParam')
546
-			->with('key')
547
-			->willReturn('background');
548
-		$this->request
549
-			->expects($this->once())
550
-			->method('getUploadedFile')
551
-			->with('image')
552
-			->willReturn([
553
-				'tmp_name' => '',
554
-				'type' => 'text/svg',
555
-				'name' => 'logo.svg',
556
-				'error' => $error,
557
-			]);
558
-		$this->l10n
559
-			->expects($this->any())
560
-			->method('t')
561
-			->willReturnCallback(function ($str) {
562
-				return $str;
563
-			});
564
-
565
-		$expected = new DataResponse(
566
-			[
567
-				'data'
568
-					=> [
569
-						'message' => $expectedErrorMessage
570
-					],
571
-				'status' => 'failure'
572
-			],
573
-			Http::STATUS_UNPROCESSABLE_ENTITY
574
-		);
575
-		$this->assertEquals($expected, $this->themingController->uploadImage());
576
-	}
577
-
578
-	public function testUndo(): void {
579
-		$this->l10n
580
-			->expects($this->once())
581
-			->method('t')
582
-			->with('Saved')
583
-			->willReturn('Saved');
584
-		$this->themingDefaults
585
-			->expects($this->once())
586
-			->method('undo')
587
-			->with('MySetting')
588
-			->willReturn('MyValue');
589
-
590
-		$expected = new DataResponse(
591
-			[
592
-				'data'
593
-					=> [
594
-						'value' => 'MyValue',
595
-						'message' => 'Saved'
596
-					],
597
-				'status' => 'success'
598
-			]
599
-		);
600
-		$this->assertEquals($expected, $this->themingController->undo('MySetting'));
601
-	}
602
-
603
-	public static function dataUndoDelete(): array {
604
-		return [
605
-			[ 'backgroundMime', 'background' ],
606
-			[ 'logoMime', 'logo' ]
607
-		];
608
-	}
609
-
610
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataUndoDelete')]
611
-	public function testUndoDelete(string $value, string $filename): void {
612
-		$this->l10n
613
-			->expects($this->once())
614
-			->method('t')
615
-			->with('Saved')
616
-			->willReturn('Saved');
617
-		$this->themingDefaults
618
-			->expects($this->once())
619
-			->method('undo')
620
-			->with($value)
621
-			->willReturn($value);
622
-
623
-		$expected = new DataResponse(
624
-			[
625
-				'data'
626
-					=> [
627
-						'value' => $value,
628
-						'message' => 'Saved',
629
-					],
630
-				'status' => 'success'
631
-			]
632
-		);
633
-		$this->assertEquals($expected, $this->themingController->undo($value));
634
-	}
635
-
636
-
637
-
638
-	public function testGetLogoNotExistent(): void {
639
-		$this->imageManager->method('getImage')
640
-			->with($this->equalTo('logo'))
641
-			->willThrowException(new NotFoundException());
642
-
643
-		$expected = new NotFoundResponse();
644
-		$this->assertEquals($expected, $this->themingController->getImage('logo'));
645
-	}
646
-
647
-	public function testGetLogo(): void {
648
-		$file = $this->createMock(ISimpleFile::class);
649
-		$file->method('getName')->willReturn('logo.svg');
650
-		$file->method('getMTime')->willReturn(42);
651
-		$this->imageManager->expects($this->once())
652
-			->method('getImage')
653
-			->willReturn($file);
654
-		$this->config
655
-			->expects($this->any())
656
-			->method('getAppValue')
657
-			->with('theming', 'logoMime', '')
658
-			->willReturn('text/svg');
659
-
660
-		@$expected = new FileDisplayResponse($file);
661
-		$expected->cacheFor(3600);
662
-		$expected->addHeader('Content-Type', 'text/svg');
663
-		$expected->addHeader('Content-Disposition', 'attachment; filename="logo"');
664
-		$csp = new ContentSecurityPolicy();
665
-		$csp->allowInlineStyle();
666
-		$expected->setContentSecurityPolicy($csp);
667
-		@$this->assertEquals($expected, $this->themingController->getImage('logo'));
668
-	}
669
-
670
-
671
-	public function testGetLoginBackgroundNotExistent(): void {
672
-		$this->imageManager->method('getImage')
673
-			->with($this->equalTo('background'))
674
-			->willThrowException(new NotFoundException());
675
-		$expected = new NotFoundResponse();
676
-		$this->assertEquals($expected, $this->themingController->getImage('background'));
677
-	}
678
-
679
-	public function testGetLoginBackground(): void {
680
-		$file = $this->createMock(ISimpleFile::class);
681
-		$file->method('getName')->willReturn('background.png');
682
-		$file->method('getMTime')->willReturn(42);
683
-		$this->imageManager->expects($this->once())
684
-			->method('getImage')
685
-			->willReturn($file);
686
-
687
-		$this->config
688
-			->expects($this->any())
689
-			->method('getAppValue')
690
-			->with('theming', 'backgroundMime', '')
691
-			->willReturn('image/png');
692
-
693
-		@$expected = new FileDisplayResponse($file);
694
-		$expected->cacheFor(3600);
695
-		$expected->addHeader('Content-Type', 'image/png');
696
-		$expected->addHeader('Content-Disposition', 'attachment; filename="background"');
697
-		$csp = new ContentSecurityPolicy();
698
-		$csp->allowInlineStyle();
699
-		$expected->setContentSecurityPolicy($csp);
700
-		@$this->assertEquals($expected, $this->themingController->getImage('background'));
701
-	}
702
-
703
-	public static function dataGetManifest(): array {
704
-		return [
705
-			[true],
706
-			[false],
707
-		];
708
-	}
709
-
710
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataGetManifest')]
711
-	public function testGetManifest(bool $standalone): void {
712
-		$this->config
713
-			->expects($this->once())
714
-			->method('getAppValue')
715
-			->with('theming', 'cachebuster', '0')
716
-			->willReturn('0');
717
-		$this->themingDefaults
718
-			->expects($this->any())
719
-			->method('getName')
720
-			->willReturn('Nextcloud');
721
-		$this->urlGenerator
722
-			->expects($this->once())
723
-			->method('getBaseUrl')
724
-			->willReturn('localhost');
725
-		$this->urlGenerator
726
-			->expects($this->exactly(2))
727
-			->method('linkToRoute')
728
-			->willReturnMap([
729
-				['theming.Icon.getTouchIcon', ['app' => 'core'], 'touchicon'],
730
-				['theming.Icon.getFavicon', ['app' => 'core'], 'favicon'],
731
-			]);
732
-		$this->config
733
-			->expects($this->exactly(2))
734
-			->method('getSystemValueBool')
735
-			->with('theming.standalone_window.enabled', true)
736
-			->willReturn($standalone);
737
-		$response = new JSONResponse([
738
-			'name' => 'Nextcloud',
739
-			'start_url' => 'localhost',
740
-			'icons'
741
-				=> [
742
-					[
743
-						'src' => 'touchicon?v=0',
744
-						'type' => 'image/png',
745
-						'sizes' => '512x512'
746
-					],
747
-					[
748
-						'src' => 'favicon?v=0',
749
-						'type' => 'image/svg+xml',
750
-						'sizes' => '16x16'
751
-					]
752
-				],
753
-			'display_override' => [$standalone ? 'minimal-ui' : ''],
754
-			'display' => $standalone ? 'standalone' : 'browser',
755
-			'short_name' => 'Nextcloud',
756
-			'theme_color' => null,
757
-			'background_color' => null,
758
-			'description' => null
759
-		]);
760
-		$response->cacheFor(3600);
761
-		$this->assertEquals($response, $this->themingController->getManifest('core'));
762
-	}
38
+    private IRequest&MockObject $request;
39
+    private IConfig&MockObject $config;
40
+    private IAppConfig&MockObject $appConfig;
41
+    private ThemingDefaults&MockObject $themingDefaults;
42
+    private IL10N&MockObject $l10n;
43
+    private IAppManager&MockObject $appManager;
44
+    private ImageManager&MockObject $imageManager;
45
+    private IURLGenerator&MockObject $urlGenerator;
46
+    private ThemesService&MockObject $themesService;
47
+    private INavigationManager&MockObject $navigationManager;
48
+
49
+    private ThemingController $themingController;
50
+
51
+    protected function setUp(): void {
52
+        $this->request = $this->createMock(IRequest::class);
53
+        $this->config = $this->createMock(IConfig::class);
54
+        $this->appConfig = $this->createMock(IAppConfig::class);
55
+        $this->themingDefaults = $this->createMock(ThemingDefaults::class);
56
+        $this->l10n = $this->createMock(L10N::class);
57
+        $this->appManager = $this->createMock(IAppManager::class);
58
+        $this->urlGenerator = $this->createMock(IURLGenerator::class);
59
+        $this->imageManager = $this->createMock(ImageManager::class);
60
+        $this->themesService = $this->createMock(ThemesService::class);
61
+        $this->navigationManager = $this->createMock(INavigationManager::class);
62
+
63
+        $timeFactory = $this->createMock(ITimeFactory::class);
64
+        $timeFactory->expects($this->any())
65
+            ->method('getTime')
66
+            ->willReturn(123);
67
+
68
+        $this->overwriteService(ITimeFactory::class, $timeFactory);
69
+
70
+        $this->themingController = new ThemingController(
71
+            'theming',
72
+            $this->request,
73
+            $this->config,
74
+            $this->appConfig,
75
+            $this->themingDefaults,
76
+            $this->l10n,
77
+            $this->urlGenerator,
78
+            $this->appManager,
79
+            $this->imageManager,
80
+            $this->themesService,
81
+            $this->navigationManager,
82
+        );
83
+
84
+        parent::setUp();
85
+    }
86
+
87
+    public static function dataUpdateStylesheetSuccess(): array {
88
+        return [
89
+            ['name', str_repeat('a', 250), 'Saved'],
90
+            ['url', 'https://nextcloud.com/' . str_repeat('a', 478), 'Saved'],
91
+            ['slogan', str_repeat('a', 500), 'Saved'],
92
+            ['primaryColor', '#0082c9', 'Saved', 'primary_color'],
93
+            ['primary_color', '#0082C9', 'Saved'],
94
+            ['backgroundColor', '#0082C9', 'Saved', 'background_color'],
95
+            ['background_color', '#0082C9', 'Saved'],
96
+            ['imprintUrl', 'https://nextcloud.com/' . str_repeat('a', 478), 'Saved'],
97
+            ['privacyUrl', 'https://nextcloud.com/' . str_repeat('a', 478), 'Saved'],
98
+        ];
99
+    }
100
+
101
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateStylesheetSuccess')]
102
+    public function testUpdateStylesheetSuccess(string $setting, string $value, string $message, ?string $realSetting = null): void {
103
+        $this->themingDefaults
104
+            ->expects($this->once())
105
+            ->method('set')
106
+            ->with($realSetting ?? $setting, $value);
107
+        $this->l10n
108
+            ->expects($this->once())
109
+            ->method('t')
110
+            ->willReturnCallback(function ($str) {
111
+                return $str;
112
+            });
113
+
114
+        $expected = new DataResponse(
115
+            [
116
+                'data'
117
+                    => [
118
+                        'message' => $message,
119
+                    ],
120
+                'status' => 'success',
121
+            ]
122
+        );
123
+        $this->assertEquals($expected, $this->themingController->updateStylesheet($setting, $value));
124
+    }
125
+
126
+    public static function dataUpdateStylesheetError(): array {
127
+        $urls = [
128
+            'url' => 'web address',
129
+            'imprintUrl' => 'legal notice address',
130
+            'privacyUrl' => 'privacy policy address',
131
+        ];
132
+
133
+        $urlTests = [];
134
+        foreach ($urls as $urlKey => $urlName) {
135
+            // Check length limit
136
+            $urlTests[] = [$urlKey, 'http://example.com/' . str_repeat('a', 501), "The given {$urlName} is too long"];
137
+            // Check potential evil javascript
138
+            $urlTests[] = [$urlKey, 'javascript:alert(1)', "The given {$urlName} is not a valid URL"];
139
+            // Check XSS
140
+            $urlTests[] = [$urlKey, 'https://example.com/"><script/src="alert(\'1\')"><a/href/="', "The given {$urlName} is not a valid URL"];
141
+        }
142
+
143
+        return [
144
+            ['name', str_repeat('a', 251), 'The given name is too long'],
145
+            ['slogan', str_repeat('a', 501), 'The given slogan is too long'],
146
+            ['primary_color', '0082C9', 'The given color is invalid'],
147
+            ['primary_color', '#0082Z9', 'The given color is invalid'],
148
+            ['primary_color', 'Nextcloud', 'The given color is invalid'],
149
+            ['background_color', '0082C9', 'The given color is invalid'],
150
+            ['background_color', '#0082Z9', 'The given color is invalid'],
151
+            ['background_color', 'Nextcloud', 'The given color is invalid'],
152
+
153
+            ['doesnotexist', 'value', 'Invalid setting key'],
154
+
155
+            ...$urlTests,
156
+        ];
157
+    }
158
+
159
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateStylesheetError')]
160
+    public function testUpdateStylesheetError(string $setting, string $value, string $message): void {
161
+        $this->themingDefaults
162
+            ->expects($this->never())
163
+            ->method('set')
164
+            ->with($setting, $value);
165
+        $this->l10n
166
+            ->expects($this->any())
167
+            ->method('t')
168
+            ->willReturnCallback(function ($str) {
169
+                return $str;
170
+            });
171
+
172
+        $expected = new DataResponse(
173
+            [
174
+                'data'
175
+                    => [
176
+                        'message' => $message,
177
+                    ],
178
+                'status' => 'error',
179
+            ],
180
+            Http::STATUS_BAD_REQUEST
181
+        );
182
+        $this->assertEquals($expected, $this->themingController->updateStylesheet($setting, $value));
183
+    }
184
+
185
+    public function testUpdateLogoNoData(): void {
186
+        $this->request
187
+            ->expects($this->once())
188
+            ->method('getParam')
189
+            ->with('key')
190
+            ->willReturn('logo');
191
+        $this->request
192
+            ->expects($this->once())
193
+            ->method('getUploadedFile')
194
+            ->with('image')
195
+            ->willReturn(null);
196
+        $this->l10n
197
+            ->expects($this->any())
198
+            ->method('t')
199
+            ->willReturnCallback(function ($str) {
200
+                return $str;
201
+            });
202
+
203
+        $expected = new DataResponse(
204
+            [
205
+                'data'
206
+                    => [
207
+                        'message' => 'No file uploaded',
208
+                    ],
209
+                'status' => 'failure',
210
+            ],
211
+            Http::STATUS_UNPROCESSABLE_ENTITY
212
+        );
213
+
214
+        $this->assertEquals($expected, $this->themingController->uploadImage());
215
+    }
216
+
217
+    public function testUploadInvalidUploadKey(): void {
218
+        $this->request
219
+            ->expects($this->once())
220
+            ->method('getParam')
221
+            ->with('key')
222
+            ->willReturn('invalid');
223
+        $this->request
224
+            ->expects($this->never())
225
+            ->method('getUploadedFile');
226
+        $this->l10n
227
+            ->expects($this->any())
228
+            ->method('t')
229
+            ->willReturnCallback(function ($str) {
230
+                return $str;
231
+            });
232
+
233
+        $expected = new DataResponse(
234
+            [
235
+                'data'
236
+                    => [
237
+                        'message' => 'Invalid key',
238
+                    ],
239
+                'status' => 'failure',
240
+            ],
241
+            Http::STATUS_BAD_REQUEST
242
+        );
243
+
244
+        $this->assertEquals($expected, $this->themingController->uploadImage());
245
+    }
246
+
247
+    /**
248
+     * Checks that trying to upload an SVG favicon without imagemagick
249
+     * results in an unsupported media type response.
250
+     */
251
+    public function testUploadSVGFaviconWithoutImagemagick(): void {
252
+        $this->imageManager
253
+            ->method('shouldReplaceIcons')
254
+            ->willReturn(false);
255
+
256
+        $this->request
257
+            ->expects($this->once())
258
+            ->method('getParam')
259
+            ->with('key')
260
+            ->willReturn('favicon');
261
+        $this->request
262
+            ->expects($this->once())
263
+            ->method('getUploadedFile')
264
+            ->with('image')
265
+            ->willReturn([
266
+                'tmp_name' => __DIR__ . '/../../../../tests/data/testimagelarge.svg',
267
+                'type' => 'image/svg',
268
+                'name' => 'testimagelarge.svg',
269
+                'error' => 0,
270
+            ]);
271
+        $this->l10n
272
+            ->expects($this->any())
273
+            ->method('t')
274
+            ->willReturnCallback(function ($str) {
275
+                return $str;
276
+            });
277
+
278
+        $this->imageManager->expects($this->once())
279
+            ->method('updateImage')
280
+            ->willThrowException(new \Exception('Unsupported image type'));
281
+
282
+        $expected = new DataResponse(
283
+            [
284
+                'data'
285
+                    => [
286
+                        'message' => 'Unsupported image type',
287
+                    ],
288
+                'status' => 'failure'
289
+            ],
290
+            Http::STATUS_UNPROCESSABLE_ENTITY
291
+        );
292
+
293
+        $this->assertEquals($expected, $this->themingController->uploadImage());
294
+    }
295
+
296
+    public function testUpdateLogoInvalidMimeType(): void {
297
+        $this->request
298
+            ->expects($this->once())
299
+            ->method('getParam')
300
+            ->with('key')
301
+            ->willReturn('logo');
302
+        $this->request
303
+            ->expects($this->once())
304
+            ->method('getUploadedFile')
305
+            ->with('image')
306
+            ->willReturn([
307
+                'tmp_name' => __DIR__ . '/../../../../tests/data/lorem.txt',
308
+                'type' => 'application/pdf',
309
+                'name' => 'logo.pdf',
310
+                'error' => 0,
311
+            ]);
312
+        $this->l10n
313
+            ->expects($this->any())
314
+            ->method('t')
315
+            ->willReturnCallback(function ($str) {
316
+                return $str;
317
+            });
318
+
319
+        $this->imageManager->expects($this->once())
320
+            ->method('updateImage')
321
+            ->willThrowException(new \Exception('Unsupported image type'));
322
+
323
+        $expected = new DataResponse(
324
+            [
325
+                'data'
326
+                    => [
327
+                        'message' => 'Unsupported image type',
328
+                    ],
329
+                'status' => 'failure'
330
+            ],
331
+            Http::STATUS_UNPROCESSABLE_ENTITY
332
+        );
333
+
334
+        $this->assertEquals($expected, $this->themingController->uploadImage());
335
+    }
336
+
337
+    public static function dataUpdateImages(): array {
338
+        return [
339
+            ['image/jpeg', false],
340
+            ['image/jpeg', true],
341
+            ['image/gif'],
342
+            ['image/png'],
343
+            ['image/svg+xml'],
344
+            ['image/svg']
345
+        ];
346
+    }
347
+
348
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateImages')]
349
+    public function testUpdateLogoNormalLogoUpload(string $mimeType, bool $folderExists = true): void {
350
+        $tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder() . '/logo.svg';
351
+        $destination = Server::get(ITempManager::class)->getTemporaryFolder();
352
+
353
+        touch($tmpLogo);
354
+        copy(__DIR__ . '/../../../../tests/data/testimage.png', $tmpLogo);
355
+        $this->request
356
+            ->expects($this->once())
357
+            ->method('getParam')
358
+            ->with('key')
359
+            ->willReturn('logo');
360
+        $this->request
361
+            ->expects($this->once())
362
+            ->method('getUploadedFile')
363
+            ->with('image')
364
+            ->willReturn([
365
+                'tmp_name' => $tmpLogo,
366
+                'type' => $mimeType,
367
+                'name' => 'logo.svg',
368
+                'error' => 0,
369
+            ]);
370
+        $this->l10n
371
+            ->expects($this->any())
372
+            ->method('t')
373
+            ->willReturnCallback(function ($str) {
374
+                return $str;
375
+            });
376
+
377
+        $this->imageManager->expects($this->once())
378
+            ->method('getImageUrl')
379
+            ->with('logo')
380
+            ->willReturn('imageUrl');
381
+
382
+        $this->imageManager->expects($this->once())
383
+            ->method('updateImage');
384
+
385
+        $expected = new DataResponse(
386
+            [
387
+                'data'
388
+                    => [
389
+                        'name' => 'logo.svg',
390
+                        'message' => 'Saved',
391
+                        'url' => 'imageUrl',
392
+                    ],
393
+                'status' => 'success'
394
+            ]
395
+        );
396
+
397
+        $this->assertEquals($expected, $this->themingController->uploadImage());
398
+    }
399
+
400
+    public function testUpdateLogoLoginScreenUpload(): void {
401
+        $tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder() . 'logo.png';
402
+
403
+        touch($tmpLogo);
404
+        copy(__DIR__ . '/../../../../tests/data/desktopapp.png', $tmpLogo);
405
+        $this->request
406
+            ->expects($this->once())
407
+            ->method('getParam')
408
+            ->with('key')
409
+            ->willReturn('background');
410
+        $this->request
411
+            ->expects($this->once())
412
+            ->method('getUploadedFile')
413
+            ->with('image')
414
+            ->willReturn([
415
+                'tmp_name' => $tmpLogo,
416
+                'type' => 'image/svg+xml',
417
+                'name' => 'logo.svg',
418
+                'error' => 0,
419
+            ]);
420
+        $this->l10n
421
+            ->expects($this->any())
422
+            ->method('t')
423
+            ->willReturnCallback(function ($str) {
424
+                return $str;
425
+            });
426
+
427
+        $this->imageManager->expects($this->once())
428
+            ->method('updateImage');
429
+
430
+        $this->imageManager->expects($this->once())
431
+            ->method('getImageUrl')
432
+            ->with('background')
433
+            ->willReturn('imageUrl');
434
+        $expected = new DataResponse(
435
+            [
436
+                'data'
437
+                    => [
438
+                        'name' => 'logo.svg',
439
+                        'message' => 'Saved',
440
+                        'url' => 'imageUrl',
441
+                    ],
442
+                'status' => 'success'
443
+            ]
444
+        );
445
+        $this->assertEquals($expected, $this->themingController->uploadImage());
446
+    }
447
+
448
+    public function testUpdateLogoLoginScreenUploadWithInvalidImage(): void {
449
+        $tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder() . '/logo.svg';
450
+
451
+        touch($tmpLogo);
452
+        file_put_contents($tmpLogo, file_get_contents(__DIR__ . '/../../../../tests/data/data.zip'));
453
+        $this->request
454
+            ->expects($this->once())
455
+            ->method('getParam')
456
+            ->with('key')
457
+            ->willReturn('logo');
458
+        $this->request
459
+            ->expects($this->once())
460
+            ->method('getUploadedFile')
461
+            ->with('image')
462
+            ->willReturn([
463
+                'tmp_name' => $tmpLogo,
464
+                'type' => 'foobar',
465
+                'name' => 'logo.svg',
466
+                'error' => 0,
467
+            ]);
468
+        $this->l10n
469
+            ->expects($this->any())
470
+            ->method('t')
471
+            ->willReturnCallback(function ($str) {
472
+                return $str;
473
+            });
474
+
475
+        $this->imageManager->expects($this->once())
476
+            ->method('updateImage')
477
+            ->willThrowException(new \Exception('Unsupported image type'));
478
+
479
+        $expected = new DataResponse(
480
+            [
481
+                'data'
482
+                    => [
483
+                        'message' => 'Unsupported image type',
484
+                    ],
485
+                'status' => 'failure'
486
+            ],
487
+            Http::STATUS_UNPROCESSABLE_ENTITY
488
+        );
489
+        $this->assertEquals($expected, $this->themingController->uploadImage());
490
+    }
491
+
492
+    public static function dataPhpUploadErrors(): array {
493
+        return [
494
+            [UPLOAD_ERR_INI_SIZE, 'The uploaded file exceeds the upload_max_filesize directive in php.ini'],
495
+            [UPLOAD_ERR_FORM_SIZE, 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'],
496
+            [UPLOAD_ERR_PARTIAL, 'The file was only partially uploaded'],
497
+            [UPLOAD_ERR_NO_FILE, 'No file was uploaded'],
498
+            [UPLOAD_ERR_NO_TMP_DIR, 'Missing a temporary folder'],
499
+            [UPLOAD_ERR_CANT_WRITE, 'Could not write file to disk'],
500
+            [UPLOAD_ERR_EXTENSION, 'A PHP extension stopped the file upload'],
501
+        ];
502
+    }
503
+
504
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataPhpUploadErrors')]
505
+    public function testUpdateLogoLoginScreenUploadWithInvalidImageUpload(int $error, string $expectedErrorMessage): void {
506
+        $this->request
507
+            ->expects($this->once())
508
+            ->method('getParam')
509
+            ->with('key')
510
+            ->willReturn('background');
511
+        $this->request
512
+            ->expects($this->once())
513
+            ->method('getUploadedFile')
514
+            ->with('image')
515
+            ->willReturn([
516
+                'tmp_name' => '',
517
+                'type' => 'image/svg+xml',
518
+                'name' => 'logo.svg',
519
+                'error' => $error,
520
+            ]);
521
+        $this->l10n
522
+            ->expects($this->any())
523
+            ->method('t')
524
+            ->willReturnCallback(function ($str) {
525
+                return $str;
526
+            });
527
+
528
+        $expected = new DataResponse(
529
+            [
530
+                'data'
531
+                    => [
532
+                        'message' => $expectedErrorMessage,
533
+                    ],
534
+                'status' => 'failure'
535
+            ],
536
+            Http::STATUS_UNPROCESSABLE_ENTITY
537
+        );
538
+        $this->assertEquals($expected, $this->themingController->uploadImage());
539
+    }
540
+
541
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataPhpUploadErrors')]
542
+    public function testUpdateLogoUploadWithInvalidImageUpload($error, $expectedErrorMessage): void {
543
+        $this->request
544
+            ->expects($this->once())
545
+            ->method('getParam')
546
+            ->with('key')
547
+            ->willReturn('background');
548
+        $this->request
549
+            ->expects($this->once())
550
+            ->method('getUploadedFile')
551
+            ->with('image')
552
+            ->willReturn([
553
+                'tmp_name' => '',
554
+                'type' => 'text/svg',
555
+                'name' => 'logo.svg',
556
+                'error' => $error,
557
+            ]);
558
+        $this->l10n
559
+            ->expects($this->any())
560
+            ->method('t')
561
+            ->willReturnCallback(function ($str) {
562
+                return $str;
563
+            });
564
+
565
+        $expected = new DataResponse(
566
+            [
567
+                'data'
568
+                    => [
569
+                        'message' => $expectedErrorMessage
570
+                    ],
571
+                'status' => 'failure'
572
+            ],
573
+            Http::STATUS_UNPROCESSABLE_ENTITY
574
+        );
575
+        $this->assertEquals($expected, $this->themingController->uploadImage());
576
+    }
577
+
578
+    public function testUndo(): void {
579
+        $this->l10n
580
+            ->expects($this->once())
581
+            ->method('t')
582
+            ->with('Saved')
583
+            ->willReturn('Saved');
584
+        $this->themingDefaults
585
+            ->expects($this->once())
586
+            ->method('undo')
587
+            ->with('MySetting')
588
+            ->willReturn('MyValue');
589
+
590
+        $expected = new DataResponse(
591
+            [
592
+                'data'
593
+                    => [
594
+                        'value' => 'MyValue',
595
+                        'message' => 'Saved'
596
+                    ],
597
+                'status' => 'success'
598
+            ]
599
+        );
600
+        $this->assertEquals($expected, $this->themingController->undo('MySetting'));
601
+    }
602
+
603
+    public static function dataUndoDelete(): array {
604
+        return [
605
+            [ 'backgroundMime', 'background' ],
606
+            [ 'logoMime', 'logo' ]
607
+        ];
608
+    }
609
+
610
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataUndoDelete')]
611
+    public function testUndoDelete(string $value, string $filename): void {
612
+        $this->l10n
613
+            ->expects($this->once())
614
+            ->method('t')
615
+            ->with('Saved')
616
+            ->willReturn('Saved');
617
+        $this->themingDefaults
618
+            ->expects($this->once())
619
+            ->method('undo')
620
+            ->with($value)
621
+            ->willReturn($value);
622
+
623
+        $expected = new DataResponse(
624
+            [
625
+                'data'
626
+                    => [
627
+                        'value' => $value,
628
+                        'message' => 'Saved',
629
+                    ],
630
+                'status' => 'success'
631
+            ]
632
+        );
633
+        $this->assertEquals($expected, $this->themingController->undo($value));
634
+    }
635
+
636
+
637
+
638
+    public function testGetLogoNotExistent(): void {
639
+        $this->imageManager->method('getImage')
640
+            ->with($this->equalTo('logo'))
641
+            ->willThrowException(new NotFoundException());
642
+
643
+        $expected = new NotFoundResponse();
644
+        $this->assertEquals($expected, $this->themingController->getImage('logo'));
645
+    }
646
+
647
+    public function testGetLogo(): void {
648
+        $file = $this->createMock(ISimpleFile::class);
649
+        $file->method('getName')->willReturn('logo.svg');
650
+        $file->method('getMTime')->willReturn(42);
651
+        $this->imageManager->expects($this->once())
652
+            ->method('getImage')
653
+            ->willReturn($file);
654
+        $this->config
655
+            ->expects($this->any())
656
+            ->method('getAppValue')
657
+            ->with('theming', 'logoMime', '')
658
+            ->willReturn('text/svg');
659
+
660
+        @$expected = new FileDisplayResponse($file);
661
+        $expected->cacheFor(3600);
662
+        $expected->addHeader('Content-Type', 'text/svg');
663
+        $expected->addHeader('Content-Disposition', 'attachment; filename="logo"');
664
+        $csp = new ContentSecurityPolicy();
665
+        $csp->allowInlineStyle();
666
+        $expected->setContentSecurityPolicy($csp);
667
+        @$this->assertEquals($expected, $this->themingController->getImage('logo'));
668
+    }
669
+
670
+
671
+    public function testGetLoginBackgroundNotExistent(): void {
672
+        $this->imageManager->method('getImage')
673
+            ->with($this->equalTo('background'))
674
+            ->willThrowException(new NotFoundException());
675
+        $expected = new NotFoundResponse();
676
+        $this->assertEquals($expected, $this->themingController->getImage('background'));
677
+    }
678
+
679
+    public function testGetLoginBackground(): void {
680
+        $file = $this->createMock(ISimpleFile::class);
681
+        $file->method('getName')->willReturn('background.png');
682
+        $file->method('getMTime')->willReturn(42);
683
+        $this->imageManager->expects($this->once())
684
+            ->method('getImage')
685
+            ->willReturn($file);
686
+
687
+        $this->config
688
+            ->expects($this->any())
689
+            ->method('getAppValue')
690
+            ->with('theming', 'backgroundMime', '')
691
+            ->willReturn('image/png');
692
+
693
+        @$expected = new FileDisplayResponse($file);
694
+        $expected->cacheFor(3600);
695
+        $expected->addHeader('Content-Type', 'image/png');
696
+        $expected->addHeader('Content-Disposition', 'attachment; filename="background"');
697
+        $csp = new ContentSecurityPolicy();
698
+        $csp->allowInlineStyle();
699
+        $expected->setContentSecurityPolicy($csp);
700
+        @$this->assertEquals($expected, $this->themingController->getImage('background'));
701
+    }
702
+
703
+    public static function dataGetManifest(): array {
704
+        return [
705
+            [true],
706
+            [false],
707
+        ];
708
+    }
709
+
710
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataGetManifest')]
711
+    public function testGetManifest(bool $standalone): void {
712
+        $this->config
713
+            ->expects($this->once())
714
+            ->method('getAppValue')
715
+            ->with('theming', 'cachebuster', '0')
716
+            ->willReturn('0');
717
+        $this->themingDefaults
718
+            ->expects($this->any())
719
+            ->method('getName')
720
+            ->willReturn('Nextcloud');
721
+        $this->urlGenerator
722
+            ->expects($this->once())
723
+            ->method('getBaseUrl')
724
+            ->willReturn('localhost');
725
+        $this->urlGenerator
726
+            ->expects($this->exactly(2))
727
+            ->method('linkToRoute')
728
+            ->willReturnMap([
729
+                ['theming.Icon.getTouchIcon', ['app' => 'core'], 'touchicon'],
730
+                ['theming.Icon.getFavicon', ['app' => 'core'], 'favicon'],
731
+            ]);
732
+        $this->config
733
+            ->expects($this->exactly(2))
734
+            ->method('getSystemValueBool')
735
+            ->with('theming.standalone_window.enabled', true)
736
+            ->willReturn($standalone);
737
+        $response = new JSONResponse([
738
+            'name' => 'Nextcloud',
739
+            'start_url' => 'localhost',
740
+            'icons'
741
+                => [
742
+                    [
743
+                        'src' => 'touchicon?v=0',
744
+                        'type' => 'image/png',
745
+                        'sizes' => '512x512'
746
+                    ],
747
+                    [
748
+                        'src' => 'favicon?v=0',
749
+                        'type' => 'image/svg+xml',
750
+                        'sizes' => '16x16'
751
+                    ]
752
+                ],
753
+            'display_override' => [$standalone ? 'minimal-ui' : ''],
754
+            'display' => $standalone ? 'standalone' : 'browser',
755
+            'short_name' => 'Nextcloud',
756
+            'theme_color' => null,
757
+            'background_color' => null,
758
+            'description' => null
759
+        ]);
760
+        $response->cacheFor(3600);
761
+        $this->assertEquals($response, $this->themingController->getManifest('core'));
762
+    }
763 763
 }
Please login to merge, or discard this patch.
Spacing   +25 added lines, -25 removed lines patch added patch discarded remove patch
@@ -87,14 +87,14 @@  discard block
 block discarded – undo
87 87
 	public static function dataUpdateStylesheetSuccess(): array {
88 88
 		return [
89 89
 			['name', str_repeat('a', 250), 'Saved'],
90
-			['url', 'https://nextcloud.com/' . str_repeat('a', 478), 'Saved'],
90
+			['url', 'https://nextcloud.com/'.str_repeat('a', 478), 'Saved'],
91 91
 			['slogan', str_repeat('a', 500), 'Saved'],
92 92
 			['primaryColor', '#0082c9', 'Saved', 'primary_color'],
93 93
 			['primary_color', '#0082C9', 'Saved'],
94 94
 			['backgroundColor', '#0082C9', 'Saved', 'background_color'],
95 95
 			['background_color', '#0082C9', 'Saved'],
96
-			['imprintUrl', 'https://nextcloud.com/' . str_repeat('a', 478), 'Saved'],
97
-			['privacyUrl', 'https://nextcloud.com/' . str_repeat('a', 478), 'Saved'],
96
+			['imprintUrl', 'https://nextcloud.com/'.str_repeat('a', 478), 'Saved'],
97
+			['privacyUrl', 'https://nextcloud.com/'.str_repeat('a', 478), 'Saved'],
98 98
 		];
99 99
 	}
100 100
 
@@ -107,7 +107,7 @@  discard block
 block discarded – undo
107 107
 		$this->l10n
108 108
 			->expects($this->once())
109 109
 			->method('t')
110
-			->willReturnCallback(function ($str) {
110
+			->willReturnCallback(function($str) {
111 111
 				return $str;
112 112
 			});
113 113
 
@@ -133,7 +133,7 @@  discard block
 block discarded – undo
133 133
 		$urlTests = [];
134 134
 		foreach ($urls as $urlKey => $urlName) {
135 135
 			// Check length limit
136
-			$urlTests[] = [$urlKey, 'http://example.com/' . str_repeat('a', 501), "The given {$urlName} is too long"];
136
+			$urlTests[] = [$urlKey, 'http://example.com/'.str_repeat('a', 501), "The given {$urlName} is too long"];
137 137
 			// Check potential evil javascript
138 138
 			$urlTests[] = [$urlKey, 'javascript:alert(1)', "The given {$urlName} is not a valid URL"];
139 139
 			// Check XSS
@@ -165,7 +165,7 @@  discard block
 block discarded – undo
165 165
 		$this->l10n
166 166
 			->expects($this->any())
167 167
 			->method('t')
168
-			->willReturnCallback(function ($str) {
168
+			->willReturnCallback(function($str) {
169 169
 				return $str;
170 170
 			});
171 171
 
@@ -196,7 +196,7 @@  discard block
 block discarded – undo
196 196
 		$this->l10n
197 197
 			->expects($this->any())
198 198
 			->method('t')
199
-			->willReturnCallback(function ($str) {
199
+			->willReturnCallback(function($str) {
200 200
 				return $str;
201 201
 			});
202 202
 
@@ -226,7 +226,7 @@  discard block
 block discarded – undo
226 226
 		$this->l10n
227 227
 			->expects($this->any())
228 228
 			->method('t')
229
-			->willReturnCallback(function ($str) {
229
+			->willReturnCallback(function($str) {
230 230
 				return $str;
231 231
 			});
232 232
 
@@ -263,7 +263,7 @@  discard block
 block discarded – undo
263 263
 			->method('getUploadedFile')
264 264
 			->with('image')
265 265
 			->willReturn([
266
-				'tmp_name' => __DIR__ . '/../../../../tests/data/testimagelarge.svg',
266
+				'tmp_name' => __DIR__.'/../../../../tests/data/testimagelarge.svg',
267 267
 				'type' => 'image/svg',
268 268
 				'name' => 'testimagelarge.svg',
269 269
 				'error' => 0,
@@ -271,7 +271,7 @@  discard block
 block discarded – undo
271 271
 		$this->l10n
272 272
 			->expects($this->any())
273 273
 			->method('t')
274
-			->willReturnCallback(function ($str) {
274
+			->willReturnCallback(function($str) {
275 275
 				return $str;
276 276
 			});
277 277
 
@@ -304,7 +304,7 @@  discard block
 block discarded – undo
304 304
 			->method('getUploadedFile')
305 305
 			->with('image')
306 306
 			->willReturn([
307
-				'tmp_name' => __DIR__ . '/../../../../tests/data/lorem.txt',
307
+				'tmp_name' => __DIR__.'/../../../../tests/data/lorem.txt',
308 308
 				'type' => 'application/pdf',
309 309
 				'name' => 'logo.pdf',
310 310
 				'error' => 0,
@@ -312,7 +312,7 @@  discard block
 block discarded – undo
312 312
 		$this->l10n
313 313
 			->expects($this->any())
314 314
 			->method('t')
315
-			->willReturnCallback(function ($str) {
315
+			->willReturnCallback(function($str) {
316 316
 				return $str;
317 317
 			});
318 318
 
@@ -347,11 +347,11 @@  discard block
 block discarded – undo
347 347
 
348 348
 	#[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateImages')]
349 349
 	public function testUpdateLogoNormalLogoUpload(string $mimeType, bool $folderExists = true): void {
350
-		$tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder() . '/logo.svg';
350
+		$tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder().'/logo.svg';
351 351
 		$destination = Server::get(ITempManager::class)->getTemporaryFolder();
352 352
 
353 353
 		touch($tmpLogo);
354
-		copy(__DIR__ . '/../../../../tests/data/testimage.png', $tmpLogo);
354
+		copy(__DIR__.'/../../../../tests/data/testimage.png', $tmpLogo);
355 355
 		$this->request
356 356
 			->expects($this->once())
357 357
 			->method('getParam')
@@ -370,7 +370,7 @@  discard block
 block discarded – undo
370 370
 		$this->l10n
371 371
 			->expects($this->any())
372 372
 			->method('t')
373
-			->willReturnCallback(function ($str) {
373
+			->willReturnCallback(function($str) {
374 374
 				return $str;
375 375
 			});
376 376
 
@@ -398,10 +398,10 @@  discard block
 block discarded – undo
398 398
 	}
399 399
 
400 400
 	public function testUpdateLogoLoginScreenUpload(): void {
401
-		$tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder() . 'logo.png';
401
+		$tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder().'logo.png';
402 402
 
403 403
 		touch($tmpLogo);
404
-		copy(__DIR__ . '/../../../../tests/data/desktopapp.png', $tmpLogo);
404
+		copy(__DIR__.'/../../../../tests/data/desktopapp.png', $tmpLogo);
405 405
 		$this->request
406 406
 			->expects($this->once())
407 407
 			->method('getParam')
@@ -420,7 +420,7 @@  discard block
 block discarded – undo
420 420
 		$this->l10n
421 421
 			->expects($this->any())
422 422
 			->method('t')
423
-			->willReturnCallback(function ($str) {
423
+			->willReturnCallback(function($str) {
424 424
 				return $str;
425 425
 			});
426 426
 
@@ -446,10 +446,10 @@  discard block
 block discarded – undo
446 446
 	}
447 447
 
448 448
 	public function testUpdateLogoLoginScreenUploadWithInvalidImage(): void {
449
-		$tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder() . '/logo.svg';
449
+		$tmpLogo = Server::get(ITempManager::class)->getTemporaryFolder().'/logo.svg';
450 450
 
451 451
 		touch($tmpLogo);
452
-		file_put_contents($tmpLogo, file_get_contents(__DIR__ . '/../../../../tests/data/data.zip'));
452
+		file_put_contents($tmpLogo, file_get_contents(__DIR__.'/../../../../tests/data/data.zip'));
453 453
 		$this->request
454 454
 			->expects($this->once())
455 455
 			->method('getParam')
@@ -468,7 +468,7 @@  discard block
 block discarded – undo
468 468
 		$this->l10n
469 469
 			->expects($this->any())
470 470
 			->method('t')
471
-			->willReturnCallback(function ($str) {
471
+			->willReturnCallback(function($str) {
472 472
 				return $str;
473 473
 			});
474 474
 
@@ -521,7 +521,7 @@  discard block
 block discarded – undo
521 521
 		$this->l10n
522 522
 			->expects($this->any())
523 523
 			->method('t')
524
-			->willReturnCallback(function ($str) {
524
+			->willReturnCallback(function($str) {
525 525
 				return $str;
526 526
 			});
527 527
 
@@ -558,7 +558,7 @@  discard block
 block discarded – undo
558 558
 		$this->l10n
559 559
 			->expects($this->any())
560 560
 			->method('t')
561
-			->willReturnCallback(function ($str) {
561
+			->willReturnCallback(function($str) {
562 562
 				return $str;
563 563
 			});
564 564
 
@@ -602,8 +602,8 @@  discard block
 block discarded – undo
602 602
 
603 603
 	public static function dataUndoDelete(): array {
604 604
 		return [
605
-			[ 'backgroundMime', 'background' ],
606
-			[ 'logoMime', 'logo' ]
605
+			['backgroundMime', 'background'],
606
+			['logoMime', 'logo']
607 607
 		];
608 608
 	}
609 609
 
Please login to merge, or discard this patch.