DependencyAnalyzer.php 11 KB

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