diff options
author | Thomas Müller <thomas.mueller@tmit.eu> | 2014-12-04 14:23:03 +0100 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2014-12-04 14:23:03 +0100 |
commit | 884eb1418163ce9a5496061312ce422d29274cbd (patch) | |
tree | 3fe9d2ca9b7a2534b82aa743665bafcfcdd6cd12 | |
parent | c2b71d955c9a11f191a582e8d5a2648a951ab83d (diff) | |
parent | b55ac514ac8a555e0fa14a391fec02d4c675748f (diff) | |
download | nextcloud-server-884eb1418163ce9a5496061312ce422d29274cbd.tar.gz nextcloud-server-884eb1418163ce9a5496061312ce422d29274cbd.zip |
Merge pull request #12533 from owncloud/app-dependencies-php-version
App dependencies php version
-rw-r--r-- | lib/private/app.php | 4 | ||||
-rw-r--r-- | lib/private/app/dependencyanalyzer.php | 87 | ||||
-rw-r--r-- | lib/private/app/infoparser.php | 74 | ||||
-rw-r--r-- | lib/private/app/platform.php | 33 | ||||
-rw-r--r-- | settings/controller/appsettingscontroller.php | 10 | ||||
-rw-r--r-- | settings/css/settings.css | 6 | ||||
-rw-r--r-- | settings/templates/apps.php | 11 | ||||
-rw-r--r-- | tests/data/app/expected-info.json | 32 | ||||
-rw-r--r-- | tests/data/app/valid-info.xml | 7 | ||||
-rw-r--r-- | tests/lib/app/dependencyanalyzer.php | 109 | ||||
-rw-r--r-- | tests/lib/app/infoparser.php | 20 |
11 files changed, 378 insertions, 15 deletions
diff --git a/lib/private/app.php b/lib/private/app.php index 8e36d43bfb1..86db8fd9f55 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -809,7 +809,7 @@ class OC_App { if(isset($info['shipped']) and ($info['shipped'] == 'true')) { $info['internal'] = true; - $info['internallabel'] = $l->t('Recommended'); + $info['internallabel'] = (string)$l->t('Recommended'); $info['internalclass'] = 'recommendedapp'; $info['removable'] = false; } else { @@ -920,7 +920,7 @@ class OC_App { $app1[$i]['score'] = $app['score']; $app1[$i]['removable'] = false; if ($app['label'] == 'recommended') { - $app1[$i]['internallabel'] = $l->t('Recommended'); + $app1[$i]['internallabel'] = (string)$l->t('Recommended'); $app1[$i]['internalclass'] = 'recommendedapp'; } diff --git a/lib/private/app/dependencyanalyzer.php b/lib/private/app/dependencyanalyzer.php new file mode 100644 index 00000000000..fb4b3761656 --- /dev/null +++ b/lib/private/app/dependencyanalyzer.php @@ -0,0 +1,87 @@ +<?php + /** + * @author Thomas Müller + * @copyright 2014 Thomas Müller deepdiver@owncloud.com + * + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\App; + +class DependencyAnalyzer { + + /** @var Platform */ + private $system; + + /** @var \OCP\IL10N */ + private $l; + + /** @var array */ + private $missing; + + /** @var array */ + private $dependencies; + + /** + * @param array $app + * @param Platform $platform + * @param \OCP\IL10N $l + */ + function __construct(array $app, $platform, $l) { + $this->system = $platform; + $this->l = $l; + $this->missing = array(); + $this->dependencies = array(); + if (array_key_exists('dependencies', $app)) { + $this->dependencies = $app['dependencies']; + } + } + + /** + * @param array $app + * @returns array of missing dependencies + */ + public function analyze() { + $this->analysePhpVersion(); + $this->analyseSupportedDatabases(); + return $this->missing; + } + + private function analysePhpVersion() { + if (isset($this->dependencies['php']['@attributes']['min-version'])) { + $minVersion = $this->dependencies['php']['@attributes']['min-version']; + if (version_compare($this->system->getPhpVersion(), $minVersion, '<')) { + $this->missing[] = (string)$this->l->t('PHP %s or higher is required.', $minVersion); + } + } + if (isset($this->dependencies['php']['@attributes']['max-version'])) { + $maxVersion = $this->dependencies['php']['@attributes']['max-version']; + if (version_compare($this->system->getPhpVersion(), $maxVersion, '>')) { + $this->missing[] = (string)$this->l->t('PHP with a version less then %s is required.', $maxVersion); + } + } + } + + private function analyseSupportedDatabases() { + if (!isset($this->dependencies['database'])) { + return; + } + + $supportedDatabases = $this->dependencies['database']; + if (empty($supportedDatabases)) { + return; + } + $supportedDatabases = array_map(function($db) { + if (isset($db['@value'])) { + return $db['@value']; + } + return $db; + }, $supportedDatabases); + $currentDatabase = $this->system->getDatabase(); + if (!in_array($currentDatabase, $supportedDatabases)) { + $this->missing[] = (string)$this->l->t('Following databases are supported: %s', join(', ', $supportedDatabases)); + } + } +} diff --git a/lib/private/app/infoparser.php b/lib/private/app/infoparser.php index b4bdbea5c04..0bfbf6bd139 100644 --- a/lib/private/app/infoparser.php +++ b/lib/private/app/infoparser.php @@ -47,7 +47,7 @@ class InfoParser { if ($xml == false) { return null; } - $array = json_decode(json_encode((array)$xml), TRUE); + $array = $this->xmlToArray($xml, false); if (is_null($array)) { return null; } @@ -60,8 +60,11 @@ class InfoParser { if (!array_key_exists('public', $array)) { $array['public'] = array(); } + if (!array_key_exists('types', $array)) { + $array['types'] = array(); + } - if (array_key_exists('documentation', $array)) { + if (array_key_exists('documentation', $array) && is_array($array['documentation'])) { foreach ($array['documentation'] as $key => $url) { // If it is not an absolute URL we assume it is a key // i.e. admin-ldap will get converted to go.php?to=admin-ldap @@ -73,9 +76,70 @@ class InfoParser { } } if (array_key_exists('types', $array)) { - foreach ($array['types'] as $type => $v) { - unset($array['types'][$type]); - $array['types'][] = $type; + if (is_array($array['types'])) { + foreach ($array['types'] as $type => $v) { + unset($array['types'][$type]); + if (is_string($type)) { + $array['types'][] = $type; + } + } + } else { + $array['types'] = array(); + } + } + + return $array; + } + + /** + * @param \SimpleXMLElement $xml + * @return array + */ + function xmlToArray($xml) { + if (!$xml->children()) { + return (string)$xml; + } + + $array = array(); + foreach ($xml->children() as $element => $node) { + $totalElement = count($xml->{$element}); + + if (!isset($array[$element])) { + $array[$element] = ""; + } + /** + * @var \SimpleXMLElement $node + */ + + // Has attributes + if ($attributes = $node->attributes()) { + $data = array( + '@attributes' => array(), + ); + if (!count($node->children())){ + $value = (string)$node; + if (!empty($value)) { + $data['@value'] = (string)$node; + } + } else { + $data = array_merge($data, $this->xmlToArray($node)); + } + foreach ($attributes as $attr => $value) { + $data['@attributes'][$attr] = (string)$value; + } + + if ($totalElement > 1) { + $array[$element][] = $data; + } else { + $array[$element] = $data; + } + // Just a value + } else { + if ($totalElement > 1) { + $array[$element][] = $this->xmlToArray($node); + } else { + $array[$element] = $this->xmlToArray($node); + } } } diff --git a/lib/private/app/platform.php b/lib/private/app/platform.php new file mode 100644 index 00000000000..39f8a2979f9 --- /dev/null +++ b/lib/private/app/platform.php @@ -0,0 +1,33 @@ +<?php + /** + * @author Thomas Müller + * @copyright 2014 Thomas Müller deepdiver@owncloud.com + * + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\App; + +use OCP\IConfig; + +class Platform { + + function __construct(IConfig $config) { + $this->config = $config; + } + + public function getPhpVersion() { + return phpversion(); + } + + public function getDatabase() { + $dbType = $this->config->getSystemValue('dbtype', 'sqlite'); + if ($dbType === 'sqlite3') { + $dbType = 'sqlite'; + } + + return $dbType; + } +} diff --git a/settings/controller/appsettingscontroller.php b/settings/controller/appsettingscontroller.php index 27205400aff..3ad52bd2187 100644 --- a/settings/controller/appsettingscontroller.php +++ b/settings/controller/appsettingscontroller.php @@ -11,6 +11,8 @@ namespace OC\Settings\Controller; +use OC\App\DependencyAnalyzer; +use OC\App\Platform; use \OCP\AppFramework\Controller; use OCP\IRequest; use OCP\IL10N; @@ -123,10 +125,16 @@ class AppSettingsController extends Controller { } $app['groups'] = $groups; $app['canUnInstall'] = !$app['active'] && $app['removable']; + + // analyse dependencies + $dependencyAnalyzer = new DependencyAnalyzer($app, new Platform($this->config), $this->l10n); + $missing = $dependencyAnalyzer->analyze(); + + $app['canInstall'] = empty($missing); + $app['missingDependencies'] = $missing; return $app; }, $apps); return array('apps' => $apps, 'status' => 'success'); } - } diff --git a/settings/css/settings.css b/settings/css/settings.css index c18d5913b6f..4594a22c6d0 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -200,6 +200,12 @@ span.version { margin-left:1em; margin-right:1em; color:#555; } border-bottom: 1px solid #e8e8e8; } +.missing-dependencies { + list-style: initial; + list-style-type: initial; + list-style-position: inside; +} + /* Transition to complete width! */ .app:hover, .app:active { max-width: inherit; } diff --git a/settings/templates/apps.php b/settings/templates/apps.php index 1ad37000f39..3bb0d45f582 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -51,6 +51,15 @@ {{/if}} </p> {{/if}} + {{#unless canInstall}} + <div><?php p($l->t('This app cannot be installed because the following dependencies are not fulfilled:')); ?></div> + <ul class="missing-dependencies"> + {{#each missingDependencies}} + <li>{{this}}</li> + {{/each}} + </ul> + {{/unless}} + {{#if update}} <input class="update" type="submit" value="<?php p($l->t('Update to %s', array('{{update}}'))); ?>" data-appid="{{id}}" /> {{/if}} @@ -61,8 +70,10 @@ <br /> <input type="hidden" id="group_select" title="<?php p($l->t('All')); ?>" style="width: 200px"> {{else}} + {{#if canInstall}} <input class="enable" type="submit" data-appid="{{id}}" data-active="false" value="<?php p($l->t("Enable"));?>"/> {{/if}} + {{/if}} {{#if canUnInstall}} <input class="uninstall" type="submit" value="<?php p($l->t('Uninstall App')); ?>" data-appid="{{id}}" /> {{/if}} diff --git a/tests/data/app/expected-info.json b/tests/data/app/expected-info.json index c67d6d657d2..fc0ab224977 100644 --- a/tests/data/app/expected-info.json +++ b/tests/data/app/expected-info.json @@ -15,5 +15,35 @@ }, "rememberlogin": "false", "types": ["filesystem"], - "ocsid": "166047" + "ocsid": "166047", + "dependencies": { + "php": { + "@attributes" : { + "min-version": "5.4", + "max-version": "5.5" + } + }, + "database": [ + { + "@attributes" : { + "min-version": "3.0" + }, + "@value": "sqlite"}, + "mysql" + ], + "command": [ + { + "@attributes" : { + "os": "linux" + }, + "@value": "grep" + }, + { + "@attributes" : { + "os": "windows" + }, + "@value": "notepad.exe" + } + ] + } } diff --git a/tests/data/app/valid-info.xml b/tests/data/app/valid-info.xml index 6fcef693bed..f01f5fd55ea 100644 --- a/tests/data/app/valid-info.xml +++ b/tests/data/app/valid-info.xml @@ -19,4 +19,11 @@ <filesystem/> </types> <ocsid>166047</ocsid> + <dependencies> + <php min-version="5.4" max-version="5.5"/> + <database min-version="3.0">sqlite</database> + <database>mysql</database> + <command os="linux">grep</command> + <command os="windows">notepad.exe</command> + </dependencies> </info> diff --git a/tests/lib/app/dependencyanalyzer.php b/tests/lib/app/dependencyanalyzer.php new file mode 100644 index 00000000000..7d06842e8ad --- /dev/null +++ b/tests/lib/app/dependencyanalyzer.php @@ -0,0 +1,109 @@ +<?php + +/** + * @author Thomas Müller + * @copyright 2014 Thomas Müller deepdiver@owncloud.com + * later. + * See the COPYING-README file. + */ + +namespace Test\App; + +use OC; +use OC\App\Platform; +use OCP\IL10N; + +class DependencyAnalyzer extends \PHPUnit_Framework_TestCase { + + /** + * @var Platform + */ + private $platformMock; + + /** + * @var IL10N + */ + private $l10nMock; + + public function setUp() { + $this->platformMock = $this->getMockBuilder('\OC\App\Platform') + ->disableOriginalConstructor() + ->getMock(); + $this->platformMock->expects($this->any()) + ->method('getPhpVersion') + ->will( $this->returnValue('5.4.3')); + $this->platformMock->expects($this->any()) + ->method('getDatabase') + ->will( $this->returnValue('mysql')); + $this->l10nMock = $this->getMockBuilder('\OCP\IL10N') + ->disableOriginalConstructor() + ->getMock(); + $this->l10nMock->expects($this->any()) + ->method('t') + ->will($this->returnCallback(function($text, $parameters = array()) { + return vsprintf($text, $parameters); + })); + } + + /** + * @dataProvider providesPhpVersion + */ + public function testPhpVersion($expectedMissing, $minVersion, $maxVersion) { + $app = array( + 'dependencies' => array( + 'php' => array() + ) + ); + if (!is_null($minVersion)) { + $app['dependencies']['php']['@attributes']['min-version'] = $minVersion; + } + if (!is_null($maxVersion)) { + $app['dependencies']['php']['@attributes']['max-version'] = $maxVersion; + } + $analyser = new \OC\App\DependencyAnalyzer($app, $this->platformMock, $this->l10nMock); + $missing = $analyser->analyze(); + + $this->assertTrue(is_array($missing)); + $this->assertEquals(count($expectedMissing), count($missing)); + $this->assertEquals($expectedMissing, $missing); + } + + /** + * @dataProvider providesDatabases + */ + public function testDatabases($expectedMissing, $databases) { + $app = array( + 'dependencies' => array( + ) + ); + if (!is_null($databases)) { + $app['dependencies']['database'] = $databases; + } + $analyser = new \OC\App\DependencyAnalyzer($app, $this->platformMock, $this->l10nMock); + $missing = $analyser->analyze(); + + $this->assertTrue(is_array($missing)); + $this->assertEquals(count($expectedMissing), count($missing)); + $this->assertEquals($expectedMissing, $missing); + } + + function providesDatabases() { + return array( + // non BC - in case on databases are defined -> all are supported + array(array(), null), + array(array(), array()), + array(array('Following databases are supported: sqlite, postgres'), array('sqlite', array('@value' => 'postgres'))), + ); + } + + function providesPhpVersion() { + return array( + array(array(), null, null), + array(array(), '5.4', null), + array(array(), null, '5.5'), + array(array(), '5.4', '5.5'), + array(array('PHP 5.4.4 or higher is required.'), '5.4.4', null), + array(array('PHP with a version less then 5.4.2 is required.'), null, '5.4.2'), + ); + } +} diff --git a/tests/lib/app/infoparser.php b/tests/lib/app/infoparser.php index 277e1582e45..13c0b51e117 100644 --- a/tests/lib/app/infoparser.php +++ b/tests/lib/app/infoparser.php @@ -39,15 +39,23 @@ class InfoParser extends \PHPUnit_Framework_TestCase { $this->parser = new \OC\App\InfoParser($httpHelper, $urlGenerator); } - public function testParsingValidXml() { - $expectedData = json_decode(file_get_contents(OC::$SERVERROOT.'/tests/data/app/expected-info.json'), true); - $data = $this->parser->parse(OC::$SERVERROOT.'/tests/data/app/valid-info.xml'); + /** + * @dataProvider providesInfoXml + */ + public function testParsingValidXml($expectedJson, $xmlFile) { + $expectedData = null; + if (!is_null($expectedJson)) { + $expectedData = json_decode(file_get_contents(OC::$SERVERROOT . "/tests/data/app/$expectedJson"), true); + } + $data = $this->parser->parse(OC::$SERVERROOT. "/tests/data/app/$xmlFile"); $this->assertEquals($expectedData, $data); } - public function testParsingInvalidXml() { - $data = $this->parser->parse(OC::$SERVERROOT.'/tests/data/app/invalid-info.xml'); - $this->assertNull($data); + function providesInfoXml() { + return array( + array('expected-info.json', 'valid-info.xml'), + array(null, 'invalid-info.xml'), + ); } } |