ConfigService   A
last analyzed

Complexity

Total Complexity 34

Size/Duplication

Total Lines 332
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 99
dl 0
loc 332
rs 9.68
c 1
b 0
f 0
wmc 34

13 Methods

Rating   Name   Duplication   Size   Complexity  
A isMimeSupported() 0 7 2
A configExists() 0 2 2
A getConfig() 0 11 2
A buildFolderConfig() 0 11 2
A __construct() 0 11 1
A validateMimeType() 0 3 2
A validatesInfoConfig() 0 14 4
A getParentConfig() 0 7 1
A getSupportedMediaTypes() 0 18 4
A collectConfig() 0 20 5
A addSvgSupport() 0 6 3
A buildErrorMessage() 0 13 2
A getFeaturesList() 0 14 4
1
<?php
2
/**
3
 * Nextcloud - Gallery
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Olivier Paroz <[email protected]>
9
 *
10
 * @copyright Olivier Paroz 2017
11
 */
12
13
namespace OCA\Gallery\Service;
14
15
use OCP\Files\Folder;
0 ignored issues
show
Bug introduced by
The type OCP\Files\Folder was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use OCP\IPreview;
0 ignored issues
show
Bug introduced by
The type OCP\IPreview was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use OCP\ILogger;
0 ignored issues
show
Bug introduced by
The type OCP\ILogger was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
19
use OCA\Gallery\Config\ConfigParser;
20
use OCA\Gallery\Config\ConfigException;
21
use OCA\Gallery\Environment\Environment;
22
23
/**
24
 * Finds configurations files and returns a configuration array
25
 *
26
 * Checks the current and parent folders for configuration files and to see if we're allowed to
27
 * look for media file
28
 * Supports explicit inheritance
29
 *
30
 * @package OCA\Gallery\Service
31
 */
