Passed
Push — master ( ef96ef...2a8452 )
by Roeland
13:23 queued 14s
created

ThemingController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
c 0
b 0
f 0
nc 1
nop 11
dl 0
loc 24
rs 9.9332

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 Bjoern Schiessle <[email protected]>
4
 * @copyright Copyright (c) 2016 Lukas Reschke <[email protected]>
5
 *
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Bjoern Schiessle <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Daniel Calviño Sánchez <[email protected]>
10
 * @author Daniel Kesselberg <[email protected]>
11
 * @author Jan-Christoph Borchardt <[email protected]>
12
 * @author Joas Schilling <[email protected]>
13
 * @author Julius Haertl <[email protected]>
14
 * @author Julius Härtl <[email protected]>
15
 * @author Kyle Fazzari <[email protected]>
16
 * @author Lukas Reschke <[email protected]>
17
 * @author Michael Weimann <[email protected]>
18
 * @author rakekniven <[email protected]>
19
 * @author Robin Appelman <[email protected]>
20
 * @author Roeland Jago Douma <[email protected]>
21
 * @author Thomas Citharel <[email protected]>
22
 *
23
 * @license GNU AGPL version 3 or any later version
24
 *
25
 * This program is free software: you can redistribute it and/or modify
26
 * it under the terms of the GNU Affero General Public License as
27
 * published by the Free Software Foundation, either version 3 of the
28
 * License, or (at your option) any later version.
29
 *
30
 * This program is distributed in the hope that it will be useful,
31
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33
 * GNU Affero General Public License for more details.
34
 *
35
 * You should have received a copy of the GNU Affero General Public License
36
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
37
 *
38
 */
39
40
namespace OCA\Theming\Controller;
41
42
use OC\Template\SCSSCacher;
43
use OCA\Theming\ImageManager;
44
use OCA\Theming\ThemingDefaults;
45
use OCP\App\IAppManager;
46
use OCP\AppFramework\Controller;
47
use OCP\AppFramework\Http;
48
use OCP\AppFramework\Http\DataResponse;
49
use OCP\AppFramework\Http\FileDisplayResponse;
50
use OCP\AppFramework\Http\NotFoundResponse;
51
use OCP\Files\IAppData;
52
use OCP\Files\NotFoundException;
53
use OCP\Files\NotPermittedException;
54
use OCP\IConfig;
55
use OCP\IL10N;
56
use OCP\IRequest;
57
use OCP\ITempManager;
58
use OCP\IURLGenerator;
59
60
/**
61
 * Class ThemingController
62
 *
63
 * handle ajax requests to update the theme
64
 *
65
 * @package OCA\Theming\Controller
66
 */
