You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DependencyAnalyzer.php 10KB

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