DependencyAnalyzer.php 10 KB

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