Completed
Push — master ( 62513d...e5c989 )
by Joas
51:33 queued 18:32
created
apps/theming/lib/Controller/ThemingController.php 1 patch
Indentation   +448 added lines, -448 removed lines patch added patch discarded remove patch
@@ -42,478 +42,478 @@
 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 ($value !== '' && !$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 ($value !== '' && !$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 ($value !== '' && !$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 ($value !== '' && !$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
-			'legalNoticeUrl' => 'imprintUrl',
312
-			'privacyPolicyUrl' => 'privacyUrl',
313
-			default => $setting,
314
-		};
315
-		$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
+            'legalNoticeUrl' => 'imprintUrl',
312
+            'privacyPolicyUrl' => 'privacyUrl',
313
+            default => $setting,
314
+        };
315
+        $value = $this->themingDefaults->undo($setting);
316 316
 
317
-		return new DataResponse(
318
-			[
319
-				'data'
320
-					=> [
321
-						'value' => $value,
322
-						'message' => $this->l10n->t('Saved'),
323
-					],
324
-				'status' => 'success'
325
-			]
326
-		);
327
-	}
317
+        return new DataResponse(
318
+            [
319
+                'data'
320
+                    => [
321
+                        'value' => $value,
322
+                        'message' => $this->l10n->t('Saved'),
323
+                    ],
324
+                'status' => 'success'
325
+            ]
326
+        );
327
+    }
328 328
 
329
-	/**
330
-	 * Revert all theming settings to their default values
331
-	 *
332
-	 * @return DataResponse
333
-	 * @throws NotPermittedException
334
-	 */
335
-	#[AuthorizedAdminSetting(settings: Admin::class)]
336
-	public function undoAll(): DataResponse {
337
-		$this->themingDefaults->undoAll();
338
-		$this->navigationManager->setDefaultEntryIds([]);
329
+    /**
330
+     * Revert all theming settings to their default values
331
+     *
332
+     * @return DataResponse
333
+     * @throws NotPermittedException
334
+     */
335
+    #[AuthorizedAdminSetting(settings: Admin::class)]
336
+    public function undoAll(): DataResponse {
337
+        $this->themingDefaults->undoAll();
338
+        $this->navigationManager->setDefaultEntryIds([]);
339 339
 
340
-		return new DataResponse(
341
-			[
342
-				'data'
343
-					=> [
344
-						'message' => $this->l10n->t('Saved'),
345
-					],
346
-				'status' => 'success'
347
-			]
348
-		);
349
-	}
340
+        return new DataResponse(
341
+            [
342
+                'data'
343
+                    => [
344
+                        'message' => $this->l10n->t('Saved'),
345
+                    ],
346
+                'status' => 'success'
347
+            ]
348
+        );
349
+    }
350 350
 
351
-	/**
352
-	 * @NoSameSiteCookieRequired
353
-	 *
354
-	 * Get an image
355
-	 *
356
-	 * @param string $key Key of the image
357
-	 * @param bool $useSvg Return image as SVG
358
-	 * @return FileDisplayResponse<Http::STATUS_OK, array{}>|NotFoundResponse<Http::STATUS_NOT_FOUND, array{}>
359
-	 * @throws NotPermittedException
360
-	 *
361
-	 * 200: Image returned
362
-	 * 404: Image not found
363
-	 */
364
-	#[PublicPage]
365
-	#[NoCSRFRequired]
366
-	#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
367
-	public function getImage(string $key, bool $useSvg = true) {
368
-		try {
369
-			$file = $this->imageManager->getImage($key, $useSvg);
370
-		} catch (NotFoundException $e) {
371
-			return new NotFoundResponse();
372
-		}
351
+    /**
352
+     * @NoSameSiteCookieRequired
353
+     *
354
+     * Get an image
355
+     *
356
+     * @param string $key Key of the image
357
+     * @param bool $useSvg Return image as SVG
358
+     * @return FileDisplayResponse<Http::STATUS_OK, array{}>|NotFoundResponse<Http::STATUS_NOT_FOUND, array{}>
359
+     * @throws NotPermittedException
360
+     *
361
+     * 200: Image returned
362
+     * 404: Image not found
363
+     */
364
+    #[PublicPage]
365
+    #[NoCSRFRequired]
366
+    #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
367
+    public function getImage(string $key, bool $useSvg = true) {
368
+        try {
369
+            $file = $this->imageManager->getImage($key, $useSvg);
370
+        } catch (NotFoundException $e) {
371
+            return new NotFoundResponse();
372
+        }
373 373
 
374
-		$response = new FileDisplayResponse($file);
375
-		$csp = new ContentSecurityPolicy();
376
-		$csp->allowInlineStyle();
377
-		$response->setContentSecurityPolicy($csp);
378
-		$response->cacheFor(3600);
379
-		$response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
380
-		$response->addHeader('Content-Disposition', 'attachment; filename="' . $key . '"');
381
-		if (!$useSvg) {
382
-			$response->addHeader('Content-Type', 'image/png');
383
-		} else {
384
-			$response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
385
-		}
386
-		return $response;
387
-	}
374
+        $response = new FileDisplayResponse($file);
375
+        $csp = new ContentSecurityPolicy();
376
+        $csp->allowInlineStyle();
377
+        $response->setContentSecurityPolicy($csp);
378
+        $response->cacheFor(3600);
379
+        $response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
380
+        $response->addHeader('Content-Disposition', 'attachment; filename="' . $key . '"');
381
+        if (!$useSvg) {
382
+            $response->addHeader('Content-Type', 'image/png');
383
+        } else {
384
+            $response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
385
+        }
386
+        return $response;
387
+    }
388 388
 
