App dependencies php versiontags/v8.0.0alpha1
@@ -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'; | |||
} | |||
@@ -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)); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -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'); | |||
} | |||
} |
@@ -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; } | |||
@@ -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}} |
@@ -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" | |||
} | |||
] | |||
} | |||
} |
@@ -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> |
@@ -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'), | |||
); | |||
} | |||
} |
@@ -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'), | |||
); | |||
} | |||
} |