Completed
Push — master ( 99edba...50a6a7 )
by Roeland
11:29
created

ThemingController::getJavascript()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
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 View Code Duplication
	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
		switch ($setting) {
139 View Code Duplication
			case 'name':
140
				if (strlen($value) > 250) {
141
					return new DataResponse([
142
						'data' => [
143
							'message' => $this->l10n->t('The given name is too long'),
144
						],
145
						'status' => 'error'
146
					]);
147
				}
148
				break;
149 View Code Duplication
			case 'url':
150
				if (strlen($value) > 500) {
151
					return new DataResponse([
152
						'data' => [
153
							'message' => $this->l10n->t('The given web address is too long'),
154
						],
155
						'status' => 'error'
156
					]);
157
				}
158
				break;
159 View Code Duplication
			case 'imprintUrl':
160
				if (strlen($value) > 500) {
161
					return new DataResponse([
162
						'data' => [
163
							'message' => $this->l10n->t('The given legal notice address is too long'),
164
						],
165
						'status' => 'error'
166
					]);
167
				}
168
				break;
169 View Code Duplication
			case 'privacyUrl':
170
				if (strlen($value) > 500) {
171
					return new DataResponse([
172
						'data' => [
173
							'message' => $this->l10n->t('The given privacy policy address is too long'),
174
						],
175
						'status' => 'error'
176
					]);
177
				}
178
				break;
179 View Code Duplication
			case 'slogan':
180
				if (strlen($value) > 500) {
181
					return new DataResponse([
182
						'data' => [
183
							'message' => $this->l10n->t('The given slogan is too long'),
184
						],
185
						'status' => 'error'
186
					]);
187
				}
188
				break;
189 View Code Duplication
			case 'color':
190
				if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) {
191
					return new DataResponse([
192
						'data' => [
193
							'message' => $this->l10n->t('The given color is invalid'),
194
						],
195
						'status' => 'error'
196
					]);
197
				}
198
				break;
199
		}
200
201
		$this->themingDefaults->set($setting, $value);
202
203
		// reprocess server scss for preview
204
		$cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
0 ignored issues
show
Unused Code introduced by
$cssCached is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
205
206
		return new DataResponse(
207
			[
208
				'data' =>
209
					[
210
						'message' => $this->l10n->t('Saved'),
211
						'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
212
					],
213
				'status' => 'success'
214
			]
215
		);
216
	}
217
218
	/**
219
	 * @return DataResponse
220
	 * @throws NotPermittedException
221
	 */