67
class ThemingController extends Controller {
68
	/** @var ThemingDefaults */
69
	private $themingDefaults;
70
	/** @var IL10N */
71
	private $l10n;
72
	/** @var IConfig */
73
	private $config;
74
	/** @var ITempManager */
75
	private $tempManager;
76
	/** @var IAppData */
77
	private $appData;
78
	/** @var SCSSCacher */
79
	private $scssCacher;
80
	/** @var IURLGenerator */
81
	private $urlGenerator;
82
	/** @var IAppManager */
83
	private $appManager;
84
	/** @var ImageManager */
85
	private $imageManager;
86
87
	/**
88
	 * ThemingController constructor.
89
	 *
90
	 * @param string $appName
91
	 * @param IRequest $request
92
	 * @param IConfig $config
93
	 * @param ThemingDefaults $themingDefaults
94
	 * @param IL10N $l
95
	 * @param ITempManager $tempManager
96
	 * @param IAppData $appData
97
	 * @param SCSSCacher $scssCacher
98
	 * @param IURLGenerator $urlGenerator
99
	 * @param IAppManager $appManager
100
	 * @param ImageManager $imageManager
101
	 */
102
	public function __construct(
103
		$appName,
104
		IRequest $request,
105
		IConfig $config,
106
		ThemingDefaults $themingDefaults,
107
		IL10N $l,
108
		ITempManager $tempManager,
109
		IAppData $appData,
110
		SCSSCacher $scssCacher,
111
		IURLGenerator $urlGenerator,
112
		IAppManager $appManager,
113
		ImageManager $imageManager
114
	) {
115
		parent::__construct($appName, $request);
116
117
		$this->themingDefaults = $themingDefaults;
118
		$this->l10n = $l;
119
		$this->config = $config;
120
		$this->tempManager = $tempManager;
121
		$this->appData = $appData;
122
		$this->scssCacher = $scssCacher;
123
		$this->urlGenerator = $urlGenerator;
124
		$this->appManager = $appManager;
125
		$this->imageManager = $imageManager;
126
	}
127
128
	/**
129
	 * @param string $setting
130
	 * @param string $value
131
	 * @return DataResponse
132
	 * @throws NotPermittedException
133
	 */
134
	public function updateStylesheet($setting, $value) {
135
		$value = trim($value);
136
		$error = null;
137
		switch ($setting) {
138
			case 'name':
139
				if (strlen($value) > 250) {
140
					$error = $this->l10n->t('The given name is too long');
141
				}
142
				break;
143
			case 'url':
144
				if (strlen($value) > 500) {
145
					$error = $this->l10n->t('The given web address is too long');
146
				}
147
				if (!$this->isValidUrl($value)) {
148
					$error = $this->l10n->t('The given web address is not a valid URL');
149
				}
150
				break;
151
			case 'imprintUrl':
152
				if (strlen($value) > 500) {
153
					$error = $this->l10n->t('The given legal notice address is too long');
154
				}
155
				if (!$this->isValidUrl($value)) {
156
					$error = $this->l10n->t('The given legal notice address is not a valid URL');
157
				}
158
				break;
159
			case 'privacyUrl':
160
				if (strlen($value) > 500) {
161
					$error = $this->l10n->t('The given privacy policy address is too long');
162
				}
163
				if (!$this->isValidUrl($value)) {
164
					$error = $this->l10n->t('The given privacy policy address is not a valid URL');
165
				}
166
				break;
167
			case 'slogan':
168
				if (strlen($value) > 500) {
169
					$error = $this->l10n->t('The given slogan is too long');
170
				}
171
				break;
172
			case 'color':
173
				if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) {
174
					$error = $this->l10n->t('The given color is invalid');
175
				}
176
				break;
177
		}
178
		if ($error !== null) {
179
			return new DataResponse([
180
				'data' => [
181
					'message' => $error,
182
				],
183
				'status' => 'error'
184
			], Http::STATUS_BAD_REQUEST);
185
		}
186
187
		$this->themingDefaults->set($setting, $value);
188
189
		// reprocess server scss for preview
190
		$cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
0 ignored issues
show
Unused Code introduced by
The assignment to $cssCached is dead and can be removed.
Loading history...
191
192
		return new DataResponse(
193
			[
194
				'data' =>
195
					[
196
						'message' => $this->l10n->t('Saved'),
197
						'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
198
					],
199
				'status' => 'success'
200
			]
201
		);
202
	}
203
204
	/**
205
	 * Check that a string is a valid http/https url
206
	 */
207
	private function isValidUrl(string $url): bool {
208
		return ((strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0) &&
209
			filter_var($url, FILTER_VALIDATE_URL) !== false);
210
	}
211
212
	/**
213
	 * @return DataResponse
214
	 * @throws NotPermittedException
215
	 */
