summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Müller <thomas.mueller@tmit.eu>2014-12-04 14:23:03 +0100
committerThomas Müller <thomas.mueller@tmit.eu>2014-12-04 14:23:03 +0100
commit884eb1418163ce9a5496061312ce422d29274cbd (patch)
tree3fe9d2ca9b7a2534b82aa743665bafcfcdd6cd12
parentc2b71d955c9a11f191a582e8d5a2648a951ab83d (diff)
parentb55ac514ac8a555e0fa14a391fec02d4c675748f (diff)
downloadnextcloud-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.php4
-rw-r--r--lib/private/app/dependencyanalyzer.php87
-rw-r--r--lib/private/app/infoparser.php74
-rw-r--r--lib/private/app/platform.php33
-rw-r--r--settings/controller/appsettingscontroller.php10
-rw-r--r--settings/css/settings.css6
-rw-r--r--settings/templates/apps.php11
-rw-r--r--tests/data/app/expected-info.json32
-rw-r--r--tests/data/app/valid-info.xml7
-rw-r--r--tests/lib/app/dependencyanalyzer.php109
-rw-r--r--tests/lib/app/infoparser.php20
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'),
+ );
}
}