222
	public function uploadImage(): DataResponse {
223
		// logo / background
224
		// new: favicon logo-header
225
		//
226
		$key = $this->request->getParam('key');
227
		$image = $this->request->getUploadedFile('image');
228
		$error = null;
229
		$phpFileUploadErrors = [
230
			UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'),
231
			UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
232
			UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
233
			UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'),
234
			UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'),
235
			UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'),
236
			UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'),
237
			UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'),
238
		];
239
		if (empty($image)) {
240
			$error = $this->l10n->t('No file uploaded');
241
		}
242
		if (!empty($image) && array_key_exists('error', $image) && $image['error'] !== UPLOAD_ERR_OK) {
243
			$error = $phpFileUploadErrors[$image['error']];
244
		}
245
246
		if ($error !== null) {
247
			return new DataResponse(
248
				[
249
					'data' => [
250
						'message' => $error
251
					],
252
					'status' => 'failure',
253
				],
254
				Http::STATUS_UNPROCESSABLE_ENTITY
255
			);
256
		}
257
258
		$name = '';
0 ignored issues
show
Unused Code introduced by
$name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
259
		try {
260
			$folder = $this->appData->getFolder('images');
261
		} catch (NotFoundException $e) {
262
			$folder = $this->appData->newFolder('images');
263
		}
264
265
		$this->imageManager->delete($key);
266
267
		$target = $folder->newFile($key);
268
		$supportedFormats = $this->getSupportedUploadImageFormats($key);
269
		$detectedMimeType = mime_content_type($image['tmp_name']);
270
		if (!in_array($image['type'], $supportedFormats) || !in_array($detectedMimeType, $supportedFormats)) {
271
			return new DataResponse(
272
				[
273
					'data' => [
274
						'message' => $this->l10n->t('Unsupported image type'),
275
					],
276
					'status' => 'failure',
277
				],
278
				Http::STATUS_UNPROCESSABLE_ENTITY
279
			);
280
		}
281
282
		$resizeKeys = ['background'];
283
		if (in_array($key, $resizeKeys, true)) {
284
			// Optimize the image since some people may upload images that will be
285
			// either to big or are not progressive rendering.
286
			$newImage = @imagecreatefromstring(file_get_contents($image['tmp_name'], 'r'));
287
288
			$tmpFile = $this->tempManager->getTemporaryFile();
289
			$newWidth = imagesx($newImage) < 4096 ? imagesx($newImage) : 4096;
290
			$newHeight = imagesy($newImage) / (imagesx($newImage) / $newWidth);
291
			$outputImage = imagescale($newImage, $newWidth, $newHeight);
292
293
			imageinterlace($outputImage, 1);
294
			imagejpeg($outputImage, $tmpFile, 75);
295
			imagedestroy($outputImage);
296
297
			$target->putContent(file_get_contents($tmpFile, 'r'));
298
		} else {
299
			$target->putContent(file_get_contents($image['tmp_name'], 'r'));
300
		}
301
		$name = $image['name'];
302
303
		$this->themingDefaults->set($key.'Mime', $image['type']);
304
305
		$cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
0 ignored issues
show
Unused Code introduced by
$cssCached is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
306
307
		return new DataResponse(
308
			[
309
				'data' =>
310
					[
311
						'name' => $name,
312
						'url' => $this->imageManager->getImageUrl($key),
313
						'message' => $this->l10n->t('Saved'),
314
						'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
315
					],
316
				'status' => 'success'
317
			]
318
		);
319
	}
320
321
	/**
322
	 * Returns a list of supported mime types for image uploads.
323
	 * "favicon" images are only allowed to be SVG when imagemagick with SVG support is available.
324
	 *
325
	 * @param string $key The image key, e.g. "favicon"
326
	 * @return array
327
	 */
328
	private function getSupportedUploadImageFormats(string $key): array {
329
		$supportedFormats = ['image/jpeg', 'image/png', 'image/gif',];
330
331
		if ($key !== 'favicon' || $this->imageManager->shouldReplaceIcons() === true) {
332
			$supportedFormats[] = 'image/svg+xml';
333
			$supportedFormats[] = 'image/svg';
334
		}
335
336
		return $supportedFormats;
337
	}
338
339
	/**
340
	 * Revert setting to default value
341
	 *
342
	 * @param string $setting setting which should be reverted
343
	 * @return DataResponse
344
	 * @throws NotPermittedException
345
	 */
346
	public function undo(string $setting): DataResponse {
347
		$value = $this->themingDefaults->undo($setting);
348
		// reprocess server scss for preview
349
		$cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
0 ignored issues
show
Unused Code introduced by
$cssCached is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
350
351
		if (strpos($setting, 'Mime') !== -1) {
352
			$imageKey = str_replace('Mime', '', $setting);
353
			$this->imageManager->delete($imageKey);
354
		}
355
356
		return new DataResponse(
357
			[
358
				'data' =>
359
					[
360
						'value' => $value,
361
						'message' => $this->l10n->t('Saved'),
362
						'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
363
					],
364
				'status' => 'success'
365
			]
366
		);
367
	}
368
369
	/**
370
	 * @PublicPage
371
	 * @NoCSRFRequired
372
	 *
373
	 * @param string $key
374
	 * @param bool $useSvg
375
	 * @return FileDisplayResponse|NotFoundResponse
376
	 * @throws NotPermittedException
377
	 */
