Passed
Push — master ( b72d27...03dc79 )
by Roeland
21:59 queued 11:25
created

DependencyAnalyzer::isMarkedCompatible()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 4
nop 1
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2016, Lukas Reschke <[email protected]>
5
 *
6
 * @author Bernhard Posselt <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author Stefan Weil <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 *
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OC\App;
31
32
use OCP\IConfig;
33
use OCP\IL10N;
34
35
class DependencyAnalyzer {
36
37
	/** @var Platform */
38
	private $platform;
39
	/** @var \OCP\IL10N */
40
	private $l;
41
	/** @var array */
42
	private $appInfo;
43
44
	/**
45
	 * @param Platform $platform
46
	 * @param \OCP\IL10N $l
47
	 */
48
	public function __construct(Platform $platform, IL10N $l) {
49
		$this->platform = $platform;
50
		$this->l = $l;
51
	}
52
53
	/**
54
	 * @param array $app
55
	 * @returns array of missing dependencies
56
	 */
57
	public function analyze(array $app, bool $ignoreMax = false) {
58
		$this->appInfo = $app;
59
		if (isset($app['dependencies'])) {
60
			$dependencies = $app['dependencies'];
61
		} else {
62
			$dependencies = [];
63
		}
64
65
		return array_merge(
66
			$this->analyzePhpVersion($dependencies),
67
			$this->analyzeDatabases($dependencies),
68
			$this->analyzeCommands($dependencies),
69
			$this->analyzeLibraries($dependencies),
70
			$this->analyzeOS($dependencies),
71
			$this->analyzeOC($dependencies, $app, $ignoreMax)
72
		);
73
	}
74
75
	public function isMarkedCompatible(array $app): bool {
76
		if (isset($app['dependencies'])) {
77
			$dependencies = $app['dependencies'];
78
		} else {
79
			$dependencies = [];
80
		}
81
82
		$maxVersion = $this->getMaxVersion($dependencies, $app);
83
		if ($maxVersion === null) {
84
			return true;
85
		}
86
		return !$this->compareBigger($this->platform->getOcVersion(), $maxVersion);
87
	}
88
89
	/**
90
	 * Truncates both versions to the lowest common version, e.g.
91
	 * 5.1.2.3 and 5.1 will be turned into 5.1 and 5.1,
92
	 * 5.2.6.5 and 5.1 will be turned into 5.2 and 5.1
93
	 * @param string $first
94
	 * @param string $second
95
	 * @return string[] first element is the first version, second element is the
96
	 * second version
97
	 */
98
	private function normalizeVersions($first, $second) {
99
		$first = explode('.', $first);
100
		$second = explode('.', $second);
101
102
		// get both arrays to the same minimum size
103
		$length = min(count($second), count($first));
104
		$first = array_slice($first, 0, $length);
105
		$second = array_slice($second, 0, $length);
106
107
		return [implode('.', $first), implode('.', $second)];
108
	}
109
110
	/**
111
	 * Parameters will be normalized and then passed into version_compare
112
	 * in the same order they are specified in the method header
113
	 * @param string $first
114
	 * @param string $second
115
	 * @param string $operator
116
	 * @return bool result similar to version_compare
117
	 */
118
	private function compare($first, $second, $operator) {
119
		// we can't normalize versions if one of the given parameters is not a
120
		// version string but null. In case one parameter is null normalization
121
		// will therefore be skipped
122
		if ($first !== null && $second !== null) {
0 ignored issues
show
introduced by
The condition $second !== null is always true.
Loading history...
123
			list($first, $second) = $this->normalizeVersions($first, $second);
124
		}
125
126
		return version_compare($first, $second, $operator);
127
	}
128
129
	/**
130
	 * Checks if a version is bigger than another version
131
	 * @param string $first
132
	 * @param string $second
133
	 * @return bool true if the first version is bigger than the second
134
	 */
135
	private function compareBigger($first, $second) {
136
		return $this->compare($first, $second, '>');
137
	}
138
139
	/**
140
	 * Checks if a version is smaller than another version
141
	 * @param string $first
142
	 * @param string $second
143
	 * @return bool true if the first version is smaller than the second
144
	 */
145
	private function compareSmaller($first, $second) {
146
		return $this->compare($first, $second, '<');
147
	}
148
149
	/**
150
	 * @param array $dependencies
151
	 * @return array
152
	 */
153
	private function analyzePhpVersion(array $dependencies) {
154
		$missing = [];
155
		if (isset($dependencies['php']['@attributes']['min-version'])) {
156
			$minVersion = $dependencies['php']['@attributes']['min-version'];
157
			if ($this->compareSmaller($this->platform->getPhpVersion(), $minVersion)) {
158
				$missing[] = (string)$this->l->t('PHP %s or higher is required.', [$minVersion]);
159
			}
160
		}
161
		if (isset($dependencies['php']['@attributes']['max-version'])) {
162
			$maxVersion = $dependencies['php']['@attributes']['max-version'];
163
			if ($this->compareBigger($this->platform->getPhpVersion(), $maxVersion)) {
164
				$missing[] = (string)$this->l->t('PHP with a version lower than %s is required.', [$maxVersion]);
165
			}
166
		}
167
		if (isset($dependencies['php']['@attributes']['min-int-size'])) {
168
			$intSize = $dependencies['php']['@attributes']['min-int-size'];
169
			if ($intSize > $this->platform->getIntSize()*8) {
170
				$missing[] = (string)$this->l->t('%sbit or higher PHP required.', [$intSize]);
171
			}
172
		}
173
		return $missing;
174
	}
175
176
	/**
177
	 * @param array $dependencies
178
	 * @return array
179
	 */
180
	private function analyzeDatabases(array $dependencies) {
181
		$missing = [];
182
		if (!isset($dependencies['database'])) {
183
			return $missing;
184
		}
185
186
		$supportedDatabases = $dependencies['database'];
187
		if (empty($supportedDatabases)) {
188
			return $missing;
189
		}
190
		if (!is_array($supportedDatabases)) {
191
			$supportedDatabases = array($supportedDatabases);
192
		}
193
		$supportedDatabases = array_map(function ($db) {
194
			return $this->getValue($db);
195
		}, $supportedDatabases);
196
		$currentDatabase = $this->platform->getDatabase();
197
		if (!in_array($currentDatabase, $supportedDatabases)) {
198
			$missing[] = (string)$this->l->t('Following databases are supported: %s', [implode(', ', $supportedDatabases)]);
199
		}
200
		return $missing;
201
	}
202
203
	/**
204
	 * @param array $dependencies
205
	 * @return array
206
	 */
207
	private function analyzeCommands(array $dependencies) {
208
		$missing = [];
209
		if (!isset($dependencies['command'])) {
210
			return $missing;
211
		}
212
213
		$commands = $dependencies['command'];
214
		if (!is_array($commands)) {
215
			$commands = array($commands);
216
		}
217
		if (isset($commands['@value'])) {
218
			$commands = [$commands];
219
		}
220
		$os = $this->platform->getOS();
221
		foreach ($commands as $command) {
222
			if (isset($command['@attributes']['os']) && $command['@attributes']['os'] !== $os) {
223
				continue;
224
			}
225
			$commandName = $this->getValue($command);
226
			if (!$this->platform->isCommandKnown($commandName)) {
227
				$missing[] = (string)$this->l->t('The command line tool %s could not be found', [$commandName]);
228
			}
229
		}
230
		return $missing;
231
	}
232
233
	/**
234
	 * @param array $dependencies
235
	 * @return array
236
	 */
237
	private function analyzeLibraries(array $dependencies) {
238
		$missing = [];
239
		if (!isset($dependencies['lib'])) {
240
			return $missing;
241
		}
242
243
		$libs = $dependencies['lib'];
244
		if (!is_array($libs)) {
245
			$libs = array($libs);
246
		}
247
		if (isset($libs['@value'])) {
248
			$libs = [$libs];
249
		}
250
		foreach ($libs as $lib) {
251
			$libName = $this->getValue($lib);
252
			$libVersion = $this->platform->getLibraryVersion($libName);
253
			if (is_null($libVersion)) {
254
				$missing[] = $this->l->t('The library %s is not available.', [$libName]);
255
				continue;
256
			}
257
258
			if (is_array($lib)) {
259
				if (isset($lib['@attributes']['min-version'])) {
260
					$minVersion = $lib['@attributes']['min-version'];
261
					if ($this->compareSmaller($libVersion, $minVersion)) {
262
						$missing[] = $this->l->t('Library %1$s with a version higher than %2$s is required - available version %3$s.',
263
							[$libName, $minVersion, $libVersion]);
264
					}
265
				}
266
				if (isset($lib['@attributes']['max-version'])) {
267
					$maxVersion = $lib['@attributes']['max-version'];
268
					if ($this->compareBigger($libVersion, $maxVersion)) {
269
						$missing[] = $this->l->t('Library %1$s with a version lower than %2$s is required - available version %3$s.',
270
							[$libName, $maxVersion, $libVersion]);
271
					}
272
				}
273
			}
274
		}
275
		return $missing;
276
	}
277
278
	/**
279
	 * @param array $dependencies
280
	 * @return array
281
	 */
282
	private function analyzeOS(array $dependencies) {
283
		$missing = [];
284
		if (!isset($dependencies['os'])) {
285
			return $missing;
286
		}
287
288
		$oss = $dependencies['os'];
289
		if (empty($oss)) {
290
			return $missing;
291
		}
292
		if (is_array($oss)) {
293
			$oss = array_map(function ($os) {
294
				return $this->getValue($os);
295
			}, $oss);
296
		} else {
297
			$oss = array($oss);
298
		}
299
		$currentOS = $this->platform->getOS();
300
		if (!in_array($currentOS, $oss)) {
301
			$missing[] = (string)$this->l->t('Following platforms are supported: %s', [implode(', ', $oss)]);
302
		}
303
		return $missing;
304
	}
305
306
	/**
307
	 * @param array $dependencies
308
	 * @param array $appInfo
309
	 * @return array
310
	 */
311
	private function analyzeOC(array $dependencies, array $appInfo, bool $ignoreMax) {
312
		$missing = [];
313
		$minVersion = null;
314
		if (isset($dependencies['nextcloud']['@attributes']['min-version'])) {
315
			$minVersion = $dependencies['nextcloud']['@attributes']['min-version'];
316
		} elseif (isset($dependencies['owncloud']['@attributes']['min-version'])) {
317
			$minVersion = $dependencies['owncloud']['@attributes']['min-version'];
318
		} elseif (isset($appInfo['requiremin'])) {
319
			$minVersion = $appInfo['requiremin'];
320
		} elseif (isset($appInfo['require'])) {
321
			$minVersion = $appInfo['require'];
322
		}
323
		$maxVersion = $this->getMaxVersion($dependencies, $appInfo);
324
325
		if (!is_null($minVersion)) {
326
			if ($this->compareSmaller($this->platform->getOcVersion(), $minVersion)) {
327
				$missing[] = (string)$this->l->t('Server version %s or higher is required.', [$this->toVisibleVersion($minVersion)]);
328
			}
329
		}
330
		if (!$ignoreMax && !is_null($maxVersion)) {
331
			if ($this->compareBigger($this->platform->getOcVersion(), $maxVersion)) {
332
				$missing[] = (string)$this->l->t('Server version %s or lower is required.', [$this->toVisibleVersion($maxVersion)]);
333
			}
334
		}
335
		return $missing;
336
	}
337
338
	private function getMaxVersion(array $dependencies, array $appInfo): ?string {
339
		if (isset($dependencies['nextcloud']['@attributes']['max-version'])) {
340
			return $dependencies['nextcloud']['@attributes']['max-version'];
341
		}
342
		if (isset($dependencies['owncloud']['@attributes']['max-version'])) {
343
			return $dependencies['owncloud']['@attributes']['max-version'];
344
		}
345
		if (isset($appInfo['requiremax'])) {
346
			return $appInfo['requiremax'];
347
		}
348
349
		return null;
350
	}
351
352
	/**
353
	 * Map the internal version number to the Nextcloud version
354
	 *
355
	 * @param string $version
356
	 * @return string
357
	 */
358
	protected function toVisibleVersion($version) {
359
		switch ($version) {
360
			case '9.1':
361
				return '10';
362
			default:
363
				if (strpos($version, '9.1.') === 0) {
364
					$version = '10.0.' . substr($version, 4);
365
				}
366
				return $version;
367
		}
368
	}
369
370
	/**
371
	 * @param $element
372
	 * @return mixed
373
	 */
374
	private function getValue($element) {
375
		if (isset($element['@value'])) {
376
			return $element['@value'];
377
		}
378
		return (string)$element;
379
	}
380
}
381