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

ConfigParser   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 225
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 26
c 2
b 0
f 0
lcom 1
cbo 3
dl 0
loc 225
ccs 66
cts 66
cp 1
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A isConfigUsable() 0 9 3
A buildAlbumConfig() 0 15 4
A isConfigInheritable() 0 12 3
A getFeaturesList() 0 10 2
A getFolderConfig() 0 8 1
A parseConfig() 0 17 2
A parseFeatures() 0 8 2
A bomFixer() 0 8 2
A isConfigItemComplete() 0 5 3
A addConfigItem() 0 12 3
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\Config;
14
15
use Symfony\Component\Yaml\Parser;
16
use Symfony\Component\Yaml\Exception\ParseException;
17
18
use OCP\Files\Folder;
19
use OCP\Files\File;
20
21
/**
22
 * Parses configuration files
23
 *
24
 * @package OCA\GalleryPlus\Config
25
 */
26
class ConfigParser {
27
	/** @var ConfigValidator */
28
	private $configValidator;
29
30
	/**
31
	 * Constructor
32
	 */
33 49
	public function __construct() {
34 49
		$this->configValidator = new ConfigValidator();
35 49
	}
36
37
	/**
38
	 * Returns a parsed global configuration if one was found in the root folder
39
	 *
40
	 * @param Folder $folder the current folder
41
	 * @param string $configName name of the configuration file
42
	 *
43
	 * @return null|array
44
	 */
45 12
	public function getFeaturesList($folder, $configName) {
46 12
		$featuresList = [];
47 12
		$parsedConfig = $this->parseConfig($folder, $configName);
48 10
		$key = 'features';
49 10
		if (array_key_exists($key, $parsedConfig)) {
50 7
			$featuresList = $this->parseFeatures($parsedConfig[$key]);
51
		}
52
53 10
		return $featuresList;
54
	}
55
56
	/**
57
	 * Returns a parsed configuration if one was found in the current folder
58
	 *
59
	 * @param Folder $folder the current folder
60
	 * @param string $configName name of the configuration file
61
	 * @param array $currentConfig the configuration collected so far
62
	 * @param array <string,bool> $completionStatus determines if we already have all we need for a
63
	 *     config sub-section
64
	 * @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
65
	 *
66
	 * @return array <null|array,array<string,bool>>
67
	 * @throws ConfigException
68
	 */
69 12
	public function getFolderConfig($folder, $configName, $currentConfig, $completionStatus, $level
70
	) {
71 12
		$parsedConfig = $this->parseConfig($folder, $configName);
72
		list($config, $completionStatus) =
73 12
			$this->buildAlbumConfig($currentConfig, $parsedConfig, $completionStatus, $level);
74
75 12
		return [$config, $completionStatus];
76
	}
77
78
	/**
79
	 * Returns a parsed configuration
80
	 *
81
	 * @param Folder $folder the current folder
82
	 * @param string $configName
83
	 *
84
	 * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string|\stdClass? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
85
	 *
86
	 * @throws ConfigException
87
	 */
88 24
	private function parseConfig($folder, $configName) {
89
		try {
90
			/** @var File $configFile */
91 24
			$configFile = $folder->get($configName);
92 23
			$rawConfig = $configFile->getContent();
93 23
			$saneConfig = $this->bomFixer($rawConfig);
94 23
			$yaml = new Parser();
95 23
			$parsedConfig = $yaml->parse($saneConfig);
96
97
			//\OC::$server->getLogger()->debug("rawConfig : {path}", ['path' => $rawConfig]);
98
99 22
			return $parsedConfig;
100 2
		} catch (\Exception  $exception) {
101 2
			$errorMessage = "Problem while reading or parsing the configuration file";
102 2
			throw new ConfigException($errorMessage);
103
		}
104
	}
105
106
	/**
107
	 * Returns only the features which have been enabled
108
	 *
109
	 * @param array <string,string> $featuresList the list of features collected from the
110
	 *     configuration file
111
	 *
112
	 * @return array
113
	 */
114 7
	private function parseFeatures($featuresList) {
115 7
		$parsedFeatures = $featuresList;
116 7
		if (!empty($parsedFeatures)) {
117 5
			$parsedFeatures = array_keys($featuresList, 'yes');
118
		}
119
120 7
		return $parsedFeatures;
121
	}
122
123
	/**
124
	 * Removes the BOM from a file
125
	 *
126
	 * http://us.php.net/manual/en/function.pack.php#104151
127
	 *
128
	 * @param string $file
129
	 *
130
	 * @return string
131
	 */
132 23
	private function bomFixer($file) {
133 23
		$bom = pack("CCC", 0xef, 0xbb, 0xbf);
134 23
		if (strncmp($file, $bom, 3) === 0) {
135 3
			$file = substr($file, 3);
136
		}
137
138 23
		return $file;
139
	}
140
141
	/**
142
	 * Returns either the local config or one merged with a config containing sorting information
143
	 *
144
	 * @param array $currentConfig the configuration collected so far
145
	 * @param array $parsedConfig the configuration collected in the current folder
146
	 * @param array <string,bool> $completionStatus determines if we already have all we need for a
147
	 *     config sub-section
148
	 * @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
149
	 *
150
	 * @return array <null|array,array<string,bool>>
151
	 */
152 12
	private function buildAlbumConfig($currentConfig, $parsedConfig, $completionStatus, $level) {
153 12
		foreach ($completionStatus as $key => $complete) {
154 12
			if (!$this->isConfigItemComplete($key, $parsedConfig, $complete)) {
155 12
				$parsedConfigItem = $parsedConfig[$key];
156 12
				if ($this->isConfigUsable($key, $parsedConfigItem, $level)) {
157
					list($configItem, $itemComplete) =
158 9
						$this->addConfigItem($key, $parsedConfigItem, $level);
159 9
					$currentConfig = array_merge($currentConfig, $configItem);
160 12
					$completionStatus[$key] = $itemComplete;
161
				}
162
			}
163
		}
164
165 12
		return [$currentConfig, $completionStatus];
166
	}
167
168
	/**
169
	 * Determines if we already have everything we need for this configuration sub-section
170
	 *
171
	 * @param string $key the configuration sub-section identifier
172
	 * @param array $parsedConfig the configuration for that sub-section
173
	 * @param bool $complete
174
	 *
175
	 * @return bool
176
	 */
177 12
	private function isConfigItemComplete($key, $parsedConfig, $complete) {
178 12
		return !(!$complete
179 12
				 && array_key_exists($key, $parsedConfig)
180 12
				 && !empty($parsedConfig[$key]));
181
	}
182
183
	/**
184
	 * Determines if we can use this configuration sub-section
185
	 *
186
	 * It's possible in two cases:
187
	 *    * the configuration was collected from the currently opened folder
188
	 *    * the configuration was collected in a parent folder and is inheritable
189
	 *
190
	 * We also need to make sure that the values contained in the configuration are safe for web use
191
	 *
192
	 * @param string $key the configuration sub-section identifier
193
	 * @param array $parsedConfigItem the configuration for a sub-section
194
	 * @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
195
	 *
196
	 * @return bool
197
	 */
198 12
	private function isConfigUsable($key, $parsedConfigItem, $level) {
199 12
		$inherit = $this->isConfigInheritable($parsedConfigItem);
200
201 12
		$usable = $level === 0 || $inherit;
202
203 12
		$safe = $this->configValidator->isConfigSafe($key, $parsedConfigItem);
204
205 12
		return $usable && $safe;
206
	}
207
208
	/**
209
	 * Adds a config sub-section to the global config
210
	 *
211
	 * @param string $key the configuration sub-section identifier
212
	 * @param array $parsedConfigItem the configuration for a sub-section
213
	 * @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
214
	 *
215
	 * @return array<null|array<string,string>,bool>
216
	 */
217 9
	private function addConfigItem($key, $parsedConfigItem, $level) {
218 9
		if ($key === 'sorting' && !array_key_exists('type', $parsedConfigItem)) {
219
220 1
			return [[], false];
221
		} else {
222 8
			$parsedConfigItem['level'] = $level;
223 8
			$configItem = [$key => $parsedConfigItem];
224 8
			$itemComplete = true;
225
226 8
			return [$configItem, $itemComplete];
227
		}
228
	}
229
230
	/**
231
	 * Determines if we can use a configuration sub-section found in parent folders
232
	 *
233
	 * @param array $parsedConfigItem the configuration for a sub-section
234
	 *
235
	 * @return bool
236
	 */
237 12
	private function isConfigInheritable($parsedConfigItem) {
238 12
		$inherit = false;
239 12
		if (array_key_exists('inherit', $parsedConfigItem)) {
240 8
			$inherit = $parsedConfigItem['inherit'];
241
		}
242
243 12
		if ($inherit === 'yes') {
244 7
			$inherit = true;
245
		}
246
247 12
		return $inherit;
248
	}
249
250
}
251