216
	public function uploadImage(): DataResponse {
217
		// logo / background
218
		// new: favicon logo-header
219
		//
220
		$key = $this->request->getParam('key');
221
		$image = $this->request->getUploadedFile('image');
222
		$error = null;
223
		$phpFileUploadErrors = [
224
			UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'),
225
			UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
226
			UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
227
			UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'),
228
			UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'),
229
			UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'),
230
			UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'),
231
			UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'),
232
		];
233
		if (empty($image)) {
234
			$error = $this->l10n->t('No file uploaded');
235
		}
236
		if (!empty($image) && array_key_exists('error', $image) && $image['error'] !== UPLOAD_ERR_OK) {
237
			$error = $phpFileUploadErrors[$image['error']];
238
		}
239
240
		if ($error !== null) {
241
			return new DataResponse(
242
				[
243
					'data' => [
244
						'message' => $error
245
					],
246
					'status' => 'failure',
247
				],
248
				Http::STATUS_UNPROCESSABLE_ENTITY
249
			);
250
		}
251
252
		$name = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $name is dead and can be removed.
Loading history...
253
		try {
254
			$folder = $this->appData->getFolder('images');
255
		} catch (NotFoundException $e) {
256
			$folder = $this->appData->newFolder('images');
257
		}
258
259
		$this->imageManager->delete($key);
260
261
		$target = $folder->newFile($key);
262
		$supportedFormats = $this->getSupportedUploadImageFormats($key);
263
		$detectedMimeType = mime_content_type($image['tmp_name']);
264
		if (!in_array($image['type'], $supportedFormats) || !in_array($detectedMimeType, $supportedFormats)) {
265
			return new DataResponse(
266
				[
267
					'data' => [
268
						'message' => $this->l10n->t('Unsupported image type'),
269
					],
270
					'status' => 'failure',
271
				],
272
				Http::STATUS_UNPROCESSABLE_ENTITY
273
			);
274
		}
275
276
		if ($key === 'background' && strpos($detectedMimeType, 'image/svg') === false) {
277
			// Optimize the image since some people may upload images that will be
278
			// either to big or are not progressive rendering.
279
			$newImage = @imagecreatefromstring(file_get_contents($image['tmp_name'], 'r'));
0 ignored issues
show
Bug introduced by
'r' of type string is incompatible with the type boolean expected by parameter $use_include_path of file_get_contents(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

279
			$newImage = @imagecreatefromstring(file_get_contents($image['tmp_name'], /** @scrutinizer ignore-type */ 'r'));
Loading history...
280
281
			$tmpFile = $this->tempManager->getTemporaryFile();
282
			$newWidth = imagesx($newImage) < 4096 ? imagesx($newImage) : 4096;
283
			$newHeight = imagesy($newImage) / (imagesx($newImage) / $newWidth);
284
			$outputImage = imagescale($newImage, $newWidth, $newHeight);
285
286
			imageinterlace($outputImage, 1);
287
			imagejpeg($outputImage, $tmpFile, 75);
288
			imagedestroy($outputImage);
289
290
			$target->putContent(file_get_contents($tmpFile, 'r'));
291
		} else {
292
			$target->putContent(file_get_contents($image['tmp_name'], 'r'));
293
		}
294
		$name = $image['name'];
295
296
		$this->themingDefaults->set($key.'Mime', $image['type']);
297
298
		$cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
0 ignored issues
show
Unused Code introduced by
The assignment to $cssCached is dead and can be removed.
Loading history...
299
300
		return new DataResponse(
301
			[
302
				'data' =>
303
					[
304
						'name' => $name,
305
						'url' => $this->imageManager->getImageUrl($key),
306
						'message' => $this->l10n->t('Saved'),
307
						'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
308
					],
309
				'status' => 'success'
310
			]
311
		);
312
	}
313
314
	/**
315
	 * Returns a list of supported mime types for image uploads.
316
	 * "favicon" images are only allowed to be SVG when imagemagick with SVG support is available.
317
	 *
318
	 * @param string $key The image key, e.g. "favicon"
319
	 * @return array
320
	 */
321
	private function getSupportedUploadImageFormats(string $key): array {
322
		$supportedFormats = ['image/jpeg', 'image/png', 'image/gif',];
323
324
		if ($key !== 'favicon' || $this->imageManager->shouldReplaceIcons() === true) {
325
			$supportedFormats[] = 'image/svg+xml';
326
			$supportedFormats[] = 'image/svg';
327
		}
328
329
		return $supportedFormats;
330
	}
331
332
	/**
333
	 * Revert setting to default value
334
	 *
335
	 * @param string $setting setting which should be reverted
336
	 * @return DataResponse
337
	 * @throws NotPermittedException
338
	 */
339
	public function undo(string $setting): DataResponse {
340
		$value = $this->themingDefaults->undo($setting);
341
		// reprocess server scss for preview
342
		$cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
0 ignored issues
show
Unused Code introduced by
The assignment to $cssCached is dead and can be removed.
Loading history...
343
344
		if (strpos($setting, 'Mime') !== -1) {
345
			$imageKey = str_replace('Mime', '', $setting);
346
			$this->imageManager->delete($imageKey);
347
		}
348
349
		return new DataResponse(
350
			[
351
				'data' =>
352
					[
353
						'value' => $value,
354
						'message' => $this->l10n->t('Saved'),
355
						'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
356
					],
357
				'status' => 'success'
358
			]
359
		);
360
	}
361
362
	/**
363
	 * @PublicPage
364
	 * @NoCSRFRequired
365
	 *
366
	 * @param string $key
367
	 * @param bool $useSvg
368
	 * @return FileDisplayResponse|NotFoundResponse
369
	 * @throws NotPermittedException
370
	 */
371
	public function getImage(string $key, bool $useSvg = true) {
372
		try {
373
			$file = $this->imageManager->getImage($key, $useSvg);
374
		} catch (NotFoundException $e) {
375
			return new NotFoundResponse();
376
		}
377
378
		$response = new FileDisplayResponse($file);
379
		$csp = new Http\ContentSecurityPolicy();
380
		$csp->allowInlineStyle();
381
		$response->setContentSecurityPolicy($csp);
382
		$response->cacheFor(3600);
383
		$response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
384
		$response->addHeader('Content-Disposition', 'attachment; filename="' . $key . '"');
385
		if (!$useSvg) {
386
			$response->addHeader('Content-Type', 'image/png');
387
		} else {
388
			$response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
389
		}
390
		return $response;
391
	}
392
393
	/**
394
	 * @NoCSRFRequired
395
	 * @PublicPage
396
	 * @NoSameSiteCookieRequired
397
	 *
398
	 * @return FileDisplayResponse|NotFoundResponse
399
	 * @throws NotPermittedException
400
	 * @throws \Exception
401
	 * @throws \OCP\App\AppPathNotFoundException
402
	 */
403
	public function getStylesheet() {
404
		$appPath = $this->appManager->getAppPath('theming');
405
406
		/* SCSSCacher is required here
407
		 * We cannot rely on automatic caching done by \OC_Util::addStyle,
408
		 * since we need to add the cacheBuster value to the url
409
		 */
410
		$cssCached = $this->scssCacher->process($appPath, 'css/theming.scss', 'theming');
411
		if (!$cssCached) {
412
			return new NotFoundResponse();
413
		}
414
415
		try {
416
			$cssFile = $this->scssCacher->getCachedCSS('theming', 'theming.css');
417
			$response = new FileDisplayResponse($cssFile, Http::STATUS_OK, ['Content-Type' => 'text/css']);
418
			$response->cacheFor(86400);
419
			return $response;
420
		} catch (NotFoundException $e) {
421
			return new NotFoundResponse();
422
		}
423
	}
424
425
	/**
426
	 * @NoCSRFRequired
427
	 * @PublicPage
428
	 *
429
	 * @return Http\JSONResponse
430
	 */
431
	public function getManifest($app) {
432
		$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
433
		$responseJS = [
434
			'name' => $this->themingDefaults->getName(),
435
			'start_url' => $this->urlGenerator->getBaseUrl(),
436
			'icons' =>
437
				[
438
					[
439
						'src' => $this->urlGenerator->linkToRoute('theming.Icon.getTouchIcon',
440
								['app' => $app]) . '?v=' . $cacheBusterValue,
441
						'type'=> 'image/png',
442
						'sizes'=> '128x128'
443
					],
444
					[
445
						'src' => $this->urlGenerator->linkToRoute('theming.Icon.getFavicon',
446
								['app' => $app]) . '?v=' . $cacheBusterValue,
447
						'type' => 'image/svg+xml',
448
						'sizes' => '16x16'
449
					]
450
				],
451
			'display' => 'standalone'
452
		];
453
		$response = new Http\JSONResponse($responseJS);
454
		$response->cacheFor(3600);
455
		return $response;
456
	}
457
}
458