Completed
Pull Request — master (#39)
by Olivier
08:23 queued 38s
created

ConfigService   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 353
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 98.15%

Importance

Changes 5
Bugs 1 Features 0
Metric Value
wmc 33
c 5
b 1
f 0
lcom 2
cbo 6
dl 0
loc 353
ccs 106
cts 108
cp 0.9815
rs 9.3999

13 Methods

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