Completed
Push — master ( 8f38ad...4035d6 )
by Joas
13:12 queued 06:02
created

DependencyAnalyzer::toVisibleVersion()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 11
rs 9.4285
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 Stefan Weil <[email protected]>
11
 * @author Thomas Müller <[email protected]>
12
 *
13
 * @license AGPL-3.0
14
 *
15
 * This code is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License, version 3,
17
 * as published by the Free Software Foundation.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License, version 3,
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
26
 *
27
 */
28
29
namespace OC\App;
30
31
use OCP\IL10N;
32
33
class DependencyAnalyzer {
34
35
	/** @var Platform */
36
	private $platform;
37
	/** @var \OCP\IL10N */
38
	private $l;
39
	/** @var array */
40
	private $appInfo;
41
42
	/**
43
	 * @param Platform $platform
44
	 * @param \OCP\IL10N $l
45
	 */
46
	function __construct(Platform $platform, IL10N $l) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
47
		$this->platform = $platform;
48
		$this->l = $l;
49
	}
50
51
	/**
52
	 * @param array $app
53
	 * @returns array of missing dependencies
54
	 */
55
	public function analyze(array $app) {
56
		$this->appInfo = $app;
57
		if (isset($app['dependencies'])) {
58
			$dependencies = $app['dependencies'];
59
		} else {
60
			$dependencies = [];
61
		}
62
63
		return array_merge(
64
			$this->analyzePhpVersion($dependencies),
65
			$this->analyzeDatabases($dependencies),
66
			$this->analyzeCommands($dependencies),
67
			$this->analyzeLibraries($dependencies),
68
			$this->analyzeOS($dependencies),
69
			$this->analyzeOC($dependencies, $app)
70
		);
71
	}
72
73
	/**
74
	 * Truncates both versions to the lowest common version, e.g.
75
	 * 5.1.2.3 and 5.1 will be turned into 5.1 and 5.1,
76
	 * 5.2.6.5 and 5.1 will be turned into 5.2 and 5.1
77
	 * @param string $first
78
	 * @param string $second
79
	 * @return string[] first element is the first version, second element is the
80
	 * second version
81
	 */
82
	private function normalizeVersions($first, $second) {
83
		$first = explode('.', $first);
84
		$second = explode('.', $second);
85
86
		// get both arrays to the same minimum size
87
		$length = min(count($second), count($first));
88
		$first = array_slice($first, 0, $length);
89
		$second = array_slice($second, 0, $length);
90
91
		return [implode('.', $first), implode('.', $second)];
92
	}
93
94
	/**
95
	 * Parameters will be normalized and then passed into version_compare
96
	 * in the same order they are specified in the method header
97
	 * @param string $first
98
	 * @param string $second
99
	 * @param string $operator
100
	 * @return bool result similar to version_compare
101
	 */
102
	private function compare($first, $second, $operator) {
103
		// we can't normalize versions if one of the given parameters is not a
104
		// version string but null. In case one parameter is null normalization
105
		// will therefore be skipped
106
		if ($first !== null && $second !== null) {
107
			list($first, $second) = $this->normalizeVersions($first, $second);
108
		}
109
110
		return version_compare($first, $second, $operator);
111
	}
112
113
	/**
114
	 * Checks if a version is bigger than another version
115
	 * @param string $first
116
	 * @param string $second
117
	 * @return bool true if the first version is bigger than the second
118
	 */
119
	private function compareBigger($first, $second) {
120
		return $this->compare($first, $second, '>');
121
	}
122
123
	/**
124
	 * Checks if a version is smaller than another version
125
	 * @param string $first
126
	 * @param string $second
127
	 * @return bool true if the first version is smaller than the second
128
	 */
129
	private function compareSmaller($first, $second) {
130
		return $this->compare($first, $second, '<');
131
	}
132
133
	/**
134
	 * @param array $dependencies
135
	 * @return array
136
	 */
