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']); - } }