389
-	/**
390
-	 * @NoSameSiteCookieRequired
391
-	 * @NoTwoFactorRequired
392
-	 *
393
-	 * Get the CSS stylesheet for a theme
394
-	 *
395
-	 * @param string $themeId ID of the theme
396
-	 * @param bool $plain Let the browser decide the CSS priority
397
-	 * @param bool $withCustomCss Include custom CSS
398
-	 * @return DataDisplayResponse<Http::STATUS_OK, array{Content-Type: 'text/css'}>|NotFoundResponse<Http::STATUS_NOT_FOUND, array{}>
399
-	 *
400
-	 * 200: Stylesheet returned
401
-	 * 404: Theme not found
402
-	 */
403
-	#[PublicPage]
404
-	#[NoCSRFRequired]
405
-	#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
406
-	public function getThemeStylesheet(string $themeId, bool $plain = false, bool $withCustomCss = false) {
407
-		$themes = $this->themesService->getThemes();
408
-		if (!in_array($themeId, array_keys($themes))) {
409
-			return new NotFoundResponse();
410
-		}
389
+    /**
390
+     * @NoSameSiteCookieRequired
391
+     * @NoTwoFactorRequired
392
+     *
393
+     * Get the CSS stylesheet for a theme
394
+     *
395
+     * @param string $themeId ID of the theme
396
+     * @param bool $plain Let the browser decide the CSS priority
397
+     * @param bool $withCustomCss Include custom CSS
398
+     * @return DataDisplayResponse<Http::STATUS_OK, array{Content-Type: 'text/css'}>|NotFoundResponse<Http::STATUS_NOT_FOUND, array{}>
399
+     *
400
+     * 200: Stylesheet returned
401
+     * 404: Theme not found
402
+     */
403
+    #[PublicPage]
404
+    #[NoCSRFRequired]
405
+    #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
406
+    public function getThemeStylesheet(string $themeId, bool $plain = false, bool $withCustomCss = false) {
407
+        $themes = $this->themesService->getThemes();
408
+        if (!in_array($themeId, array_keys($themes))) {
409
+            return new NotFoundResponse();
410
+        }
411 411
 
412
-		$theme = $themes[$themeId];
413
-		$customCss = $theme->getCustomCss();
412
+        $theme = $themes[$themeId];
413
+        $customCss = $theme->getCustomCss();
414 414
 
415
-		// Generate variables
416
-		$variables = '';
417
-		foreach ($theme->getCSSVariables() as $variable => $value) {
418
-			$variables .= "$variable:$value; ";
419
-		};
415
+        // Generate variables
416
+        $variables = '';
417
+        foreach ($theme->getCSSVariables() as $variable => $value) {
418
+            $variables .= "$variable:$value; ";
419
+        };
420 420
 
421
-		// If plain is set, the browser decides of the css priority
422
-		if ($plain) {
423
-			$css = ":root { $variables } " . $customCss;
424
-		} else {
425
-			// If not set, we'll rely on the body class
426
-			// We need to separate @-rules from normal selectors, as they can't be nested
427
-			// This is a replacement for the SCSS compiler that did this automatically before f1448fcf0777db7d4254cb0a3ef94d63be9f7a24
428
-			// We need a better way to handle this, but for now we just remove comments and split the at-rules
429
-			// from the rest of the CSS.
430
-			$customCssWithoutComments = preg_replace('!/\*.*?\*/!s', '', $customCss);
431
-			$customCssWithoutComments = preg_replace('!//.*!', '', $customCssWithoutComments);
432
-			preg_match_all('/(@[^{]+{(?:[^{}]*|(?R))*})/', $customCssWithoutComments, $atRules);
433
-			$atRulesCss = implode('', $atRules[0]);
434
-			$scopedCss = preg_replace('/(@[^{]+{(?:[^{}]*|(?R))*})/', '', $customCssWithoutComments);
421
+        // If plain is set, the browser decides of the css priority
422
+        if ($plain) {
423
+            $css = ":root { $variables } " . $customCss;
424
+        } else {
425
+            // If not set, we'll rely on the body class
426
+            // We need to separate @-rules from normal selectors, as they can't be nested
427
+            // This is a replacement for the SCSS compiler that did this automatically before f1448fcf0777db7d4254cb0a3ef94d63be9f7a24
428
+            // We need a better way to handle this, but for now we just remove comments and split the at-rules
429
+            // from the rest of the CSS.
430
+            $customCssWithoutComments = preg_replace('!/\*.*?\*/!s', '', $customCss);
431
+            $customCssWithoutComments = preg_replace('!//.*!', '', $customCssWithoutComments);
432
+            preg_match_all('/(@[^{]+{(?:[^{}]*|(?R))*})/', $customCssWithoutComments, $atRules);
433
+            $atRulesCss = implode('', $atRules[0]);
434
+            $scopedCss = preg_replace('/(@[^{]+{(?:[^{}]*|(?R))*})/', '', $customCssWithoutComments);
435 435
 
436
-			$css = "$atRulesCss [data-theme-$themeId] { $variables $scopedCss }";
437
-		}
436
+            $css = "$atRulesCss [data-theme-$themeId] { $variables $scopedCss }";
437
+        }
438 438
 
439
-		try {
440
-			$response = new DataDisplayResponse($css, Http::STATUS_OK, ['Content-Type' => 'text/css']);
441
-			$response->cacheFor(86400);
442
-			return $response;
443
-		} catch (NotFoundException $e) {
444
-			return new NotFoundResponse();
445
-		}
446
-	}
439
+        try {
440
+            $response = new DataDisplayResponse($css, Http::STATUS_OK, ['Content-Type' => 'text/css']);
441
+            $response->cacheFor(86400);
442
+            return $response;
443
+        } catch (NotFoundException $e) {
444
+            return new NotFoundResponse();
445
+        }
446
+    }
447 447
 
