DependencyAnalyzer.php 10 KB

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