diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml
index 72b6311b2fc0f..21007bec0bf26 100644
--- a/build/psalm-baseline.xml
+++ b/build/psalm-baseline.xml
@@ -3338,18 +3338,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
fastCache[$app][$key] ?? $default]]>
diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php
index 1911bce12bfdf..7778393b3b38c 100644
--- a/lib/private/App/AppManager.php
+++ b/lib/private/App/AppManager.php
@@ -771,7 +771,7 @@ public function getAppInfoByPath(string $path, ?string $lang = null): ?array {
$data = $parser->parse($path);
if (is_array($data)) {
- $data = \OC_App::parseAppInfo($data, $lang);
+ $data = $parser->applyL10N($data, $lang);
}
return $data;
diff --git a/lib/private/App/DependencyAnalyzer.php b/lib/private/App/DependencyAnalyzer.php
index 1e56612132bc1..bde8719c41d12 100644
--- a/lib/private/App/DependencyAnalyzer.php
+++ b/lib/private/App/DependencyAnalyzer.php
@@ -1,22 +1,18 @@
appInfo = $app;
+ public function analyze(array $app, bool $ignoreMax = false): array {
if (isset($app['dependencies'])) {
$dependencies = $app['dependencies'];
} else {
@@ -64,12 +58,10 @@ public function isMarkedCompatible(array $app): bool {
* Truncates both versions to the lowest common version, e.g.
* 5.1.2.3 and 5.1 will be turned into 5.1 and 5.1,
* 5.2.6.5 and 5.1 will be turned into 5.2 and 5.1
- * @param string $first
- * @param string $second
* @return string[] first element is the first version, second element is the
* second version
*/
- private function normalizeVersions($first, $second) {
+ private function normalizeVersions(string $first, string $second): array {
$first = explode('.', $first);
$second = explode('.', $second);
@@ -84,47 +76,31 @@ private function normalizeVersions($first, $second) {
/**
* Parameters will be normalized and then passed into version_compare
* in the same order they are specified in the method header
- * @param string $first
- * @param string $second
- * @param string $operator
* @return bool result similar to version_compare
*/
- private function compare($first, $second, $operator) {
- // we can't normalize versions if one of the given parameters is not a
- // version string but null. In case one parameter is null normalization
- // will therefore be skipped
- if ($first !== null && $second !== null) {
- [$first, $second] = $this->normalizeVersions($first, $second);
- }
+ private function compare(string $first, string $second, string $operator): bool {
+ [$first, $second] = $this->normalizeVersions($first, $second);
return version_compare($first, $second, $operator);
}
/**
* Checks if a version is bigger than another version
- * @param string $first
- * @param string $second
* @return bool true if the first version is bigger than the second
*/
- private function compareBigger($first, $second) {
+ private function compareBigger(string $first, string $second): bool {
return $this->compare($first, $second, '>');
}
/**
* Checks if a version is smaller than another version
- * @param string $first
- * @param string $second
* @return bool true if the first version is smaller than the second
*/
- private function compareSmaller($first, $second) {
+ private function compareSmaller(string $first, string $second): bool {
return $this->compare($first, $second, '<');
}
- /**
- * @param array $dependencies
- * @return array
- */
- private function analyzePhpVersion(array $dependencies) {
+ private function analyzePhpVersion(array $dependencies): array {
$missing = [];
if (isset($dependencies['php']['@attributes']['min-version'])) {
$minVersion = $dependencies['php']['@attributes']['min-version'];
@@ -147,7 +123,7 @@ private function analyzePhpVersion(array $dependencies) {
return $missing;
}
- private function analyzeArchitecture(array $dependencies) {
+ private function analyzeArchitecture(array $dependencies): array {
$missing = [];
if (!isset($dependencies['architecture'])) {
return $missing;
@@ -170,11 +146,7 @@ private function analyzeArchitecture(array $dependencies) {
return $missing;
}
- /**
- * @param array $dependencies
- * @return array
- */
- private function analyzeDatabases(array $dependencies) {
+ private function analyzeDatabases(array $dependencies): array {
$missing = [];
if (!isset($dependencies['database'])) {
return $missing;
@@ -187,6 +159,9 @@ private function analyzeDatabases(array $dependencies) {
if (!is_array($supportedDatabases)) {
$supportedDatabases = [$supportedDatabases];
}
+ if (isset($supportedDatabases['@value'])) {
+ $supportedDatabases = [$supportedDatabases];
+ }
$supportedDatabases = array_map(function ($db) {
return $this->getValue($db);
}, $supportedDatabases);
@@ -197,11 +172,7 @@ private function analyzeDatabases(array $dependencies) {
return $missing;
}
- /**
- * @param array $dependencies
- * @return array
- */
- private function analyzeCommands(array $dependencies) {
+ private function analyzeCommands(array $dependencies): array {
$missing = [];
if (!isset($dependencies['command'])) {
return $missing;
@@ -227,11 +198,7 @@ private function analyzeCommands(array $dependencies) {
return $missing;
}
- /**
- * @param array $dependencies
- * @return array
- */
- private function analyzeLibraries(array $dependencies) {
+ private function analyzeLibraries(array $dependencies): array {
$missing = [];
if (!isset($dependencies['lib'])) {
return $missing;
@@ -272,11 +239,7 @@ private function analyzeLibraries(array $dependencies) {
return $missing;
}
- /**
- * @param array $dependencies
- * @return array
- */
- private function analyzeOS(array $dependencies) {
+ private function analyzeOS(array $dependencies): array {
$missing = [];
if (!isset($dependencies['os'])) {
return $missing;
@@ -300,12 +263,7 @@ private function analyzeOS(array $dependencies) {
return $missing;
}
- /**
- * @param array $dependencies
- * @param array $appInfo
- * @return array
- */
- private function analyzeOC(array $dependencies, array $appInfo, bool $ignoreMax) {
+ private function analyzeOC(array $dependencies, array $appInfo, bool $ignoreMax): array {
$missing = [];
$minVersion = null;
if (isset($dependencies['nextcloud']['@attributes']['min-version'])) {
@@ -321,12 +279,12 @@ private function analyzeOC(array $dependencies, array $appInfo, bool $ignoreMax)
if (!is_null($minVersion)) {
if ($this->compareSmaller($this->platform->getOcVersion(), $minVersion)) {
- $missing[] = $this->l->t('Server version %s or higher is required.', [$this->toVisibleVersion($minVersion)]);
+ $missing[] = $this->l->t('Server version %s or higher is required.', [$minVersion]);
}
}
if (!$ignoreMax && !is_null($maxVersion)) {
if ($this->compareBigger($this->platform->getOcVersion(), $maxVersion)) {
- $missing[] = $this->l->t('Server version %s or lower is required.', [$this->toVisibleVersion($maxVersion)]);
+ $missing[] = $this->l->t('Server version %s or lower is required.', [$maxVersion]);
}
}
return $missing;
@@ -347,25 +305,7 @@ private function getMaxVersion(array $dependencies, array $appInfo): ?string {
}
/**
- * Map the internal version number to the Nextcloud version
- *
- * @param string $version
- * @return string
- */
- protected function toVisibleVersion($version) {
- switch ($version) {
- case '9.1':
- return '10';
- default:
- if (str_starts_with($version, '9.1.')) {
- $version = '10.0.' . substr($version, 4);
- }
- return $version;
- }
- }
-
- /**
- * @param $element
+ * @param mixed $element
* @return mixed
*/
private function getValue($element) {
diff --git a/lib/private/App/InfoParser.php b/lib/private/App/InfoParser.php
index e7a75afdf0b46..634dc1fbdd52a 100644
--- a/lib/private/App/InfoParser.php
+++ b/lib/private/App/InfoParser.php
@@ -23,7 +23,7 @@ public function __construct(
* @param string $file the xml file to be loaded
* @return null|array where null is an indicator for an error
*/
- public function parse($file) {
+ public function parse(string $file): ?array {
if (!file_exists($file)) {
return null;
}
@@ -44,7 +44,7 @@ public function parse($file) {
}
$array = $this->xmlToArray($xml);
- if (is_null($array)) {
+ if (is_string($array)) {
return null;
}
@@ -208,11 +208,7 @@ public function parse($file) {
return $array;
}
- /**
- * @param $data
- * @return bool
- */
- private function isNavigationItem($data): bool {
+ private function isNavigationItem(array $data): bool {
// Allow settings navigation items with no route entry
$type = $data['type'] ?? 'link';
if ($type === 'settings') {
@@ -221,17 +217,17 @@ private function isNavigationItem($data): bool {
return isset($data['name'], $data['route']);
}
- /**
- * @param \SimpleXMLElement $xml
- * @return array
- */
- public function xmlToArray($xml) {
- if (!$xml->children()) {
+ public function xmlToArray(\SimpleXMLElement $xml): array|string {
+ $children = $xml->children();
+ if ($children === null || count($children) === 0) {
return (string)$xml;
}
$array = [];
- foreach ($xml->children() as $element => $node) {
+ foreach ($children as $element => $node) {
+ if ($element === null) {
+ throw new \InvalidArgumentException('xml contains a null element');
+ }
$totalElement = count($xml->{$element});
if (!isset($array[$element])) {
@@ -243,15 +239,18 @@ public function xmlToArray($xml) {
$data = [
'@attributes' => [],
];
- if (!count($node->children())) {
- $value = (string)$node;
- if (!empty($value)) {
- $data['@value'] = $value;
+ $converted = $this->xmlToArray($node);
+ if (is_string($converted)) {
+ if (!empty($converted)) {
+ $data['@value'] = $converted;
}
} else {
- $data = array_merge($data, $this->xmlToArray($node));
+ $data = array_merge($data, $converted);
}
foreach ($attributes as $attr => $value) {
+ if ($attr === null) {
+ throw new \InvalidArgumentException('xml contains a null element');
+ }
$data['@attributes'][$attr] = (string)$value;
}
@@ -272,4 +271,78 @@ public function xmlToArray($xml) {
return $array;
}
+
+ /**
+ * Select the appropriate l10n version for fields name, summary and description
+ */
+ public function applyL10N(array $data, ?string $lang = null): array {
+ if ($lang !== '' && $lang !== null) {
+ if (isset($data['name']) && is_array($data['name'])) {
+ $data['name'] = $this->findBestL10NOption($data['name'], $lang);
+ }
+ if (isset($data['summary']) && is_array($data['summary'])) {
+ $data['summary'] = $this->findBestL10NOption($data['summary'], $lang);
+ }
+ if (isset($data['description']) && is_array($data['description'])) {
+ $data['description'] = trim($this->findBestL10NOption($data['description'], $lang));
+ }
+ } elseif (isset($data['description']) && is_string($data['description'])) {
+ $data['description'] = trim($data['description']);
+ } else {
+ $data['description'] = '';
+ }
+
+ return $data;
+ }
+
+ protected function findBestL10NOption(array $options, string $lang): string {
+ // only a single option
+ if (isset($options['@value'])) {
+ return $options['@value'];
+ }
+
+ $fallback = $similarLangFallback = $englishFallback = false;
+
+ $lang = strtolower($lang);
+ $similarLang = $lang;
+ $pos = strpos($similarLang, '_');
+ if ($pos !== false && $pos > 0) {
+ // For "de_DE" we want to find "de" and the other way around
+ $similarLang = substr($lang, 0, $pos);
+ }
+
+ foreach ($options as $option) {
+ if (is_array($option)) {
+ if ($fallback === false) {
+ $fallback = $option['@value'];
+ }
+
+ if (!isset($option['@attributes']['lang'])) {
+ continue;
+ }
+
+ $attributeLang = strtolower($option['@attributes']['lang']);
+ if ($attributeLang === $lang) {
+ return $option['@value'];
+ }
+
+ if ($attributeLang === $similarLang) {
+ $similarLangFallback = $option['@value'];
+ } elseif (str_starts_with($attributeLang, $similarLang . '_')) {
+ if ($similarLangFallback === false) {
+ $similarLangFallback = $option['@value'];
+ }
+ }
+ } else {
+ $englishFallback = $option;
+ }
+ }
+
+ if ($similarLangFallback !== false) {
+ return $similarLangFallback;
+ } elseif ($englishFallback !== false) {
+ return $englishFallback;
+ }
+ return (string)$fallback;
+ }
}
diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php
index 24982ab9e8025..d4d7b5d30aaa5 100644
--- a/lib/private/legacy/OC_App.php
+++ b/lib/private/legacy/OC_App.php
@@ -775,81 +775,6 @@ private static function setupLiveMigrations(string $appId, array $steps) {
}
}
- protected static function findBestL10NOption(array $options, string $lang): string {
- // only a single option
- if (isset($options['@value'])) {
- return $options['@value'];
- }
-
- $fallback = $similarLangFallback = $englishFallback = false;
-
- $lang = strtolower($lang);
- $similarLang = $lang;
- if (strpos($similarLang, '_')) {
- // For "de_DE" we want to find "de" and the other way around
- $similarLang = substr($lang, 0, strpos($lang, '_'));
- }
-
- foreach ($options as $option) {
- if (is_array($option)) {
- if ($fallback === false) {
- $fallback = $option['@value'];
- }
-
- if (!isset($option['@attributes']['lang'])) {
- continue;
- }
-
- $attributeLang = strtolower($option['@attributes']['lang']);
- if ($attributeLang === $lang) {
- return $option['@value'];
- }
-
- if ($attributeLang === $similarLang) {
- $similarLangFallback = $option['@value'];
- } elseif (str_starts_with($attributeLang, $similarLang . '_')) {
- if ($similarLangFallback === false) {
- $similarLangFallback = $option['@value'];
- }
- }
- } else {
- $englishFallback = $option;
- }
- }
-
- if ($similarLangFallback !== false) {
- return $similarLangFallback;
- } elseif ($englishFallback !== false) {
- return $englishFallback;
- }
- return (string)$fallback;
- }
-
- /**
- * parses the app data array and enhanced the 'description' value
- *
- * @param array $data the app data
- * @param string $lang
- * @return array improved app data
- */
- public static function parseAppInfo(array $data, $lang = null): array {
- if ($lang && isset($data['name']) && is_array($data['name'])) {
- $data['name'] = self::findBestL10NOption($data['name'], $lang);
- }
- if ($lang && isset($data['summary']) && is_array($data['summary'])) {
- $data['summary'] = self::findBestL10NOption($data['summary'], $lang);
- }
- if ($lang && isset($data['description']) && is_array($data['description'])) {
- $data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
- } elseif (isset($data['description']) && is_string($data['description'])) {
- $data['description'] = trim($data['description']);
- } else {
- $data['description'] = '';
- }
-
- return $data;
- }
-
/**
* @param \OCP\IConfig $config
* @param \OCP\IL10N $l
diff --git a/tests/lib/App/DependencyAnalyzerTest.php b/tests/lib/App/DependencyAnalyzerTest.php
index db53d6788816e..88cb6009cc0e3 100644
--- a/tests/lib/App/DependencyAnalyzerTest.php
+++ b/tests/lib/App/DependencyAnalyzerTest.php
@@ -277,18 +277,6 @@ public static function providesOC(): array {
],
],
],
- [
- [
- 'Server version 10 or higher is required.',
- ],
- [
- 'nextcloud' => [
- '@attributes' => [
- 'min-version' => '9.1',
- ],
- ],
- ],
- ],
[
[
'Server version 9.2 or higher is required.',
@@ -382,18 +370,6 @@ public static function providesOC(): array {
],
],
],
- [
- [
- 'Server version 10 or higher is required.',
- ],
- [
- 'owncloud' => [
- '@attributes' => [
- 'min-version' => '9.1',
- ],
- ],
- ],
- ],
[
[
'Server version 9.2 or higher is required.',
diff --git a/tests/lib/App/InfoParserTest.php b/tests/lib/App/InfoParserTest.php
index f7c6a2eb7bd43..449f827df439b 100644
--- a/tests/lib/App/InfoParserTest.php
+++ b/tests/lib/App/InfoParserTest.php
@@ -53,4 +53,57 @@ public static function providesInfoXml(): array {
['various-single-item.json', 'various-single-item.xml'],
];
}
+
+ /**
+ * Providers for the app data values
+ */
+ public static function appDataProvider(): array {
+ return [
+ [
+ ['description' => " \t This is a multiline \n test with \n \t \n \n some new lines "],
+ ['description' => "This is a multiline \n test with \n \t \n \n some new lines"],
+ ],
+ [
+ ['description' => " \t This is a multiline \n test with \n \t some new lines "],
+ ['description' => "This is a multiline \n test with \n \t some new lines"],
+ ],
+ [
+ ['description' => hex2bin('5065726d657420646520732761757468656e7469666965722064616e732070697769676f20646972656374656d656e74206176656320736573206964656e74696669616e7473206f776e636c6f75642073616e73206c65732072657461706572206574206d657420c3a0206a6f757273206365757820636920656e20636173206465206368616e67656d656e74206465206d6f742064652070617373652e0d0a0d')],
+ ['description' => "Permet de s'authentifier dans piwigo directement avec ses identifiants owncloud sans les retaper et met à jours ceux ci en cas de changement de mot de passe."],
+ ],
+ [
+ ['not-a-description' => " \t This is a multiline \n test with \n \t some new lines "],
+ [
+ 'not-a-description' => " \t This is a multiline \n test with \n \t some new lines ",
+ 'description' => '',
+ ],
+ ],
+ [
+ ['description' => [100, 'bla']],
+ ['description' => ''],
+ ],
+ ];
+ }
+
+ /**
+ * Test app info parser
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('appDataProvider')]
+ public function testApplyL10NNoLanguage(array $data, array $expected): void {
+ $parser = new InfoParser();
+ $this->assertSame($expected, $parser->applyL10N($data));
+ }
+
+ public function testApplyL10N(): void {
+ $parser = new InfoParser();
+ $data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-multi-lang.xml');
+ $this->assertEquals('English', $parser->applyL10N($data, 'en')['description']);
+ $this->assertEquals('German', $parser->applyL10N($data, 'de')['description']);
+ }
+
+ public function testApplyL10NSingleLanguage(): void {
+ $parser = new InfoParser();
+ $data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-single-lang.xml');
+ $this->assertEquals('English', $parser->applyL10N($data, 'en')['description']);
+ }
}
diff --git a/tests/lib/AppTest.php b/tests/lib/AppTest.php
index e174a59bfafeb..c56e31aadf8c1 100644
--- a/tests/lib/AppTest.php
+++ b/tests/lib/AppTest.php
@@ -9,7 +9,6 @@
namespace Test;
use OC\App\AppManager;
-use OC\App\InfoParser;
use OC\AppConfig;
use OC\Config\ConfigManager;
use OCP\EventDispatcher\IEventDispatcher;
@@ -586,59 +585,4 @@ private function restoreAppConfig() {
// Remove the cache of the mocked apps list with a forceRefresh
\OC_App::getEnabledApps();
}
-
- /**
- * Providers for the app data values
- */
- public static function appDataProvider(): array {
- return [
- [
- ['description' => " \t This is a multiline \n test with \n \t \n \n some new lines "],
- ['description' => "This is a multiline \n test with \n \t \n \n some new lines"],
- ],
- [
- ['description' => " \t This is a multiline \n test with \n \t some new lines "],
- ['description' => "This is a multiline \n test with \n \t some new lines"],
- ],
- [
- ['description' => hex2bin('5065726d657420646520732761757468656e7469666965722064616e732070697769676f20646972656374656d656e74206176656320736573206964656e74696669616e7473206f776e636c6f75642073616e73206c65732072657461706572206574206d657420c3a0206a6f757273206365757820636920656e20636173206465206368616e67656d656e74206465206d6f742064652070617373652e0d0a0d')],
- ['description' => "Permet de s'authentifier dans piwigo directement avec ses identifiants owncloud sans les retaper et met à jours ceux ci en cas de changement de mot de passe."],
- ],
- [
- ['not-a-description' => " \t This is a multiline \n test with \n \t some new lines "],
- [
- 'not-a-description' => " \t This is a multiline \n test with \n \t some new lines ",
- 'description' => '',
- ],
- ],
- [
- ['description' => [100, 'bla']],
- ['description' => ''],
- ],
- ];
- }
-
- /**
- * Test app info parser
- *
- * @param array $data
- * @param array $expected
- */
- #[\PHPUnit\Framework\Attributes\DataProvider('appDataProvider')]
- public function testParseAppInfo(array $data, array $expected): void {
- $this->assertSame($expected, \OC_App::parseAppInfo($data));
- }
-
- public function testParseAppInfoL10N(): void {
- $parser = new InfoParser();
- $data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-multi-lang.xml');
- $this->assertEquals('English', \OC_App::parseAppInfo($data, 'en')['description']);
- $this->assertEquals('German', \OC_App::parseAppInfo($data, 'de')['description']);
- }
-
- public function testParseAppInfoL10NSingleLanguage(): void {
- $parser = new InfoParser();
- $data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-single-lang.xml');
- $this->assertEquals('English', \OC_App::parseAppInfo($data, 'en')['description']);
- }
}