Completed
Pull Request — stable9 (#52)
by Olivier
09:56
created

ConfigService   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 332
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 100%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 33
c 4
b 0
f 0
lcom 2
cbo 6
dl 0
loc 332
ccs 86
cts 86
cp 1
rs 9.3999

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getConfig() 0 12 2
A validateMimeType() 0 5 2
A collectConfig() 0 19 4
A buildFolderConfig() 0 12 2
A buildErrorMessage() 0 14 2
A getParentConfig() 0 9 1
A __construct() 0 12 1
A getFeaturesList() 0 15 4
A getSupportedMediaTypes() 0 19 4
A configExists() 0 3 2
A addSvgSupport() 0 7 3
A isMimeSupported() 0 9 2
A validatesInfoConfig() 0 15 4
1
<?php
2
/**
3
 * ownCloud - galleryplus
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 2016
11
 */
12
13
namespace OCA\GalleryPlus\Service;
14
15
use OCP\Template;
16
use OCP\Files\Folder;
17
use OCP\ILogger;
18
19
use OCA\GalleryPlus\Config\ConfigParser;
20
use OCA\GalleryPlus\Config\ConfigException;
21
use OCA\GalleryPlus\Environment\Environment;
22
use OCA\GalleryPlus\Preview\Preview;
23
24
/**
25
 * Finds configurations files and returns a configuration array
26
 *
27
 * Checks the current and parent folders for configuration files and to see if we're allowed to
28
 * look for media file
29
 * Supports explicit inheritance
30
 *
31
 * @package OCA\GalleryPlus\Service
32
 */
33
class ConfigService extends FilesService {
34
35
	/** @var string */
36
	private $configName = 'gallery.cnf';
37
	/** @var array <string,bool> */
38
	private $completionStatus = ['design' => false, 'information' => false, 'sorting' => false];
39
	/** @var ConfigParser */
40
	private $configParser;
41
	/** @var Preview */
42
	private $previewManager;
43
	/**
44
	 * @todo This hard-coded array could be replaced by admin settings
45
	 *
46
	 * @var string[]
47
	 */
48
	private $baseMimeTypes = [
49
		'image/png',
50
		'image/jpeg',
51
		'image/gif',
52
		'image/x-xbitmap',
53
		'image/bmp',
54
		'image/tiff',
55
		'image/x-dcraw',
56
		'application/x-photoshop',
57
		'application/illustrator',
58
		'application/postscript',
59
	];
60
	/**
61
	 * These types are useful for files preview in the files app, but
62
	 * not for the gallery side
63
	 *
64
	 * @var string[]
65
	 */
66
	private $slideshowMimeTypes = [
67
		'application/font-sfnt',
68
		'application/x-font',
69
	];
70
71
	/**
72
	 * Constructor
73
	 *
74
	 * @param string $appName
75
	 * @param Environment $environment
76
	 * @param ConfigParser $configParser
77
	 * @param Preview $previewManager
78
	 * @param ILogger $logger
79
	 */
80 58
	public function __construct(
81
		$appName,
82
		Environment $environment,
83
		ConfigParser $configParser,
84
		Preview $previewManager,
85
		ILogger $logger
86
	) {
87 58
		parent::__construct($appName, $environment, $logger);
88
89 58
		$this->configParser = $configParser;
90 58
		$this->previewManager = $previewManager;
91 58
	}
92
93
	/**
94
	 * Returns a list of supported features
95
	 *
96
	 * @return string[]
97
	 */
98 6
	public function getFeaturesList() {
99 6
		$featuresList = [];
100
		/** @var Folder $rootFolder */
101 6
		$rootFolder = $this->environment->getVirtualRootFolder();
102 6
		if ($this->isAllowedAndAvailable($rootFolder) && $this->configExists($rootFolder)) {
103
			try {
104
				$featuresList =
105 5
					$this->configParser->getFeaturesList($rootFolder, $this->configName);
106 1
			} catch (ConfigException $exception) {
107 1
				$featuresList = $this->buildErrorMessage($exception, $rootFolder);
108
			}
109
		}
110
111 6
		return $featuresList;
112
	}
113
114
	/**
115
	 * This builds and returns a list of all supported media types
116
	 *
117
	 * @todo Native SVG could be disabled via admin settings
118
	 *
119
	 * @param bool $extraMediaTypes
120
	 * @param bool $nativeSvgSupport
121
	 *
122
	 * @return string[] all supported media types
123
	 */
124 33
	public function getSupportedMediaTypes($extraMediaTypes, $nativeSvgSupport) {
125 33
		$supportedMimes = [];
126 33
		$wantedMimes = $this->baseMimeTypes;
127 33
		if ($extraMediaTypes) {
128 25
			$wantedMimes = array_merge($wantedMimes, $this->slideshowMimeTypes);
129
		}
130 33
		foreach ($wantedMimes as $wantedMime) {
131
			// Let's see if a preview of files of that media type can be generated
132 33
			if ($this->isMimeSupported($wantedMime)) {
133
				// We store the media type
134 33
				$supportedMimes[] = $wantedMime;
135
			}
136
		}
137 33
		$supportedMimes = $this->addSvgSupport($supportedMimes, $nativeSvgSupport);
138
139
		//$this->logger->debug("Supported Mimes: {mimes}", ['mimes' => $supportedMimes]);
140
141 33
		return $supportedMimes;
142
	}
143
144
	/**
145
	 * Returns the configuration of the currently selected folder
146
	 *
147
	 *    * information (description, copyright)
148
	 *    * sorting (date, name, inheritance)
149
	 *    * design (colour)
150
	 *    * if the album should be ignored
151
	 *
152
	 * @param Folder $folderNode the current folder
153
	 * @param array $features the list of features retrieved fro the configuration file
154
	 *
155
	 * @return array|null
156
	 * @throws ForbiddenServiceException
157
	 */
158 6
	public function getConfig($folderNode, $features) {
159 6
		$this->features = $features;
160
		list ($albumConfig, $ignored) =
161 6
			$this->collectConfig($folderNode, $this->ignoreAlbum, $this->configName);
162 6
		if ($ignored) {
163 1
			throw new ForbiddenServiceException(
164 1
				'The owner has placed a restriction or the storage location is unavailable'
165
			);
166
		}
167
168 5
		return $albumConfig;
169
	}
170
171
	/**
172
	 * Throws an exception if the media type of the file is not part of what the app allows
173
	 *
174
	 * @param $mimeType
175
	 *
176
	 * @throws ForbiddenServiceException
177
	 */
178 22
	public function validateMimeType($mimeType) {
179 22
		if (!in_array($mimeType, $this->getSupportedMediaTypes(true, true))) {
180 6
			throw new ForbiddenServiceException('Media type not allowed');
181
		}
182 16
	}
183
184
	/**
185
	 * Determines if we have a configuration file to work with
186
	 *
187
	 * @param Folder $rootFolder the virtual root folder
188
	 *
189
	 * @return bool
190
	 */
191 6
	private function configExists($rootFolder) {
192 6
		return $rootFolder && $rootFolder->nodeExists($this->configName);
193
	}
194
195
	/**
196
	 * Adds the SVG media type if it's not already there
197
	 *
198
	 * If it's enabled, but doesn't work, an exception will be raised when trying to generate a
199
	 * preview. If it's disabled, we support it via the browser's native support
200
	 *
201
	 * @param string[] $supportedMimes
202
	 * @param bool $nativeSvgSupport
203
	 *
204
	 * @return string[]
205
	 */
206 37
	private function addSvgSupport($supportedMimes, $nativeSvgSupport) {
207 37
		if (!in_array('image/svg+xml', $supportedMimes) && $nativeSvgSupport) {
208 25
			$supportedMimes[] = 'image/svg+xml';
209
		}
210
211 37
		return $supportedMimes;
212
	}
213
214
	/**
215
	 * Returns true if the passed mime type is supported
216
	 *
217
	 * In case of a failure, we just return that the media type is not supported
218
	 *
219
	 * @param string $mimeType
220
	 *
221
	 * @return boolean
222
	 */
223 33
	private function isMimeSupported($mimeType = '*') {
224
		try {
225 33
			return $this->previewManager->isMimeSupported($mimeType);
226 1
		} catch (\Exception $exception) {
227 1
			unset($exception);
228
229 1
			return false;
230
		}
231
	}
232
233
	/**
234
	 * Returns an album configuration array
235
	 *
236
	 * Goes through all the parent folders until either we're told the album is private or we've
237
	 * reached the root folder
238
	 *
239
	 * @param Folder $folder the current folder
240
	 * @param string $ignoreAlbum name of the file which blacklists folders
241
	 * @param string $configName name of the configuration file
242
	 * @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
243
	 * @param array $configSoFar the configuration collected so far
244
	 *
245
	 * @return array <null|array,bool>
246
	 */
247 6
	private function collectConfig(
248
		$folder, $ignoreAlbum, $configName, $level = 0, $configSoFar = []
249
	) {
250 6
		if ($folder->nodeExists($ignoreAlbum)) {
251
			// Cancel as soon as we find out that the folder is private or external
252 1
			return [null, true];
253
		}
254 5
		$isRootFolder = $this->isRootFolder($folder, $level);
255 5
		if ($folder->nodeExists($configName)) {
256 4
			$configSoFar = $this->buildFolderConfig($folder, $configName, $configSoFar, $level);
257
		}
258 5
		if (!$isRootFolder) {
259 1
			return $this->getParentConfig($folder, $ignoreAlbum, $configName, $level, $configSoFar);
260
		}
261 5
		$configSoFar = $this->validatesInfoConfig($configSoFar);
262
263
		// We have reached the root folder
264 5
		return [$configSoFar, false];
265
	}
266
267
	/**
268
	 * Returns a parsed configuration if one was found in the current folder or generates an error
269
	 * message to send back
270
	 *
271
	 * @param Folder $folder the current folder
272
	 * @param string $configName name of the configuration file
273
	 * @param array $collectedConfig the configuration collected so far
274
	 * @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
275
	 *
276
	 * @return array
277
	 */
278 5
	private function buildFolderConfig($folder, $configName, $collectedConfig, $level) {
279
		try {
280 5
			list($collectedConfig, $completionStatus) = $this->configParser->getFolderConfig(
281 5
				$folder, $configName, $collectedConfig, $this->completionStatus, $level
282
			);
283 4
			$this->completionStatus = $completionStatus;
284 1
		} catch (ConfigException $exception) {
285 1
			$collectedConfig = $this->buildErrorMessage($exception, $folder);
286
		}
287
288 5
		return $collectedConfig;
289
	}
290
291
	/**
292
	 * Builds the error message to send back when there is an error
293
	 *
294
	 * @fixme Missing translation
295
	 *
296
	 * @param ConfigException $exception
297
	 * @param Folder $folder the current folder
298
	 *
299
	 * @return array<array<string,string>,bool>
300
	 */
301 2
	private function buildErrorMessage($exception, $folder) {
302 2
		$configPath = $this->environment->getPathFromVirtualRoot($folder);
303 2
		$errorMessage = $exception->getMessage() . ". Config location: /$configPath";
304 2
		$this->logger->error($errorMessage);
305 2
		$config = ['error' => ['message' => $errorMessage]];
306
307 2
		$completionStatus = $this->completionStatus;
308 2
		foreach ($completionStatus as $key) {
309 2
			$completionStatus[$key] = true;
310
		}
311 2
		$this->completionStatus = $completionStatus;
312
313 2
		return [$config];
314
	}
315
316
	/**
317
	 * Removes links if they were collected outside of the virtual root
318
	 *
319
	 * This is for shared folders which have a virtual root
320
	 *
321
	 * @param array $albumConfig
322
	 *
323
	 * @return array
324
	 */
325 8
	private function validatesInfoConfig($albumConfig) {
326 8
		$this->virtualRootLevel;
327 8
		if (array_key_exists('information', $albumConfig)) {
328 7
			$info = $albumConfig['information'];
329 7
			if (array_key_exists('level', $info)) {
330 7
				$level = $info['level'];
331 7
				if ($level > $this->virtualRootLevel) {
332 1
					$albumConfig['information']['description_link'] = null;
333 1
					$albumConfig['information']['copyright_link'] = null;
334
				}
335
			}
336
		}
337
338 8
		return $albumConfig;
339
	}
340
341
	/**
342
	 * Looks for an album configuration in the parent folder
343
	 *
344
	 * We will look up to the virtual root of a shared folder, for privacy reasons
345
	 *
346
	 * @param Folder $folder the current folder
347
	 * @param string $privacyChecker name of the file which blacklists folders
348
	 * @param string $configName name of the configuration file
349
	 * @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
350
	 * @param array $collectedConfig the configuration collected so far
351
	 *
352
	 * @return array<null|array,bool>
353
	 */
354 1
	private function getParentConfig($folder, $privacyChecker, $configName, $level, $collectedConfig
355
	) {
356 1
		$parentFolder = $folder->getParent();
357 1
		$level++;
358
359 1
		return $this->collectConfig(
360
			$parentFolder, $privacyChecker, $configName, $level, $collectedConfig
361
		);
362
	}
363
364
}
365