32
class ConfigService extends FilesService {
33
34
	/** @var string */
35
	private $configName = 'gallery.cnf';
36
	/** @var array <string,bool> */
37
	private $completionStatus = ['design' => false, 'information' => false, 'sorting' => false];
38
	/** @var ConfigParser */
39
	private $configParser;
40
	/** @var IPreview */
41
	private $previewManager;
42
	/**
43
	 * @todo This hard-coded array could be replaced by admin settings
44
	 *
45
	 * @var string[]
46
	 */
47
	private $baseMimeTypes = [
48
		'image/png',
49
		'image/jpeg',
50
		'image/gif',
51
		'image/x-xbitmap',
52
		'image/bmp',
53
		'image/tiff',
54
		'image/x-dcraw',
55
		'image/heic',
56
		'image/heif',
57
		'application/x-photoshop',
58
		'application/illustrator',
59
		'application/postscript',
60
	];
61
	/**
62
	 * These types are useful for files preview in the files app, but
63
	 * not for the gallery side
64
	 *
65
	 * @var string[]
66
	 */
67
	private $slideshowMimeTypes = [
68
		'application/font-sfnt',
69
		'application/x-font',
70
	];
71
72
	/**
73
	 * Constructor
74
	 *
75
	 * @param string $appName
76
	 * @param Environment $environment
77
	 * @param ConfigParser $configParser
78
	 * @param IPreview $previewManager
79
	 * @param ILogger $logger
80
	 */
81
	public function __construct(
82
		$appName,
83
		Environment $environment,
84
		ConfigParser $configParser,
85
		IPreview $previewManager,
86
		ILogger $logger
87
	) {
88
		parent::__construct($appName, $environment, $logger);
89
90
		$this->configParser = $configParser;
91
		$this->previewManager = $previewManager;
92
	}
93
94
	/**
95
	 * Returns a list of supported features
96
	 *
97
	 * @return string[]
98
	 */
99
	public function getFeaturesList() {
100
		$featuresList = [];
101
		/** @var Folder $rootFolder */
102
		$rootFolder = $this->environment->getVirtualRootFolder();
103
		if ($this->isAllowedAndAvailable($rootFolder) && $this->configExists($rootFolder)) {
104
			try {
105
				$featuresList =
106
					$this->configParser->getFeaturesList($rootFolder, $this->configName);
107
			} catch (ConfigException $exception) {
108
				$featuresList = $this->buildErrorMessage($exception, $rootFolder);
109
			}
110
		}
111
112
		return $featuresList;
113
	}
114
115
	/**
116
	 * This builds and returns a list of all supported media types
117
	 *
118
	 * @todo Native SVG could be disabled via admin settings
119
	 *
120
	 * @param bool $extraMediaTypes
121
	 * @param bool $nativeSvgSupport
122
	 *
123
	 * @return string[] all supported media types
124
	 */
125
	public function getSupportedMediaTypes($extraMediaTypes, $nativeSvgSupport) {
126
		$supportedMimes = [];
127
		$wantedMimes = $this->baseMimeTypes;
128
		if ($extraMediaTypes) {
129
			$wantedMimes = array_merge($wantedMimes, $this->slideshowMimeTypes);
130
		}
131
		foreach ($wantedMimes as $wantedMime) {
132
			// Let's see if a preview of files of that media type can be generated
133
			if ($this->isMimeSupported($wantedMime)) {
134
				// We store the media type
135
				$supportedMimes[] = $wantedMime;
136
			}
137
		}
138
		$supportedMimes = $this->addSvgSupport($supportedMimes, $nativeSvgSupport);
139
140
		//$this->logger->debug("Supported Mimes: {mimes}", ['mimes' => $supportedMimes]);
141
142
		return $supportedMimes;
143
	}
144
145
	/**
146
	 * Returns the configuration of the currently selected folder
147
	 *
148
	 *    * information (description, copyright)
149
	 *    * sorting (date, name, inheritance)
150
	 *    * design (colour)
151
	 *    * if the album should be ignored
152
	 *
153
	 * @param Folder $folderNode the current folder
154
	 * @param array $features the list of features retrieved fro the configuration file
155
	 *
156
	 * @return array|null
157
	 * @throws ForbiddenServiceException
158
	 */
159
	public function getConfig($folderNode, $features) {
160
		$this->features = $features;
161
		list ($albumConfig, $ignored) =
162
			$this->collectConfig($folderNode, $this->ignoreAlbumStrings, $this->configName);
163
		if ($ignored) {
164
			throw new ForbiddenServiceException(
165
				'The owner has placed a restriction or the storage location is unavailable'
166
			);
167
		}
168
169
		return $albumConfig;
170
	}
171
172
	/**
173
	 * Throws an exception if the media type of the file is not part of what the app allows
174
	 *
175
	 * @param $mimeType
176
	 *
177
	 * @throws ForbiddenServiceException
178
	 */
179
	public function validateMimeType($mimeType) {
180
		if (!in_array($mimeType, $this->getSupportedMediaTypes(true, true))) {
181
			throw new ForbiddenServiceException('Media type not allowed');
182
		}
183
	}
184
185
	/**
186
	 * Determines if we have a configuration file to work with
187
	 *
188
	 * @param Folder $rootFolder the virtual root folder
189
	 *
190
	 * @return bool
191
	 */
192
	private function configExists($rootFolder) {
193
		return $rootFolder && $rootFolder->nodeExists($this->configName);
194
	}
195
196
	/**
197
	 * Adds the SVG media type if it's not already there
198
	 *
199
	 * If it's enabled, but doesn't work, an exception will be raised when trying to generate a
200
	 * preview. If it's disabled, we support it via the browser's native support
201
	 *
202
	 * @param string[] $supportedMimes
203
	 * @param bool $nativeSvgSupport
204
	 *
205
	 * @return string[]
206
	 */
207
	private function addSvgSupport($supportedMimes, $nativeSvgSupport) {
208
		if (!in_array('image/svg+xml', $supportedMimes) && $nativeSvgSupport) {
209
			$supportedMimes[] = 'image/svg+xml';
210
		}
211
212
		return $supportedMimes;
213
	}
214
215
	/**
216
	 * Returns true if the passed mime type is supported
217
	 *
218
	 * In case of a failure, we just return that the media type is not supported
219
	 *
220
	 * @param string $mimeType
221
	 *
222
	 * @return boolean
223
	 */
224
	private function isMimeSupported($mimeType = '*') {
225
		try {
226
			return $this->previewManager->isMimeSupported($mimeType);
227
		} catch (\Exception $exception) {
228
			unset($exception);
229
230
			return false;
231
		}
232
	}
233
234
	/**
235
	 * Returns an album configuration array
236
	 *
237
	 * Goes through all the parent folders until either we're told the album is private or we've
238
	 * reached the root folder
239
	 *
240
	 * @param Folder $folder the current folder
241
	 * @param array $ignoreAlbumStrings names of the files which blacklist folders
242
	 * @param string $configName name of the configuration file
243
	 * @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
244
	 * @param array $configSoFar the configuration collected so far
245
	 *
246
	 * @return array <null|array,bool>
247
	 */
248
	private function collectConfig(
249
		$folder, $ignoreAlbumStrings, $configName, $level = 0, $configSoFar = []
250
	) {
251
		foreach ($ignoreAlbumStrings as $ignoreAlbum) {
252
			if ($folder->nodeExists($ignoreAlbum)) {
253
				// Cancel as soon as we find out that the folder is private or external
254
				return [null, true];
255
			}
256
		}
257
		$isRootFolder = $this->isRootFolder($folder, $level);
258
		if ($folder->nodeExists($configName)) {
259
			$configSoFar = $this->buildFolderConfig($folder, $configName, $configSoFar, $level);
260
		}
261
		if (!$isRootFolder) {
262
			return $this->getParentConfig($folder, $ignoreAlbumStrings, $configName, $level, $configSoFar);
0 ignored issues
show
Bug introduced by
$ignoreAlbumStrings of type array is incompatible with the type string expected by parameter $privacyChecker of OCA\Gallery\Service\Conf...vice::getParentConfig(). ( Ignorable by Annotation )

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

262
			return $this->getParentConfig($folder, /** @scrutinizer ignore-type */ $ignoreAlbumStrings, $configName, $level, $configSoFar);
Loading history...
263
		}
264
		$configSoFar = $this->validatesInfoConfig($configSoFar);
265
266
		// We have reached the root folder
267
		return [$configSoFar, false];
268
	}
269
270
	/**
271
	 * Returns a parsed configuration if one was found in the current folder or generates an error
272
	 * message to send back
273
	 *
274
	 * @param Folder $folder the current folder
275
	 * @param string $configName name of the configuration file
276
	 * @param array $collectedConfig the configuration collected so far
277
	 * @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
278
	 *
279
	 * @return array
280
	 */
281
	private function buildFolderConfig($folder, $configName, $collectedConfig, $level) {
282
		try {
283
			list($collectedConfig, $completionStatus) = $this->configParser->getFolderConfig(
284
				$folder, $configName, $collectedConfig, $this->completionStatus, $level
285
			);
286
			$this->completionStatus = $completionStatus;
287
		} catch (ConfigException $exception) {
288
			$collectedConfig = $this->buildErrorMessage($exception, $folder);
289
		}
290
291
		return $collectedConfig;
292
	}
293
294
	/**
295
	 * Builds the error message to send back when there is an error
296
	 *
297
	 * @fixme Missing translation
298
	 *
299
	 * @param ConfigException $exception
300
	 * @param Folder $folder the current folder
301
	 *
302
	 * @return array<array<string,string>,bool>
303
	 */
304
	private function buildErrorMessage($exception, $folder) {
305
		$configPath = $this->environment->getPathFromVirtualRoot($folder);
306
		$errorMessage = $exception->getMessage() . ". Config location: /$configPath";
307
		$this->logger->error($errorMessage);
308
		$config = ['error' => ['message' => $errorMessage]];
309
310
		$completionStatus = $this->completionStatus;
311
		foreach ($completionStatus as $key) {
312
			$completionStatus[$key] = true;
313
		}
314
		$this->completionStatus = $completionStatus;
315
316
		return [$config];
317
	}
318
319
	/**
320
	 * Removes links if they were collected outside of the virtual root
321
	 *
322
	 * This is for shared folders which have a virtual root
323
	 *
324
	 * @param array $albumConfig
325
	 *
326
	 * @return array
327
	 */
328
	private function validatesInfoConfig($albumConfig) {
329
		$this->virtualRootLevel;
330
		if (array_key_exists('information', $albumConfig)) {
331
			$info = $albumConfig['information'];
332
			if (array_key_exists('level', $info)) {
333
				$level = $info['level'];
334
				if ($level > $this->virtualRootLevel) {
335
					$albumConfig['information']['description_link'] = null;
336
					$albumConfig['information']['copyright_link'] = null;
337
				}
338
			}
339
		}
340
341
		return $albumConfig;
342
	}
343
344
	/**
345
	 * Looks for an album configuration in the parent folder
346
	 *
347
	 * We will look up to the virtual root of a shared folder, for privacy reasons
348
	 *
349
	 * @param Folder $folder the current folder
350
	 * @param string $privacyChecker names of the files which blacklist folders
351
	 * @param string $configName name of the configuration file
352
	 * @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
353
	 * @param array $collectedConfig the configuration collected so far
354
	 *
355
	 * @return array<null|array,bool>
356
	 */
357
	private function getParentConfig($folder, $privacyChecker, $configName, $level, $collectedConfig
358
	) {
359
		$parentFolder = $folder->getParent();
360
		$level++;
361
362
		return $this->collectConfig(
363
			$parentFolder, $privacyChecker, $configName, $level, $collectedConfig
0 ignored issues
show
Bug introduced by
$privacyChecker of type string is incompatible with the type array expected by parameter $ignoreAlbumStrings of OCA\Gallery\Service\ConfigService::collectConfig(). ( Ignorable by Annotation )

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

363
			$parentFolder, /** @scrutinizer ignore-type */ $privacyChecker, $configName, $level, $collectedConfig
Loading history...
364
		);
365
	}
366
367
}
368