Passed
Push — master ( 436f7b...f1066f )
by Roeland
10:06 queued 17s
created

ThemingController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 12
dl 0
loc 26
rs 9.9
c 0
b 0
f 0

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

282
			$newImage = @imagecreatefromstring(file_get_contents($image['tmp_name'], /** @scrutinizer ignore-type */ 'r'));
Loading history...
283
284
			$tmpFile = $this->tempManager->getTemporaryFile();
285
			$newWidth = imagesx($newImage) < 4096 ? imagesx($newImage) : 4096;
286
			$newHeight = imagesy($newImage) / (imagesx($newImage) / $newWidth);
287
			$outputImage = imagescale($newImage, $newWidth, $newHeight);
288
289
			imageinterlace($outputImage, 1);
290
			imagejpeg($outputImage, $tmpFile, 75);
291
			imagedestroy($outputImage);
292
293
			$target->putContent(file_get_contents($tmpFile, 'r'));
294
		} else {
295
			$target->putContent(file_get_contents($image['tmp_name'], 'r'));
296
		}
297
		$name = $image['name'];
298
299
		$this->themingDefaults->set($key.'Mime', $image['type']);
300
301
		$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...
302
303
		return new DataResponse(
304
			[
305
				'data' =>
306
					[
307
						'name' => $name,
308
						'url' => $this->imageManager->getImageUrl($key),
309
						'message' => $this->l10n->t('Saved'),
310
						'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
311
					],
312
				'status' => 'success'
313
			]
314
		);
315
	}
316
317
	/**
318
	 * Returns a list of supported mime types for image uploads.
319
	 * "favicon" images are only allowed to be SVG when imagemagick with SVG support is available.
320
	 *
321
	 * @param string $key The image key, e.g. "favicon"
322
	 * @return array
323
	 */
324
	private function getSupportedUploadImageFormats(string $key): array {
325
		$supportedFormats = ['image/jpeg', 'image/png', 'image/gif',];
326
327
		if ($key !== 'favicon' || $this->imageManager->shouldReplaceIcons() === true) {
328
			$supportedFormats[] = 'image/svg+xml';
329
			$supportedFormats[] = 'image/svg';
330
		}
331
332
		return $supportedFormats;
333
	}
334
335
	/**
336
	 * Revert setting to default value
337
	 *
338
	 * @param string $setting setting which should be reverted
339
	 * @return DataResponse
340
	 * @throws NotPermittedException
341
	 */
342
	public function undo(string $setting): DataResponse {
343
		$value = $this->themingDefaults->undo($setting);
344
		// reprocess server scss for preview
345
		$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...
346
347
		if (strpos($setting, 'Mime') !== -1) {
348
			$imageKey = str_replace('Mime', '', $setting);
349
			$this->imageManager->delete($imageKey);
350
		}
351
352
		return new DataResponse(
353
			[
354
				'data' =>
355
					[
356
						'value' => $value,
357
						'message' => $this->l10n->t('Saved'),
358
						'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
359
					],
360
				'status' => 'success'
361
			]
362
		);
363
	}
364
365
	/**
366
	 * @PublicPage
367
	 * @NoCSRFRequired
368
	 *
369
	 * @param string $key
370
	 * @param bool $useSvg
371
	 * @return FileDisplayResponse|NotFoundResponse
372
	 * @throws NotPermittedException
373
	 */
374
	public function getImage(string $key, bool $useSvg = true) {
375
		try {
376
			$file = $this->imageManager->getImage($key, $useSvg);
377
		} catch (NotFoundException $e) {
378
			return new NotFoundResponse();
379
		}
380
381
		$response = new FileDisplayResponse($file);
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
	 * @NoSameSiteCookieRequired
429
	 *
430
	 * @return DataDownloadResponse
431
	 */
432
	public function getJavascript() {
433
		$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
434
		$responseJS = '(function() {
435
	OCA.Theming = {
436
		name: ' . json_encode($this->themingDefaults->getName()) . ',
437
		url: ' . json_encode($this->themingDefaults->getBaseUrl()) . ',
438
		slogan: ' . json_encode($this->themingDefaults->getSlogan()) . ',
439
		color: ' . json_encode($this->themingDefaults->getColorPrimary()) . ',
440
		imprintUrl: ' . json_encode($this->themingDefaults->getImprintUrl()) . ',
441
		privacyUrl: ' . json_encode($this->themingDefaults->getPrivacyUrl()) . ',
442
		inverted: ' . json_encode($this->util->invertTextColor($this->themingDefaults->getColorPrimary())) . ',
443
		cacheBuster: ' . json_encode($cacheBusterValue) . '
444
	};
445
})();';
446
		$response = new DataDownloadResponse($responseJS, 'javascript', 'text/javascript');
447
		$response->cacheFor(3600);
448
		return $response;
449
	}
450
451
	/**
452
	 * @NoCSRFRequired
453
	 * @PublicPage
454
	 *
455
	 * @return Http\JSONResponse
456
	 */
457
	public function getManifest($app) {
458
		$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
459
		$responseJS = [
460
			'name' => $this->themingDefaults->getName(),
461
			'start_url' => $this->urlGenerator->getBaseUrl(),
462
			'icons' =>
463
				[
464
					[
465
						'src' => $this->urlGenerator->linkToRoute('theming.Icon.getTouchIcon',
466
								['app' => $app]) . '?v=' . $cacheBusterValue,
467
						'type'=> 'image/png',
468
						'sizes'=> '128x128'
469
					],
470
					[
471
						'src' => $this->urlGenerator->linkToRoute('theming.Icon.getFavicon',
472
								['app' => $app]) . '?v=' . $cacheBusterValue,
473
						'type' => 'image/svg+xml',
474
						'sizes' => '16x16'
475
					]
476
				],
477
			'display' => 'standalone'
478
		];
479
		$response = new Http\JSONResponse($responseJS);
480
		$response->cacheFor(3600);
481
		return $response;
482
	}
483
}
484