Passed
Push — master ( c9639f...ec07ca )
by Morris
24:32 queued 11:33
created

DependencyAnalyzer::compare()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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