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