448
-	/**
449
-	 * Get the manifest for an app
450
-	 *
451
-	 * @param string $app ID of the app
452
-	 * @psalm-suppress LessSpecificReturnStatement The content of the Manifest doesn't need to be described in the return type
453
-	 * @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{}>
454
-	 *
455
-	 * 200: Manifest returned
456
-	 * 404: App not found
457
-	 */
458
-	#[PublicPage]
459
-	#[NoCSRFRequired]
460
-	#[BruteForceProtection(action: 'manifest')]
461
-	#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
462
-	public function getManifest(string $app): JSONResponse {
463
-		$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
464
-		if ($app === 'core' || $app === 'settings') {
465
-			$name = $this->themingDefaults->getName();
466
-			$shortName = $this->themingDefaults->getName();
467
-			$startUrl = $this->urlGenerator->getBaseUrl();
468
-			$description = $this->themingDefaults->getSlogan();
469
-		} else {
470
-			if (!$this->appManager->isEnabledForUser($app)) {
471
-				$response = new JSONResponse([], Http::STATUS_NOT_FOUND);
472
-				$response->throttle(['action' => 'manifest', 'app' => $app]);
473
-				return $response;
474
-			}
448
+    /**
449
+     * Get the manifest for an app
450
+     *
451
+     * @param string $app ID of the app
452
+     * @psalm-suppress LessSpecificReturnStatement The content of the Manifest doesn't need to be described in the return type
453
+     * @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{}>
454
+     *
455
+     * 200: Manifest returned
456
+     * 404: App not found
457
+     */
458
+    #[PublicPage]
459
+    #[NoCSRFRequired]
460
+    #[BruteForceProtection(action: 'manifest')]
461
+    #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
462
+    public function getManifest(string $app): JSONResponse {
463
+        $cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
464
+        if ($app === 'core' || $app === 'settings') {
465
+            $name = $this->themingDefaults->getName();
466
+            $shortName = $this->themingDefaults->getName();
467
+            $startUrl = $this->urlGenerator->getBaseUrl();
468
+            $description = $this->themingDefaults->getSlogan();
469
+        } else {
470
+            if (!$this->appManager->isEnabledForUser($app)) {
471
+                $response = new JSONResponse([], Http::STATUS_NOT_FOUND);
472
+                $response->throttle(['action' => 'manifest', 'app' => $app]);
473
+                return $response;
474
+            }
475 475
 
476
-			$info = $this->appManager->getAppInfo($app, false, $this->l10n->getLanguageCode());
477
-			$name = $info['name'] . ' - ' . $this->themingDefaults->getName();
478
-			$shortName = $info['name'];
479
-			if (str_contains($this->request->getRequestUri(), '/index.php/')) {
480
-				$startUrl = $this->urlGenerator->getBaseUrl() . '/index.php/apps/' . $app . '/';
481
-			} else {
482
-				$startUrl = $this->urlGenerator->getBaseUrl() . '/apps/' . $app . '/';
483
-			}
484
-			$description = $info['summary'] ?? '';
485
-		}
486
-		/**
487
-		 * @var string $description
488
-		 * @var string $shortName
489
-		 */
490
-		$responseJS = [
491
-			'name' => $name,
492
-			'short_name' => $shortName,
493
-			'start_url' => $startUrl,
494
-			'theme_color' => $this->themingDefaults->getColorPrimary(),
495
-			'background_color' => $this->themingDefaults->getColorPrimary(),
496
-			'description' => $description,
497
-			'icons'
498
-				=> [
499
-					[
500
-						'src' => $this->urlGenerator->linkToRoute('theming.Icon.getTouchIcon',
501
-							['app' => $app]) . '?v=' . $cacheBusterValue,
502
-						'type' => 'image/png',
503
-						'sizes' => '512x512'
504
-					],
505
-					[
506
-						'src' => $this->urlGenerator->linkToRoute('theming.Icon.getFavicon',
507
-							['app' => $app]) . '?v=' . $cacheBusterValue,
508
-						'type' => 'image/svg+xml',
509
-						'sizes' => '16x16'
510
-					]
511
-				],
512
-			'display_override' => [$this->config->getSystemValueBool('theming.standalone_window.enabled', true) ? 'minimal-ui' : ''],
513
-			'display' => $this->config->getSystemValueBool('theming.standalone_window.enabled', true) ? 'standalone' : 'browser'
514
-		];
515
-		$response = new JSONResponse($responseJS);
516
-		$response->cacheFor(3600);
517
-		return $response;
518
-	}
476
+            $info = $this->appManager->getAppInfo($app, false, $this->l10n->getLanguageCode());
477
+            $name = $info['name'] . ' - ' . $this->themingDefaults->getName();
478
+            $shortName = $info['name'];
479
+            if (str_contains($this->request->getRequestUri(), '/index.php/')) {
480
+                $startUrl = $this->urlGenerator->getBaseUrl() . '/index.php/apps/' . $app . '/';
481
+            } else {
482
+                $startUrl = $this->urlGenerator->getBaseUrl() . '/apps/' . $app . '/';
483
+            }
484
+            $description = $info['summary'] ?? '';
485
+        }
486
+        /**
487
+         * @var string $description
488
+         * @var string $shortName
489
+         */
490
+        $responseJS = [
491
+            'name' => $name,
492
+            'short_name' => $shortName,
493
+            'start_url' => $startUrl,
494
+            'theme_color' => $this->themingDefaults->getColorPrimary(),
495
+            'background_color' => $this->themingDefaults->getColorPrimary(),
496
+            'description' => $description,
497
+            'icons'
498
+                => [
499
+                    [
500
+                        'src' => $this->urlGenerator->linkToRoute('theming.Icon.getTouchIcon',
501
+                            ['app' => $app]) . '?v=' . $cacheBusterValue,
502
+                        'type' => 'image/png',
503
+                        'sizes' => '512x512'
504
+                    ],
505
+                    [
506
+                        'src' => $this->urlGenerator->linkToRoute('theming.Icon.getFavicon',
507
+                            ['app' => $app]) . '?v=' . $cacheBusterValue,
508
+                        'type' => 'image/svg+xml',
509
+                        'sizes' => '16x16'
510
+                    ]
511
+                ],
512
+            'display_override' => [$this->config->getSystemValueBool('theming.standalone_window.enabled', true) ? 'minimal-ui' : ''],
513
+            'display' => $this->config->getSystemValueBool('theming.standalone_window.enabled', true) ? 'standalone' : 'browser'
514
+        ];
515
+        $response = new JSONResponse($responseJS);
516
+        $response->cacheFor(3600);
517
+        return $response;
518
+    }
519 519
 }
Please login to merge, or discard this patch.