137
	private function analyzePhpVersion(array $dependencies) {
138
		$missing = [];
139 View Code Duplication
		if (isset($dependencies['php']['@attributes']['min-version'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
140
			$minVersion = $dependencies['php']['@attributes']['min-version'];
141
			if ($this->compareSmaller($this->platform->getPhpVersion(), $minVersion)) {
142
				$missing[] = (string)$this->l->t('PHP %s or higher is required.', $minVersion);
143
			}
144
		}
145 View Code Duplication
		if (isset($dependencies['php']['@attributes']['max-version'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
			$maxVersion = $dependencies['php']['@attributes']['max-version'];
147
			if ($this->compareBigger($this->platform->getPhpVersion(), $maxVersion)) {
148
				$missing[] = (string)$this->l->t('PHP with a version lower than %s is required.', $maxVersion);
149
			}
150
		}
151 View Code Duplication
		if (isset($dependencies['php']['@attributes']['min-int-size'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
152
			$intSize = $dependencies['php']['@attributes']['min-int-size'];
153
			if ($intSize > $this->platform->getIntSize()*8) {
154
				$missing[] = (string)$this->l->t('%sbit or higher PHP required.', $intSize);
155
			}
156
		}
157
		return $missing;
158
	}
159
160
	/**
161
	 * @param array $dependencies
162
	 * @return array
163
	 */
164 View Code Duplication
	private function analyzeDatabases(array $dependencies) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
165
		$missing = [];
166
		if (!isset($dependencies['database'])) {
167
			return $missing;
168
		}
169
170
		$supportedDatabases = $dependencies['database'];
171
		if (empty($supportedDatabases)) {
172
			return $missing;
173
		}
174
		if (!is_array($supportedDatabases)) {
175
			$supportedDatabases = array($supportedDatabases);
176
		}
177
		$supportedDatabases = array_map(function ($db) {
178
			return $this->getValue($db);
179
		}, $supportedDatabases);
180
		$currentDatabase = $this->platform->getDatabase();
181
		if (!in_array($currentDatabase, $supportedDatabases)) {
182
			$missing[] = (string)$this->l->t('Following databases are supported: %s', join(', ', $supportedDatabases));
183
		}
184
		return $missing;
185
	}
186
187
	/**
188
	 * @param array $dependencies
189
	 * @return array
190
	 */
191
	private function analyzeCommands(array $dependencies) {
192
		$missing = [];
193
		if (!isset($dependencies['command'])) {
194
			return $missing;
195
		}
196
197
		$commands = $dependencies['command'];
198
		if (!is_array($commands)) {
199
			$commands = array($commands);
200
		}
201
		if (isset($commands['@value'])) {
202
			$commands = [$commands];
203
		}
204
		$os = $this->platform->getOS();
205
		foreach ($commands as $command) {
206
			if (isset($command['@attributes']['os']) && $command['@attributes']['os'] !== $os) {
207
				continue;
208
			}
209
			$commandName = $this->getValue($command);
210
			if (!$this->platform->isCommandKnown($commandName)) {
211
				$missing[] = (string)$this->l->t('The command line tool %s could not be found', $commandName);
212
			}
213
		}
214
		return $missing;
215
	}
216
217
	/**
218
	 * @param array $dependencies
219
	 * @return array
220
	 */
221
	private function analyzeLibraries(array $dependencies) {
222
		$missing = [];
223
		if (!isset($dependencies['lib'])) {
224
			return $missing;
225
		}
226
227
		$libs = $dependencies['lib'];
228
		if (!is_array($libs)) {
229
			$libs = array($libs);
230
		}
231
		if (isset($libs['@value'])) {
232
			$libs = [$libs];
233
		}
234
		foreach ($libs as $lib) {
235
			$libName = $this->getValue($lib);
236
			$libVersion = $this->platform->getLibraryVersion($libName);
237
			if (is_null($libVersion)) {
238
				$missing[] = (string)$this->l->t('The library %s is not available.', $libName);
239
				continue;
240
			}
241
242
			if (is_array($lib)) {
243 View Code Duplication
				if (isset($lib['@attributes']['min-version'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
244
					$minVersion = $lib['@attributes']['min-version'];
245
					if ($this->compareSmaller($libVersion, $minVersion)) {
246
						$missing[] = (string)$this->l->t('Library %s with a version higher than %s is required - available version %s.',
247
							array($libName, $minVersion, $libVersion));
248
					}
249
				}
250 View Code Duplication
				if (isset($lib['@attributes']['max-version'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
251
					$maxVersion = $lib['@attributes']['max-version'];
252
					if ($this->compareBigger($libVersion, $maxVersion)) {
253
						$missing[] = (string)$this->l->t('Library %s with a version lower than %s is required - available version %s.',
254
							array($libName, $maxVersion, $libVersion));
255
					}
256
				}
257
			}
258
		}
259
		return $missing;
260
	}
261
262
	/**
263
	 * @param array $dependencies
264
	 * @return array
265
	 */
266 View Code Duplication
	private function analyzeOS(array $dependencies) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
267
		$missing = [];
268
		if (!isset($dependencies['os'])) {
269
			return $missing;
270
		}
271
272
		$oss = $dependencies['os'];
273
		if (empty($oss)) {
274
			return $missing;
275
		}
276
		if (is_array($oss)) {
277
			$oss = array_map(function ($os) {
278
				return $this->getValue($os);
279
			}, $oss);
280
		} else {
281
			$oss = array($oss);
282
		}
283
		$currentOS = $this->platform->getOS();
284
		if (!in_array($currentOS, $oss)) {
285
			$missing[] = (string)$this->l->t('Following platforms are supported: %s', join(', ', $oss));
286
		}
287
		return $missing;
288
	}
289
290
	/**
291
	 * @param array $dependencies
292
	 * @param array $appInfo
293
	 * @return array
294
	 */
295
	private function analyzeOC(array $dependencies, array $appInfo) {
296
		$missing = [];
297
		$minVersion = null;
298
		if (isset($dependencies['nextcloud']['@attributes']['min-version'])) {
299
			$minVersion = $dependencies['nextcloud']['@attributes']['min-version'];
300
		} elseif (isset($dependencies['owncloud']['@attributes']['min-version'])) {
301
			$minVersion = $dependencies['owncloud']['@attributes']['min-version'];
302
		} elseif (isset($appInfo['requiremin'])) {
303
			$minVersion = $appInfo['requiremin'];
304
		} elseif (isset($appInfo['require'])) {
305
			$minVersion = $appInfo['require'];
306
		}
307
		$maxVersion = null;
308
		if (isset($dependencies['nextcloud']['@attributes']['max-version'])) {
309
			$maxVersion = $dependencies['nextcloud']['@attributes']['max-version'];
310
		} elseif (isset($dependencies['owncloud']['@attributes']['max-version'])) {
311
			$maxVersion = $dependencies['owncloud']['@attributes']['max-version'];
312
		} elseif (isset($appInfo['requiremax'])) {
313
			$maxVersion = $appInfo['requiremax'];
314
		}
315
316 View Code Duplication
		if (!is_null($minVersion)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
317
			if ($this->compareSmaller($this->platform->getOcVersion(), $minVersion)) {
318
				$missing[] = (string)$this->l->t('Server version %s or higher is required.', $this->toVisibleVersion($minVersion));
0 ignored issues
show
Documentation introduced by
$this->toVisibleVersion($minVersion) is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
319
			}
320
		}
321 View Code Duplication
		if (!is_null($maxVersion)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
322
			if ($this->compareBigger($this->platform->getOcVersion(), $maxVersion)) {
323
				$missing[] = (string)$this->l->t('Server version %s or lower is required.', $this->toVisibleVersion($maxVersion));
0 ignored issues
show
Documentation introduced by
$this->toVisibleVersion($maxVersion) is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
324
			}
325
		}
326
		return $missing;
327
	}
328
329
	/**
330
	 * Map the internal version number to the Nextcloud version
331
	 *
332
	 * @param string $version
333
	 * @return string
334
	 */
335
	protected function toVisibleVersion($version) {
336
		switch ($version) {
337
			case '9.1':
338
				return '10';
339
			default:
340
				if (strpos($version, '9.1.') === 0) {
341
					$version = '10.0.' . substr($version, 4);
342
				}
343
				return $version;
344
		}
345
	}
346
347
	/**
348
	 * @param $element
349
	 * @return mixed
350
	 */
351
	private function getValue($element) {
352
		if (isset($element['@value']))
353
			return $element['@value'];
354
		return (string)$element;
355
	}
356
}
357