378
	public function getImage(string $key, bool $useSvg = true) {
379
		try {
380
			$file = $this->imageManager->getImage($key, $useSvg);
381
		} catch (NotFoundException $e) {
382
			return new NotFoundResponse();
383
		}
384
385
		$response = new FileDisplayResponse($file);
386
		$response->cacheFor(3600);
387
		$response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
388
		$response->addHeader('Content-Disposition', 'attachment; filename="' . $key . '"');
389
		if (!$useSvg) {
390
			$response->addHeader('Content-Type', 'image/png');
391
		} else {
392
			$response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
393
		}
394
		return $response;
395
	}
396
397
	/**
398
	 * @NoCSRFRequired
399
	 * @PublicPage
400
	 *
401
	 * @return FileDisplayResponse|NotFoundResponse
402
	 * @throws NotPermittedException
403
	 * @throws \Exception
404
	 * @throws \OCP\App\AppPathNotFoundException
405
	 */
406
	public function getStylesheet() {
407
		$appPath = $this->appManager->getAppPath('theming');
408
409
		/* SCSSCacher is required here
410
		 * We cannot rely on automatic caching done by \OC_Util::addStyle,
411
		 * since we need to add the cacheBuster value to the url
412
		 */
413
		$cssCached = $this->scssCacher->process($appPath, 'css/theming.scss', 'theming');
414
		if(!$cssCached) {
415
			return new NotFoundResponse();
416
		}
417
418
		try {
419
			$cssFile = $this->scssCacher->getCachedCSS('theming', 'theming.css');
420
			$response = new FileDisplayResponse($cssFile, Http::STATUS_OK, ['Content-Type' => 'text/css']);
421
			$response->cacheFor(86400);
422
			return $response;
423
		} catch (NotFoundException $e) {
424
			return new NotFoundResponse();
425
		}
426
	}
427
428
	/**
429
	 * @NoCSRFRequired
430
	 * @PublicPage
431
	 *
432
	 * @return DataDownloadResponse
433
	 */
434
	public function getJavascript() {
435
		$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
436
		$responseJS = '(function() {
437
	OCA.Theming = {
438
		name: ' . json_encode($this->themingDefaults->getName()) . ',
439
		url: ' . json_encode($this->themingDefaults->getBaseUrl()) . ',
440
		slogan: ' . json_encode($this->themingDefaults->getSlogan()) . ',
441
		color: ' . json_encode($this->themingDefaults->getColorPrimary()) . ',
442
		imprintUrl: ' . json_encode($this->themingDefaults->getImprintUrl()) . ',
443
		privacyUrl: ' . json_encode($this->themingDefaults->getPrivacyUrl()) . ',
444
		inverted: ' . json_encode($this->util->invertTextColor($this->themingDefaults->getColorPrimary())) . ',
445
		cacheBuster: ' . json_encode($cacheBusterValue) . '
446
	};
447
})();';
448
		$response = new DataDownloadResponse($responseJS, 'javascript', 'text/javascript');
449
		$response->cacheFor(3600);
450
		return $response;
451
	}
452
453
	/**
454
	 * @NoCSRFRequired
455
	 * @PublicPage
456
	 *
457
	 * @return Http\JSONResponse
458
	 */
459
	public function getManifest($app) {
460
		$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
461
		$responseJS = [
462
			'name' => $this->themingDefaults->getName(),
463
			'start_url' => $this->urlGenerator->getBaseUrl(),
464
			'icons' =>
465
				[
466
					[
467
						'src' => $this->urlGenerator->linkToRoute('theming.Icon.getTouchIcon',
468
								['app' => $app]) . '?v=' . $cacheBusterValue,
469
						'type'=> 'image/png',
470
						'sizes'=> '128x128'
471
					],
472
					[
473
						'src' => $this->urlGenerator->linkToRoute('theming.Icon.getFavicon',
474
								['app' => $app]) . '?v=' . $cacheBusterValue,
475
						'type' => 'image/svg+xml',
476
						'sizes' => '16x16'
477
					]
478
				],
479
			'display' => 'standalone'
480
		];
481
		$response = new Http\JSONResponse($responseJS);
482
		$response->cacheFor(3600);
483
		return $response;
484
	}
485
}
486