diff options
author | Joas Schilling <coding@schilljs.com> | 2018-02-07 16:03:21 +0100 |
---|---|---|
committer | Joas Schilling <coding@schilljs.com> | 2018-02-16 10:23:51 +0100 |
commit | 17a26dfcc1f4d02b54e6cbb500f41bbe25609f1e (patch) | |
tree | 045682f754bec9284239df359715660148251011 | |
parent | 620ee00ac5d176e85ba0dd9dd10cff6d1ef5172f (diff) | |
download | nextcloud-server-17a26dfcc1f4d02b54e6cbb500f41bbe25609f1e.tar.gz nextcloud-server-17a26dfcc1f4d02b54e6cbb500f41bbe25609f1e.zip |
Validate the info.xml against the appstore schema file
Signed-off-by: Joas Schilling <coding@schilljs.com>
-rw-r--r-- | core/Command/App/CheckCode.php | 54 | ||||
-rw-r--r-- | core/register_command.php | 3 | ||||
-rw-r--r-- | lib/private/App/AppManager.php | 5 | ||||
-rw-r--r-- | lib/private/App/CodeChecker/InfoChecker.php | 149 | ||||
-rw-r--r-- | resources/app-info-shipped.xsd | 671 | ||||
-rw-r--r-- | resources/app-info.xsd | 652 | ||||
-rw-r--r-- | tests/apps/testapp-dependency-missing/appinfo/info.xml | 9 | ||||
-rw-r--r-- | tests/apps/testapp-infoxml/appinfo/info.xml | 12 | ||||
-rw-r--r-- | tests/apps/testapp-name-missing/appinfo/info.xml | 11 | ||||
-rw-r--r-- | tests/apps/testapp-version/appinfo/info.xml | 11 | ||||
-rw-r--r-- | tests/apps/testapp-version/appinfo/version | 1 | ||||
-rw-r--r-- | tests/apps/testapp_dependency_missing/appinfo/info.xml | 13 | ||||
-rw-r--r-- | tests/apps/testapp_infoxml/appinfo/info.xml | 16 | ||||
-rw-r--r-- | tests/apps/testapp_name_missing/appinfo/info.xml | 15 | ||||
-rw-r--r-- | tests/apps/testapp_version/appinfo/info.xml | 15 | ||||
-rw-r--r-- | tests/lib/App/CodeChecker/InfoCheckerTest.php | 19 |
16 files changed, 1461 insertions, 195 deletions
diff --git a/core/Command/App/CheckCode.php b/core/Command/App/CheckCode.php index 82a137e58e1..a129bcf1e10 100644 --- a/core/Command/App/CheckCode.php +++ b/core/Command/App/CheckCode.php @@ -44,20 +44,12 @@ use OC\App\CodeChecker\PrivateCheck; class CheckCode extends Command implements CompletionAwareInterface { - /** @var InfoParser */ - private $infoParser; - protected $checkers = [ 'private' => PrivateCheck::class, 'deprecation' => DeprecationCheck::class, 'strong-comparison' => StrongComparisonCheck::class, ]; - public function __construct(InfoParser $infoParser) { - parent::__construct(); - $this->infoParser = $infoParser; - } - protected function configure() { $this ->setName('app:check-code') @@ -134,50 +126,12 @@ class CheckCode extends Command implements CompletionAwareInterface { } if(!$input->getOption('skip-validate-info')) { - $infoChecker = new InfoChecker($this->infoParser); - - $infoChecker->listen('InfoChecker', 'mandatoryFieldMissing', function($key) use ($output) { - $output->writeln("<error>Mandatory field missing: $key</error>"); - }); - - $infoChecker->listen('InfoChecker', 'deprecatedFieldFound', function($key, $value) use ($output) { - if($value === [] || is_null($value) || $value === '') { - $output->writeln("<info>Deprecated field available: $key</info>"); - } else { - $output->writeln("<info>Deprecated field available: $key => $value</info>"); - } - }); - - $infoChecker->listen('InfoChecker', 'missingRequirement', function($minMax) use ($output) { - $output->writeln("<error>Nextcloud $minMax version requirement missing</error>"); - }); - - $infoChecker->listen('InfoChecker', 'differentVersions', function($versionFile, $infoXML) use ($output) { - $output->writeln("<error>Different versions provided (appinfo/version: $versionFile - appinfo/info.xml: $infoXML)</error>"); + // Can not inject because of circular dependency + $infoChecker = new InfoChecker(\OC::$server->getAppManager()); + $infoChecker->listen('InfoChecker', 'parseError', function($error) use ($output) { + $output->writeln("<error>Invalid appinfo.xml file found: $error</error>"); }); - $infoChecker->listen('InfoChecker', 'sameVersions', function($path) use ($output) { - $output->writeln("<info>Version file isn't needed anymore and can be safely removed ($path)</info>"); - }); - - $infoChecker->listen('InfoChecker', 'migrateVersion', function($version) use ($output) { - $output->writeln("<error>Migrate the app version to appinfo/info.xml (add <version>$version</version> to appinfo/info.xml and remove appinfo/version)</error>"); - }); - - if(OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { - $infoChecker->listen('InfoChecker', 'mandatoryFieldFound', function($key, $value) use ($output) { - $output->writeln("<info>Mandatory field available: $key => $value</info>"); - }); - - $infoChecker->listen('InfoChecker', 'optionalFieldFound', function($key, $value) use ($output) { - $output->writeln("<info>Optional field available: $key => $value</info>"); - }); - - $infoChecker->listen('InfoChecker', 'unusedFieldFound', function($key, $value) use ($output) { - $output->writeln("<info>Unused field available: $key => $value</info>"); - }); - } - $infoErrors = $infoChecker->analyse($appId); $errors = array_merge($errors, $infoErrors); diff --git a/core/register_command.php b/core/register_command.php index 372d775dc14..578b4f799b6 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -40,8 +40,7 @@ $application->add(new \Stecman\Component\Symfony\Console\BashCompletion\CompletionCommand()); $application->add(new OC\Core\Command\Status); $application->add(new OC\Core\Command\Check(\OC::$server->getSystemConfig())); -$infoParser = new \OC\App\InfoParser(); -$application->add(new OC\Core\Command\App\CheckCode($infoParser)); +$application->add(new OC\Core\Command\App\CheckCode()); $application->add(new OC\Core\Command\L10n\CreateJs()); $application->add(new \OC\Core\Command\Integrity\SignApp( \OC::$server->getIntegrityCodeChecker(), diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index 0e921ba1b7f..81037b42613 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -411,6 +411,7 @@ class AppManager implements IAppManager { /** * @inheritdoc + * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped() */ public function isShipped($appId) { $this->loadShippedJson(); @@ -422,6 +423,10 @@ class AppManager implements IAppManager { return in_array($appId, $alwaysEnabled, true); } + /** + * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson() + * @throws \Exception + */ private function loadShippedJson() { if ($this->shippedApps === null) { $shippedJson = \OC::$SERVERROOT . '/core/shipped.json'; diff --git a/lib/private/App/CodeChecker/InfoChecker.php b/lib/private/App/CodeChecker/InfoChecker.php index e8791bd7037..f5de6d376d8 100644 --- a/lib/private/App/CodeChecker/InfoChecker.php +++ b/lib/private/App/CodeChecker/InfoChecker.php @@ -23,121 +23,88 @@ namespace OC\App\CodeChecker; -use OC\App\InfoParser; use OC\Hooks\BasicEmitter; +use OCP\App\AppPathNotFoundException; +use OCP\App\IAppManager; class InfoChecker extends BasicEmitter { - /** @var InfoParser */ - private $infoParser; + /** @var string[] */ + private $shippedApps; - private $mandatoryFields = [ - 'author', - 'description', - 'dependencies', - 'id', - 'licence', - 'name', - 'version', - ]; - private $optionalFields = [ - 'bugs', - 'category', - 'default_enable', - 'documentation', - 'namespace', - 'ocsid', - 'public', - 'remote', - 'repository', - 'types', - 'website', - ]; - private $deprecatedFields = [ - 'info', - 'require', - 'requiremax', - 'requiremin', - 'shipped', - 'standalone', - ]; - - public function __construct(InfoParser $infoParser) { - $this->infoParser = $infoParser; - } + /** @var string[] */ + private $alwaysEnabled; /** * @param string $appId * @return array + * @throws \RuntimeException */ - public function analyse($appId) { + public function analyse($appId): array { $appPath = \OC_App::getAppPath($appId); if ($appPath === false) { throw new \RuntimeException("No app with given id <$appId> known."); } - $errors = []; - - $info = $this->infoParser->parse($appPath . '/appinfo/info.xml'); - - if (!isset($info['dependencies']['nextcloud']['@attributes']['min-version'])) { - $errors[] = [ - 'type' => 'missingRequirement', - 'field' => 'min', - ]; - $this->emit('InfoChecker', 'missingRequirement', ['min']); - } - - if (!isset($info['dependencies']['nextcloud']['@attributes']['max-version'])) { - $errors[] = [ - 'type' => 'missingRequirement', - 'field' => 'max', - ]; - $this->emit('InfoChecker', 'missingRequirement', ['max']); - } - - foreach ($info as $key => $value) { - if(is_array($value)) { - $value = json_encode($value); - } - if (in_array($key, $this->mandatoryFields)) { - $this->emit('InfoChecker', 'mandatoryFieldFound', [$key, $value]); - continue; - } + $xml = new \DOMDocument(); + $xml->load($appPath . '/appinfo/info.xml'); - if (in_array($key, $this->optionalFields)) { - $this->emit('InfoChecker', 'optionalFieldFound', [$key, $value]); - continue; + $schema = \OC::$SERVERROOT . '/resources/app-info.xsd'; + try { + if ($this->isShipped($appId)) { + // Shipped apps are allowed to have the public and default_enabled tags + $schema = \OC::$SERVERROOT . '/resources/app-info-shipped.xsd'; } - - if (in_array($key, $this->deprecatedFields)) { - // skip empty arrays - empty arrays for remote and public are always added - if($value === '[]' && in_array($key, ['public', 'remote', 'info'])) { - continue; - } - $this->emit('InfoChecker', 'deprecatedFieldFound', [$key, $value]); - continue; - } - - $this->emit('InfoChecker', 'unusedFieldFound', [$key, $value]); + } catch (\Exception $e) { + // Assume it is not shipped } - foreach ($this->mandatoryFields as $key) { - if(!isset($info[$key])) { - $this->emit('InfoChecker', 'mandatoryFieldMissing', [$key]); + $errors = []; + if (!$xml->schemaValidate($schema)) { + foreach (libxml_get_errors() as $error) { $errors[] = [ - 'type' => 'mandatoryFieldMissing', - 'field' => $key, + 'type' => 'parseError', + 'field' => $error->message, ]; + $this->emit('InfoChecker', 'parseError', [$error->message]); } } - $versionFile = $appPath . '/appinfo/version'; - if (is_file($versionFile)) { - $version = trim(file_get_contents($versionFile)); - $this->emit('InfoChecker', 'migrateVersion', [$version]); - } - return $errors; } + + /** + * This is a copy of \OC\App\AppManager::isShipped(), keep both in sync. + * This method is copied, so the code checker works even when Nextcloud is + * not installed yet. The AppManager requires a database connection, which + * fails in that case. + * + * @param string $appId + * @return bool + * @throws \Exception + */ + protected function isShipped(string $appId): bool { + $this->loadShippedJson(); + return \in_array($appId, $this->shippedApps, true); + } + + /** + * This is a copy of \OC\App\AppManager::loadShippedJson(), keep both in sync + * This method is copied, so the code checker works even when Nextcloud is + * not installed yet. The AppManager requires a database connection, which + * fails in that case. + * + * @throws \Exception + */ + protected function loadShippedJson() { + if ($this->shippedApps === null) { + $shippedJson = \OC::$SERVERROOT . '/core/shipped.json'; + if (!file_exists($shippedJson)) { + throw new \Exception("File not found: $shippedJson"); + } + $content = json_decode(file_get_contents($shippedJson), true); + $this->shippedApps = $content['shippedApps']; + $this->alwaysEnabled = $content['alwaysEnabled']; + } + } } diff --git a/resources/app-info-shipped.xsd b/resources/app-info-shipped.xsd new file mode 100644 index 00000000000..97221d1fb9c --- /dev/null +++ b/resources/app-info-shipped.xsd @@ -0,0 +1,671 @@ +<?xml version="1.0"?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" + elementFormDefault="qualified" attributeFormDefault="unqualified"> + + <xs:element name="info"> + <xs:complexType> + <xs:sequence> + <xs:element name="id" type="id" minOccurs="1" maxOccurs="1"/> + <xs:element name="name" type="l10n-string" minOccurs="1" + maxOccurs="unbounded"/> + <xs:element name="summary" type="l10n-string" minOccurs="1" + maxOccurs="unbounded"/> + <xs:element name="description" type="l10n-text" minOccurs="1" + maxOccurs="unbounded"/> + <xs:element name="version" type="semver" + minOccurs="1" maxOccurs="1"/> + <xs:element name="licence" type="licence" minOccurs="1" + maxOccurs="unbounded"/> + <xs:element name="author" type="author" minOccurs="1" + maxOccurs="unbounded"/> + <xs:element name="namespace" type="limited-string" + minOccurs="0" maxOccurs="1"/> + <xs:element name="default_enable" minOccurs="0" + maxOccurs="1"/> + <xs:element name="types" type="types" minOccurs="0" + maxOccurs="1"/> + <xs:element name="documentation" type="documentation" + minOccurs="0" maxOccurs="1"/> + <xs:element name="category" type="category" minOccurs="1" + maxOccurs="unbounded"/> + <xs:element name="website" type="url" minOccurs="0" + maxOccurs="1"/> + <xs:element name="discussion" type="url" minOccurs="0" + maxOccurs="1"/> + <xs:element name="bugs" type="url" minOccurs="1" + maxOccurs="1"/> + <xs:element name="repository" type="repository" minOccurs="0" + maxOccurs="1"/> + <xs:element name="screenshot" type="screenshot" minOccurs="0" + maxOccurs="10"/> + <xs:element name="dependencies" type="dependencies" + minOccurs="1" maxOccurs="1"/> + <xs:element name="background-jobs" type="jobs" + minOccurs="0" maxOccurs="1"/> + <xs:element name="repair-steps" type="repair-steps" + minOccurs="0" maxOccurs="1"/> + <xs:element name="two-factor-providers" + type="two-factor-providers" + minOccurs="0" maxOccurs="1"/> + <xs:element name="commands" type="commands" + minOccurs="0" maxOccurs="1"/> + <xs:element name="settings" type="settings" minOccurs="0" + maxOccurs="1"/> + <xs:element name="activity" type="activity" minOccurs="0" + maxOccurs="1"/> + <xs:element name="navigations" type="navigations" minOccurs="0" + maxOccurs="1"/> + <xs:element name="contactsmenu" type="contactsmenu" minOccurs="0" + maxOccurs="1"/> + <xs:element name="collaboration" type="collaboration" minOccurs="0" + maxOccurs="1" /> + <xs:element name="sabre" type="sabre" minOccurs="0" + maxOccurs="1" /> + <xs:element name="public" type="public" minOccurs="0" + maxOccurs="1" /> + </xs:sequence> + </xs:complexType> + <xs:unique name="uniqueNameL10n"> + <xs:selector xpath="name"/> + <xs:field xpath="@lang"/> + </xs:unique> + <xs:unique name="uniqueSummaryL10n"> + <xs:selector xpath="summary"/> + <xs:field xpath="@lang"/> + </xs:unique> + <xs:unique name="uniqueDescriptionL10n"> + <xs:selector xpath="description"/> + <xs:field xpath="@lang"/> + </xs:unique> + <xs:unique name="uniqueLicense"> + <xs:selector xpath="licence"/> + <xs:field xpath="."/> + </xs:unique> + <xs:unique name="uniqueTypes"> + <xs:selector xpath="types/type"/> + <xs:field xpath="."/> + </xs:unique> + <xs:unique name="uniqueCategory"> + <xs:selector xpath="category"/> + <xs:field xpath="."/> + </xs:unique> + <xs:unique name="uniqueDatabase"> + <xs:selector xpath="dependencies/database"/> + <xs:field xpath="."/> + </xs:unique> + <xs:unique name="uniqueLib"> + <xs:selector xpath="dependencies/lib"/> + <xs:field xpath="."/> + </xs:unique> + <xs:unique name="uniqueCommand"> + <xs:selector xpath="dependencies/command"/> + <xs:field xpath="."/> + </xs:unique> + </xs:element> + + <!-- basic types --> + <xs:simpleType name="empty-string"> + <xs:restriction base="xs:string"> + <xs:maxLength value="0"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="non-empty-string"> + <xs:restriction base="xs:string"> + <xs:minLength value="1"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="limited-string"> + <xs:restriction base="non-empty-string"> + <xs:maxLength value="256"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="l10n-text"> + <xs:simpleContent> + <xs:extension base="non-empty-string"> + <xs:attribute name="lang" type="l10n-code" default="en" + use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="l10n-string"> + <xs:simpleContent> + <xs:restriction base="l10n-text"> + <xs:maxLength value="128"/> + </xs:restriction> + </xs:simpleContent> + </xs:complexType> + + + <xs:simpleType name="l10n-code"> + <xs:restriction base="xs:string"> + <xs:enumeration value="af"/> + <xs:enumeration value="ar"/> + <xs:enumeration value="ast"/> + <xs:enumeration value="az"/> + <xs:enumeration value="bg"/> + <xs:enumeration value="be"/> + <xs:enumeration value="bn"/> + <xs:enumeration value="br"/> + <xs:enumeration value="bs"/> + <xs:enumeration value="ca"/> + <xs:enumeration value="cs"/> + <xs:enumeration value="cy"/> + <xs:enumeration value="da"/> + <xs:enumeration value="de"/> + <xs:enumeration value="el"/> + <xs:enumeration value="en"/> + <xs:enumeration value="eo"/> + <xs:enumeration value="es"/> + <xs:enumeration value="es-ar"/> + <xs:enumeration value="es-co"/> + <xs:enumeration value="es-mx"/> + <xs:enumeration value="es-ni"/> + <xs:enumeration value="es-ve"/> + <xs:enumeration value="et"/> + <xs:enumeration value="eu"/> + <xs:enumeration value="fa"/> + <xs:enumeration value="fi"/> + <xs:enumeration value="fr"/> + <xs:enumeration value="fy"/> + <xs:enumeration value="ga"/> + <xs:enumeration value="gd"/> + <xs:enumeration value="gl"/> + <xs:enumeration value="he"/> + <xs:enumeration value="hi"/> + <xs:enumeration value="hr"/> + <xs:enumeration value="hu"/> + <xs:enumeration value="ia"/> + <xs:enumeration value="id"/> + <xs:enumeration value="io"/> + <xs:enumeration value="is"/> + <xs:enumeration value="it"/> + <xs:enumeration value="ja"/> + <xs:enumeration value="ka"/> + <xs:enumeration value="kk"/> + <xs:enumeration value="km"/> + <xs:enumeration value="kn"/> + <xs:enumeration value="ko"/> + <xs:enumeration value="lb"/> + <xs:enumeration value="lt"/> + <xs:enumeration value="lv"/> + <xs:enumeration value="mk"/> + <xs:enumeration value="ml"/> + <xs:enumeration value="mn"/> + <xs:enumeration value="mr"/> + <xs:enumeration value="my"/> + <xs:enumeration value="nb"/> + <xs:enumeration value="ne"/> + <xs:enumeration value="nl"/> + <xs:enumeration value="nn"/> + <xs:enumeration value="os"/> + <xs:enumeration value="pa"/> + <xs:enumeration value="pl"/> + <xs:enumeration value="pt"/> + <xs:enumeration value="pt-br"/> + <xs:enumeration value="ro"/> + <xs:enumeration value="ru"/> + <xs:enumeration value="sk"/> + <xs:enumeration value="sl"/> + <xs:enumeration value="sq"/> + <xs:enumeration value="sr"/> + <xs:enumeration value="sr-latn"/> + <xs:enumeration value="sv"/> + <xs:enumeration value="sw"/> + <xs:enumeration value="ta"/> + <xs:enumeration value="te"/> + <xs:enumeration value="th"/> + <xs:enumeration value="tr"/> + <xs:enumeration value="tt"/> + <xs:enumeration value="udm"/> + <xs:enumeration value="uk"/> + <xs:enumeration value="ur"/> + <xs:enumeration value="vi"/> + <xs:enumeration value="zh-hans"/> + <xs:enumeration value="zh-hant"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="semver"> + <xs:restriction base="limited-string"> + <xs:pattern + value="(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(\-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="version"> + <xs:restriction base="limited-string"> + <xs:pattern value="[0-9]+(\.[0-9]+){0,2}"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="url"> + <xs:restriction base="xs:anyURI"> + <xs:pattern value="https?://.+"/> + <xs:maxLength value="256"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="doc-user-url"> + <xs:restriction base="non-empty-string"> + <xs:pattern value="https://.+|user-[a-z]+[\-a-z]*[a-z]+"/> + <xs:maxLength value="256"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="doc-admin-url"> + <xs:restriction base="non-empty-string"> + <xs:pattern value="https://.+|admin-[a-z]+[\-a-z]*[a-z]+"/> + <xs:maxLength value="256"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="doc-developer-url"> + <xs:restriction base="non-empty-string"> + <xs:pattern value="https://.+|developer-[a-z]+[\-a-z]*[a-z]+"/> + <xs:maxLength value="256"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="secure-url"> + <xs:restriction base="xs:anyURI"> + <xs:pattern value="https://.+"/> + <xs:maxLength value="256"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="email"> + <xs:restriction base="limited-string"> + <xs:pattern value="[^@]+@[^\.]+\..+"/> + </xs:restriction> + </xs:simpleType> + + <!-- first level elements --> + <xs:complexType name="screenshot"> + <xs:simpleContent> + <xs:extension base="secure-url"> + <xs:attribute name="small-thumbnail" use="optional" + type="secure-url"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:simpleType name="id"> + <xs:restriction base="limited-string"> + <xs:pattern value="[a-z]+[a-z0-9_]*[a-z0-9]+"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="author"> + <xs:simpleContent> + <xs:extension base="limited-string"> + <xs:attribute name="mail" type="email" use="optional"/> + <xs:attribute name="homepage" type="url" use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="repository"> + <xs:simpleContent> + <xs:extension base="url"> + <xs:attribute name="type" type="vcs" use="optional" + default="git"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:simpleType name="vcs"> + <xs:restriction base="xs:string"> + <xs:enumeration value="git"/> + <xs:enumeration value="mercurial"/> + <xs:enumeration value="subversion"/> + <xs:enumeration value="bzr"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="types"> + <xs:sequence> + <xs:element name="prelogin" minOccurs="0" maxOccurs="1"/> + <xs:element name="filesystem" minOccurs="0" maxOccurs="1"/> + <xs:element name="authentication" minOccurs="0" maxOccurs="1"/> + <xs:element name="logging" minOccurs="0" maxOccurs="1"/> + <xs:element name="dav" minOccurs="0" maxOccurs="1"/> + <xs:element name="prevent_group_restriction" minOccurs="0" + maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:simpleType name="category"> + <xs:restriction base="xs:string"> + <xs:enumeration value="security"/> + <xs:enumeration value="customization"/> + <xs:enumeration value="files"/> + <xs:enumeration value="integration"/> + <xs:enumeration value="monitoring"/> + <xs:enumeration value="multimedia"/> + <xs:enumeration value="office"/> + <xs:enumeration value="organization"/> + <xs:enumeration value="social"/> + <xs:enumeration value="tools"/> + <xs:enumeration value="games"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="licence"> + <xs:restriction base="xs:string"> + <xs:enumeration value="agpl"/> + <xs:enumeration value="mpl"/> + <xs:enumeration value="apache"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="databases"> + <xs:restriction base="xs:string"> + <xs:enumeration value="sqlite"/> + <xs:enumeration value="mysql"/> + <xs:enumeration value="pgsql"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="documentation"> + <xs:sequence> + <xs:element name="user" type="doc-user-url" minOccurs="0" maxOccurs="1"/> + <xs:element name="admin" type="doc-admin-url" minOccurs="0" maxOccurs="1"/> + <xs:element name="developer" type="doc-developer-url" minOccurs="0" + maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="settings"> + <xs:sequence> + <xs:element name="admin" type="php-class" minOccurs="0" + maxOccurs="unbounded"/> + <xs:element name="admin-section" type="php-class" minOccurs="0" + maxOccurs="unbounded"/> + <xs:element name="personal" type="php-class" minOccurs="0" + maxOccurs="unbounded"/> + <xs:element name="personal-section" type="php-class" minOccurs="0" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="activity"> + <xs:sequence> + <xs:element name="settings" type="activity-settings" minOccurs="0" + maxOccurs="1"/> + <xs:element name="filters" type="activity-filters" minOccurs="0" maxOccurs="1"/> + <xs:element name="providers" type="activity-providers" minOccurs="0" + maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="activity-settings"> + <xs:sequence> + <xs:element name="setting" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="activity-filters"> + <xs:sequence> + <xs:element name="filter" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="activity-providers"> + <xs:sequence> + <xs:element name="provider" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="navigations"> + <xs:sequence> + <xs:element name="navigation" type="navigation" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="navigation"> + <xs:sequence> + <xs:element name="id" type="id" minOccurs="0" maxOccurs="1"/> + <xs:element name="name" type="non-empty-string" minOccurs="1" maxOccurs="1"/> + <xs:element name="route" type="route" minOccurs="1" maxOccurs="1"/> + <xs:element name="icon" type="xs:anyURI" minOccurs="0" maxOccurs="1"/> + <xs:element name="order" type="xs:int" minOccurs="0" maxOccurs="1"/> + <xs:element name="type" type="navigation-type" minOccurs="0" maxOccurs="1"/> + </xs:sequence> + <xs:attribute name="role" type="navigation-role" default="all" use="optional"/> + </xs:complexType> + + <xs:simpleType name="navigation-role"> + <xs:restriction base="xs:string"> + <xs:enumeration value="all"/> + <xs:enumeration value="admin"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="navigation-type"> + <xs:restriction base="xs:string"> + <xs:enumeration value="link"/> + <xs:enumeration value="settings"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="route"> + <xs:restriction base="non-empty-string"> + <xs:pattern value="[0-9a-zA-Z_]+(\.[0-9a-zA-Z_]+){2}"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="contactsmenu"> + <xs:sequence> + <xs:element name="provider" type="php-class" minOccurs="1" + maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="collaboration"> + <xs:sequence> + <xs:element name="plugins" type="collaboration-plugins" minOccurs="0" maxOccurs="1"> + </xs:element> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="collaboration-plugins"> + <xs:sequence> + <xs:element name="plugin" type="collaboration-plugins-plugin" minOccurs="1" maxOccurs="unbounded"> + </xs:element> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="collaboration-plugins-plugin"> + <xs:simpleContent> + <xs:extension base="php-class"> + <xs:attribute name="type" type="collaboration-plugin-type" use="required"/> + <xs:attribute name="share-type" type="share-type" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:simpleType name="collaboration-plugin-type"> + <xs:restriction base="xs:string"> + <xs:enumeration value="collaborator-search"/> + <xs:enumeration value="autocomplete-sort"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="sabre"> + <xs:sequence> + <xs:element name="collections" type="sabre-collections" minOccurs="0" maxOccurs="1"/> + <xs:element name="plugins" type="sabre-plugins" minOccurs="0" maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="sabre-collections"> + <xs:sequence> + <xs:element name="collection" type="php-class" minOccurs="1" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="sabre-plugins"> + <xs:sequence> + <xs:element name="plugin" type="php-class" minOccurs="1" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:simpleType name="share-type"> + <xs:restriction base="xs:string"> + <xs:enumeration value="SHARE_TYPE_USER"/> + <xs:enumeration value="SHARE_TYPE_GROUP"/> + <xs:enumeration value="SHARE_TYPE_LINK"/> + <xs:enumeration value="SHARE_TYPE_EMAIL"/> + <xs:enumeration value="SHARE_TYPE_CONTACT"/> + <xs:enumeration value="SHARE_TYPE_REMOTE"/> + <xs:enumeration value="SHARE_TYPE_CIRCLE"/> + <xs:enumeration value="SHARE_TYPE_GUEST"/> + </xs:restriction> + </xs:simpleType> + + + <xs:complexType name="public"> + <xs:sequence> + <xs:element name="webdav" type="path" minOccurs="0" maxOccurs="1"/> + <xs:element name="files" type="path" minOccurs="0" maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <!-- dependencies --> + <xs:complexType name="dependencies"> + <xs:sequence> + <xs:element name="php" type="php" minOccurs="0" maxOccurs="1"/> + <xs:element name="database" type="database" minOccurs="0" + maxOccurs="unbounded"/> + <xs:element name="command" type="shell-command" minOccurs="0" + maxOccurs="unbounded"/> + <xs:element name="lib" type="min-max-version" minOccurs="0" + maxOccurs="unbounded"/> + <xs:element name="owncloud" type="owncloud" minOccurs="0" + maxOccurs="1"/> + <xs:element name="nextcloud" type="nextcloud" minOccurs="1" + maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="owncloud"> + <xs:attribute name="min-version" type="version" use="required"/> + <xs:attribute name="max-version" type="version" use="optional"/> + </xs:complexType> + + <xs:complexType name="nextcloud"> + <xs:attribute name="min-version" type="version" use="required"/> + <xs:attribute name="max-version" type="version" use="required"/> + </xs:complexType> + + <xs:simpleType name="shell-command"> + <xs:restriction base="limited-string"> + <xs:pattern value="[a-zA-Z\-_]+"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="min-max-version"> + <xs:simpleContent> + <xs:extension base="limited-string"> + <xs:attribute name="min-version" type="version" + use="optional"/> + <xs:attribute name="max-version" type="version" + use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="database"> + <xs:simpleContent> + <xs:extension base="databases"> + <xs:attribute name="min-version" type="version" + use="optional"/> + <xs:attribute name="max-version" type="version" + use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="php"> + <xs:simpleContent> + <xs:extension base="empty-string"> + <xs:attribute name="min-int-size" type="bits" use="optional" + default="32"/> + <xs:attribute name="min-version" type="version" + use="optional"/> + <xs:attribute name="max-version" type="version" + use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:simpleType name="bits"> + <xs:restriction base="xs:int"> + <xs:enumeration value="32"/> + <xs:enumeration value="64"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="repair-steps"> + <xs:sequence> + <xs:element name="pre-migration" type="steps" minOccurs="0" + maxOccurs="1"/> + <xs:element name="post-migration" type="steps" minOccurs="0" + maxOccurs="1"/> + <xs:element name="live-migration" type="steps" minOccurs="0" + maxOccurs="1"/> + <xs:element name="install" type="steps" minOccurs="0" + maxOccurs="1"/> + <xs:element name="uninstall" type="steps" minOccurs="0" + maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="jobs"> + <xs:sequence> + <xs:element name="job" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="steps"> + <xs:sequence> + <xs:element name="step" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="two-factor-providers"> + <xs:sequence> + <xs:element name="provider" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="commands"> + <xs:sequence> + <xs:element name="command" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:simpleType name="php-class"> + <xs:restriction base="xs:string"> + <xs:pattern + value="[a-zA-Z_][0-9a-zA-Z_]*(\\[a-zA-Z_][0-9a-zA-Z_]*)*"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="path"> + <xs:restriction base="xs:string"> + <xs:pattern + value="[a-zA-Z_][0-9a-zA-Z_]*(/[a-zA-Z_][0-9a-zA-Z_]*)*\.php"/> + </xs:restriction> + </xs:simpleType> + +</xs:schema> diff --git a/resources/app-info.xsd b/resources/app-info.xsd new file mode 100644 index 00000000000..4460b0f63b9 --- /dev/null +++ b/resources/app-info.xsd @@ -0,0 +1,652 @@ +<?xml version="1.0"?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" + elementFormDefault="qualified" attributeFormDefault="unqualified"> + + <xs:element name="info"> + <xs:complexType> + <xs:sequence> + <xs:element name="id" type="id" minOccurs="1" maxOccurs="1"/> + <xs:element name="name" type="l10n-string" minOccurs="1" + maxOccurs="unbounded"/> + <xs:element name="summary" type="l10n-string" minOccurs="1" + maxOccurs="unbounded"/> + <xs:element name="description" type="l10n-text" minOccurs="1" + maxOccurs="unbounded"/> + <xs:element name="version" type="semver" + minOccurs="1" maxOccurs="1"/> + <xs:element name="licence" type="licence" minOccurs="1" + maxOccurs="unbounded"/> + <xs:element name="author" type="author" minOccurs="1" + maxOccurs="unbounded"/> + <xs:element name="namespace" type="limited-string" + minOccurs="0" maxOccurs="1"/> + <xs:element name="types" type="types" minOccurs="0" + maxOccurs="1"/> + <xs:element name="documentation" type="documentation" + minOccurs="0" maxOccurs="1"/> + <xs:element name="category" type="category" minOccurs="1" + maxOccurs="unbounded"/> + <xs:element name="website" type="url" minOccurs="0" + maxOccurs="1"/> + <xs:element name="discussion" type="url" minOccurs="0" + maxOccurs="1"/> + <xs:element name="bugs" type="url" minOccurs="1" + maxOccurs="1"/> + <xs:element name="repository" type="repository" minOccurs="0" + maxOccurs="1"/> + <xs:element name="screenshot" type="screenshot" minOccurs="0" + maxOccurs="10"/> + <xs:element name="dependencies" type="dependencies" + minOccurs="1" maxOccurs="1"/> + <xs:element name="background-jobs" type="jobs" + minOccurs="0" maxOccurs="1"/> + <xs:element name="repair-steps" type="repair-steps" + minOccurs="0" maxOccurs="1"/> + <xs:element name="two-factor-providers" + type="two-factor-providers" + minOccurs="0" maxOccurs="1"/> + <xs:element name="commands" type="commands" + minOccurs="0" maxOccurs="1"/> + <xs:element name="settings" type="settings" minOccurs="0" + maxOccurs="1"/> + <xs:element name="activity" type="activity" minOccurs="0" + maxOccurs="1"/> + <xs:element name="navigations" type="navigations" minOccurs="0" + maxOccurs="1"/> + <xs:element name="contactsmenu" type="contactsmenu" minOccurs="0" + maxOccurs="1"/> + <xs:element name="collaboration" type="collaboration" minOccurs="0" + maxOccurs="1" /> + <xs:element name="sabre" type="sabre" minOccurs="0" + maxOccurs="1" /> + </xs:sequence> + </xs:complexType> + <xs:unique name="uniqueNameL10n"> + <xs:selector xpath="name"/> + <xs:field xpath="@lang"/> + </xs:unique> + <xs:unique name="uniqueSummaryL10n"> + <xs:selector xpath="summary"/> + <xs:field xpath="@lang"/> + </xs:unique> + <xs:unique name="uniqueDescriptionL10n"> + <xs:selector xpath="description"/> + <xs:field xpath="@lang"/> + </xs:unique> + <xs:unique name="uniqueLicense"> + <xs:selector xpath="licence"/> + <xs:field xpath="."/> + </xs:unique> + <xs:unique name="uniqueTypes"> + <xs:selector xpath="types/type"/> + <xs:field xpath="."/> + </xs:unique> + <xs:unique name="uniqueCategory"> + <xs:selector xpath="category"/> + <xs:field xpath="."/> + </xs:unique> + <xs:unique name="uniqueDatabase"> + <xs:selector xpath="dependencies/database"/> + <xs:field xpath="."/> + </xs:unique> + <xs:unique name="uniqueLib"> + <xs:selector xpath="dependencies/lib"/> + <xs:field xpath="."/> + </xs:unique> + <xs:unique name="uniqueCommand"> + <xs:selector xpath="dependencies/command"/> + <xs:field xpath="."/> + </xs:unique> + </xs:element> + + <!-- basic types --> + <xs:simpleType name="empty-string"> + <xs:restriction base="xs:string"> + <xs:maxLength value="0"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="non-empty-string"> + <xs:restriction base="xs:string"> + <xs:minLength value="1"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="limited-string"> + <xs:restriction base="non-empty-string"> + <xs:maxLength value="256"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="l10n-text"> + <xs:simpleContent> + <xs:extension base="non-empty-string"> + <xs:attribute name="lang" type="l10n-code" default="en" + use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="l10n-string"> + <xs:simpleContent> + <xs:restriction base="l10n-text"> + <xs:maxLength value="128"/> + </xs:restriction> + </xs:simpleContent> + </xs:complexType> + + + <xs:simpleType name="l10n-code"> + <xs:restriction base="xs:string"> + <xs:enumeration value="af"/> + <xs:enumeration value="ar"/> + <xs:enumeration value="ast"/> + <xs:enumeration value="az"/> + <xs:enumeration value="bg"/> + <xs:enumeration value="be"/> + <xs:enumeration value="bn"/> + <xs:enumeration value="br"/> + <xs:enumeration value="bs"/> + <xs:enumeration value="ca"/> + <xs:enumeration value="cs"/> + <xs:enumeration value="cy"/> + <xs:enumeration value="da"/> + <xs:enumeration value="de"/> + <xs:enumeration value="el"/> + <xs:enumeration value="en"/> + <xs:enumeration value="eo"/> + <xs:enumeration value="es"/> + <xs:enumeration value="es-ar"/> + <xs:enumeration value="es-co"/> + <xs:enumeration value="es-mx"/> + <xs:enumeration value="es-ni"/> + <xs:enumeration value="es-ve"/> + <xs:enumeration value="et"/> + <xs:enumeration value="eu"/> + <xs:enumeration value="fa"/> + <xs:enumeration value="fi"/> + <xs:enumeration value="fr"/> + <xs:enumeration value="fy"/> + <xs:enumeration value="ga"/> + <xs:enumeration value="gd"/> + <xs:enumeration value="gl"/> + <xs:enumeration value="he"/> + <xs:enumeration value="hi"/> + <xs:enumeration value="hr"/> + <xs:enumeration value="hu"/> + <xs:enumeration value="ia"/> + <xs:enumeration value="id"/> + <xs:enumeration value="io"/> + <xs:enumeration value="is"/> + <xs:enumeration value="it"/> + <xs:enumeration value="ja"/> + <xs:enumeration value="ka"/> + <xs:enumeration value="kk"/> + <xs:enumeration value="km"/> + <xs:enumeration value="kn"/> + <xs:enumeration value="ko"/> + <xs:enumeration value="lb"/> + <xs:enumeration value="lt"/> + <xs:enumeration value="lv"/> + <xs:enumeration value="mk"/> + <xs:enumeration value="ml"/> + <xs:enumeration value="mn"/> + <xs:enumeration value="mr"/> + <xs:enumeration value="my"/> + <xs:enumeration value="nb"/> + <xs:enumeration value="ne"/> + <xs:enumeration value="nl"/> + <xs:enumeration value="nn"/> + <xs:enumeration value="os"/> + <xs:enumeration value="pa"/> + <xs:enumeration value="pl"/> + <xs:enumeration value="pt"/> + <xs:enumeration value="pt-br"/> + <xs:enumeration value="ro"/> + <xs:enumeration value="ru"/> + <xs:enumeration value="sk"/> + <xs:enumeration value="sl"/> + <xs:enumeration value="sq"/> + <xs:enumeration value="sr"/> + <xs:enumeration value="sr-latn"/> + <xs:enumeration value="sv"/> + <xs:enumeration value="sw"/> + <xs:enumeration value="ta"/> + <xs:enumeration value="te"/> + <xs:enumeration value="th"/> + <xs:enumeration value="tr"/> + <xs:enumeration value="tt"/> + <xs:enumeration value="udm"/> + <xs:enumeration value="uk"/> + <xs:enumeration value="ur"/> + <xs:enumeration value="vi"/> + <xs:enumeration value="zh-hans"/> + <xs:enumeration value="zh-hant"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="semver"> + <xs:restriction base="limited-string"> + <xs:pattern + value="(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(\-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="version"> + <xs:restriction base="limited-string"> + <xs:pattern value="[0-9]+(\.[0-9]+){0,2}"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="url"> + <xs:restriction base="xs:anyURI"> + <xs:pattern value="https?://.+"/> + <xs:maxLength value="256"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="doc-user-url"> + <xs:restriction base="non-empty-string"> + <xs:pattern value="https://.+|user-[a-z]+[\-a-z]*[a-z]+"/> + <xs:maxLength value="256"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="doc-admin-url"> + <xs:restriction base="non-empty-string"> + <xs:pattern value="https://.+|admin-[a-z]+[\-a-z]*[a-z]+"/> + <xs:maxLength value="256"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="doc-developer-url"> + <xs:restriction base="non-empty-string"> + <xs:pattern value="https://.+|developer-[a-z]+[\-a-z]*[a-z]+"/> + <xs:maxLength value="256"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="secure-url"> + <xs:restriction base="xs:anyURI"> + <xs:pattern value="https://.+"/> + <xs:maxLength value="256"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="email"> + <xs:restriction base="limited-string"> + <xs:pattern value="[^@]+@[^\.]+\..+"/> + </xs:restriction> + </xs:simpleType> + + <!-- first level elements --> + <xs:complexType name="screenshot"> + <xs:simpleContent> + <xs:extension base="secure-url"> + <xs:attribute name="small-thumbnail" use="optional" + type="secure-url"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:simpleType name="id"> + <xs:restriction base="limited-string"> + <xs:pattern value="[a-z]+[a-z0-9_]*[a-z0-9]+"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="author"> + <xs:simpleContent> + <xs:extension base="limited-string"> + <xs:attribute name="mail" type="email" use="optional"/> + <xs:attribute name="homepage" type="url" use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="repository"> + <xs:simpleContent> + <xs:extension base="url"> + <xs:attribute name="type" type="vcs" use="optional" + default="git"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:simpleType name="vcs"> + <xs:restriction base="xs:string"> + <xs:enumeration value="git"/> + <xs:enumeration value="mercurial"/> + <xs:enumeration value="subversion"/> + <xs:enumeration value="bzr"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="types"> + <xs:sequence> + <xs:element name="prelogin" minOccurs="0" maxOccurs="1"/> + <xs:element name="filesystem" minOccurs="0" maxOccurs="1"/> + <xs:element name="authentication" minOccurs="0" maxOccurs="1"/> + <xs:element name="logging" minOccurs="0" maxOccurs="1"/> + <xs:element name="dav" minOccurs="0" maxOccurs="1"/> + <xs:element name="prevent_group_restriction" minOccurs="0" + maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:simpleType name="category"> + <xs:restriction base="xs:string"> + <xs:enumeration value="security"/> + <xs:enumeration value="customization"/> + <xs:enumeration value="files"/> + <xs:enumeration value="integration"/> + <xs:enumeration value="monitoring"/> + <xs:enumeration value="multimedia"/> + <xs:enumeration value="office"/> + <xs:enumeration value="organization"/> + <xs:enumeration value="social"/> + <xs:enumeration value="tools"/> + <xs:enumeration value="games"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="licence"> + <xs:restriction base="xs:string"> + <xs:enumeration value="agpl"/> + <xs:enumeration value="mpl"/> + <xs:enumeration value="apache"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="databases"> + <xs:restriction base="xs:string"> + <xs:enumeration value="sqlite"/> + <xs:enumeration value="mysql"/> + <xs:enumeration value="pgsql"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="documentation"> + <xs:sequence> + <xs:element name="user" type="doc-user-url" minOccurs="0" maxOccurs="1"/> + <xs:element name="admin" type="doc-admin-url" minOccurs="0" maxOccurs="1"/> + <xs:element name="developer" type="doc-developer-url" minOccurs="0" + maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="settings"> + <xs:sequence> + <xs:element name="admin" type="php-class" minOccurs="0" + maxOccurs="unbounded"/> + <xs:element name="admin-section" type="php-class" minOccurs="0" + maxOccurs="unbounded"/> + <xs:element name="personal" type="php-class" minOccurs="0" + maxOccurs="unbounded"/> + <xs:element name="personal-section" type="php-class" minOccurs="0" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="activity"> + <xs:sequence> + <xs:element name="settings" type="activity-settings" minOccurs="0" + maxOccurs="1"/> + <xs:element name="filters" type="activity-filters" minOccurs="0" maxOccurs="1"/> + <xs:element name="providers" type="activity-providers" minOccurs="0" + maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="activity-settings"> + <xs:sequence> + <xs:element name="setting" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="activity-filters"> + <xs:sequence> + <xs:element name="filter" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="activity-providers"> + <xs:sequence> + <xs:element name="provider" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="navigations"> + <xs:sequence> + <xs:element name="navigation" type="navigation" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="navigation"> + <xs:sequence> + <xs:element name="id" type="id" minOccurs="0" maxOccurs="1"/> + <xs:element name="name" type="non-empty-string" minOccurs="1" maxOccurs="1"/> + <xs:element name="route" type="route" minOccurs="1" maxOccurs="1"/> + <xs:element name="icon" type="xs:anyURI" minOccurs="0" maxOccurs="1"/> + <xs:element name="order" type="xs:int" minOccurs="0" maxOccurs="1"/> + <xs:element name="type" type="navigation-type" minOccurs="0" maxOccurs="1"/> + </xs:sequence> + <xs:attribute name="role" type="navigation-role" default="all" use="optional"/> + </xs:complexType> + + <xs:simpleType name="navigation-role"> + <xs:restriction base="xs:string"> + <xs:enumeration value="all"/> + <xs:enumeration value="admin"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="navigation-type"> + <xs:restriction base="xs:string"> + <xs:enumeration value="link"/> + <xs:enumeration value="settings"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="route"> + <xs:restriction base="non-empty-string"> + <xs:pattern value="[0-9a-zA-Z_]+(\.[0-9a-zA-Z_]+){2}"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="contactsmenu"> + <xs:sequence> + <xs:element name="provider" type="php-class" minOccurs="1" + maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="collaboration"> + <xs:sequence> + <xs:element name="plugins" type="collaboration-plugins" minOccurs="0" maxOccurs="1"> + </xs:element> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="collaboration-plugins"> + <xs:sequence> + <xs:element name="plugin" type="collaboration-plugins-plugin" minOccurs="1" maxOccurs="unbounded"> + </xs:element> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="collaboration-plugins-plugin"> + <xs:simpleContent> + <xs:extension base="php-class"> + <xs:attribute name="type" type="collaboration-plugin-type" use="required"/> + <xs:attribute name="share-type" type="share-type" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:simpleType name="collaboration-plugin-type"> + <xs:restriction base="xs:string"> + <xs:enumeration value="collaborator-search"/> + <xs:enumeration value="autocomplete-sort"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="sabre"> + <xs:sequence> + <xs:element name="collections" type="sabre-collections" minOccurs="0" maxOccurs="1"/> + <xs:element name="plugins" type="sabre-plugins" minOccurs="0" maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="sabre-collections"> + <xs:sequence> + <xs:element name="collection" type="php-class" minOccurs="1" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="sabre-plugins"> + <xs:sequence> + <xs:element name="plugin" type="php-class" minOccurs="1" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:simpleType name="share-type"> + <xs:restriction base="xs:string"> + <xs:enumeration value="SHARE_TYPE_USER"/> + <xs:enumeration value="SHARE_TYPE_GROUP"/> + <xs:enumeration value="SHARE_TYPE_LINK"/> + <xs:enumeration value="SHARE_TYPE_EMAIL"/> + <xs:enumeration value="SHARE_TYPE_CONTACT"/> + <xs:enumeration value="SHARE_TYPE_REMOTE"/> + <xs:enumeration value="SHARE_TYPE_CIRCLE"/> + <xs:enumeration value="SHARE_TYPE_GUEST"/> + </xs:restriction> + </xs:simpleType> + + <!-- dependencies --> + <xs:complexType name="dependencies"> + <xs:sequence> + <xs:element name="php" type="php" minOccurs="0" maxOccurs="1"/> + <xs:element name="database" type="database" minOccurs="0" + maxOccurs="unbounded"/> + <xs:element name="command" type="shell-command" minOccurs="0" + maxOccurs="unbounded"/> + <xs:element name="lib" type="min-max-version" minOccurs="0" + maxOccurs="unbounded"/> + <xs:element name="owncloud" type="owncloud" minOccurs="0" + maxOccurs="1"/> + <xs:element name="nextcloud" type="nextcloud" minOccurs="1" + maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="owncloud"> + <xs:attribute name="min-version" type="version" use="required"/> + <xs:attribute name="max-version" type="version" use="optional"/> + </xs:complexType> + + <xs:complexType name="nextcloud"> + <xs:attribute name="min-version" type="version" use="required"/> + <xs:attribute name="max-version" type="version" use="required"/> + </xs:complexType> + + <xs:simpleType name="shell-command"> + <xs:restriction base="limited-string"> + <xs:pattern value="[a-zA-Z\-_]+"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="min-max-version"> + <xs:simpleContent> + <xs:extension base="limited-string"> + <xs:attribute name="min-version" type="version" + use="optional"/> + <xs:attribute name="max-version" type="version" + use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="database"> + <xs:simpleContent> + <xs:extension base="databases"> + <xs:attribute name="min-version" type="version" + use="optional"/> + <xs:attribute name="max-version" type="version" + use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="php"> + <xs:simpleContent> + <xs:extension base="empty-string"> + <xs:attribute name="min-int-size" type="bits" use="optional" + default="32"/> + <xs:attribute name="min-version" type="version" + use="optional"/> + <xs:attribute name="max-version" type="version" + use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:simpleType name="bits"> + <xs:restriction base="xs:int"> + <xs:enumeration value="32"/> + <xs:enumeration value="64"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="repair-steps"> + <xs:sequence> + <xs:element name="pre-migration" type="steps" minOccurs="0" + maxOccurs="1"/> + <xs:element name="post-migration" type="steps" minOccurs="0" + maxOccurs="1"/> + <xs:element name="live-migration" type="steps" minOccurs="0" + maxOccurs="1"/> + <xs:element name="install" type="steps" minOccurs="0" + maxOccurs="1"/> + <xs:element name="uninstall" type="steps" minOccurs="0" + maxOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="jobs"> + <xs:sequence> + <xs:element name="job" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="steps"> + <xs:sequence> + <xs:element name="step" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="two-factor-providers"> + <xs:sequence> + <xs:element name="provider" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="commands"> + <xs:sequence> + <xs:element name="command" type="php-class" minOccurs="1" + maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + + <xs:simpleType name="php-class"> + <xs:restriction base="xs:string"> + <xs:pattern + value="[a-zA-Z_][0-9a-zA-Z_]*(\\[a-zA-Z_][0-9a-zA-Z_]*)*"/> + </xs:restriction> + </xs:simpleType> + +</xs:schema> diff --git a/tests/apps/testapp-dependency-missing/appinfo/info.xml b/tests/apps/testapp-dependency-missing/appinfo/info.xml deleted file mode 100644 index c765400a76f..00000000000 --- a/tests/apps/testapp-dependency-missing/appinfo/info.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0"?> -<info> - <id>testapp-infoxml-version</id> - <version>1.2.3</version> - <author>Jane</author> - <description>A b c</description> - <licence>Abc</licence> - <name>Test app</name> -</info> diff --git a/tests/apps/testapp-infoxml/appinfo/info.xml b/tests/apps/testapp-infoxml/appinfo/info.xml deleted file mode 100644 index d4df1c3cd3f..00000000000 --- a/tests/apps/testapp-infoxml/appinfo/info.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0"?> -<info> - <id>testapp-infoxml</id> - <version>1.2.3</version> - <author>Jane</author> - <description>A b c</description> - <licence>Abc</licence> - <name>Test app</name> - <dependencies> - <nextcloud min-version="12.0" max-version="12.0"/> - </dependencies> -</info> diff --git a/tests/apps/testapp-name-missing/appinfo/info.xml b/tests/apps/testapp-name-missing/appinfo/info.xml deleted file mode 100644 index 591c4361899..00000000000 --- a/tests/apps/testapp-name-missing/appinfo/info.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0"?> -<info> - <id>testapp-version</id> - <version>1.1.1</version> - <author>Jane</author> - <description>A b c</description> - <licence>Abc</licence> - <dependencies> - <nextcloud min-version="12.0" max-version="12.0"/> - </dependencies> -</info> diff --git a/tests/apps/testapp-version/appinfo/info.xml b/tests/apps/testapp-version/appinfo/info.xml deleted file mode 100644 index 28e2475800f..00000000000 --- a/tests/apps/testapp-version/appinfo/info.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0"?> -<info> - <id>testapp-version</id> - <author>Jane</author> - <description>A b c</description> - <licence>Abc</licence> - <name>Test app</name> - <dependencies> - <nextcloud min-version="12.0" max-version="12.0"/> - </dependencies> -</info> diff --git a/tests/apps/testapp-version/appinfo/version b/tests/apps/testapp-version/appinfo/version deleted file mode 100644 index 0495c4a88ca..00000000000 --- a/tests/apps/testapp-version/appinfo/version +++ /dev/null @@ -1 +0,0 @@ -1.2.3 diff --git a/tests/apps/testapp_dependency_missing/appinfo/info.xml b/tests/apps/testapp_dependency_missing/appinfo/info.xml new file mode 100644 index 00000000000..b0ca188aefc --- /dev/null +++ b/tests/apps/testapp_dependency_missing/appinfo/info.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd"> + <id>testapp_infoxml</id> + <name>Test app</name> + <summary>A b c</summary> + <description>A b c</description> + <version>1.2.3</version> + <licence>agpl</licence> + <author>Jane</author> + <category>games</category> + <bugs>https://example.org</bugs> +</info> diff --git a/tests/apps/testapp_infoxml/appinfo/info.xml b/tests/apps/testapp_infoxml/appinfo/info.xml new file mode 100644 index 00000000000..b7575687fe5 --- /dev/null +++ b/tests/apps/testapp_infoxml/appinfo/info.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd"> + <id>testapp_infoxml</id> + <name>Test app</name> + <summary>A b c</summary> + <description>A b c</description> + <version>1.2.3</version> + <licence>agpl</licence> + <author>Jane</author> + <category>games</category> + <bugs>https://example.org</bugs> + <dependencies> + <nextcloud min-version="12.0" max-version="12.0"/> + </dependencies> +</info> diff --git a/tests/apps/testapp_name_missing/appinfo/info.xml b/tests/apps/testapp_name_missing/appinfo/info.xml new file mode 100644 index 00000000000..872c8752c75 --- /dev/null +++ b/tests/apps/testapp_name_missing/appinfo/info.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd"> + <id>testapp_name_missing</id> + <summary>A b c</summary> + <description>A b c</description> + <version>1.2.3</version> + <licence>agpl</licence> + <author>Jane</author> + <category>games</category> + <bugs>https://example.org</bugs> + <dependencies> + <nextcloud min-version="12.0" max-version="12.0"/> + </dependencies> +</info> diff --git a/tests/apps/testapp_version/appinfo/info.xml b/tests/apps/testapp_version/appinfo/info.xml new file mode 100644 index 00000000000..3a48fccd45f --- /dev/null +++ b/tests/apps/testapp_version/appinfo/info.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd"> + <id>testapp_version</id> + <name>Test app</name> + <summary>A b c</summary> + <description>A b c</description> + <licence>agpl</licence> + <author>Jane</author> + <category>games</category> + <bugs>https://example.org</bugs> + <dependencies> + <nextcloud min-version="12.0" max-version="12.0"/> + </dependencies> +</info> diff --git a/tests/lib/App/CodeChecker/InfoCheckerTest.php b/tests/lib/App/CodeChecker/InfoCheckerTest.php index 760d9880739..9f354a4611c 100644 --- a/tests/lib/App/CodeChecker/InfoCheckerTest.php +++ b/tests/lib/App/CodeChecker/InfoCheckerTest.php @@ -44,19 +44,21 @@ class InfoCheckerTest extends TestCase { protected function setUp() { parent::setUp(); - $this->infoChecker = new InfoChecker(new InfoParser()); + $this->infoChecker = new InfoChecker(\OC::$server->getAppManager()); } public function appInfoData() { return [ - ['testapp-infoxml', []], - ['testapp-version', [['type' => 'mandatoryFieldMissing', 'field' => 'version']]], - ['testapp-dependency-missing', [ - ['type' => 'missingRequirement', 'field' => 'min'], - ['type' => 'missingRequirement', 'field' => 'max'], - ['type' => 'mandatoryFieldMissing', 'field' => 'dependencies'], + ['testapp_infoxml', []], + ['testapp_version', [ + ['type' => 'parseError', 'field' => 'Element \'licence\': This element is not expected. Expected is one of ( description, version ).' . "\n"], + ]], + ['testapp_dependency_missing', [ + ['type' => 'parseError', 'field' => 'Element \'info\': Missing child element(s). Expected is one of ( repository, screenshot, dependencies ).' . "\n"], + ]], + ['testapp_name_missing', [ + ['type' => 'parseError', 'field' => 'Element \'summary\': This element is not expected. Expected is ( name ).' . "\n"], ]], - ['testapp-name-missing', [['type' => 'mandatoryFieldMissing', 'field' => 'name']]], ]; } @@ -68,6 +70,7 @@ class InfoCheckerTest extends TestCase { */ public function testApps($appId, $expectedErrors) { $errors = $this->infoChecker->analyse($appId); + libxml_clear_errors(); $this->assertEquals($expectedErrors, $errors); } |